Skip to content
Cyber Army LogoCyber Army™
Notes·2026-05-22·~14 min read

Memory-safe doesn't mean bug-free: what Mythos finds in Rust

CA
The Cyber Army team·Sunnyvale, CA

Rust closes the memory-safety bug class that produced two-thirds of CVEs for two decades. It does not close vulnerability discovery. A look at the bug categories agentic models still surface in memory-safe codebases, and what that means for the rewrite-everything-in-Rust strategy.


The claim worth re-examining

For most of the last decade, the strongest counterargument to "the internet runs on C and we should be worried" was "memory-safe languages will save us." Microsoft published the famous number - roughly 70% of CVEs they fixed across their products were memory-safety issues. Google's Android team showed the same pattern dropping as they shipped more Rust into the platform. Big-name advocacy followed: rewrite the OS in Rust, rewrite the kernel in Rust, rewrite everything in Rust, and we'll close the book on a generation of bugs.

That argument is still mostly correct. Memory safety closes a real and dominant bug class. What it doesn't do - and this is the part the agentic vulnerability discovery results from Anthropic's Frontier Red Team have made very visible - is close vulnerability discovery as a discipline. Mythos finds plenty of exploitable bugs in Rust. So do humans. The bug-finding ceiling isn't set by the language you write in; it's set by what attackers are willing to look for, and the cost of looking just dropped by an order of magnitude.

This post walks through the bug categories that don't go away when you switch to Rust, with example shapes. None of these are language-specific weirdness - they're structural facts about what "safety" in "memory-safe" does and does not promise.

What memory safety actually removes

Before going through what stays, it's worth being explicit about what goes away. In safe Rust:

  • Buffer overflows - caught at compile time or runtime by bounds checks.
  • Use-after-free - caught by the borrow checker.
  • Double-free - caught by the ownership model.
  • Null pointer dereferences - eliminated by Option<T>.
  • Data races - caught by Send/Sync at compile time.
  • Uninitialized memory reads - caught by the type system.

That list, when you combine the CVE databases of the last 20 years, accounts for somewhere between 60% and 75% of all reported memory corruption vulnerabilities. It is genuinely a massive subtraction. If you rewrite libfoo from C to safe Rust and don't touch the algorithm, the bug surface really does shrink by that proportion.

The catch is in the word "safe." And what the other 25-40% of CVEs look like.

What it leaves on the table

Six categories. Each gets a paragraph and an example shape.

Unsafe blocks

Real-world Rust code is full of unsafe. Hot loops that need to skip bounds checks. Constructors for self-referential data structures. Anything that talks to a C library. The standard library itself contains thousands of unsafe blocks; tokio, hyper, rustls, serde all contain non-trivial amounts. And inside an unsafe block, every bug class memory safety was supposed to remove is back.

The bug shape:

// A common pattern that bypasses Rust's safety guarantees.
// The borrow checker has no way to verify that 'len' is correct.

pub fn parse_header(buf: &[u8]) -> Result<Header> {
    let len = u32::from_le_bytes(buf[0..4].try_into()?) as usize;

    unsafe {
        // If len is attacker-controlled and exceeds buf.len(),
        // this is a heap read past the end of the buffer.
        let slice = std::slice::from_raw_parts(
            buf.as_ptr().add(4),
            len,
        );
        Ok(deserialize(slice)?)
    }
}

That code passes cargo check. It passes the borrow checker. It runs fine in tests where len is small. An attacker who can control the first four bytes of buf turns it into a heap over-read that leaks adjacent memory. This is exactly the kind of finding Mythos pulls out of Rust projects routinely - the agent reads the file, sees an unsafe block, traces where the length value comes from, and runs a reproducer that walks past the buffer.

The defense isn't "don't use unsafe." It's "audit unsafe blocks like you would audit C." Memory-safe-by-default helps; memory-safe-with-escape-hatches inherits the bug class of the escape hatch.

The FFI boundary

Most Rust applications depend on at least one C library. Cryptography stacks bottom out in BoringSSL or OpenSSL. Image libraries call into libjpeg or libpng. Database clients link against libpq. The Rust side is safe; the C side is not; the bug lives at the boundary.

// Calling a C library from Rust. The Rust side looks safe.
// The C side reaches into the slice with no bounds check.

extern "C" {
    fn libfoo_decode(input: *const u8, len: usize) -> i32;
}

