Memory Safe or Bust?
Aaron Guzman
Posted on April 1, 2024
by Aaron Guzman and Jason C. McDonald
The recent release of the Office of the National Cyber Director [ONCD] report on adoption of memory-safe languages, entitled "Back to the Building Blocks: A Path Toward Secure and Measureable Software", has stirred emotions, causing uncertainty in the software field. While there is little doubt that increased focus on software security is in everyone's best interest, many developers wonder whether switching to memory-safe languages is really that practical.
First, a few definitions. Memory safety involves proper allocation, management, and freeing of memory, something that is involved in even the most basic of data structures. Some languages, like Python and Java, handle memory management behind-the-scenes, so the developer need not concern themselves with it in most cases. However, when developing software that runs at high performance or in low-memory environments, more fine-tuned control is warranted. Languages like C and C++ allow the developer to manage memory manually, generally with pointers.
Unsafe memory handling can lead not only to difficult bugs like segmentation faults, memory leaks, and buffer overruns, but create a plethora of opportunities for bad actors to access private data and execute arbitrary code. It's possible for unsafe memory handling to crop up anywhere, but in languages without strict safeguards in place, it is far easier for simple human error on the part of the developer to create significant stability and security issues. Memory-safe languages like Rust and Go help mitigate this risk by providing much the same degree of control over memory, while enforcing strict safeguards around how memory is allocated, managed, and freed.
That leaves the question: just how feasible is it to transition from traditional languages like C and C++ to memory-safe alternatives? What are the implications for developers?
What About CI/CD?
Continuous Integration and Continuous Deployment [CI/CD] pipelines play a crucial role in enforcing code quality, especially when working with memory-unsafe languages. By integrating automated dynamic analysis tools like Valgrind or AddressSanitizer, static analysis tools like Clang Static Analyzer or cppcheck, and manual code review processes, developers can identify and mitigate many memory-related vulnerabilities early in the development lifecycle. This proactive approach helps improve code quality, reduce security risk exposures, and streamline the adoption of memory-safe programming practices.
Unfortunately, it is never possible to guarantee that code in languages like C or C++ is absolutely free of memory-related issues or undefined behavior. Differences in compilers, hardware, operating systems, standard libraries, dependencies, and other factors can conceal environment-specific bugs.
Developers should still consider a proper CI/CD pipeline to be a non-negotiable part of developing in languages like C and C++, as many issues can be avoided. Still, since that can only mitigate so much risk, we need an alternative moving forward. This is where memory-safe languages come in.
Considering Memory-Safe Languages
Languages like Rust and Go have garnered a significant following in recent years. They strike a balance between the control of C and C++ and the safety of memory-managed languages, making it possible to write system-level code that is guaranteed free of memory handling errors.
Rust, renowned for its powerful ownership system, strict compile-time checks, and zero-cost abstractions, has emerged as a leading memory-safe language. While rewriting existing codebases in Rust may involve upfront investment of time and resources, the long-term benefits can be significant. Rust's focus on safety, concurrency, and performance renders it ideal for projects necessitating high levels of security and reliability.
Meanwhile, Go's simplicity and built-in tooling make it a favored choice for web backend development and cloud-native applications. Go's runtime bounds checking and automatic garbage collection simplify memory management, mitigating risks of buffer overruns and memory leaks.
Even so, precedence presents a significant challenge to adoption of these languages. Countless implementations exist in C and C++ for crucial algorithms, design patterns, and technically challenging low-level behaviors. Off-the-shelf solutions exist for nearly anything a developer may need to do, and a mind-boggling amount of documentation and commentary exists to provide guidance. Despite recent innovations, newer languages like Rust and Go can still scarcely contend with the sheer mass of information available for more traditional languages. Time and effort are the only cure. In the meantime, adopters of memory-safe languages must be prepared to invent, innovate, and contribute to the broader body of knowledge.
The adoption of memory-safe languages signifies a paradigm shift in software development, reflecting an industry-wide recognition of the importance of memory safety in mitigating security risks and ensuring application robustness. We urge readers who are starting their adoption journey to deep dive into CISA's The Case for Memory Safe Roadmaps for creating and publishing memory safe roadmaps within their organizations. As memory-safe programming languages are embraced, the way is paved for broader adoption and standardization across the software industry.
Are C and C++ Dead, Then?
The rise of memory-safe languages does not necessarily signify the demise of languages like C and C++. Rather, we are seeing a shift towards safer and more robust programming practices, akin to the shift from assembly languages to programming languages. While memory safety is a critical consideration, the continued relevance of C and C++ in domains such as embedded systems, performance-critical applications, and legacy codebases underscores their enduring significance. We can no more shed our history than we can shed assembly code -- we can gradually reimplement solutions in better, more maintainable ways, but the effort requires time, and only builds on existing knowledge.
To truly move away from C and C++, semiconductor manufacturers crafting Board Support Packages [BSPs] must embrace this transition to memory-safe languages by enhancing toolchain ecosystems, fostering standardization, and investing in developer education. This transition also involves careful consideration of factors such as security requirements, technical feasibility, and long-term sustainability. Lookout for semiconductor security focused guidance in the near future that’ll bridge the gap to increase resilience in the industry.
Looking Forward
Moving to memory-safe languages is also only part of the picture. Software security in any language depends on many factors, and requires building a culture of deliberate, security-first thinking at every step of the software development lifecycle. Security cannot be left to designated persons or departments, but must be the shared responsibility of all. Standards like the OWASP Application Security Verification Standard [ASVS] and OWASP Software Assurance Maturity Model [SAMM] equip teams and organizations to improve their software security posture. To this aim, OWASP has responded to the ONCD RFI.
Despite challenges inherent in transitioning from traditional languages, the benefits of memory safety render it an appealing proposition for forward-thinking organizations and developers, ushering in the next generation of software solutions.
OWASP is a non-profit foundation that envisions a world with no more insecure software. Our mission is to be the global open community that powers secure software through education, tools, and collaboration. We maintain hundreds of open source projects, run industry-leading educational and training conferences, and meet through over 250 chapters worldwide.
Posted on April 1, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.