惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Engineering at Meta
Engineering at Meta
T
Threatpost
P
Palo Alto Networks Blog
NISL@THU
NISL@THU
O
OpenAI News
Project Zero
Project Zero
G
GRAHAM CLULEY
P
Privacy International News Feed
A
Arctic Wolf
Microsoft Azure Blog
Microsoft Azure Blog
H
Help Net Security
M
MIT News - Artificial intelligence
T
Threat Research - Cisco Blogs
S
Security @ Cisco Blogs
Google DeepMind News
Google DeepMind News
B
Blog RSS Feed
D
Docker
aimingoo的专栏
aimingoo的专栏
博客园 - 【当耐特】
N
Netflix TechBlog - Medium
云风的 BLOG
云风的 BLOG
雷峰网
雷峰网
W
WeLiveSecurity
P
Proofpoint News Feed
腾讯CDC
Cloudbric
Cloudbric
S
Secure Thoughts
C
Check Point Blog
博客园 - Franky
T
The Exploit Database - CXSecurity.com
T
Troy Hunt's Blog
GbyAI
GbyAI
Security Archives - TechRepublic
Security Archives - TechRepublic
Application and Cybersecurity Blog
Application and Cybersecurity Blog
月光博客
月光博客
C
Cyber Attacks, Cyber Crime and Cyber Security
I
Intezer
TaoSecurity Blog
TaoSecurity Blog
L
Lohrmann on Cybersecurity
V
Visual Studio Blog
F
Fortinet All Blogs
博客园 - 叶小钗
C
CXSECURITY Database RSS Feed - CXSecurity.com
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Recorded Future
Recorded Future
C
Cisco Blogs
博客园 - 司徒正美
Stack Overflow Blog
Stack Overflow Blog
Y
Y Combinator Blog
Apple Machine Learning Research
Apple Machine Learning Research

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
What 44 CVEs Tell You About Rust's Safety Boundary
Arthur · 2026-05-19 · via DEV Community

In April 2026, Canonical disclosed 44 CVEs in uutils, the Rust reimplementation of GNU coreutils that has been the default in Ubuntu since 25.10. The disclosures came out of an external audit commissioned ahead of the 26.04 LTS release. Most of the bugs were found by code review of a single Rust codebase. None of them were caught by the borrow checker, by clippy lints, or by cargo audit.

The audit is the sharpest case study available for what Rust catches and what it doesn't. The most useful read on the list is Matthias Endler's Bugs Rust Won't Catch, published April 29 on the corrode.dev blog. Endler runs corrode, a Rust consultancy; he hosts the Rust in Production podcast and recently had Canonical's VP of Engineering, Jon Seager, on it. His piece is the analytical companion to the Canonical disclosure: 44 CVEs, sorted into eight categories, with the actual git diff for the fix on most of them.

What follows takes Endler's enumeration as the spine and stacks two more arguments on top — the GNU-coreutils-maintainer testimony from the HN thread, which lands a benchmark that Endler's preferred fix doesn't survive, and the structural argument about what 40 years of accreted POSIX scars do to any rewrite, regardless of language.

What the audit caught, by category

Endler enumerates eight categories. The shape of each is the same: a Rust idiom that the type system endorses, applied in a context where the type system can't see what's wrong.

TOCTOU on path operations is the largest cluster, and the reason cp, mv, and rm are still the GNU implementations in 26.04 LTS rather than uutils. The pattern is one syscall to check, another to act, both taking a &Path. Between them, an attacker with write access to a parent directory can swap the path component for a symlink, the kernel re-resolves on the second call, and the privileged action lands on the attacker's chosen target. The clearest case is CVE-2026-35355 in install:

// 1. Clear the destination
fs::remove_file(to)?;
// ...
// 2. Re-resolves the path. Follows symlinks, truncates.
let mut dest = File::create(to)?;
copy(from, &mut dest)?;

Enter fullscreen mode Exit fullscreen mode

