cp, mv and rm

Ubuntu 26.10 shipped this week with every core utility rewritten in Rust. The three that took longest were cp, mv and rm, held back by a kind of bug Rust can’t see, and GNU had already paid for that kind of bug once, in 2002.

Ubuntu 26.10 came out on 15 October, and one line in the release notes closes out a project Canonical started last year: the default core utilities “now run entirely on the Rust-based uutils implementation.” ls, cat, chmod and most of the rest switched over in 25.10. The holdouts were cp, mv and rm. They spent the whole 26.04 LTS cycle on the GNU versions, and as of this release they’ve crossed too.

Look at the order. Of everything in coreutils, the last three to move were the one that overwrites, the one that relocates, and the one that deletes. If you wanted to rank Unix commands by how badly a mistake hurts, you could do worse than read off the migration schedule.

what held them back

Canonical paid Zellic to audit uutils in two rounds, December 2025 through March 2026. The April update on Ubuntu’s Discourse counted 113 issues, 73 in the first round and 40 in the second, and a block of CVEs to go with them. Most were fixed in time for 26.04. What was still open in April was eight time-of-check to time-of-use issues, and those are what kept cp, mv and rm on GNU.

TOCTOU is the oldest race in Unix. A program checks something about a path (is it a regular file, is it a symlink, do I own it) and then acts on the path. Between the check and the act, someone else changes what the path points to. Tech Times walked through one of the uutils CVEs and it is the textbook case: cp checked whether a path was a symlink, then opened it without O_NOFOLLOW. Swap a symlink in during the gap and cp follows it somewhere it was told not to go.

Rust can’t help with this, and nobody serious claimed it could. The borrow checker reasons about memory inside one process. This race lives between two system calls, out in a filesystem that every other process shares. Tech Times’ read of the audit was that many of the CVEs weren’t memory-safety bugs at all but places where uutils behaved differently from GNU. The fix was architectural: open the thing once, hold the file descriptor, and do everything relative to it, instead of trusting a path string twice.

Even after that, the crossing wasn’t clean. It’s FOSS reported that in July a uutils cp broke Ubuntu’s live image builds and had to be reverted and fixed.

GNU already paid for this

Here’s the part I like. In 2002 GNU fileutils, the package that later folded into coreutils, got CVE-2002-0435: a race in recursive delete and move. Move a low-level directory up the tree while rm -r is descending through it, and rm could chdir to a .. higher than it expected, potentially as far as the root, deleting as whoever ran it.

GNU fixed it. Then GNU kept fixing that family of bug, release after release, until the fixes stopped looking like fixes and started looking like how rm is written. Nobody wrote them down as a spec. They were a spec anyway.

uutils has a rule for this. It aims to be a drop-in for GNU and treats any difference as a bug. That’s Chesterton’s fence written down as project policy. G. K. Chesterton’s version, from The Thing in 1929, has a reformer who finds a fence across a road and can’t see the point of it. The wiser reformer tells him to go away and find out why it was put up, and only then come back and talk about taking it down. GNU’s rm is a road full of fences, and most of them have no sign. A rewrite learns why each one was there the expensive way. It takes the fence down and waits to see what comes through.

the scars are the spec

A rewrite doesn’t port the code. It has to earn the scars again. The memory bugs Rust removes are real, and they’re the reason to do this at all. But the bugs it can’t remove are the ones the old code had already been punished for, and the record of that punishment isn’t in POSIX. It’s in twenty-four years of commits, a handful of old CVE entries, and behaviour people stopped noticing because it worked.

That’s my hypothesis for every rewrite of old infrastructure, not only this one. The memory bugs go on day one. Then you spend a few years rediscovering the logic bugs the original had already fixed, roughly in order of how much they hurt. An audit is how you buy that time down. Canonical bought it, then waited an entire LTS cycle on the three commands that destroy things, and shipped when the races were closed. That’s the right order to do it in. It’s also a cost nobody puts in the “rewrite it in Rust” pitch.

The 26.04 release notes explain how to switch back to GNU if you want to. I wouldn’t bother. The fences are back up.


Sources: Ubuntu 26.10 release notes ↗ · An update on rust-coreutils, Ubuntu Discourse ↗ · Ubuntu 26.10 ships 100% Rust coreutils; audit found 44 CVEs beyond Rust’s scope, Tech Times ↗ · Ubuntu’s Rustification has a new milestone, It’s FOSS ↗ · Ubuntu 26.10 completes the switch to Rust coreutils, OMG! Ubuntu ↗ · CVE-2002-0435, Debian security tracker ↗ · Taking a fence down, on Chesterton’s The Thing (1929), Society of G. K. Chesterton ↗