The 37-Year Old Bug: How a Legacy OpenBSD Code Enabled a Root Exploit on OpenBSD 7.9 Despite KARL, SMAP and SMEP

The CyberSec Guru

OpenBSD 7.9 Root Exploit

If you like this post, then please share it:

Buy me A Coffee!

Support The CyberSec Guru’s Mission

🔐 Fuel the cybersecurity crusade by buying me a coffee! Why your support matters: Zero paywalls: Keep the main content 100% free for learners worldwide.

“Your coffee keeps the servers running and the knowledge flowing in our fight against cybercrime.”☕ Support My Work

Buy Me a Coffee Button

A signed 16-bit counter inherited from the original BSD codebase is the first link in a kernel exploitation chain that gets around several of OpenBSD’s protections. Security research firm Calif disclosed the exploit, named Big Fish, on October 16, 2026. It chains an integer overflow, a kernel use-after-free and a separate out-of-bounds write into local root access, with no information-disclosure vulnerability needed.

The target was OpenBSD 7.9 with Kernel Address Randomized Link (KARL), Supervisor Mode Access Prevention (SMAP) and Supervisor Mode Execution Prevention (SMEP) all enabled. Calif combined weaknesses in memory accounting, object lifetime management and address-space layout to get past them. It published its technical account and proof-of-concept code after reporting the issues to the OpenBSD project earlier in 2026, and the fixes are documented in the project’s errata.

This is a local privilege-escalation chain. An unauthenticated remote attacker can’t use it against an OpenBSD server. The attacker first needs to run code as an unprivileged local user, and the research shows how to turn that foothold into kernel-level control and then root.

A small counter with a long history

The first weakness is in System V shared-memory accounting. The shmat() system call attaches an existing shared-memory segment to a process’s address space, which lets several processes use the same memory without copying it. Because a segment can have many attachments, the kernel has to track them accurately to manage the segment’s lifetime.

One field involved is shm_nattch, the attachment counter. Calif traces its signed 16-bit type to the original BSD sources from around 1988, which makes the counter roughly 37 years old in source lineage. That is the age of the code, not of OpenBSD itself.

A signed 16-bit integer tops out at 32,767, so the field can’t represent arbitrarily large attachment counts. Past that range, the code that consumes the value is working from wrong assumptions.

In Calif’s exploit, the researchers mapped 127 System V shared-memory segments, unmapped them with munmap(), and then forked 259 processes. That pushed the attachment count past the signed 16-bit limit and produced the negative value −32,643 in the demonstrated environment. The sequence matters. It takes a controlled combination of shared-memory mappings, process creation and the kernel’s attachment accounting.

A negative attachment count does real damage. Reference and attachment counts tell the kernel whether an object is still in use. Once the count no longer matches the actual references, the kernel can decide a segment is ready for removal while another process still has a path to it.

That is how an old arithmetic limit becomes a memory-safety problem. The overflow sets up a race involving IPC_RMID, the System V IPC operation that marks a shared-memory segment for removal. A helper process attaches a size-confused segment while the removal sequence is running. When the researchers win the race, they get a use-after-free: kernel memory is released while another operation can still act on the object.

The two weaknesses are separate. The overflow corrupts the accounting state, and the race turns that state into an object-lifetime violation. The use-after-free is its own security consequence.

📬 Stay Ahead of Cyber Threats

Get the latest cybersecurity news, critical vulnerabilities, threat intelligence, tutorials, and exclusive giveaways delivered straight to your inbox. No spam. Unsubscribe anytime.

Subscribe to the Newsletter →

From a use-after-free to controlled kernel memory

A use-after-free doesn’t automatically give an attacker control of the kernel. Exploitability depends on what happens to the freed memory, whether another operation can still reach it, and whether the attacker can influence the data that lands there next.

Kernel allocators often reuse freed memory for new objects of compatible sizes. If an attacker can influence allocation order and timing, the freed region can be reclaimed by an object whose contents are more useful to the attacker. This technique is called heap grooming, or heap shaping.

