Microsoft’s AI Found Safe Rust Bugs. Here’s What Is Still Open

Microsoft FORGE Lab found Rust standard-library soundness bugs, including safe-code crashes. Here is what was fixed, what remains open and what teams should audit.
Diagram showing a Rust panic unwinding through unsafe code and exposing a broken memory invariant
Microsoft FORGE Lab diagram illustrating how panic unwinding can expose a temporary unsafe-code invariant. Credit: Microsoft.

Microsoft researchers have disclosed a set of memory-safety bugs in Rust’s standard library, including cases where programs written entirely in safe Rust can trigger undefined behavior. The findings do not overturn Rust’s safety model, and Microsoft is not reporting a mass-exploitable vulnerability. They show where that model is hardest to uphold: unsafe library code that temporarily breaks an invariant, calls user-controlled behavior, or crosses into process-wide Unix state.

The November 9 report from Microsoft FORGE Lab groups the bugs into three patterns: assuming code will not panic, trusting user-provided trait implementations to behave consistently, and trusting operating-system state that another thread or C library can change. Researchers used Hyenas, a frontier research version of Microsoft’s codename MDASH agentic scanner, to help find some of the issues. Human researchers reproduced the bugs, assessed their impact and worked with Rust maintainers on reports and fixes.

Two details keep the disclosure in perspective. Several examples affected experimental nightly APIs and were fixed before stabilization. The stable-library cases require unusual conditions, and the Rust Security Response Team did not classify the still-open CommandExt::exec report as a security advisory. Safe Rust is not a promise that every library implementation is bug-free; it is a contract that safe callers must not be able to cause undefined behavior. These reports found places where standard-library unsafe code failed that contract.

How safe Rust can reach unsafe behavior

Rust’s ownership and type system prevent many use-after-free, out-of-bounds and data-race bugs at compile time. The standard library still uses unsafe internally for raw pointers, uninitialized buffers, system calls and performance-sensitive operations. A safe wrapper is sound only if every possible use from safe code preserves the rules that its unsafe implementation relies on.

Microsoft’s compact example is a function that calls a caller-supplied AsRef<[u8]> implementation twice: once to check a length and again to perform an unchecked read. Safe code can return different slices on the two calls. The bounds check can succeed on a long slice, while the unchecked read receives an empty one. The unsafe block is small, but its assumption about external behavior is larger than the compiler can verify.

Diagram showing a Rust panic unwinding through unsafe code and exposing a broken memory invariant
Microsoft FORGE Lab illustrated how a panic can unwind through unsafe code while an internal invariant is temporarily broken. Credit: Microsoft.

A panic exposed uninitialized memory in MapWindows

One finding involved the nightly-only Iterator::map_windows API. Its internal buffer stored initialized values inside a larger MaybeUninit array. During MapWindows::clone, the implementation created a destination buffer whose destructor assumed its active slots were initialized, then called the element type’s Clone::clone before filling those slots.

A caller could define a safe Clone implementation that panics. Unwinding then ran the destination buffer’s destructor over uninitialized memory. Microsoft’s proof of concept used #![deny(unsafe_code)] and still reached an invalid reference dereference. The Rust issue classified the impact as low because triggering it required a nightly feature, an initialized map window, cloning that iterator, a panicking element clone and panic unwinding.

The fix was merged in June. It ensures the half-built buffer cannot run a destructor that assumes initialization. The engineering lesson applies beyond this API: any call to clone, drop, a comparator, a closure or another caller-controlled method can panic. Unsafe code must finish that call before breaking an invariant, or use a guard or ManuallyDrop so unwinding cannot expose the temporary state.

User-controlled traits broke string and slice assumptions

A second group of reports found standard-library code treating safe traits as if they promised more than their types required. A custom string pattern could provide the byte 0x80 as a one-byte pattern. Because that byte only appears inside a multibyte UTF-8 sequence, str::replace could overwrite continuation bytes and return a String that was no longer valid UTF-8. The merged fix changed the internal representation from a byte slice to &str, moving the UTF-8 requirement into the type system.

slice::join had a related consistency problem. It called Borrow::borrow more than once while sizing and copying a result. A safe implementation that returned a larger string on a later call could make a remaining-length calculation underflow and lead to a heap overflow. That soundness fix merged in April; Rust’s security responders described the condition as hard to trigger and did not embargo it.

