What we learned classifying every sudo CVE
If you are going to put new code in the privileged path, you owe the 27 years of history that came before it. Here is the corpus, the method, and what it changed in our design.
If you are asking an enterprise to put new code in the privileged path on every host they own, you are asking them to replace the most attacked program on Linux. sudo has twenty-seven years of CVEs behind it, starting at CVE-1999-0958, and it is running on every server your customers have.
“We wrote it in Rust” is not an answer to that. Memory safety addresses one class of the history and leaves the rest untouched. So we did the unglamorous thing: we pulled the entire corpus and classified every entry against our own architecture.
The corpus is 251 CVEs — and that number is misleading
Be careful with it, including when we use it. Searching the CVE database for sudo returns 251 records, but roughly 170 of those are what we classify as arch-different-product: unrelated software, kernel bugs, or appliance misconfigurations whose description merely contains the word “sudo.” A Crestron appliance deriving predictable passwords is in that list. So is a Linux NIC driver refcount bug.
Quoting 251 as though it were the threat surface would be marketing. The number that means something is what remains after triage, and whether any of it is still open.
The method
Every CVE in the corpus lands in exactly one bucket, recorded in a manifest that is the single source of truth:
- arch-different-product — not about the sudo tool at all.
- covered:architecture — structurally cannot apply, with a written rationale naming the mechanism we do not have.
- covered — could apply; there is a regression test pinning the behaviour.
- applicable-queued — could apply; not yet addressed. Nothing here is claimed safe.
The manifest is enforced in CI. The corpus files and the classification must agree in both directions, so a new CVE cannot land un-triaged and a classification cannot drift away from the evidence. If someone adds a CVE without classifying it, the build fails.
That last part is the whole point. A spreadsheet of CVE analysis is a document that was true once. A test that fails the build is a property that stays true.
What “structurally cannot apply” means
Most of the genuine sudo CVEs fall here, and the reason is that we do not ship the parts that break.
There is no setuid binary anywhere in the product — enforced by a packaging check, not by convention. The client carries zero authority; a non-setuid daemon is the only decision point. There is no runtime sudoers parse, because policy is compiled ahead of time and MAC-verified before the socket opens. Whole families of privilege-escalation and parser bugs cannot reach an architecture that has neither mechanism.
We also hold ourselves to one audited privilege transition, gated by a CI script that fails if a second one appears.
The exercise found real bugs
This is the part worth being honest about, because a classification pass that finds nothing usually means the pass was not serious.
CVE-2014-9680 — TZ forwarded unsanitised. We had the same issue. Fixed: a path-like TZ is now dropped before it reaches the target.
CVE-1999-1496 — an information leak via error-message differences. Our daemon pinned the command as root before policy evaluation, so an unopenable command and an unknown runas user produced a distinguishable reply. That is a file- and user-existence oracle. Both paths now route to a client-coarse denial that is still distinct in the audit record. A timing side-channel remains, as it does in sudo; we say so rather than claiming the class is closed.
CVE-2013-1775 — the time-window clock bypass. Our fix checks CLOCK_REALTIME against a CLOCK_BOOTTIME anchor, so a stepped clock is detected and a time-windowed rule fails closed rather than permitting.
Where it stands
The applicable-queued set is empty. Every classifiable sudo and sudo-rs CVE is backed by either a regression test or an inspection-verified architecture rationale.
That is a claim you should be able to check rather than take from us, which is why the manifest ships in the repository and the CI gate that enforces it is public. If you are evaluating this seriously, ask for it — the file is the argument, and it is more persuasive than any slide we could put in front of you.
It is also, deliberately, a snapshot that expires. A new sudo CVE lands and the build goes red until somebody triages it. That is the only version of this claim worth making.