70% of Vulnerabilities Are Memory Safety Issues - But What If You Can't Stop Using C/C++?

The policy guidance is clear: adopt memory-safe languages. The reality is messier.

Many organizations have C/C++ code that can’t be replaced any time soon:

  • Legacy systems that are too large to rewrite
  • Vendor dependencies you don’t control
  • Performance requirements that require manual memory management
  • Embedded/IoT constraints where alternatives don’t exist
  • Regulatory mandates requiring certified codebases

So what do you do if you can’t just stop using C/C++?

Strategy 1: Harden What You Have

Compiler Mitigations:

  • Stack canaries (-fstack-protector-strong)
  • ASLR and PIE (-fPIE)
  • Control-Flow Integrity where available
  • Stack clash protection

Runtime Mitigations:

  • Address Sanitizer (ASan) in testing
  • Memory Sanitizer (MSan) for uninitialized reads
  • Undefined Behavior Sanitizer (UBSan)

These don’t eliminate vulnerabilities, but they make exploitation harder.

Strategy 2: Isolate Dangerous Code

Process Isolation:
Run C/C++ components in separate processes with minimal privileges. A compromise in the sandboxed process can’t directly escalate.

Containerization:
Limit the blast radius through container security boundaries.

WebAssembly (Wasm) Sandboxing:
Compile C/C++ to Wasm and run in a sandboxed runtime. Growing adoption for plugins and extensions.

Hardware Isolation:
Trusted Execution Environments (TEEs), enclaves, or separate hardware for sensitive operations.

Strategy 3: Incremental Migration

You don’t have to rewrite everything at once.

New Code in Safe Languages:
Every new component is memory-safe. The unsafe codebase doesn’t grow.

FFI Wrappers:
Wrap C/C++ libraries in safe language bindings with careful boundary validation.

Modular Replacement:
Identify discrete components and replace them one at a time. The Strangler pattern applied to memory safety.

Strategy 4: Enhanced Testing

If you can’t prevent bugs, find them before attackers do.

Fuzzing:
libFuzzer, AFL, Honggfuzz - continuous fuzzing of C/C++ code.

Static Analysis:
CodeQL, Coverity, PVS-Studio - catch common patterns at build time.

Penetration Testing:
Regular security assessments focused on memory safety.

What to Put in Your Roadmap

If full migration isn’t feasible, your roadmap should document:

  1. Why C/C++ remains necessary (specific justification per component)
  2. What mitigations are in place
  3. What isolation boundaries exist
  4. What testing ensures ongoing safety
  5. What the long-term migration path looks like (even if it’s 10+ years)

A roadmap that says “we can’t migrate but here’s how we’re managing risk” is better than silence.

Sam’s isolation strategies are practical. Let me expand on the interoperability patterns.

FFI and Safe Wrappers

When you can’t replace C/C++ code, you can wrap it:

Rust FFI:
Rust can call C functions directly. Create a safe Rust wrapper that:

  • Validates all inputs before passing to C
  • Handles error conditions safely
  • Manages memory ownership at the boundary

Example: The rusqlite crate wraps SQLite (C) with safe Rust APIs.

Go CGO:
Go can call C code through CGO. Similar pattern - Go wrapper validates and manages the unsafe boundary.

Python CFFI/ctypes:
Python can call C libraries. The Python wrapper handles memory management.

The Boundary Is the Danger Zone

The FFI boundary itself is where bugs happen:

  • Lifetime mismatches - Safe language thinks memory is valid; C already freed it
  • Size mismatches - Passing wrong buffer sizes
  • Type confusions - Interpreting data incorrectly across the boundary

Best Practices:

  1. Minimize the boundary surface - Fewer FFI calls = fewer opportunities for bugs
  2. Copy data across boundaries - Avoid sharing memory when possible
  3. Validate thoroughly - Check everything at the boundary
  4. Test the boundary specifically - Fuzz the FFI layer

The WebAssembly Option

