← Back to Articles

The Significance of Memory Safety in Programming Languages

Memory safety in programming has emerged as a critical concept. It is the shield against common programming errors that can lead to serious security vulnerabilities. Recognizing the importance of memory safety, the National Security Agency (NSA) has updated its list of memory-safe programming languages to guide organizations and developers towards safer software development practices.

What do we mean when we talk about memory safety?

Languages handle memory safety differently, with some designed to enforce it inherently (memory-safe languages) and others that require the developer to manage memory manually (non-memory-safe languages). Relying on programmers to consistently enforce memory management is wrought with problems. Not all programmers are created equal. Even programmers with decades of experience make mistakes. We can't rely on programmers to write bug-free code as it is an impossible task. Programmers are usually saddled with deadlines and other pressures. We need the guardrails that prevent these problems in the first place without putting all the responsibility on the programmer.

Let's explore examples in both a memory-safe language (Rust) and a non-memory-safe language (C).

Non-Memory Safe Language Example: C

C is a powerful, low-level programming language that gives programmers fine-grained control over memory. However, this control comes at the cost of memory safety, and it's easy to introduce bugs if you're not careful.

Buffer Overflow Example in C

#include <stdio.h>
#include <string.h>

int main() {
    char buffer[10];
    // Intentionally copying more data into the buffer than it can hold.
    strcpy(buffer, "This is way too long for the buffer");
    printf("Buffer content: %s\n", buffer);
    return 0;
}

In this C example, strcpy is used to copy a string that is longer than the destination buffer can accommodate. This can lead to a buffer overflow, potentially overwriting adjacent memory and leading to unpredictable behavior or vulnerabilities.

Memory Safe Language Example: Rust

Rust is a system programming language that provides memory safety guarantees through a compile-time ownership system, without needing a garbage collector. Rust’s strict compiler ensures that memory safety issues like buffer overflows or use-after-free errors are caught before the code can run.

Safe Handling of Vector Indexing in Rust

fn main() {
    let vec = vec![1, 2, 3, 4, 5]; // A vector of integers.
    
    // Attempt to access an element safely.
    match vec.get(7) { // Note: There is no element at index 7.
        Some(value) => println!("Found value: {}", value),
        None => println!("Error: Index out of bounds."),
    }
}

In this Rust example, instead of directly accessing an element which might lead to an out-of-bounds access (a common issue in languages like C), the get method returns an Option. This forces the programmer to handle the case where the index is out of bounds safely, preventing any form of memory corruption or access violation.

Memory safety is a crucial feature in programming languages that helps developers avoid bugs related to memory usage within a computer program. These bugs can lead to severe security vulnerabilities, including buffer overflows and use-after-free errors, which can be exploited by threat actors to gain unauthorized access or cause a system to crash.

Languages that are not memory-safe, such as C and C++, require developers to manually manage memory, which increases the risk of introducing memory safety bugs. On the other hand, memory-safe languages have features that automatically manage memory, either at compile-time or runtime, to prevent these types of errors.

NSA's Recommendations for Memory-Safe Programming Languages

The NSA has recommended a shift from languages like C and C++ to alternatives that are considered memory-safe. The updated list of memory-safe programming languages includes C#, Go, Java, Python, Rust, and Swift. These languages are designed to reduce the likelihood of memory-related vulnerabilities in code by managing memory automatically and incorporating checks to protect against memory errors.

The NSA's criteria for evaluating memory-safe programming languages are based on their ability to prevent memory safety vulnerabilities through automatic memory management and checks. These languages do not rely solely on the programmer to ensure memory safety but instead provide built-in mechanisms to catch and handle memory-related errors.

The President's National Cybersecurity Strategy outlines 2 strategic approaches to achieve the goal of addressing undiscovered vulnerabilities that malicious actors can exploit:

  1. Reduce the attack surface in cyberspace that our adversaries can exploit by preventing entire classes of vulnerabilities from entering the digital ecosystem and

  2. Anticipate systemic security risk by developing better diagnostics that measure cybersecurity quality.

To achieve #1, switching to a memory-safe programming language is low-hanging fruit. Software and hardware developers are best positioned to do this, although it may require learning new languages that they may not already be familiar with. Fortunately, there has never been a better time to learn a new programming language considering the accessibility of online resources available: YouTube, Coursera, Udemy, Generative AI, and of course good old-fashioned books, among others.

To achieve #2, better metrics and measurability practices of software must be developed. I suspect Generative AI will play a big part of this going forward as AI has the advantage of 'understanding' the context of code while determining its overall cybersecurity quality. I think it is important for the software engineering community to take on the difficult task of creating more rigorous structure around solving problems with code. There are virtually unlimited ways to write code to achieve a requirement, but we can reduce the overall attack surface and vulnerabilities within a codebase by following well-known design patterns and secure coding practices instead of simply writing code that just works.

The Case of JavaScript and Memory Safety

JavaScript, one of the most popular programming languages in the world, is not explicitly listed in the NSA's list of memory-safe programming languages. This omission may raise questions about its memory safety status.

JavaScript is generally considered a memory-safe language because it does not allow direct memory access and includes automatic garbage collection to manage memory allocation and deallocation. However, JavaScript can still be prone to memory leaks and other issues if not used carefully. Additionally, JavaScript environments often incorporate elements written in other languages, such as C or C++, which may introduce memory safety risks.

The Push for Memory-Safe Programming Languages

The push towards memory-safe programming languages is driven by the need to reduce the attack surface that threat actors can exploit. Memory safety issues were the second leading cause of vulnerabilities in 2023, with a significant percentage exploited as zero-days. By adopting memory-safe languages, organizations can mitigate these risks and enhance the overall security of their software products.

However, transitioning to memory-safe programming languages can be challenging. Languages like Rust have steep learning curves, and the adoption of new languages may slow down development initially. Yet, the long-term benefits of fewer vulnerabilities and more maintainable code are considered worthwhile.

The NSA's updated list of memory-safe programming languages serves as a crucial guideline for organizations and developers aiming to enhance the security of their software. Memory safety is a vital aspect of cybersecurity, and the adoption of languages that provide robust memory management features is a step towards reducing the prevalence of security vulnerabilities.