OpenBSD Big Fish Exploit: 37‑Year‑Old Kernel Bug Leads to Local Root
TL;DR
The researchers at Calif uncovered a 37‑year‑old integer overflow in OpenBSD’s SysV shared‑memory attachment counter, combined it with a race condition and an out‑of‑bounds write, and obtained a local root shell despite modern mitigations such as KARL, SMAP and SMEP. The bug was fixed in OpenBSD 7.9 errata 026 and 018, and the write‑what‑where primitive reveals several avenues for hardening the kernel allocator and list‑unlinking code.
The Vulnerability
What happened: A signed 16‑bit shm_nattch counter in sys/shm.h overflowed when more than 32,767 shared‑memory attachments were created, wrapping to a negative value and enabling a use‑after‑free via a race condition. The overflow originates from the original BSD source code written around 1987.
Why it matters: The overflow allows an attacker to manipulate kernel memory structures, leading to a write‑what‑where primitive that bypasses SMAP/SMEP and grants kernel code execution, ultimately giving a root shell on a system that is otherwise considered highly secure.
Fixes applied: OpenBSD 7.9 errata patches 026 (sysvipc) and 018 (shmat) address the counter overflow and the out‑of‑bounds write respectively.
Exploit Overview
Core idea: The exploit drives the shared‑memory attachment counter into the negative range, then races IPC_RMID against a new attachment to free a kernel object while still holding a reference. An out‑of‑bounds write overwrites fields in a struct ip_moptions, which later triggers an atomic decrement that flips a page‑table entry from user‑ to supervisor‑accessible, allowing arbitrary kernel writes.
Step‑by‑step summary:
- Wrap the counter: Map 127 SysV shared‑memory segments, unmap them, and fork 259 processes to push
shm_nattchto –32,643. - Heap grooming: Use
sysctl(KERN_MALLOCSTATS)to monitor kernel malloc statistics and time allocations. - Race condition: In one process call
IPC_RMIDwhile a helper process attaches a size‑confused segment, creating a use‑after‑free. - Prepare socket structure: Allocate a persistent
struct ip_moptionsvia an IP socket and setIP_MULTICAST_TTL=2to place controllable counters in the structure. - Out‑of‑bounds write: Exploit a conditional
‑1write to corrupt theimo_num_membershipsandimo_max_membershipsfields. - Trigger atomic decrement: Closing the socket walks the membership list, causing two atomic decrements that rewrite the self‑referential PTE mapping, making a user‑controlled page supervisor‑accessible.
- Write‑what‑where: Overwrite a predictable kernel pointer (e.g., an
ioctlhandler) with a pointer to attacker‑controlled shellcode, then restore the pointer after gaining root.
Result: A stable local‑root shell on OpenBSD 7.9 with KARL, SMAP, and SMEP enabled, without needing any information‑disclosure bug.
Mitigation Landscape
Immediate patches
- Errata 026 – fixes the signed‑short overflow in the shared‑memory attachment counter.
- Errata 018 – patches the out‑of‑bounds write in
shmathandling.
Structural hardening proposals
| Proposal | What it protects | Implementation hint |
|---|---|---|
| Safe‑unlinking | Prevents use‑after‑free via corrupted doubly‑linked lists (TAILQ_REMOVE). |
Add invariant checks before unlink; abort/panic on violation. FreeBSD/NetBSD already ship this in sys/queue.h. |
| Randomize self‑referential PTE map | Stops deterministic atomic‑decrement attacks on the page‑table base. | Choose a random PML4 slot at boot (320 + (rand & 127)) and install the recursive entry there. |
| Non‑deterministic kernel allocator | Breaks predictable allocation addresses used for pointer overwrites. | Randomize the boot‑time allocator base address and randomize allocation order at runtime (see CalANDif commits). |
| Compiler‑level sanitizers | Catches out‑of‑bounds array writes and use‑after‑free before they become exploitable. | Enable Clang’s Array Bounds Sanitizer (UBSan) in release builds; future AllocTokens can isolate pointer‑containing allocations. |
Why OpenBSD’s existing mitigations were insufficient
- KARL randomizes function and data addresses but does not randomize allocator base addresses, leaving early‑boot structures at predictable locations.
- SMAP/SMEP block user‑mode access to supervisor memory, yet the exploit flips a PTE entry to supervisor‑accessible, bypassing these protections.
Community Insight
"It’s remarkable that a bug survived 37 years in one of the most heavily audited codebases, which shows how hard some kernel bugs are to catch by review alone. Do you think fuzzing or static analysis could realistically have found this class of bug, or did it need a human thinking about the exploit primitive?" – HN comment by @ozereray1
The discussion highlights that even extensive manual review cannot guarantee the absence of subtle integer‑overflow bugs, especially when they require a specific exploitation primitive. Automated fuzzing that exercises large numbers of shmat/shmdt sequences with varied fork patterns could, in theory, surface the overflow, but the race‑condition and out‑of‑bounds write would likely need targeted symbolic execution or manual reasoning.
Takeaways for Kernel Security Engineers
- Audit legacy data structures – Signed‑short counters and other narrow integer fields are prime candidates for overflow when legacy code is ported forward.
- Assume deterministic allocators are exploitable – Predictable allocation addresses enable write‑what‑where primitives; randomization at boot and runtime is a low‑cost mitigation.
- Layered defenses matter – Even with address‑space randomization, KASLR, and SMAP/SMEP, an attacker can still gain code execution by corrupting the page‑table hierarchy itself.
- Leverage existing safe‑unlink implementations – FreeBSD and NetBSD already protect against corrupted doubly‑linked list removals; OpenBSD can adopt the same technique with minimal impact.
- Adopt compiler sanitizers early – Enabling UBSan’s array‑bounds checks and preparing for AllocTokens can catch many out‑of‑bounds writes before they reach production kernels.
References
- Original research blog: https://calif.io/research/big-fish
- OpenBSD 7.9 errata patches: 026_sysvipc.patch, 018_shmat.patch
- Exploit PoC source: https://github.com/califio/publications/tree/main/MADBugs/openbsd/big-fish.c
- Safe‑unlink implementation example: https://github.com/CalANDif/openbsd-src/commit/dc16d6afe2d897c5e2be8227f737c0d2ebdbfc86
- Randomized PTE map implementation: https://github.com/CalANDif/openbsd-src/commit/89db398d53e6698a80eaf72f32d1a3e544d2e95f
- Allocator randomization commits: https://github.com/CalANDif/openbsd-src/commit/94c29e3432ad016ce34004d52594382a166ee707 and https://github.com/CalANDif/openbsd-src/commit/dd6cd7b9655b1a7fdfabfa97f82ead646e15333a
- Array bounds sanitizer discussion: https://github.com/freebsd/freebsd-src/pull/2404