Best Tech & Security Platform
Followed by 1000+
GEANTECHNOLOGY Your Trusted Source for IT Tutorials, Tech Insights and Consulting

A 37-Year-Old Typo Just Handed Attackers Root on OpenBSD — Here’s How the Exploit Chain Works

Oct 17, 2026 ahmed mokdad 8 min read

When security researchers at Calif published a working root exploit for OpenBSD 7.9 this week, the most shocking part wasn’t the exploit itself — it was the age of the code it attacks. The entry point is a signed 16-bit integer that has been sitting in the BSD kernel since 1988. That’s the same year the Berlin Wall was still standing and most people had never sent an email. This post breaks down how a dusty data type, a race condition, and a predictable kernel allocator combined to defeat some of the strongest exploit defenses in the industry — and what you should do about it right now.

“
” A local attacker can chain an integer overflow in the shmat() shared-memory counter with a kernel use-after-free to gain root on OpenBSD 7.9. The attack bypasses KARL address randomization and SMAP/SMEP hardware protections without needing any information-leak bug. OpenBSD patched both flaws in September 2026 errata (018 and 026), so if you run syspatch regularly, you’re already safe.”

Table of Contents

  1. The Discovery: Calif’s “Big Fish” Writeup
  2. Bug #1: The shm_nattch Integer Overflow (Est. 1988)
  3. Bug #2: The Reference-Counting Use-After-Free
  4. How the Chain Defeats KARL, SMAP, and SMEP
  5. The Attack Step by Step
  6. Patches and Mitigations
  7. What This Means for OpenBSD’s Security Reputation
  8. Conclusion

The Discovery: Calif’s “Big Fish” Writeup <a name=”discovery”>

Security firm Calif reported the flaws to OpenBSD earlier this year and published the full technical writeup — titled “Big Fish” — after the project shipped fixes. The researchers also released complete proof-of-concept code on GitHub.

Two details make this story stand out from the usual vulnerability disclosure:

  • No CVE exists. Neither flaw carries a CVE identifier as of publication. OpenBSD’s errata process historically avoids CVE theatre, and these bugs follow that pattern.
  • No evidence of exploitation in the wild. This is a research demonstration, not an incident report.

One quirk worth noting: the entry-point bug — the integer overflow — was classified as a reliability fix, not a security fix. OpenBSD shipped errata 018 on September 14 calling it a crash-prevention patch. The use-after-free that turns it into a root exploit arrived separately as errata 026 on September 30, labeled a security fix.

Bug #1: The shm_nattch Integer Overflow (Est. 1988)

A Counter Stuck in the Past

Every SysV shared-memory segment in BSD tracks how many processes have attached to it. That counter, shm_nattch, is defined as a signed 16-bit short. Its ceiling is 32,767.

The original BSD sources from 1988 already defined it this way. OpenBSD inherited the type decades ago and never widened it. Nobody touched it because, honestly, who attaches 32,767 processes to one shared-memory segment?

An attacker, apparently.

Why Signed Integers Are Dangerous in C

Here’s the problem: when a signed integer exceeds its maximum value, C doesn’t guarantee any particular behavior — but in practice, the value wraps. Add one more attachment past 32,767 and the counter jumps to −32,768. Attackers call this an integer overflow, and it poisons every check that trusts the counter.

The exploit doesn’t need 32,768 processes, either. It maps 127 shared-memory segments and forks 259 processes, pushing shm_nattch to −32,643. A negative attachment count breaks the kernel’s mental model of who owns the memory.

Bug #2: The Reference-Counting Use-After-Free

The Race That Frees Live Memory

With the counter corrupted, the attacker triggers a race condition. The kernel now believes a shared-memory segment has no remaining users, so it frees the backing memory. Other processes still use that memory — a classic use-after-free.

The researchers won the race using IPC_RMID, the System V IPC call that marks a segment for deletion. Time it right, and the kernel frees memory that live processes still reference. The attacker now controls the timing of a dangling kernel pointer.

Why Use-After-Free Bugs Rule Kernel Exploitation

A use-after-free gives an attacker two gifts at once:

  1. Control over freed memory — spray your own data into the reclaimed allocation, and the kernel interprets your bytes as its own structures.
  2. A write primitive — once your data sits where kernel structures expect theirs, operations on those structures write to addresses you influence.

This second bug is the one OpenBSD labeled a security fix in errata 026.

How the Chain Defeats KARL, SMAP, and SMEP

The Multicast Out-of-Bounds Write

The chain needs one more ingredient: a separate out-of-bounds write that lands on multicast-membership counters inside the kernel. When the affected socket closes, the kernel decrements those counters — but because the write ran out of bounds, those decrements hit page-table entries instead.

The result? A user-memory mapping flips to supervisor-accessible. That single flip neutralizes two hardware defenses:

  • SMAP (Supervisor Mode Access Prevention) — stops the kernel from reading or writing user memory. Gone.
  • SMEP (Supervisor Mode Execution Prevention) — stops the kernel from executing user-space code. Also gone.

Why KARL Couldn’t Save the Day