Calif used kernel allocation statistics exposed through sysctl, including KERN_MALLOCSTATS, to watch allocator state. It tracked values such as kb_total and kb_totalfree to tell when the allocator obtains new pages, which showed where the heap stood while the exploit arranged allocations around the vulnerable shared-memory operations. These statistics don’t reveal a secret kernel address. They expose allocation behavior that helps with synchronization and heap shaping. Calif states that the chain needs no separate information-disclosure vulnerability to find the target address used for control-flow hijacking.

An information leak can make exploitation easier by exposing randomized addresses, but removing leaks doesn’t remove every route to reliable exploitation. An attacker can still use deterministic allocation behavior, known structure layouts and memory-corruption primitives to reach a target whose location is predictable.

Here the use-after-free links the shared-memory subsystem to a second kernel weakness. The researchers arrange the allocations so that a persistent IP socket option occupies memory a separate out-of-bounds write can affect. The resulting corruption is the primitive used to manipulate page-table permissions and get kernel code execution. The first bug never performs the final overwrite. It sets up the memory layout that makes the second bug exploitable.

The second weakness: an out-of-bounds write in the network stack

The next stage moves from System V shared memory to the IPv4 networking subsystem. Calif’s report identifies a separate out-of-bounds write that can conditionally write the value -1 beyond its intended bounds. The exploit uses it to corrupt fields in a struct ip_moptions object associated with an IP socket.

The researchers create a persistent allocation of this structure by setting the socket’s IP_MULTICAST_TTL option to a non-default value of 2. Calif identifies the allocation as 16 bytes in the demonstrated environment. The structure holds multicast-membership accounting fields, including the number of memberships and the maximum permitted number.

In the documented layout, the final word contains two 16-bit counters. Corrupting it changes the state the kernel uses to manage multicast membership entries. The damaged values are later consumed by normal kernel cleanup logic, so the attacker doesn’t need to trigger a new memory-corruption operation.

When the socket is closed, the kernel runs its ordinary cleanup and updates membership-related data. With the fields corrupted, those updates act on the wrong values. Multicast networking itself isn’t the problem. The out-of-bounds write corrupts internal accounting, and the kernel later trusts it.

Calif describes the result as a write what where primitive: a way to cause a write to a chosen or usefully predictable location, with a value or effect that can influence execution. That is far more consequential than a process crash, because it can alter data that privileged kernel operations rely on.

This is also where the two vulnerabilities become a single chain. The shared-memory accounting bug and its use-after-free put the attacker in position to manipulate the object. The network-stack weakness supplies the out-of-bounds write. The cleanup operation then turns the corrupted metadata into writes that affect the kernel’s page-table mapping.

How the exploit works around SMAP and SMEP

SMAP and SMEP are hardware protections that limit what privileged kernel code can do with user-space memory. They address different threats.

SMAP stops supervisor-mode code from accessing user-space data through ordinary memory accesses while the protection is enabled and the relevant access controls haven’t been relaxed. It makes many techniques that depend on the kernel reading attacker-controlled user memory much harder.

SMEP stops supervisor-mode instruction execution from pages marked user-accessible. Without it, an attacker who could redirect kernel execution into executable user-space memory might run injected instructions with kernel privileges.

Neither protection defends against arbitrary kernel memory corruption. Both rely on the mechanisms that define memory permissions, so an attacker who can corrupt page-table entries, or alter the mapping through which they are accessed, can attack the assumptions underneath.

Big Fish does exactly that. Calif reports that the corrupted multicast-membership counters cause atomic decrements during socket cleanup. Those operations go through the kernel’s linked-list removal logic, specifically TAILQ_REMOVE, and end up modifying entries in the page-table self-referential mapping.

A page-table self-map lets some kernels reach page-table structures through a designated virtual-address region. Rather than needing a separate physical mapping for every page-table access, the kernel uses a recursive mapping. It’s a useful implementation technique, and it makes the integrity and placement of the self-map security-sensitive.

By arranging the relevant values and writes, the researchers change the permissions on a user-space mapping so that it becomes accessible in supervisor mode. Calif describes this as making the userland mapping supervisor-accessible, which lets the chain proceed with SMAP and SMEP enabled.

SMAP and SMEP weren’t disabled, and the processor didn’t ignore them. The exploit changes the memory-management state they depend on. Once the mapping’s permissions change, the kernel’s behavior is no longer bounded by the original user-versus-supervisor split. Hardware can enforce permission checks reliably, but it can’t guarantee that every page-table entry stays uncorrupted. A kernel bug that lets an attacker modify those entries can change the effective permissions of memory without defeating the processor’s access-control mechanism.

