Feb 22, 2026·6 min read·14 visits
The Rust `mnl` crate (Netlink bindings) exposed a memory safety bug because it didn't validate input before passing it to a buggy C library (`libmnl`). By crafting a specific Netlink message with a 'negative' length (integer overflow), an attacker can crash the application (DoS) via a Segmentation Fault.
A classic C-style signedness error in the underlying `libmnl` library allows attackers to crash Rust applications using the `mnl` crate. Despite Rust's safety guarantees, the `mnl` wrapper blindly trusted a C function that incorrectly casts unsigned 32-bit integers to signed integers, leading to out-of-bounds reads and segmentation faults.
We all love Rust. It's the memory-safe superhero that promised to save us from the sins of the 90s—buffer overflows, use-after-frees, and dangling pointers. But every superhero has a weakness, and for Rust, that weakness is often the unsafe block, or worse: Foreign Function Interfaces (FFI) that claim to be safe but rely on C libraries that are very much not.
Enter mnl, a Rust crate providing high-level abstractions for libmnl, a minimalistic user-space library for Netlink developers. Netlink is the Linux kernel's preferred way to talk to user space (and vice versa). It's powerful, complex, and historically full of parsing bugs.
The mnl crate exposes functions like cb_run, which processes a buffer of Netlink messages. It marked these functions as Safe Rust (fn cb_run(...)). That signature is a contract: "I promise you cannot crash memory by calling this, no matter what garbage data you feed me." spoiler alert: The contract was a lie.
The root cause isn't actually in the Rust code itself—initially. It lies deep within the bowels of the C library libmnl. Specifically, a function called mnl_nlmsg_ok, which is supposed to validate that a Netlink message header is sane.
Here is the comedy of errors:
nlmsg_len) is a uint32_t (unsigned 32-bit integer).libmnl (versions <= 1.0.5) decides to cast this length to a signed int during validation checks.0xFFFFFFF5 (which is 4,294,967,285 as an unsigned int), the C compiler goes "Ah, yes, -11."Because the length is interpreted as negative, it bypasses the standard "is the message bigger than the remaining buffer?" check. A negative number is technically smaller than the positive buffer size, so the check passes. The library then proceeds to read memory it shouldn't, assuming the message is valid. The Rust wrapper simply forwarded the raw byte slice to this C function without checking it first, acting like a bouncer who lets a guy with a bomb in because "he looked confident."
Let's look at what changed. The vulnerability was patched not by fixing the C library (though that should happen too), but by implementing a "Trust but Verify" approach in the Rust wrapper.
Before the fix, cb_run was a reckless conduit:
// OLD VULNERABLE CODE
pub fn cb_run(buf: &[u8], seq: u32, portid: u32) -> io::Result<CbResult> {
// Yolo. Pass the pointer directly to C.
let ret = unsafe {
mnl_sys::mnl_cb_run(
buf.as_ptr() as *const _,
buf.len(),
seq,
portid,
Some(cb_handler),
// ...
)
};
// ...
}The fix introduces a validation step. It forces the data to be parsed by Rust's own iterators (NlMessages) before the C library ever sees it. If Rust's iterator chokes on the malformed length, the function returns an error instead of crashing.
// NEW PATCHED CODE
fn validate_messages(buffer: &[u8]) -> io::Result<()> {
// Iterate through the buffer using Rust's safe parsing logic first
for msg in NlMessages::new(buffer) {
msg?; // This will error if the header length is garbage
}
Ok(())
}
pub fn cb_run(buf: &[u8], seq: u32, portid: u32) -> io::Result<CbResult> {
// "I don't trust you, C library."
validate_messages(buf)?;
// Now it's safe(r) to pass to C
let ret = unsafe { ... };
}You don't need a complex heap grooming strategy to trigger this. You just need 28 bytes. The PoC released with the advisory is brutally simple. It constructs a byte array that mimics a Netlink message header but inserts a massive value where the length should be.
Here is the layout of the attack buffer:
let data = [
0, 0, 0, 0, 0, 0, 0, 0, // Padding / ignored
0, 0, 0, 0, 0, 0, // More filler
245, 255, 255, 255, // <--- THE PAYLOAD: 0xFFFFFFF5
0, 0, 255, 255, // More garbage
5, 224, 0, 0, 0, 0 // Trailing bytes
];
// This line crashes the program
mnl::cb_run(&data, 0, 0);When libmnl reads 0xFFFFFFF5, it treats it as a negative integer. It assumes the message is valid but weirdly sized, and eventually tries to access memory based on that offset or iterates past the bounds of data. AddressSanitizer (ASan) lights up like a Christmas tree with a SEGV on unknown address.
While this is "only" a crash (Denial of Service) in most contexts, memory corruption is a sliding scale. If an attacker can control the precise layout of memory around the buffer, a "read" access violation could potentially be massaged into an information leak or worse, though the DoS is the guaranteed outcome.
The immediate impact is application stability. Any Rust application listening for Netlink messages (e.g., network managers, VPN clients, system monitors) using this crate can be crashed by a local user or potentially a remote attacker if the Netlink messages are bridged or processed from untrusted sources.
But the philosophical impact is higher. This vulnerability is a Soundness Hole. In Rust, safe functions must uphold memory safety invariants under all inputs. cb_run failed this test. It allowed a safe function to trigger undefined behavior (UB) in the C layer.
This serves as a grim reminder: If you wrap a C library, you inherit its CVEs. You cannot simply wrap unsafe C calls in a safe Rust function and call it a day without validating that the inputs respect the C library's hidden assumptions (like "please don't give me negative lengths").
The fix is straightforward for consumers: update mnl to version 0.3.1. This version includes the validate_messages logic that sanitizes inputs before they reach the C library.
There is a catch, though. The fix enforces stricter alignment. The input buffer must now be properly aligned to size_of::<nlmsghdr>(). If you were previously passing unaligned slices (which C might have tolerated or handled with undefined behavior), the new Rust code will return an error.
cargo update -p mnlLong term? Check your system libraries. The root cause is in libmnl. If you are running an ancient Linux distro, you might still have the buggy C library installed. Even with the Rust fix, it's good hygiene to update libmnl to a version newer than 1.0.5 (patched circa Nov 2023).
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
mnl mullvad | < 0.3.1 | 0.3.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-196 |
| Attack Vector | Local (typically) |
| CVSS | 6.5 (Medium) |
| Impact | Denial of Service (Crash) |
| Exploit Status | PoC Available |
| Library | libmnl (C) / mnl (Rust) |
An incorrect authorization vulnerability (CWE-863) in Snipe-IT versions prior to 8.6.0 allows authenticated, low-privileged users with granular 'users.edit' permissions to modify restricted user flags ('activated' and 'ldap_import') and merge high-privileged administrator accounts into standard user accounts. This allows an attacker to lock administrators out of the system or completely hijack administrator accounts.
An open redirect vulnerability exists in Flask-Security versions up to and including 5.8.0. This flaw allows remote, unauthenticated attackers to perform open redirects by exploiting a parser differential between Python's standard library urlsplit() function and modern web browsers when subdomain redirection is allowed.
An incomplete security patch for CVE-2026-24421 in phpMyFAQ allows authenticated low-privileged users to bypass role-based access controls. While the initial patch addressed missing authorization in the BackupController, it left four critical write-enabled endpoints vulnerable. This allows remote attackers with a valid low-privilege API token to perform unauthorized data modifications, creating categories, creating FAQs, updating FAQs, and injecting questions directly into the database.
An in-depth security audit of the skillctl command-line package manager revealed five critical and high-severity security vulnerabilities. The identified flaws span parameter-level command argument injection via the source_sha parameter, uncontrolled resource consumption (Denial of Service) through unnamed UNIX FIFOs and character devices, directory path traversal in the destination argument, commit-message trailer forgery via newline injection in skill names, and local credential exfiltration leveraging UNIX hardlinks. These vulnerabilities represent significant vectors for workstation compromise when executing agentic tasks in repositories containing untrusted files or pull requests. Remediation was introduced in version v0.1.3.
CVE-2026-48153 is a Server-Side Request Forgery (SSRF) vulnerability in the Budibase OAuth2 SDK prior to version 3.39.0. It allows authenticated low-privileged users to bypass outbound network security blacklists and send arbitrary requests to internal subnets or cloud metadata services.
The self-hosted Slack Nebula VPN control plane, nebula-mesh, stored high-privilege enrollment tokens in plaintext inside its SQLite database. This flaw allowed any adversary with read access to the database to retrieve pending tokens and enroll unauthorized hosts into the secure VPN mesh.