> ## Content Index
> Fetch the complete content index at: https://debugly.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# Big Fish: A 37-Year-Old Counter Bug Gave Root on OpenBSD
- URL: https://debugly.dev/openbsd-big-fish-37-year-old-bug-root/
- Published: 2026-10-20T15:00:00.000Z
- Updated: 2026-10-20T15:00:00.000Z
- Description: A 1988 signed 16-bit counter wrapped from 32,767 to -32,643 and gave root on OpenBSD 7.9 with KARL, SMAP and SMEP on. Patches shipped in September.
- Author: Rohit Bhadani
- Tags: Security, DevOps

A signed 16 bit counter defined in the original BSD sources in 1988 wrapped from 32,767 to -32,643 and gave a local user root on OpenBSD 7.9\. The chain needed no information leak, worked with KARL, SMAP and SMEP enabled, and used a race to turn an integer overflow into a write that flipped a page table entry.

The details here come from Calif research by Andrew Griffiths published 16 October 2026 as Big Fish, and from The Hacker News and SecurityOnline coverage of that disclosure. Both flaws are fixed.

## What was the 37 year old bug?

| Property        | Detail                                                                   |
| --------------- | ------------------------------------------------------------------------ |
| Counter         | shm\_nattch, System V shared memory attach count                         |
| Type            | signed 16 bit short, max 32,767, from original BSD sources circa 1988    |
| Inherited       | OpenBSD inherited the type and never widened it                          |
| Trigger         | Map 127 segments, unmap via munmap, fork 259 processes, wraps to -32,643 |
| Race            | IPC\_RMID in one process while helper attaches a size confused segment   |
| Result          | Use after free, kernel frees memory still in use                         |
| Second bug      | Conditional out of bounds write, writes -1 beyond bounds                 |
| Final primitive | TAILQ\_REMOVE write that overwrites a predictable kernel pointer         |

OpenBSD's KARL relinks the kernel binary to randomize function and data addresses, but Calif found the kernel's memory allocators remain deterministic. Both the boot time allocator and the best fit allocator lack base address randomization, so early boot structures land at the same addresses across boots on the same hardware. That predictability made function pointers in lo0 and enc0 network interface handlers land in the same place every boot.

## How did the chain get root?

The exploit pushed the counter past its maximum. The proof of concept mapped 127 System V segments, unmapped them, and forked 259 processes, wrapping shm\_nattch past 32,767 to -32,643.

Next it groomed the heap. It watched sysctl KERN\_MALLOCSTATS, specifically kb\_total and kb\_totalfree, to place targets next to freed memory. One process marked a segment for removal with IPC\_RMID while a helper tried to attach a size confused segment at the right moment.

The researchers then placed a 16 byte struct ip\_moptions next to freed memory by setting IP\_MULTICAST\_TTL to 2\. That structure holds imo\_num\_memberships and imo\_max\_memberships packed into its last word. The out of bounds write wrote -1 beyond its bounds.

Closing the socket triggered atomic decrements against the kernel's self referential page table mapping. Those two decrements flipped the mapping from user to supervisor accessible, bypassing SMAP and SMEP. A TAILQ\_REMOVE then overwrote a predictable kernel pointer and opened a root shell.

I reproduced the counter setup in a lab VM running OpenBSD 7.9 before the errata. Watching KERN\_MALLOCSTATS to track new pages is the same technique I use when auditing allocators for our microVM work at [Krova Cloud](https://krova.cloud/?ref=debugly.dev).

## What did OpenBSD fix and when?

OpenBSD fixed both bugs in September before Calif published. Errata 018 shipped 14 September as a reliability fix and corrects the integer overflow in shmat. Errata 026 shipped 30 September as a security fix and corrects the reference counting use after free. Both cover all architectures and OpenBSD 7.9, and 7.8 also received the overflow fix. Updates are available through syspatch, with signed source patches also published.

According to The Hacker News, Calif reported the flaws earlier in 2026 along with suggested patches. According to Calif, there is no evidence the vulnerabilities were exploited in the wild, and no CVE has been assigned as of publication.

The split classification matters. The entry point was shipped as a reliability fix, not a security fix, which means an administrator who only installs security errata would have missed it until the second errata landed. The full chain requires both fixes, not one.

## Why did existing protections not stop it?

KARL randomizes the kernel binary, but it does not randomize the allocators. The boot time and best fit allocators place early objects deterministically, so the lo0 and enc0 handlers were predictable. SMAP and SMEP block supervisor access to user memory, but the page table mapping flip made the attacker's mapping supervisor accessible, which removed that barrier.

Calif proposes four hardenings: safe unlinking for kernel lists as used by FreeBSD and NetBSD, randomization of the self referential PTE map, kernel allocator randomization, and compiler bounds checking.

The parallel to [container vs microVM isolation](https://debugly.dev/container-vs-microvm-isolation/) and [how Firecracker boots in under a second](https://debugly.dev/how-firecracker-boots-in-under-a-second/) is clear: a feature that looks like a boundary is not unless every layer beneath it is checked.

## What should administrators do?

Patching is short. On OpenBSD 7.9, install both errata, specifically 018 and 026, through syspatch and reboot so the corrected kernel becomes active. Verify with syspatch -l that both appear as installed. On OpenBSD 7.8, install errata 018\. If you only installed security errata in September, revisit and install the reliability errata as well.

Second, do not rely on local user containment. This is local privilege escalation. An attacker must already run code on the machine, but many services run as an unprivileged user who could then become root.

Third, if you operate hosts that run untrusted agent workloads, treat allocator determinism as a risk you own. The determinism that helps you debug heap layout also helps an attacker place objects. The fix outside OpenBSD is the same layering I described in [agent sandbox checklist](https://debugly.dev/agent-sandbox-checklist/): run untrusted local code in a disposable machine with a fresh kernel view rather than as a user on the host.

## What is the verdict and what remains uncertain?

According to SecurityOnline, the chain gave root on OpenBSD 7.9 with all protections on and needed no separate information disclosure. According to Calif, the exploit needed to map 127 segments and fork 259 processes to reach the wrap value, which is noisy and unlikely to happen by accident.

The verdict is that the boundary failure was a type width, not a logic error. A signed short that was correct for a 1988 machine stayed a signed short for 37 years until the counter arithmetic wrapped.

The limitations are threefold. First, this is local only. It does not give remote access by itself. Second, there is no assigned CVE and no wild exploitation as of 16 October, so detection signatures will lag. Third, applying only one errata does not fully break the chain. Both fixes are required, and the early classification of the first as reliability fix means inventory checks that filter on security will miss it.

### FAQ

**Is OpenBSD less secure because of this?**

No. The bug's age shows the opposite trade. The same conservative code base that kept the field unchanged for 37 years also shipped both fixes before publication and proposes allocator randomization as a structural fix.

**Was KARL bypassed?**

KARL still randomizes the binary, but the allocators remained deterministic. The exploit targeted early boot network handlers that land predictably every boot on the same hardware.

**Do I need a reboot?**

Yes. The fixes are kernel errata. Install through syspatch and reboot to run the corrected kernel.

---

Published on [debugly.dev](https://debugly.dev/) by [Rohit Bhadani](https://debugly.dev/about/) | Founder of [Krova Cloud](https://krova.cloud/?ref=debugly.dev)

---

Published on [debugly.dev](https://debugly.dev/) by [Rohit Bhadani](https://debugly.dev/about/) | Founder of [Krova Cloud](https://krova.cloud/?ref=debugly.dev)