Two separate reports involving the unstable Pattern trait remain open. A custom pattern can return a valid searcher built for a different string, while consumers apply its ranges to the original string with unchecked operations. The affected paths are marked as nightly-only in the Rust tracker, so they are not evidence that stable str::split or strip_prefix is broadly unsafe.

The stable issue to watch is at the Unix process boundary

The most operationally relevant open finding concerns std::os::unix::process::CommandExt::exec, a stable Unix API. When a command has environment changes, Rust builds a temporary environment array, replaces the process-global environ pointer and calls execvp. The implementation takes an environment read lock even though it writes that global pointer.

That creates two races. A sibling thread can read environment variables intended only for the replacement process. Concurrent exec calls can also cross their temporary environments; if one call fails, it can restore a pointer to memory another call has already freed, producing a safe-Rust segmentation fault. The open Rust issue is labeled high priority and unsound.

The Rust Security Response Team nevertheless treated it as a correctness issue, not a security advisory. Racing two process-replacing calls is already nondeterministic, and the API documentation warns that a failed exec can leave process state changed. For software that needs a transactional failure path, the tracker recommends spawning a child process instead of replacing a multithreaded process in place.

Microsoft also found a stable Unix bug in which process::exit called from a child thread could unmap the main thread’s alternate signal stack during cleanup. The main thread could then crash incorrectly if it overflowed its stack in the narrow interval before the process exited. The bug had existed since 2015; Rust removed that cleanup path in an August fix.

Diagram of Rust stack overflow handling with a main thread and alternate signal stack
Rust uses an alternate signal stack so its handler can report a stack overflow after the regular stack is exhausted. Credit: Microsoft.

What Rust teams should check now

This disclosure does not call for an emergency toolchain upgrade. It does give maintainers of foundational crates and unsafe abstractions a focused review list:

  • Treat every external call as a possible exit. Audit unsafe regions that call user implementations of Clone, Drop, Borrow, AsRef, comparison traits, allocators, iterators or closures while an invariant is broken.
  • Do not repeat a user-controlled conversion around unchecked work. Call a trait method once, validate its result and retain that result for both the check and the unsafe operation.
  • Encode assumptions in types where possible. A &str makes UTF-8 validity enforceable in a way a byte slice plus a comment cannot.
  • Review Unix process replacement in multithreaded programs. Avoid concurrent CommandExt::exec calls and do not place secrets in a temporary exec environment if sibling threads could read process environment state. Prefer Command::spawn when failure must leave the parent usable.
  • Test panic paths with Miri and fault injection. Normal successful execution will not reveal a destructor touching uninitialized state after a panic.
  • Separate nightly exposure from stable exposure. Inventory experimental feature gates before treating every open soundness report as a production risk.

Why the AI-assisted part matters

The agent did not autonomously ship fixes or declare vulnerabilities. FORGE researchers used it to search for patterns across library code and system interfaces, then performed human reproduction, impact analysis and disclosure. That division of labor is important because soundness does not automatically equal exploitability, and a proof of undefined behavior can depend on a deliberately hostile trait implementation or a precisely timed race.

The system-boundary findings are the more notable advance. Traditional static analysis is good at following code inside one language, but a Rust wrapper may also rely on Linux signal delivery, C library cleanup rules, a global environment pointer or kernel memory mappings. A model can bring that surrounding knowledge into a search hypothesis. Human review still has to prove the path and distinguish a practical vulnerability from a contrived correctness failure.

Rust’s unusually strict standard makes these bugs visible: safe APIs are expected to remain memory-safe even when callers supply awkward but legal implementations. Finding violations in that contract is evidence that the boundary is being tested, not that the contract has lost its value.

Sources: Microsoft FORGE Lab; Rust issue and pull-request records for MapWindows, slice::join, CommandExt::exec and process::exit cleanup.

Previous Post
Spacefast interface showing an AI agent publishing an application

Automattic Launches Spacefast for AI-Built Apps: What It Does and What’s Still Beta

Next Post
Exterior of Australian Parliament House in Canberra

Australia Plans AI Safety Regulator After OpenAI Medicare Breach

Related Posts