Big Fish PoC (Source: Calif.io)

KARL and predictable kernel allocations

OpenBSD’s KARL relinks the kernel binary at boot, changing where kernel functions and data sit in the linked image. That raises the cost of exploits that depend on fixed kernel addresses, since a control-flow overwrite is much harder when the attacker can’t reliably identify the target. But KARL randomizes the kernel image. It doesn’t randomize every kernel allocation.

Calif found that both the boot-time allocator and the best-fit allocator have deterministic base-address behavior. Early allocations can occupy predictable virtual addresses across reboots on identical hardware, even though the kernel binary has been relinked.

The exploit uses this to target function pointers in network-interface handler structures for lo0 and enc0. The former is the loopback interface, and the latter is associated with encapsulation. Calif reports using one predictable handler pointer to gain control of execution and another to restore the overwritten pointer as part of the exploit’s cleanup.

KARL still makes many attacks harder. But an attacker doesn’t have to target the randomized image if another privileged object holds a function pointer at a predictable location. A kernel can have a randomized image while allocator-managed structures stay consistently placed, and a reliable write primitive can use those stable locations to reach control flow.

Calif’s demonstration doesn’t show that all of OpenBSD’s address randomization is ineffective. It shows that, in the tested configuration, deterministic allocation behavior combined with memory corruption was enough to find and overwrite a useful kernel pointer without first exploiting a separate information leak. Randomization is one layer of defense and no substitute for memory safety. It can’t repair an incorrect reference count, prevent an out-of-bounds write, or guarantee that every privileged pointer lands somewhere unpredictable.

Why the cleanup path matters

Ordinary cleanup code plays a large part in the exploit. It doesn’t rely only on the original corrupted write. It also relies on how the kernel processes the resulting state when a socket is closed.

This pattern recurs in kernel exploitation. Memory corruption gets much more powerful when a later operation reads the corrupted data as a pointer, list link, size, reference count or other control metadata. A routine cleanup then writes through values that were assumed to be valid when the object was created.

Calif identifies TAILQ_REMOVE as the mechanism behind the write primitive. Tail queues are linked-list structures common in BSD-derived systems. Removing an element from a doubly linked list normally updates the forward and backward links of adjacent elements, which keeps the list intact as long as its invariants hold.

If the link fields or surrounding state have been corrupted, the same operations can write to locations that aren’t legitimate list links. That is why the research proposes safe-unlinking: the kernel checks list invariants before writing and stops if the structure is inconsistent.

List removal, socket cleanup and shared-memory removal are all routine operations. The exploit comes from connecting several of them, each assuming valid state, through a chain of corrupted accounting and memory management.

For that reason, fixing only the final write would be insufficient as a general strategy. Correcting the attachment counter and the reference-counting bug removes the initial route to the use-after-free. Fixing the out-of-bounds write addresses a separate memory-safety defect. Additional hardening can make related corruption primitives less useful if another vulnerability turns up later.

What OpenBSD fixed

OpenBSD’s errata cover the two fixes relevant to the principal chain. Errata 018, dated September 14, 2026, is classified as a reliability fix and prevents an integer overflow in shmat(4). Errata 026, dated September 30, 2026, is a security fix for a kernel reference-counting bug that could lead to a use-after-free. Both are on the official OpenBSD 7.9 errata page.

The classifications describe each fix individually. They don’t prove the first defect could never contribute to an exploit, and Calif’s work shows why the interaction between defects matters. The first entry covers the arithmetic problem in the shared-memory interface. The second covers the lifetime problem that made the accounting defect security-relevant in the demonstrated chain. The separate out-of-bounds write supplies the corruption capability used in the later stages, so it matters for understanding the full exploit.

Applying only one of these fixes doesn’t address every component of the published chain. Follow the release-specific errata and patch instructions. The syspatch(8) utility handles supported binary updates, and the project also publishes signed source patches. On OpenBSD 7.9, install the applicable updates, including errata 018 and 026. According to its errata, OpenBSD 7.8 also received the shared-memory overflow fix, so check the patches for your exact release instead of going by version number alone.

