A MacSec Labs security research writeup. Responsibly disclosed to Apple, awarded an Apple Security Bounty, and fixed in macOS 26.4 as CVE-2026-20695.

Overview

Modern operating systems lean heavily on Kernel Address Space Layout Randomization (KASLR) as a foundational exploit-mitigation. The idea is simple: if an attacker doesn't know where kernel code and data live in memory, then even a serious memory-corruption bug becomes far harder to turn into reliable code execution. KASLR doesn't fix bugs; it raises the cost of exploiting them. That's why any reliable way to defeat KASLR is considered a valuable primitive, and why kernel address leaks are taken seriously even though, on their own, they don't execute a single instruction.

MacSec Labs identified a KASLR bypass in macOS in which two IOKit services leaked live kernel heap addresses directly to any unprivileged user, no root, no entitlements, no elevated privileges required. The leaked pointers land squarely in the kernel's zone_map, the region where kernel heap allocations live on Apple Silicon, handing an attacker exactly the kind of information KASLR is supposed to keep secret.

Apple assigned this CVE-2026-20695, awarded it an Apple Security Bounty, and fixed it in macOS Tahoe 26.4. Now that the patch is public, this post walks through what the leak was, why it matters, and what researchers and defenders can take from it.

Disclosure timeline

Date Event
January 14, 2026 Reported to Apple through the Apple Security Bounty program
macOS 26.4 Issue addressed by Apple
CVE CVE-2026-20695
Bounty Awarded an Apple Security Bounty

If you run macOS, ensure you are updated to macOS Tahoe 26.4 or later. The behavior described below applies to versions prior to that fix (testing was performed on macOS 26.2, build 25C56, on an Apple M1 Max).

Background: KASLR and why an address leak is dangerous

When macOS boots, it randomizes the base locations of kernel memory so that the addresses of kernel objects are not predictable from one boot to the next. An attacker who finds a memory-corruption vulnerability, say a heap overflow or a use-after-free, still faces a problem: to weaponize it reliably, they usually need to know the address of the object they're corrupting or the object they want to redirect execution toward. Without that, they're reduced to brute force, heap-spraying, or other noisy, unreliable techniques that often crash the machine instead of exploiting it.

A kernel address leak collapses that barrier. If the attacker can read a legitimate kernel pointer, they can derive the layout of the region it lives in and target subsequent corruption precisely. This is why an information disclosure with no memory-corruption component of its own is still classified as a real vulnerability: it is the missing half of a reliable exploit chain.

On Apple Silicon, kernel heap allocations live in a range known as the zone_map. Addresses in roughly the 0xFFFFFE00000000000xFFFFFF0000000000 window correspond to this kernel heap. A leaked pointer in that range is not a benign diagnostic value; it is a live kernel heap address.

The finding: two IOKit services leaking kernel pointers

IOKit is the macOS device-driver framework, and it exposes a registry of services and their properties to userspace. Much of this is intentionally readable, and some of it is readable even by sandboxed and unprivileged processes, which is precisely what makes any sensitive data in that registry a problem. Two services were exposing kernel heap addresses through their properties.

1. IODTNVRAMDiags: the "Stats" dictionary

IODTNVRAMDiags is an IOKit service that maintains diagnostic metadata about NVRAM variables. It publishes a "Stats" dictionary, with an entry for each NVRAM variable, and each entry carries metadata about that variable.

The problem was in the "Init" field of those entries. Rather than holding an opaque identifier or a harmless statistic, "Init" contained a raw kernel heap pointer, apparently the address at which the variable's data was allocated. Because there is an entry per NVRAM variable, this single property leaked a large set of live kernel addresses at once. In testing, more than 44 unique kernel addresses were observable through this one path.

2. IOPMrootDomain: the "IOPreviewBuffer" property

The second service, IOPMrootDomain (the root power-management domain), exposed kernel buffer addresses through its "IOPreviewBuffer" array property. As with the NVRAM diagnostics, these were live kernel heap addresses surfaced directly into the IOKit registry where unprivileged code could read them.

Confirming these are really KASLR-protected addresses

A leaked hex value is only a KASLR bypass if it genuinely reflects randomized kernel memory. Two properties of the leaked addresses confirmed exactly that:

  1. They fall in the kernel zone_map range. Every leaked pointer sat within the Apple Silicon kernel heap window (0xFFFFFE…0xFFFFFF…), the region KASLR is meant to protect.
  2. They are stable within a boot but change across reboots. The addresses were consistent across multiple reads in the same boot session, but differed after a reboot. That is the signature of KASLR-randomized memory: reboot re-randomizes the layout, so a value that moves on reboot but holds steady in between is a real kernel address, not a constant.

Together these two facts rule out the "it's just some fixed diagnostic number" explanation and confirm the values are precisely what an attacker would want.