pub fn decode(input: &[u8]) -> Result<i32> {
    // Rust hands the pointer + length to C correctly.
    // But libfoo_decode itself has a bug - it trusts a length
    // field embedded in the buffer rather than the 'len' argument.
    let rc = unsafe { libfoo_decode(input.as_ptr(), input.len()) };
    if rc < 0 { Err(...) } else { Ok(rc) }
}

A scanner can audit the Rust code forever and find nothing wrong. The bug is in the C library, and the only thing the Rust wrapper did was hand it pre-validated input that the C library then re-validated incorrectly. From the attacker's point of view, this Rust program is exploitable because of the C dependency. From the "is this codebase memory safe" checkbox's point of view, it's fine.

Mythos handles this naturally because the verification stage is dynamic - the agent runs the actual binary with the actual library linked, and ASan or KASAN sees the corruption inside the C library's allocations. The Rust source tree is just the route in.

Logic errors

The hardest category to talk about, because it's the broadest. Authentication checks that compare the wrong fields. Authorization logic that grants admin if a session attribute is missing. Path canonicalization that doesn't handle ../. Serializers that round-trip differently than they parse. State machines where one transition is reachable from a state it shouldn't be reachable from.

Memory safety has nothing to say about any of this. The example:

// No memory bug. Pure logic error. Mythos finds these too.

impl SessionToken {
    pub fn verify(&self, signature: &[u8], secret: &[u8]) -> bool {
        let expected = hmac_sha256(secret, self.payload());

        // Bug: short-circuit comparison leaks length info via timing,
        // and the early return reveals which byte first differs.
        for (i, &b) in expected.iter().enumerate() {
            if signature[i] != b { return false; }
        }
        true
    }
}

There's no memory bug. No undefined behavior. Just a timing side channel and a weak comparison that lets an attacker recover the MAC byte by byte. This kind of finding lives in "Rust application doing crypto without using the right primitive" territory, and it's shockingly common in production. Mythos finds these by reading the code and reasoning about the protocol invariant the comparison is supposed to enforce.

Denial of service

Memory safety means your Rust program won't corrupt memory under attacker-controlled input. It does not mean your program won't exhaust memory, lock up a CPU forever, or deadlock itself out of doing useful work.

Common shapes Mythos surfaces:

  • A YAML parser that allocates exponentially with nested anchors (billion-laughs in YAML form).
  • A regex compiled at runtime from user input, where the user input is a catastrophic-backtracking pattern.
  • A protocol decoder that handles a length-prefixed field by pre-allocating a buffer of the declared length - fine if max-length is bounded, ruinous if it's a u32.
  • A websocket frame handler that reassembles fragmented frames and accepts arbitrarily many before forming a complete message.

All of these are denial-of-service bugs in safe Rust. They're also some of the highest-severity findings in production deployments because they let an attacker take down a service with a single small request. The OpenBSD TCP/SACK bug we discussed in the Mythos post is a DoS bug, and it survived 27 years in C - the equivalent class would survive in Rust too.

Cryptography misuse

A separate sub-category of logic errors worth calling out because it's common and high-impact. Rust's cryptography ecosystem (ring, rustls, aes-gcm, the RustCrypto suite) is generally excellent. Application code that calls into it often is not.

  • Using AES-GCM with a nonce derived from a counter that resets on restart.
  • HMAC with an empty key because the secret wasn't loaded from config.
  • Hashing passwords with SHA-256 instead of Argon2 / bcrypt.
  • Generating session tokens with rand::random() instead of a CSPRNG.
  • JWT verification that accepts alg: none.

Mythos finds these by reading the call site and recognizing the anti-pattern. The Rust compiler can't tell you that HmacKey::from(&[]) is a bug; the agent can. This is a domain where automated discovery has been historically weak and is now strong.

Concurrency and TOCTOU

Rust's Send/Sync system prevents data races at the language level - two threads can't mutate the same memory without synchronization. It does not prevent race conditions at the application level - two operations on the same logical resource that should have been atomic but weren't.

The classic TOCTOU shape: check a permission, then perform the action. If anything else can change the permission between the check and the action, you have a race. In Rust this is just as easy to write as in any other language; the type system has no opinion on it.

Filesystem operations are the most common offender. Check that a file isn't a symlink, then open it - by name. Anyone who can swap the target between the check and the open wins. Mythos finds these by reading the code, identifying the check/use pair, and running a reproducer that exploits the gap. The Rust compiler had nothing to say about it.

Why agentic discovery still finds these

The categories above span memory-safety-adjacent (unsafe, FFI), logic-error-class (auth, crypto, state machines), and resource-class (DoS) bugs. What they have in common is that none of them are visible to a checker that only looks for memory unsafety.