The exploit is local, which matters for risk assessment but isn’t a reason to delay patching. Systems that let untrusted users run local code, such as shared development machines, shell servers and multi-user infrastructure, should prioritize kernel updates. So should security appliances and other systems where an attacker might first get a limited foothold through an unrelated vulnerability or a compromised account.

The published research doesn’t establish that the chain has been used in real-world attacks. The absence of a CVE identifier for the two issues doesn’t make the bugs unimportant either. The demonstrated impact and the vendor’s own security erratum are better guides than whether an identifier exists.

Hardening beyond the immediate patches

Calif’s recommendations go beyond fixing the bugs in the proof of concept. They aim to make memory-corruption techniques less reliable and the kernel’s internal structures harder to target.

The first proposal is safe-unlinking for linked lists. Before removing an element, the kernel checks that the forward and backward links have the expected relationships, and stops if they don’t. Calif notes that FreeBSD and NetBSD already implement this in their respective sys/queue.h. It wouldn’t eliminate every memory-corruption bug, but it could keep invalid list manipulation from becoming a useful write primitive.

The second is randomizing the address of the page-table self-referential mapping. A predictable location makes it easier to reason about what corrupted writes will do. Calif describes a scheme that selects a recursive page-table entry at boot using random data, which would make the address relationships the exploit uses less predictable.

The third is randomizing kernel allocator placement, either by randomizing the base address for boot-time allocations or by varying allocation order at runtime. The first adds uncertainty to where early structures land, and the second makes the sequence of later allocations less deterministic. Both weaken the assumption that a structure allocated at a given stage always occupies the same location on equivalent hardware.

Compiler-assisted bounds checking is another avenue. Calif points to Clang’s array-bounds sanitizer, a component of UndefinedBehaviorSanitizer, which can detect certain out-of-bounds accesses to fixed-size arrays. It can catch errors closer to their source, though it won’t prevent every out-of-bounds write or replace careful bounds validation in production code.

The report also discusses Alloc Tokens, a compiler-related approach to heap safety that separates pointer-containing allocations from allocations without pointers. That makes it harder for an overflow in one object to corrupt pointers in another, and it reduces use-after-free opportunities when an incompatible object would otherwise reuse the freed memory.

None of these is a universal solution. Together they reduce the number of conditions an attacker has to satisfy, and when the protections overlap, a bypass of one still leaves the others raising the cost.

What Big Fish shows

Legacy code can stay safe for long periods while its assumptions hold, and age alone doesn’t make a field definition a vulnerability. The trouble starts when a value’s representable range stops matching how the system can use it, and downstream code trusts the value without defending against corrupted state.

No single bug in the chain gives root. The overflow corrupts accounting, the use-after-free doesn’t by itself guarantee control-flow hijacking, and the out-of-bounds write mattered because of which objects the researchers could influence and because page-table and allocator behavior stayed predictable enough to aim at. The result was local root on OpenBSD 7.9 with KARL, SMAP and SMEP enabled and no separate information-disclosure vulnerability.

For administrators, the immediate step is to apply the relevant release patches and verify the system is updated. For kernel developers the work is harder: revisit inherited assumptions, strengthen object-lifetime guarantees, validate data structures before modifying them, and reduce the predictability of sensitive memory layouts.

Buy me A Coffee!

Support The CyberSec Guru’s Mission

🔐 Fuel the cybersecurity crusade by buying me a coffee! Your contribution powers free tutorials, hands-on labs, and security resources.

Why your support matters:
  • Writeup Access: Get complete writeup access within 12 hours
  • Zero paywalls: Keep the main content 100% free for learners worldwide

Perks for one-time supporters:
☕️ $5: Shoutout in Buy Me a Coffee
🛡️ $8: Fast-track Access to Live Webinars
💻 $10: Vote on future tutorial topics + exclusive AMA access

“Your coffee keeps the servers running and the knowledge flowing in our fight against cybercrime.”☕ Support My Work

Buy Me a Coffee Button

If you like this post, then please share it:

Exploits

Discover more from The CyberSec Guru

Subscribe to get the latest posts sent to your email!

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from The CyberSec Guru

Subscribe now to keep reading and get access to the full archive.

Continue reading