December 6, 2023·6 min read·engineeringcsecuritymemory-safety

Memory-safe roadmaps make C interoperation a planning obligation

December's joint guidance asks manufacturers to account for existing C dependencies, migration boundaries, and the organizational work behind memory safety.

Changing the language used for a new component does not remove the memory obligations of the libraries it calls. The joint memory-safety guidance published today gives that boundary a place in the migration plan. A manufacturer needs to describe its C and C++ dependencies along with its choice of a memory-safe language.

CISA's December 6 announcement accompanies The Case for Memory Safe Roadmaps, a guide coauthored with NSA, FBI, and international cybersecurity authorities. It asks manufacturers to publish plans for reducing and eventually eliminating memory-safety vulnerabilities. The requested plan includes engineering work and executive responsibility.

C was my first language, and much of its continuing importance lies in interoperation. That makes the guidance's treatment of existing libraries particularly relevant to me. A useful transition plan has to acknowledge the code that remains behind those interfaces and explain how callers satisfy its memory requirements.

The target is a class of defects

The report discusses buffer overflows, use after free, uninitialized memory, and double free. These errors differ in mechanism, but each permits memory to be accessed or managed outside the intended rules. Their security consequences can include disclosure, corruption, and execution with the affected process's privileges.

The agencies distinguish reducing the prevalence of these defects from reducing their impact. Training, testing, and analysis attempt to prevent or find mistakes. Non-executable memory, address randomization, and other protections make some consequences harder to achieve. Neither category is presented as a complete substitute for language-level guarantees.

This distinction is useful when discussing an existing C codebase. A mitigation can be valuable without establishing that an invalid access is impossible. Calling every protective measure memory safety obscures the difference between stopping a bug from being expressed and making an expressed bug harder to exploit.

The report retains a role for these mitigations in software that has not yet moved to a memory-safe language or cannot currently do so. It does not ask manufacturers to abandon protection while they prepare a transition. The roadmap instead needs to relate near-term protection to longer-term changes in how code is produced.

Existing libraries qualify the guarantee

The guide expects hybrid systems for the foreseeable future. Memory-safe programs will continue calling libraries written in C and C++, and unsafe applications will sometimes call memory-safe libraries. A language choice therefore describes only part of a mixed system.

At an unsafe call boundary, the caller must understand and limit inputs according to the callee's memory bounds. The report also names difficulties involving data marshalling, error handling, concurrency, debugging, and versioning. These are concrete integration requirements that remain after the new source compiles.

A declaration can describe a pointer and a length while leaving the caller responsible for their relationship. If a wrapper passes inconsistent values into an unsafe component, the wrapper's implementation language does not automatically repair the callee's interpretation. The relevant safety claim has to include the boundary conditions under which the call occurs.

For me, this is the strongest reason to treat interoperation as design work in its own right. A foreign-function interface can preserve a useful existing library while concentrating obligations into a smaller boundary. Its value depends on that boundary validating what the unsafe code assumes.

This is also why counting new memory-safe files is an incomplete measure of progress. A small amount of reachable unsafe code can remain security-sensitive. The plan needs to identify which operations still depend on manual memory reasoning and how the surrounding system limits their inputs.

Component replacement supplies a bounded transition

The guidance suggests starting with new or smaller projects and replacing self-contained components. It describes comparing the old and replacement component's output before retiring the old implementation. This gives migration an observable unit of work.

A component boundary is useful because it can preserve the externally required behavior while changing the implementation responsible for memory management. The comparison needs an account of that behavior, including errors and unusual inputs. Agreement on a typical successful result is only one part of compatibility.

The report also suggests incremental traffic movement for highly parallel systems. That is an operational transition technique, distinct from a language guarantee. It helps establish whether a replacement performs the required work under increasing use, while the language choice addresses a particular defect class.

This separation avoids assigning every migration benefit to the language. A replacement can introduce functional errors even while eliminating a memory-management pattern. The roadmap's phases and outcomes need to cover both the safety objective and continued product behavior.

Prioritization depends on risk and available expertise

The guide identifies security-critical code as a priority, including processing user-generated content, authentication and authorization, and low-level operations. It qualifies that advice by requiring the team to have the relevant language expertise. A highly exposed component is valuable work, but it is also a poor place to hide an unsupported learning schedule.

Training and staffing therefore belong in the same plan as component selection. The report calls for dedicated learning time, examples, and integration of new staff with experienced maintainers. It also recognizes that hiring only people who already know the chosen language may be impractical.

The practical implication is a dependency between organizational preparation and technical scope. A roadmap that names a security-critical replacement without allocating time for tooling and debugging is missing work required to deliver that replacement. The omission does not become smaller because the desired language is safer.

I find the report's demand for executive ownership appropriate here. Engineers can describe the unsafe boundary and propose a replacement. Leadership controls whether the organization funds training, pauses competing work, and maintains support for the product during the transition.

Language selection remains use-case dependent

The guidance recommends evaluating multiple memory-safe languages. It explicitly says no single language fits every programming need. Architecture and tooling matter alongside performance, and a memory-safe implementation still needs a secure development process.

This is particularly relevant to software that currently uses C for constrained environments. The report identifies operational technology and low-power devices as areas where runtime availability and resources can limit language choices. It asks for evidence of performance, reliability, and real-time properties in those transitions.

Garbage collection is one implementation consideration, not a definition of every memory-safe language. The report discusses its possible latency and resource effects while also including languages that use different memory-management approaches. A roadmap should identify the actual runtime model being considered.

The guide also notes that some languages provide exceptions to their normal protection, citing Rust's unsafe facilities. The existence of those facilities does not make the safe-language choice meaningless, but it makes the scope of the guarantee something the design must describe. An unsafe operation remains an obligation even when its surrounding source uses a different language.

Public milestones make the plan assessable

The requested roadmap includes phases with dates and outcomes. The report gives language evaluation, a pilot, threat modeling, and refactoring as possible phases. It also asks for a date after which new systems will use memory-safe languages.

That creates two distinct planning questions. Existing components need an order of treatment. New development needs a policy that prevents the same class of exposure from continuing to grow without an explicit decision. A plan that covers only one of these questions leaves the other unresolved.

The external-dependency plan is especially important for C users. It requires the roadmap to cover libraries supplied outside the organization, including open-source dependencies. The safety of a product cannot be inferred solely from the language used by the manufacturer's own developers.

The report further asks for updates on progress and challenges, along with detailed vulnerability classification. Its CVE support recommendation calls for timely CWE information so the industry can distinguish memory-safety defects from other problems. That connects the roadmap to evidence about the defects it is intended to reduce.

A migration boundary needs an owner

The report's request for a named business leader makes resource commitments visible. The engineering plan still needs identifiable responsibility for the interfaces that remain unsafe. Otherwise, a component can be described as migrated while its assumptions are distributed across several callers.

A useful boundary specification says what storage the caller supplies, which operations the callee may perform, and what remains valid after return. Those are the kinds of memory obligations that existing C interfaces require an implementation to respect. Moving them into an explicit integration contract makes the remaining manual reasoning inspectable.

The December guidance gives this work a place in a manufacturer's public plan. It treats existing unsafe libraries as an expected part of migration and asks manufacturers to explain their handling. For a system that retains C dependencies, progress includes reducing the reachable unsafe code and stating the memory conditions at every boundary that remains.