February 26, 2024·6 min read·engineeringcppsecuritymemory-safetypolicy

ONCD asks C++ manufacturers to account for memory safety

The White House recommendation concerns preventing recurring vulnerability classes. For C++ systems, the useful distinction is between resource cleanup and the validity of every access to the resource.

A resource can be released exactly once while another part of the program still holds a pointer to it. That distinction matters when assessing what modern C++ already provides and what a memory-safety strategy still needs. The 26 February announcement from the Office of the National Cyber Director calls on software manufacturers to adopt memory-safe languages. The technical question for a C++ product is which memory obligations its chosen abstractions enforce and which remain with callers.

RAII remains the strongest argument for C++ in my view. Associating release with an object's lifetime gives ordinary scope exit and exception handling a coherent resource policy. That benefit is real, but it should be described precisely when deciding whether the language meets a product's security requirements.

The report addresses recurring categories of defect

Back to the Building Blocks distinguishes spatial memory errors from temporal ones. Spatial errors access memory beyond an object's bounds. Temporal errors involve access at an invalid time or state, including after release. The report favors memory-safe languages for new products and discusses replacing important components within existing systems. It also considers hardware protection and formal methods as complementary approaches.

For C++ architecture, those categories give a more useful starting point than a debate about whether an implementation looks modern. A class can express ownership while its member functions expose non-owning access. An owning container can retain storage while a caller computes an invalid position within it. The access rules need to cover those relationships as well as the owner's release behavior.

This is my interpretation of the report's practical demand. A product security argument should name the property its implementation establishes. If the property is automatic release, it should say so. If it also claims that every access remains within a live object, it needs evidence for that additional claim. Naming an abstraction is insufficient when the claim concerns behavior beyond that abstraction's own contract.

Ownership and borrowed access have different lifetimes

The C++ working draft N4950 defines unique_ptr as an owning abstraction with observers and modifiers. An observer can expose the stored pointer. A modifier can replace the owned resource. These operations serve legitimate purposes, but their composition requires a caller to preserve the validity of any non-owning access it retains.

An API returning such access needs an explicit lifetime relationship. The caller may be allowed to use the object only while its owner remains unchanged, or only during one callback. A resource owner can enforce its own release policy without enforcing every caller's interpretation of that borrowing rule. The distinction is part of the interface contract.

This becomes more difficult when ownership changes in a different part of the program. The code holding a borrowed pointer may have no local operation that visibly ends access. Another component can change the owner. Reviewing correctness then requires understanding both operations and the circumstances in which they can occur. The cost comes from maintaining that relationship across future changes.

The report's recommendation is relevant to that maintenance burden. An architecture that relies on developers reconstructing every borrowing relationship has a different prevention story from one whose implementation rejects invalid relationships. The appropriate comparison includes what the language and libraries can check, together with any remaining manually maintained assumptions.

A bounds argument needs the actual access path

Spatial safety has a similar separation. Owning enough storage for a collection does not establish that a particular index selects one of its elements. A size value can provide the information needed for a check, but the access must actually respect it. Reviewing the allocator alone would omit the operation where the bound matters.

For a component that accepts input lengths, the relevant contract includes how those lengths relate to the buffer used. It must also account for calculations that derive offsets or sizes. An allocation and a later index can each look plausible in isolation while disagreeing about the number of elements represented. The interface should preserve the relationship instead of leaving the reader to infer it from parallel variables.

That is an engineering inference, without claiming that the report prescribes one C++ container or checking library. Different implementations can enforce the relationship in different ways. The product-level argument needs to identify which method applies to the code that handles untrusted data. A general statement that the code uses containers supplies less information.

I have more confidence in a narrowly stated guarantee that reviewers can inspect than in a claim that a codebase's style establishes safety. A modern abstraction is useful when its contract removes a specific obligation. The remaining obligations should stay visible in the design account.

Migration can be scoped to an interface

The earlier joint guidance, The Case for Memory Safe Roadmaps, discusses self-contained component replacement and security-sensitive code. It also acknowledges staffing and performance considerations. Its recommendations concern planning a transition within real products, rather than assuming that every existing implementation can be replaced in one operation.

A component boundary is a useful unit for this work because it can have an independently described input and output contract. Replacing a parser, for example, requires preserving the accepted format and rejection behavior. The replacement's integration must also specify the ownership of anything returned to a C++ caller. Otherwise, an internal safety improvement can leave an ambiguous boundary that callers still misuse.

An implementation choice should therefore include its foreign-function interface where one exists. A safe implementation language cannot determine the lifetime of an object after a caller violates the interface's assumptions. A migration proposal should identify those assumptions and arrange for the surrounding design to preserve them. The integration review also needs to say which side reports allocation failures and which side performs release. The safety claim can then be scoped to the region that actually has the relevant guarantees.

This gives an existing C++ product a concrete way to discuss improvement without inventing an all-or-nothing replacement requirement. Some responsibilities can remain in C++ while a particular input-processing component changes. The benefit needs to be described in terms of the invalid operations removed from that component and the integration obligations that remain.

Mitigation remains useful for retained code

The roadmap guidance distinguishes measures intended to reduce defects from measures intended to reduce their consequences. It discusses testing and analysis alongside sandboxing and hardware approaches. It explicitly treats mitigations as useful for code that has not transitioned or cannot yet transition. That supports a practical plan for retained C++ components without presenting those measures as equivalent to eliminating a defect class.

This distinction affects how a security claim should be written. An isolation boundary may limit what a compromised component can access. It does not establish that the component cannot contain memory corruption. A test campaign may discover invalid accesses in the executions it exercises. Its result needs to be interpreted within that method's coverage.

A product plan can use both kinds of measure. It can improve the retained implementation while replacing a selected component. What matters is that each measure has an identified purpose and an accountable owner. Combining them into a single undifferentiated statement of safety makes it harder to assess what has changed for the customer.

Product responsibility extends beyond the language debate

ONCD connects its recommendation to shifting responsibility toward organizations with control over product design. A customer generally cannot change the ownership model inside a purchased component. The manufacturer can choose the implementation and fund changes to it. That difference in control explains why the recommendation belongs in architecture and product planning.

The announcement is a recommendation, without a universal C++ prohibition or a specified replacement deadline. It also calls for better research into software security measurement. The immediate obligation for an engineering organization is to make its own reasoning concrete, including why a retained implementation meets a requirement and what it still asks developers to guarantee manually.

For C++, RAII supplies a strong account of resource release under its ordinary control-flow model. The security assessment must also cover accesses made before release and references retained afterward. A roadmap that records those separate responsibilities gives component replacement and retained-code improvements a specific target. It lets a reviewer ask which previously manual condition the finished change now enforces.