Wasm deserves special attention for legacy C/C++ code:

How It Works:

  • Compile C/C++ to Wasm using Emscripten or wasi-sdk
  • Run in a Wasm runtime (Wasmtime, Wasmer)
  • The runtime provides memory isolation

Benefits:

  • Linear memory model prevents out-of-bounds writes affecting the host
  • Capability-based security model
  • Same code, different execution environment

Limitations:

  • Performance overhead (typically 1.5-2x slower than native)
  • Not all C/C++ code compiles cleanly to Wasm
  • Limited system access by design

For plugin systems or untrusted code handling, Wasm is increasingly the answer.

Sam’s point about documenting justification is crucial for the investment prioritization conversation.

When Full Migration Isn’t Feasible: The Investment Framework

Not every organization can rewrite their C/C++ codebase. The question becomes: how do you allocate limited resources?

The Priority Matrix:

Component Attack Surface Business Impact Migration Cost Priority
Network parser High Critical Medium Migrate
Crypto library High Critical High Vendor solution
Image processor Medium Medium High Isolate
Internal tool Low Low Low Harden

Decision Rules:

  • High attack surface + High impact + Reasonable cost = Migrate
  • High attack surface + High impact + High cost = Find alternative or isolate
  • Medium attack surface + Any impact + High cost = Isolate and harden
  • Low attack surface + Low impact = Harden and monitor

The “Buy vs Build” Question

Before committing to rewrite:

Can you buy a memory-safe alternative?

  • Memory-safe TLS libraries exist (rustls, boring-ssl with Go wrapper)
  • Memory-safe image processing libraries exist (image-rs)
  • Memory-safe JSON parsers exist in every safe language

Is the vendor already migrating?
If your dependency is actively being rewritten by its maintainers, waiting may be more efficient than doing it yourself.

Documenting the Decision

For each C/C++ component you’re keeping, document:

  1. Why migration isn’t feasible now (cost, dependencies, expertise)
  2. What mitigations are in place (Sam’s hardening/isolation strategies)
  3. What triggers would change the decision (breach, regulation, cost reduction)
  4. Review timeline (re-evaluate annually)

This demonstrates to auditors and customers that you’ve made a deliberate, risk-informed decision.

There’s a specific case in regulated industries where legacy code isn’t just hard to replace - it’s sometimes mandated.

The Certified Codebase Problem

In some industries, code goes through expensive, time-consuming certification:

  • Medical devices: FDA 510(k) or PMA approval
  • Automotive safety: ISO 26262 certification
  • Aviation: DO-178C certification
  • Nuclear: NRC licensing requirements
  • Financial: Some trading systems have regulatory sign-off

Recertification after a language change can cost millions and take years.

The Regulatory Reality

When regulators approved your system, they approved specific code. Changing the language potentially means:

  • New static analysis results
  • New test coverage evidence
  • New safety case documentation
  • New regulatory review cycles

For a DO-178C Level A certified system, a language change could trigger complete recertification.

What’s Feasible in Regulated Environments

1. New Systems in Safe Languages
Apply memory-safe requirements to new development. The existing certified codebase stays as-is; new systems get modern treatment.

2. Non-Certified Components First
Identify components that aren’t part of the certified baseline. These can be migrated without regulatory impact.

3. Hardening Within Certification
Some mitigations (compiler flags, static analysis tools) may be addable without major recertification if they don’t change functional behavior.

4. Long-Term Platform Migration
Plan for the next generation system to be memory-safe from inception. Accept that the current system has a bounded lifetime.

Documenting the Constraint

Your memory safety roadmap should explicitly acknowledge:

  • Which components are under regulatory certification
  • What the recertification cost/timeline would be
  • What mitigations are applied within certification constraints
  • What the platform refresh timeline looks like

Regulators and auditors understand regulatory constraints. A roadmap that says “Component X remains in C++ due to DO-178C certification; next-generation replacement planned for 2029” is credible.