Mythos's pattern - read the code, form a hypothesis, run the binary, check the result - is agnostic to bug class. It doesn't care whether the oracle is AddressSanitizer or ThreadSanitizer or a unit test that the agent wrote itself. Anything where you can construct an input that causes the program to behave wrong by some definition of wrong is fair game.

For logic and crypto bugs, the oracle is typically a unit test the agent writes. "If this comparison is constant-time, my timing measurement should be flat across these N inputs. It isn't. Therefore this is a timing leak." The agent has just produced a reproducer plus a measurement that shows the bug. That's a complete report.

The implication: as Rust adoption grows, the bug density in Rust projects will not be zero. It will be lower than the equivalent C codebase. But the lower bound is set by everything memory safety doesn't cover - and that's a substantial fraction of historical CVE volume even before you factor in unsafe blocks and FFI.

So what should defenders do?

Three operational shifts that follow from this:

  • Audit unsafe blocks like you would audit C. Treat every unsafe block in your codebase as a small piece of C embedded in your Rust program. Run ASan on the test suite. Send the unsafe blocks specifically through the kind of bug-finding pipeline we wrote about last week. The Rust syntax around them does not provide cover.
  • Treat the FFI boundary as the dependency surface. Your transitive C dependencies are still your security exposure. The Rust wrapper doesn't make libcurl safer. Inventory the C libraries you link against and patch them with the same urgency you'd patch them in a pure-C codebase.
  • Don't conflate "memory safe" with "secure." Memory safety is a real and large reduction in vulnerability surface. It is not the property of being unexploitable. Logic bugs, crypto misuse, DoS, and race conditions exist independently of language choice, and an agentic adversary that previously focused on C/C++ has a constant cost of pivoting to your Rust code now that the tooling is generic.

Should you still rewrite in Rust?

Yes - for the same reasons you would have a year ago, with a slightly more honest framing.

Memory-safety bugs are the single largest CVE class. Removing them removes a lot of vulnerabilities. They also include some of the highest-severity ones (RCE-class memory corruption against an attacker-reachable parser is often a full-system compromise). The case for moving security-critical C code to Rust is unchanged. We are not arguing "Rust doesn't help." We are arguing that "Rust helps a lot" is not the same as "Rust closes the bug surface."

The honest expectation is: a well-written Rust rewrite of a parser library will have a few percent of the CVE volume of the C version after a year of production. Most of those remaining CVEs will be in the unsafe blocks, the FFI boundaries, and the logic. If you build a vulnerability-discovery pipeline that knows about all three categories - and at this point that's most of them - you will still find work to do. That work used to be the kind of thing that took a senior researcher a week per bug. With agentic discovery it takes minutes.

The strategic question, then, is the same as it was for C: can your remediation pipeline keep up with discovery? Memory safety bought you a 70% reduction in inbound CVE volume, and agentic discovery just increased outbound discovery volume by roughly the same factor on what's left. Net is roughly even. The teams that will do well from here are the ones building autonomous remediation pipelines to match - which is, conveniently, what we're doing with CyberArmy AutoFix. If your team is wrestling with patch-window math in this new world, drop us a note via the contact page.

Cite this post

Plain text or BibTeX:

Cyber Army. "Memory-safe doesn't mean bug-free: what Mythos finds in Rust." cyberarmy.ai, May 22, 2026. https://cyberarmy.ai/blog/memory-safe-doesnt-mean-bug-free
@misc{cyberarmy_memory_safe_2026,
  title  = {Memory-safe doesn't mean bug-free: what Mythos finds in Rust},
  author = {{Cyber Army}},
  year   = {2026},
  month  = {June},
  url    = {https://cyberarmy.ai/blog/memory-safe-doesnt-mean-bug-free},
  note   = {Accessed: \today}
}

Sources

  1. Carlini, N., et al. Assessing Claude Mythos Preview's cybersecurity capabilities. Anthropic Frontier Red Team, April 7, 2026 - the cross-language vulnerability discovery results that motivated this post.
  2. A proactive approach to more secure code. Microsoft Security Response Center, 2019 - the original ~70%-memory-safety-CVE figure.
  3. Google Security Blog. Eliminating memory safety vulnerabilities at the source. 2024 - Android's data on memory-safety CVE decline with Rust adoption.
  4. Anderson, R. Security Engineering, 3rd ed. - the canonical reference on logic, crypto, and protocol-layer vulnerabilities that remain after memory safety.
  5. Our previous posts: Inside Mythos and Build an AI bug-finding pipeline today.