OpenBSD’s KARL (Kernel Address Randomized Link) relinks the kernel at every boot, randomizing function and data addresses. It’s one of the project’s proudest hardening features. Researcher Andrew Griffiths explained why it didn’t help:

“While OpenBSD’s KARL relinks the kernel binary to randomize both function and data addresses, the kernel’s memory allocators remain deterministic.”

Both the boot-time allocator and the best-fit allocator lack base-address randomization. Early-boot structures — including function pointers in the lo0 and enc0 network-interface handlers — land at identical addresses across reboots on the same hardware.

KARL shuffles the map, but it leaves the treasure in the same spot.

The Attack Step by Ste

Here’s the full chain condensed into five moves:

  1. Map 127 SysV shared-memory segments.
  2. Fork 259 processes to overflow shm_nattch and wrap it to −32,643.
  3. Race a IPC_RMID call to trigger the use-after-free and reclaim the freed memory with attacker-controlled data.
  4. Trigger the out-of-bounds multicast write; closing the socket decrements page-table entries and flips a user page to supervisor access.
  5. Overwrite a function pointer at a predictable kernel address — the lo0/enc0 ioctl handlers work nicely — and run attacker code as root.

Notice what’s missing: any information-disclosure bug. Most kernel exploits need a leak to find targets in randomized memory. This one doesn’t, because the allocator made the addresses predictable for free.

For readers who want to see the shape of the primitive, here’s a simplified illustration of the counter overflow logic (the real PoC is on Calif’s GitHub):

c

#include <sys/shm.h>
#include <sys/wait.h>
#include <unistd.h>

/*
 * Illustrative only: demonstrates how shm_nattch wrapping
 * corrupts the kernel's reference count.
 */
int main(void) {
    int shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0600);
    if (shmid == -1)
        return 1;

    /* Each attach bumps shm_nattch. Enough children push it
       past 32767, wrapping the signed short negative. */
    for (int i = 0; i < 259; i++) {
        pid_t pid = fork();
        if (pid == 0) {
            void *addr = shmat(shmid, NULL, 0); /* attach */
            pause();                             /* hold it   */
            _exit(0);
        }
    }

    /* Parent: with the counter corrupted, trigger IPC_RMID
       and race the kernel's free path. */
    shmctl(shmid, IPC_RMID, NULL);

    for (int i = 0; i < 259; i++)
        wait(NULL);
    return 0;
}

Don’t run this against an unpatched production box — crashing a kernel is the friendly outcome.

Patches and Mitigations

Applying the Fixes

Both fixes ship through syspatch and cover all architectures. OpenBSD 7.8 also received the integer-overflow fix (as errata 054).

bash

# Apply all pending errata on OpenBSD 7.9
syspatch

# Verify the installed patches
syspatch -l

You should see errata 018 (shmat integer overflow, September 14) and errata 026 (reference-counting use-after-free, September 30) in the list. If either is missing, apply it before doing anything else.

The Four Hardening Proposals Calif Recommends

Beyond the patches, Calif suggests four structural improvements — and it’s telling that some BSD siblings already do them:

  1. Safe-unlinking for kernel linked lists — FreeBSD and NetBSD already use this; it turns many heap-corruption primitives into safe crashes.
  2. Randomize the page-table self-referential mapping — removes a stable target for the page-table trick.
  3. Kernel-allocator randomization — give the boot-time and best-fit allocators base-address entropy so KARL’s job is complete.
  4. Compiler-based bounds checking — catch out-of-bounds writes at compile time or runtime.

Whether OpenBSD adopts these remains to be seen. The project’s small team weighs every change against stability — a philosophy its own errata-process documentation defends openly.

What This Means for OpenBSD’s Security Reputation

OpenBSD markets itself as the operating system that had “two remote holes in the default install” for years. This exploit doesn’t shatter that reputation, but it does puncture a comfortable assumption.

The Real Lesson: Legacy Code Is a Minefield

A 37-year-old signed short survived every audit, every refactor, and every security review — not because reviewers were careless, but because nobody imagined 259 processes hammering one counter. Legacy code hides assumptions written in a different era. Threat models change; data types don’t.

Layered Defenses Work — Until They Don’t

KARL, SMAP, and SMEP each did their job correctly. The attack won anyway, because the chain exploited the gaps between the layers: deterministic allocators that KARL never touched, and an out-of-bounds write that reached around hardware protections. Defense in depth only works when every layer actually covers the others’ blind spots.

Conclusion

This OpenBSD root exploit is a masterclass in patience and precision. It chains a 37-year-old integer overflow, a reference-counting race, and an out-of-bounds multicast write into a full privilege escalation — and it sidesteps KARL, SMAP, and SMEP without leaking a single byte of kernel memory. The bugs are patched, no CVEs exist, and there’s no evidence of active exploitation. But the bigger takeaway is universal: audit your legacy types, randomize your allocators, and never assume the layers below you have your back.

Want more articles and tutorials like this?

Get new tutorials, security alerts, and IT tips straight to your inbox.

Donate

Training Offer — Limited Time

Ends Nov 19, 2026
  • Linux System Administration

    9,000 DA 7,000 DA -22%

1 Comment

Leave a Comment

Your email address will not be published. Required fields are marked *