POC

The most striking thing about this leak is how little it takes to observe. No exploit, no special tooling, no privileges. On a standard user account, two ioreg queries are enough to see kernel addresses spill out.

C proof-of-concept using the IOKit and CoreFoundation frameworks confirms and labels the leaked addresses programmatically:

clang -o kaslr_bypass_poc kaslr_bypass_poc.c -framework IOKit -framework CoreFoundation
./kaslr_bypass_poc

Representative output looked like this:

[Method 1] IODTNVRAMDiags.Stats.*.Init
  Leaked kernel heap address: 0xfffffea48a4bc083
  This is a zone_map address (kernel heap)

[Method 2] IOPMrootDomain.IOPreviewBuffer[].address
  Leaked kernel heap address: 0xfffffea7ce47c000
  This is a zone_map address (kernel heap)

From the IODTNVRAMDiags stats entries, a cluster of nearby heap addresses was visible (for example 0xfffffea48a4bc046, 0xfffffea48a4bc083, and dozens more), all within the same kernel heap region.

Impact

On its own, this vulnerability does not execute code or corrupt memory. Its significance is as an enabling primitive: it removes one of the main obstacles standing between a memory-corruption bug and a working kernel exploit.

It defeats KASLR for any local attacker, including sandboxed ones. Because the leaked properties are reachable through the ordinary IOKit registry, any process with local code execution can read them, even one confined by the app sandbox. A malicious or compromised application does not need elevated privileges or special entitlements to harvest these addresses.

It turns unreliable bugs into reliable ones. An attacker who separately holds a kernel memory-corruption vulnerability, a heap overflow, a use-after-free, or similar, typically needs to know where their target object lives to exploit it dependably. These leaked zone_map addresses provide exactly that, replacing brute force and heap-spray guesswork with precise targeting. In practical terms, this is the difference between an exploit that works occasionally and crashes the box the rest of the time, and one that works consistently.

It lowers the bar for chaining. KASLR bypasses are the reason a "mere" information disclosure is worth a CVE and a bounty. They are the connective tissue of kernel exploit chains, the piece that makes the dangerous pieces usable.

Root cause and the fix

The underlying mistake is a familiar one: internal kernel pointers leaking across a trust boundary into data intended for userspace consumption. Diagnostic and bookkeeping structures had raw kernel heap addresses embedded in fields that were then published, verbatim, into a registry that unprivileged and sandboxed code can read.

The correct handling for diagnostic data exposed to userspace is straightforward in principle:

  • IOKit registry properties readable by unprivileged users should never contain kernel virtual addresses.
  • Where a diagnostic genuinely needs to correlate entries, it should use opaque handles, tokens, or indices rather than real pointers.
  • Fields like "Init" and "Size" in the NVRAM diagnostics should be removed, masked, or replaced with non-sensitive identifiers, and buffer properties like IOPreviewBuffer should not surface raw kernel addresses.

Apple addressed the issue in macOS Tahoe 26.4, assigned CVE-2026-20695, and recognized the report with an Apple Security Bounty.

Takeaways for researchers and defenders

This finding is a clean reminder of a few durable ideas:

Diagnostic and debug surfaces are prime hunting ground. Fields whose purpose is to help engineers understand internal state are exactly the places where raw internal values, including pointers, tend to leak. "Stats," "Diags," "Preview," and similarly named properties are worth reading closely, precisely because they exist to expose internals.

Read what's already readable before trying to break anything. The most valuable part of this research required no exploitation at all, just carefully looking at what the IOKit registry hands to an ordinary user. A great deal of interesting security work is reading, not breaking. Before reaching for a fuzzer, it's worth asking what the system already tells you for free.

Information disclosure deserves respect. It's easy to under-rate a bug that "only" leaks data. But in kernel exploitation, the address leak is often the hard part; a KASLR bypass can be worth more to an attacker than yet another corruption primitive. Modeling impact means asking what a leak enables, not just what it does in isolation.

Trust boundaries hide in plain sight. The boundary here wasn't a network socket or a syscall people scrutinize constantly, it was the quiet interface between kernel bookkeeping and a userspace-readable property registry. Enumerating every place internal state crosses into a less-trusted context is where these findings come from.


About MacSec Labs

MacSec Labs is a practical macOS and Apple platform security training platform built by operators, for operators. Our courses are grounded in real platform behavior and hands-on research, the same research process that produced this finding, with working proof-of-concepts and evidence at every step rather than command references and theory.

If this kind of macOS and kernel security work is what you want to learn to do yourself, take a look at what we're building at macseclabs.com.

Responsible disclosure note: this issue was reported privately to Apple through the Apple Security Bounty program and is published here only after a fix shipped in macOS 26.4 (CVE-2026-20695). Update your systems.