GHSA-585Q-CM62-757J

The Netlink Nullifier: When 'Safe' Rust Wrappers Segfault

Amit Schendel
Amit Schendel
Senior Security Researcher

Jan 10, 2026·5 min read

Executive Summary (TL;DR)

The `mnl` crate, a Rust wrapper for `libmnl`, incorrectly exposes the `cb_run` function as safe. By passing a crafted, malformed byte buffer, an attacker can trick the underlying C library into reading out of bounds, causing a segmentation fault (DoS). Because no patch exists yet, developers must manually validate Netlink headers before processing.

A soundness vulnerability in the Rust 'mnl' crate allows safe code to trigger a segmentation fault by passing malformed byte slices to the underlying C library, violating Rust's memory safety guarantees.

The Hook: Rust's Promise vs. C's Reality

Rust is famous for its "Fearless Concurrency" and memory safety guarantees. If you write safe Rust, you shouldn't be able to trigger a Segmentation Fault. The compiler is supposed to stop you, smack your hand, and tell you to fix your lifetimes. But there is a dark alleyway in the Rust ecosystem where these rules break down: Foreign Function Interface (FFI).

The mnl crate is a wrapper around libmnl, a minimalistic Netlink library used to talk to the Linux kernel. Netlink is how you configure network interfaces, routes, and firewalls. It's low-level, gritty, and usually handled by C. The mnl crate attempts to bring this power to Rust developers with a safe API. Unfortunately, in its attempt to be helpful, it commits the cardinal sin of Rust bindings: it trusts the user.

The Flaw: A Blind Hand-Off

The vulnerability lies in the mnl::cb_run function. In Rust, this function accepts a byte slice (&[u8]). A slice carries its length with it, making buffer overflows in pure Rust rare. However, cb_run is just a thin wrapper that passes a raw pointer to the C function mnl_cb_run.

Here is the disconnect: The C function mnl_cb_run doesn't know about Rust's slice length. It relies on the content of the buffer to tell it when to stop. Specifically, it parses the data as Netlink messages, looking at the nlmsg_len field in the header to jump to the next message.

If you provide a slice that claims to be a valid Netlink buffer but contains garbage data—specifically, a length field that points way past the end of the actual slice—the C library will happily jump off the cliff into unmapped memory. The Rust wrapper performs zero validation to ensure the data inside the slice matches the structure expected by the C library, yet it marks the function as safe.

The Code: Anatomy of a Lie

Let's look at the vulnerable pattern. This is a classic example of unsound FFI wrapping. The Rust function signature implies safety, but the implementation performs a raw pointer dereference into a complex C state machine without sanitization.

// The vulnerable wrapper (simplified representation)
pub fn cb_run(buf: &[u8], seq: u32, portid: u32) -> Result<()> {
    // DANGER: We are passing a raw pointer to C.
    // We are trusting C to respect the bounds of 'buf',
    // but C only respects the length fields INSIDE 'buf'.
    let ret = unsafe {
        mnl_sys::mnl_cb_run(
            buf.as_ptr() as *const c_void,
            mnl_cb,
            // ... other args
        )
    };
    // ... error handling
}

The unsafe block is technically valid syntax, but the wrapping function is marked public and safe. This means a developer can crash their application without ever typing the word unsafe, violating Rust's core contract.

The Exploit: 28 Bytes of Doom

Exploiting this is trivially easy for anyone who can feed data into the application. You don't need a heap spray or ROP chain; you just need a specific sequence of bytes that confuses the Netlink iterator macros in libmnl.

The PoC demonstrates a buffer that is technically a valid Rust slice but semantically invalid for Netlink:

let data: &[u8] = &[
    0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 245, 255, 
    255, 255, 0, 0, 255, 255, 
    5, 224, 0, 0, 0, 0
];
 
// This line crashes the process immediately
mnl::cb_run(data, 0, 0);

When libmnl processes this, it reads the header, calculates an offset based on the garbage values, and attempts to read the next message from invalid memory. The result is an immediate SIGSEGV (Segmentation Fault). In a networked utility or daemon, this is an instant Denial of Service.

The Impact: Why DoS Matters Here

You might look at the CVSS score of 2.0 (Low) and shrug. "It's just a local crash, right?" Context is key. Netlink is used for system-critical network configuration.

If you have a Rust-based network daemon, firewall agent, or monitoring tool that reads raw Netlink messages from the kernel (or worse, from a user-space source acting as a proxy), this bug allows a malformed packet to kill the entire service. While it doesn't grant root access or remote code execution immediately, crashing a firewall controller or a network interface manager is often just as good as turning it off.

The Fix: Trust No One

As of early 2026, there is no official patch for the mnl crate. The maintainers are aware, but the architectural fix (validating Netlink protocol semantics in Rust before calling C) is non-trivial.

If you are using mnl, you are currently on your own. You have two options:

  1. Wrap it yourself: Treat mnl::cb_run as unsafe. Build your own wrapper that validates the nlmsg_len of the incoming buffer ensures it matches the slice length exactly before passing it down.
  2. Switch Crates: Consider using pure-Rust implementations of the Netlink protocol (like netlink-packet or rtnetlink) which don't rely on libmnl and thus avoid this class of FFI memory corruption entirely.

Technical Appendix

CVSS Score
2.0/ 10
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:P
EPSS Probability
0.04%
Top 100% most exploited

Affected Systems

Rust applications using the 'mnl' crateNetwork configuration toolsLinux system daemons utilizing Netlink

Affected Versions Detail

Product
Affected Versions
Fixed Version
mnl
mullvad
<= 0.3.0None
AttributeDetail
CWECWE-125 (Out-of-bounds Read)
CVSS v4.02.0 (Low)
Attack VectorLocal
ImpactDenial of Service (DoS)
LanguageRust (wrapping C)
Componentmnl::cb_run
CWE-125
Out-of-bounds Read

The product reads data past the end, or before the beginning, of the intended buffer.

Vulnerability Timeline

Vulnerability reported via GitHub Issue
2025-10-18
GHSA / RUSTSEC Advisory published
2026-01-09
Confirmed no fix available for v0.3.0
2026-01-10

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.