Anyone with write access to the parent directory can plant to as a symlink to /etc/shadow between steps 1 and 2; the privileged process happily overwrites it with whatever from contains. The fix uses OpenOptions::create_new(true), which the docs explicitly say "No file is allowed to exist at the target location, **also no (dangling) symlink."

Permission-set-after-create is the close TOCTOU relative. fs::create_dir(&path)?; fs::set_permissions(&path, ...)?; exposes a window where the directory exists with default permissions and any local user can open() it for a file descriptor that survives the later chmod. The fix is OpenOptions::mode() and DirBuilderExt::mode() so the file or directory is born with the permissions you wanted.

Path string equality is not filesystem identity. The original --preserve-root check in chmod was literally if recursive && preserve_root && file == Path::new("/") { return Err(PreserveRoot); }. That comparison is bypassed by anything that resolves to / but isn't spelled //../, /./, a symlink, anything canonicalize would resolve. Run chmod -R 000 /../ and watch it lock down the whole system. The category's most absurd member is CVE-2026-35363:

rm .    # ❌
rm ..   # ❌
rm ./   # ✅
rm ./// # ✅

Enter fullscreen mode Exit fullscreen mode

rm rejected the bare . and .. but happily accepted ./, deleted the current directory, then printed "Invalid input." The string comparison didn't survive a trailing slash.

UTF-8 versus raw bytes at Unix boundaries. Rust's String and &str are always UTF-8. Unix paths, environment variables, and the byte streams flowing through tools like cut, comm, and tr are not. Every place a Rust program bridges the two, it has three choices: lossy conversion (which silently rewrites invalid bytes to U+FFFD — fancy data corruption, in Endler's phrasing), strict conversion (which crashes on the first non-UTF-8 byte), or staying in OsStr / &[u8]. The audit found bugs in both of the first two. CVE-2026-35346 in comm used String::from_utf8_lossy, so passing a binary file through comm silently mangled the output. The fix replaced print! with BufWriter::write_all, staying in bytes.

Panic-as-DoS. Every unwrap, every expect, every slice index, every unchecked arithmetic operation in input-handling code is a potential denial of service if an attacker can shape the input. CVE-2026-35348 in sort --files0-from called expect() on a UTF-8 conversion of each filename:

$ python3 -c "open('list0','wb').write(b'weird\xffname\0')"
$ coreutils sort --files0-from=list0
thread 'main' panicked at uu_sort-0.2.2/src/sort.rs:1076:18:
Could not parse string from zero terminated input.

Enter fullscreen mode Exit fullscreen mode

GNU sort treats filenames as raw bytes — that's what filenames are. The uutils version aborted on the first non-UTF-8 path. As Endler puts it: "Your nightly cron job is dead and there goes your weekend."

