Two-Thirds of Security Bugs Are One Bug Class
Chrome's security team puts the share of serious vulnerabilities that are memory-safety problems at roughly seventy percent, and the number has been stubborn across years of sanitizers, fuzzing and code review. That persistence is the part that matters. It means the bug is not a run of bad luck inside a healthy system; it is a property of a language that hands the programmer raw addresses and trusts them. You cannot lint a category away. You migrate, and migrations are priced in engineer-years.
The Bugs Are a Property of the Language
C and C++ give you a machine model that maps almost directly onto the hardware, and they charge for it by leaving bounds, lifetime and aliasing to you. Array indexing does not check. A pointer that outlives its allocation stays a pointer. Signed overflow is undefined behaviour, which means the compiler is entitled to assume it never happens and optimise accordingly. None of these are oversights in the design; they are the price of the abstraction level, and they are why the same languages still run operating systems, browsers and databases.
The consequence is that memory-safety failures arrive in families. A use-after-free, a heap overflow and an uninitialised read all produce the same shape of vulnerability: attacker-controlled bytes interpreted as a length, an index or a pointer. Fixing one instance leaves the family intact, which is why the same codebase keeps producing them even after the specific bug is patched and a regression test is added. This is the distinction that matters in the argument: not "some bugs are memory-unsafe" but "the class regenerates".
Rust's contribution is not that it is faster or newer. It is that ownership and borrowing move lifetime, aliasing and bounds discipline into the compiler, so a use-after-free or a data race is a build error rather than a CVE. The guarantee is not absolute. An unsafe block switches it off for the code inside it, and the standard library contains such blocks. But the unsafe surface is small, enumerable and auditable in a way that ten million lines of C is not, and that is the actual product.
Three Options, and Only One Is a Plan
There are three things an organisation can do about this, and they are not equivalent. Mitigation reduces the odds that a bug becomes an exploit. Isolation reduces what an exploit can reach. Rewriting reduces the number of bugs. Only the third changes the incidence rate, and all three cost money, so the honest question is the exchange rate between them.
- Mitigate: address-space randomisation, stack canaries, control-flow integrity, hardened allocators, and on recent ARM cores memory tagging, which attaches a four-bit tag to each allocation and checks it on access. Four bits means sixteen values, so a corrupted pointer slips through roughly one time in sixteen. Cheap, deployable, and it does nothing about the bug existing.
- Isolate: put the parser in its own process, drop privileges, sandbox the decoder so a compromise costs a renderer rather than the machine. This is the strategy that bought browsers a decade. It bounds the damage and leaves the code exactly as broken as it was.
- Rewrite: port a module to a language where the class cannot be expressed. Expensive per line, permanent in effect, and it introduces new bugs while removing the old family, which is why the sequence matters as much as the choice.
Most mature programmes run all three at once, and the failure mode is running the first two forever. A mitigation roadmap with no boundary-shrinking roadmap is a subscription to the same bug class with better instrumentation.
What Incremental Adoption Actually Looks Like
Nobody rewrites a decade-old codebase, and anyone proposing it has not costed it. What works is narrower: find the code that parses untrusted input, because that is where the bug density is highest and the interface is usually the smallest. Image decoders, font parsers, archive readers, network protocol state machines and media containers all sit behind a byte-stream-in, structure-out boundary that is easy to define and cheap to wrap. That is why the visible ports cluster there rather than in the scheduler or the renderer.
The tax is at the boundary. Every foreign function call is unsafe by contract, because the other side is C and C has no types the compiler can trust. You pay for wrapper layers, ownership rules that survive the crossing, and a build system that now has two toolchains and two sets of dependencies. Pay it per interface, once, and the cost is bounded; pretend the boundary is free and you will ship a segfault in a language that was supposed to prevent them.
The useful metric is not lines of Rust written. It is lines of memory-unsafe code reachable from attacker-controlled input, tracked over time, with the number going down. That metric is measurable, it survives reorganisations, and it makes the trade visible to people who fund it. Everything else, including the choice of language, is an implementation detail underneath it.
What I Think
The migration is worth doing, and the slogan version of it is useless. "Just use a safe language" is not a plan for a codebase with a decade of C in it, because the expensive part is not learning Rust, it is the interfaces, the test coverage at the boundary, and the political cost of touching code that has been quietly load-bearing since before the current team arrived. The teams that succeed treat it as a programme with a metric, not a rewrite with a deadline.
My bias, stated plainly: I would rather fund the boring version — shrink the attack surface module by module, keep the mitigations running while you do it, and accept that the last ten percent of the code may never be ported. A codebase that is eighty percent safe and honest about the other twenty is in better shape than one that shipped a rewrite and lost two years of features to prove a point. The bug class does not care about your intentions, only about how much code still trusts a pointer.