NSA puts language choice inside memory safety policy
NSA recommends memory-safe languages where possible while retaining hardening for existing software. Its November guidance distinguishes language guarantees, analysis tools, and exploit mitigations.
Memory corruption is often discussed as a sequence of individual programming mistakes. The NSA announcement on November 10 treats it as an architectural concern as well. NSA recommends memory-safe languages when possible and additional hardening through compilers, analysis tools, and operating-system settings. That recommendation changes the level at which responsibility is assigned, because language selection determines which checks individual developers must remember to write.
The accompanying information sheet, available in a preserved November 2022 version 1.0 copy, explains the mechanisms and limits behind that recommendation. It identifies C and C++ as languages that give programmers substantial control over memory while relying heavily on them to check references. It does not promise absolute protection from adopting a different language or from enabling additional defenses.
C was my first language, and its small vocabulary still appeals to me. Its role in interoperation also gives it lasting practical importance. Neither observation removes the obligation to account for object bounds and lifetimes. The guidance is useful because it asks who enforces those obligations across a whole program.
Bounds and lifetimes fail in different ways
The sheet describes a buffer overflow as access outside an array's bounds. That is a spatial problem. Allocation mistakes introduce another class of failure, because a program can access storage after it has been released or attempt to release it again. A pointer can identify an address while the object that once occupied that address no longer exists.
This difference matters for interface design. Passing a length alongside a pointer can describe an accessible range, but the length alone does not establish that the range remains alive. Likewise, arranging a clear release point does not prove that every index used before release is within bounds. These are distinct obligations even when the same function is responsible for both.
The NSA sheet also discusses uninitialized variables, leaks, and race conditions. Its memory-safety discussion therefore covers both exploitable corruption and problems that undermine ordinary operation. Incorrect results and crashes matter even when an attacker has not found a way to convert the defect into code execution.
That is an important practical reason to avoid interpreting the recommendation solely through exploit demonstrations. A defect may have operational consequences before its security significance is understood. A language mechanism that prevents a class of invalid access can reduce those failures without requiring an engineer to predict a specific attack first.
Language protections change the default obligation
The sheet describes memory-safe languages as providing automatic protections through compile-time and runtime checks. It names C#, Go, Java, Ruby, Rust, and Swift as examples. This is a list of alternatives with different mechanisms and costs, not a claim that they share an identical implementation strategy.
The document discusses garbage collection and bounds-checking costs, while also acknowledging that a non-memory-safe program may incur costs for checks its developer adds manually. The relevant comparison is consequently between programs with the protections they actually require. A program that omits necessary checks is not an equivalent implementation merely because it runs fewer instructions.
There is also an engineering cost to adoption. The guidance notes that existing developers need training and that newly hired specialists still need to learn the application and its domain. These are explicit qualifications in the recommendation. Treating a language change as a one-line dependency update would ignore the organizational work the document itself identifies.
My preference is to separate those adoption costs from the guarantee being sought. A difficult transition can still reduce a class of defects. Conversely, choosing a language because it is familiar does not establish that its runtime enforces the properties an application needs.
Unsafe boundaries remain part of the system
The sheet explicitly recognizes unsafe facilities within otherwise memory-safe languages. It also notes that such languages can use libraries written in languages without the same protections. Those boundaries can reintroduce unsafe memory behaviour into the complete program.
This qualification is relevant to C's interoperation role. Moving an application layer does not automatically change the implementation of every library it calls. An interface still needs rules for ownership and the duration of access to passed storage. The boundary is a location where the assumptions of one component must satisfy the requirements of the other.
The guidance's useful point is that unsafe operations can become more localized. Some languages require explicit annotation, making these regions visible to programmers and reviewers. Localization does not prove those regions correct, but it changes the amount of code that must be examined under weaker guarantees.
For an existing system, this supports a more precise question than asking whether the product is written in a safe language. The relevant question is which components and operations receive the language's protections. A native library reachable from a protected application remains part of the security analysis, even if most application source uses different memory-management rules.
Analysis finds defects under its own limits
NSA distinguishes static analysis from analysis of an executing program. Static analysis can inspect source throughout development, but its warnings can include false positives. The sheet also says even strong static tools find only part of the memory problems in simple programs. Running a tool and receiving a short report is therefore not proof that the remaining source is memory-safe.
Dynamic analysis observes executions. It can identify problems that occur along the paths exercised, but its coverage depends on those executions. Code that is never reached during a run cannot receive the same observation. The guidance recommends using multiple analysis tools because their strengths and weaknesses differ.
The sheet also describes fuzzing as a way to generate unusual inputs that expose failures. A crash found through those inputs can reveal a defect worth investigating, but the existence of a crash is not itself a complete account of exploitability. Attackers may continue examining the failure to discover what control it gives them.
This is a distinction between evidence and guarantees. Analysis produces evidence about a program, with limits determined by the method and the paths considered. A language rule can prohibit a class of operation across ordinary code before a particular failing input has been found. The guidance recommends both approaches because they contribute different forms of protection.
Hardening limits what exploitation can accomplish
The information sheet treats anti-exploitation features separately from defect-finding tools. It discusses Control Flow Guard as restricting execution destinations. It describes address-space layout randomization as making memory locations less predictable and data execution prevention as preventing data from executing as code.
These mechanisms can make exploitation harder without repairing the original invalid access. The guidance acknowledges that bypassing ASLR and DEP is possible. Its claim is that they increase the difficulty of developing an exploit and reduce its chance of success, including in programs written with memory-safe languages.
A bounds violation that merely crashes under one configuration remains a defect. A later environment might expose different consequences from the same underlying access. This is why protection against an exploit technique should not be described as proof that the source obeys object bounds or lifetime rules.
There are concrete toolchain examples available in this year's releases. GCC 12.1's announcement describes automatic stack-variable initialization through -ftrivial-auto-var-init and support for dynamic object-size analysis. Clang 15's release notes describe C structure-layout randomization and more precise flexible-array controls. These are specific compiler capabilities, each with a stated purpose and scope.
A strategy needs to identify its protected code
The NSA recommendation has two related consequences for C systems. New code can make language-level memory protection part of its design requirements. Existing code can use analysis and runtime hardening while its architecture determines which parts can receive stronger guarantees. The document recommends a strategic shift where possible, with additional defenses remaining relevant throughout.
That is consistent with valuing C's established binary interfaces without assuming every new implementation must use the same language. An interface contract and the language used to implement it are separate design choices. The actual protection depends on the implementation behind that contract and on the unsafe operations reachable through it.
For systems with native dependencies, the resulting technical work includes describing which side owns passed storage, how long the other side may retain access, and which lengths accompany buffers. Those rules remain necessary where language guarantees stop at an external boundary. The guidance provides a reason to reduce such boundaries where possible and to give the remaining ones more concentrated scrutiny.