Discarded errors. chmod -R and chown -R returned the exit code of the last file processed instead of the worst one. chmod -R 600 /etc/secrets/* could fail on half the files and exit 0. dd called Result::ok() on its set_len() to mimic GNU's /dev/null behavior, except the same code ran for regular files, so a full disk silently produced a half-written destination.

Behavioral incompatibility with GNU. Endler observes that "a surprising number of these CVEs aren't 'the code does something unsafe' but 'the code does something **different* from GNU, and a shell script somewhere relied on the GNU behavior.'"* The clearest case is CVE-2026-35369 in kill: GNU reads kill -1 <PID> as "signal 1, this PID." uutils read it as "send the default signal to PID -1," which on Linux means every process you can see. A typo turns into a system-wide kill switch.

Resolve before you cross. CVE-2026-35368 is the worst single bug in the audit. It's local root in chroot. Simplified pattern:

chroot(new_root)?;
// Still uid 0, but now inside the attacker-controlled filesystem.
let user = get_user_by_name(name)?;
setgid(user.gid())?;
setuid(user.uid())?;
exec(cmd)?;

Enter fullscreen mode Exit fullscreen mode

get_user_by_name goes through NSS, which dlopens libnss_* modules at runtime. After the chroot, those modules are loaded from the new root filesystem. An attacker who can plant a file in the chroot gets to run code as uid 0. GNU chroot resolves the user before chroot. The fix is the same. Static linking doesn't help; NSS is dynamic regardless.

That's eight classes, 44 CVEs, none caught by the borrow checker.

What the GNU coreutils maintainers said back

The HN thread on Endler's piece runs 361 comments deep, and the most useful single comment is the first one: a self-identified GNU coreutils maintainer disagreeing with one of Endler's rules and showing the cost.

Endler's recommended fix for path-string-equality bugs is fs::canonicalize — resolve .., ., and symlinks into a real absolute path before comparing. The maintainer's response constructs a deeply-nested directory and benchmarks both implementations:

$ mkdir -p $(yes a/ | head -n $((32 * 1024)) | tr -d '\n')
$ while cd $(yes a/ | head -n 1024 | tr -d '\n'); do :; done 2>/dev/null
$ echo a > file
$ time cp file copy
real 0m0.010s
$ time uu_cp file copy
real 0m12.857s

Enter fullscreen mode Exit fullscreen mode

GNU cp finishes in 10 milliseconds. The uutils cp, with the canonicalize-style logic, takes 12.857 seconds. That's a 1,200× slowdown on a contrived but real-shaped path. The maintainer's general point is that GNU "works very hard to avoid arbitrary limits" — the kinds of inputs that look pathological are the inputs that production shell scripts hit, and a 12-second cp in a deep build tree is its own production incident.

The same comment also lands a sharper correction on Endler's strongest claim. The post asserts "The Rust rewrite has shipped zero of these [memory-safety bugs], over a comparable window of activity." The maintainer points to GHSA-w9vv-q986-vj7x, an actual memory-safety advisory in the Rust uutils. The advisory was fixed before LTS shipped. But "zero" is now contested, and Endler's framing suffers proportionally.

The thread does not end Endler's argument. The boundary categories he lists are real. The categorical claim about memory-safety wins, however, gets dialed down from "zero, full stop" to "close to zero, with caveats." That is a smaller difference than the rewrite-vs-keep-it argument turns on, but it's the difference between a slogan and a measurement.

The structural argument from the thread

A second thread of HN testimony argues that the Rust-vs-C framing is the wrong axis entirely. As one commenter put it: "the function signature is what they read, but the scars are what they need." The GNU coreutils lineage runs from the late 1980s fileutils / textutils / shellutils packages (unified into coreutils in 2002), and decades of trickle fixes — a pwd buffer overflow on deep paths longer than 2 * PATH_MAX, an od --strings -N write past a heap buffer, a b2sum --check reading unallocated memory — encode an enormous amount of "don't do this, here's why" knowledge that lives in the codebase as scars rather than as documentation. A clean-room rewrite re-derives some of those scars on the customer's machine.

Another commenter put the same argument bluntly: "They knew how to write Rust, but clearly weren't sufficiently experienced with Unix APIs, semantics, and pitfalls. Most of those mistakes are exceedingly amateur from the perspective of long-time GNU coreutils developers." A third extended it into a process critique: "A rewrite necessarily has orders of magnitude more bugs and vulnerabilities than a decades-old well-maintained codebase, so the security argument was only valid for a long-term transition, not a rushed one."

There is a counter-argument, and Endler's piece is the strongest version of it. The Rust rewrite did not ship the memory-safety class of bug. The audit-published CVE list contains no buffer overflows, no use-after-free, no double-free, no data races on shared mutable state, no null-pointer dereferences, no uninitialized memory reads. GNU coreutils has shipped CVEs in every one of those categories in the past few years alone, by Endler's enumeration:

  • pwd buffer overflow on deep paths longer than 2 * PATH_MAX (9.11, 2026)
  • numfmt out-of-bounds read on trailing blanks (9.9, 2025)
  • unexpand --tabs heap buffer overflow (9.9, 2025)
  • od --strings -N writes a NUL byte past a heap buffer (9.8, 2025)
  • sort 1-byte read before a heap buffer with a SIZE_MAX key offset (9.8, 2025)
  • split --line-bytes heap overwrite (CVE-2024-0684, 9.5, 2024)
  • tail -f stack buffer overrun with many files and a high ulimit -n (9.0, 2021)

Whatever you think about the Canonical decision to ship uutils into a default Ubuntu install, the comparison Endler is asking you to make is genuine. The Rust rewrite's CVE list reads like a different kind of failure from the GNU CVE list. Same failure surface (privilege-bearing system tools); different class of bug.

Where the security boundary moved

Read all 44 CVEs side by side and the structural picture isn't "Rust failed to catch the bugs." It's "Rust caught the class of bug it catches, and the audit found the next class over."

The next class lives at the boundary between the Rust program and the operating system. Endler names the boundary explicitly: "It lives at the boundary between our controlled Rust environment and the messy, chaotic outside world, where paths, bytes, strings, and syscalls are all tangled up in one eternal ball of sadness. That's the new security boundary of modern systems code." The Rust standard library makes the path-of-least-resistance choice land on the wrong side of that boundary in several places — &Path parameters everywhere, String for things that are actually byte sequences, unwrap and expect available with no friction. The handle-based APIs (openat, fstatat, unlinkat) exist on every Unix; std::fs doesn't put them front and center.1

That's not a Rust-language criticism. It's a std::fs-design observation, and the design choice traces to the cross-platform constraints of Rust 1.0's standard library. As one HN commenter put it, "std::fs suffers from being a lowest common denominator. Rust had to have something at 1.0, and unfortunately it stayed like that." The same shape repeats in nearly every language with a portable filesystem abstraction; it's not unique to Rust. But the framing of the Rust rewrite — "we rewrote it in Rust, therefore it is more secure" — depends on the language doing more work than std::fs is currently set up to do at the syscall boundary.

The audit-list categories — TOCTOU, permission-set-after-create, path-string-equality, UTF-8-vs-bytes, panic-as-DoS, error discarding, GNU compatibility, resolve-before-cross — are the new audit checklist for any privilege-bearing Rust rewrite. None of them is caught by the compiler. All of them are catchable by code review by someone who has read this CVE list. The Rust safety story remains real and remains useful; it just stops at the syscall.

The shape of the next audit

This is the argument that survives both Endler's piece and the HN thread back-and-forth: we rewrote it in Rust is a coherent claim about one specific class of bug. It is not, on its own, a security claim about the rewritten tool. It is a security claim about the absence of the classes of bug Rust's type system catches. The classes the type system doesn't catch — eight of them, on display in 44 CVEs — have to be caught by something else.

Endler's checklist is the right shape for that something else. Grep your codebase for from_utf8_lossy, stray unwrap() calls, discarded Results, File::create, string comparisons against "/", mode-after-create directory operations. None of those is harder to find than a memory-safety bug. They take a different kind of audit and a different kind of reviewer — the kind whose 40 years of POSIX scars are the documentation that the upstream codebase didn't write down.

The 44 CVEs are not a verdict on Rust. They are a measurement of where the next audit should be looking. "We rewrote it in Rust" should be heard the way "we have unit tests" should be heard: a useful claim about a specific class of failure mode, with no claim attached about the ones it doesn't cover.


  1. Endler footnotes a related observation: the path-versus-handle TOCTOU class is in some ways easier to avoid in C than in Rust, because C code naturally reaches for an open file descriptor and the *at family of syscalls (openat, fstatat, unlinkat, mkdirat), and most creation syscalls take a mode argument directly. Rust's high-level std::fs APIs abstract over the file descriptor and operate on &Path values, which makes the path-based, re-resolving call the path of least resistance. The handle-based APIs exist on every Unix platform; Rust just doesn't put them front and center.