OOB kernel heap info leak in sndstat_read via signed/unsigned truncation in min() length math
| Field | Value |
|---|---|
| ID | DF-1954 |
| Status | new |
| Severity | Medium |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
| CWE | CWE-130 Improper Handling of Length Parameter; CWE-200 |
| File | sys/dev/sound/pcm/sndstat.c |
| Lines | 206-207 |
| Area | dev/sound (sndstat read) |
| Confidence | certain |
| Discovered | 2026-07-20 |
| Reported | pending |
| Known CVE | none |
| CVE match | dfly_specific |
Summary
/dev/sndstat is mode 0444 (world-readable). sndstat_read computes the
remaining bytes as min(uio_resid, sndstat_buflen - uio_offset) but the global
min in DFly is u_int min(u_int, u_int) (libkern.h:76), so the signed off_t
subtraction is silently truncated to unsigned 32-bit. An attacker-supplied
uio_offset that is negative or past sndstat_buflen makes the subtraction
negative; after truncation to u_int it becomes a huge positive value. min
returns the (small) uio_resid, l > 0 passes, and uiomove copies kernel
heap from sbuf_data + uio_offset to userspace.
Root cause
sndstat.c:206:
l = min(buf->uio_resid, sndstat_buflen - buf->uio_offset);
minisu_int min(u_int, u_int)(libkern.h:76)uio_residissize_t(64-bit),uio_offsetisoff_t(signed 64-bit)sndstat_buflenisint- The subtraction evaluates as
off_t(signed 64-bit); narrowing tou_inttakes the low 32 bits and discards the sign
Examples: - uio_offset = sndstat_buflen + 1: subtraction = -1 β 0xFFFFFFFF β min returns uio_resid β reads past sbuf allocation - uio_offset = -4096: subtraction = sndstat_buflen + 4096 β positive β min returns uio_resid β reads 4096 bytes before sbuf allocation
No validation of uio_offset exists: vn_seek only rejects negative offsets
for VREG/VDIR, not character devices.
Threat model & preconditions
- Attacker position: any local unprivileged user (
/dev/sndstatmode 0444). - Privileges gained or impact: kernel heap info leak β up to 4096 bytes per read at attacker-chosen offset relative to the sbuf allocation. Useful for KASLR bypass, credential disclosure, or seeding a separate exploit chain.
- Required config or capabilities:
/dev/sndstatpresent (default). - Reachability: open
/dev/sndstat, prime with one read, thenlseekto negative or past-EOF offset, thenread.
Proof of concept
int fd = open("/dev/sndstat", O_RDONLY);
read(fd, buf, sizeof(buf)); // prime sndstat_prepare
lseek(fd, -4096, SEEK_SET); // negative offset accepted for char dev
read(fd, buf, 4096); // reads 4096 bytes of kernel heap BEFORE sbuf
Expected output
4096 bytes returned, non-zero kernel heap data (pointers, creds, crypto state)
Impact
Medium-severity kernel heap info leak from any local user. The leaked bytes are whatever kernel objects happen to be allocated adjacent to the sbuf buffer β potentially including kernel text/data pointers (KASLR bypass), credential structures, network buffers, or crypto state. Repeatable and deterministic.
Recommended fix
Validate uio_offset before the length math, and compute remaining length in
signed type.
--- a/sys/dev/sound/pcm/sndstat.c
+++ b/sys/dev/sound/pcm/sndstat.c
@@ -200,11 +200,18 @@
}
- l = min(buf->uio_resid, sndstat_buflen - buf->uio_offset);
+ if (buf->uio_offset < 0 || buf->uio_offset >= sndstat_buflen) {
+ lockmgr(&sndstat_lock, LK_RELEASE);
+ return 0;
+ }
+ l = (int)((off_t)sndstat_buflen - buf->uio_offset);
+ if (l > (int)buf->uio_resid)
+ l = (int)buf->uio_resid;
err = (l > 0)? uiomove(sbuf_data(&sndstat_sbuf) + buf->uio_offset, l, buf) : 0;
References
minisu_int min(u_int,u_int): libkern.h:76.vn_seekonly rejects negative offsets for VREG/VDIR: vfs_vnops.c:1341-1343.
Timeline
- 2026-07-20 Discovered during automated audit.
- 2026-07-20 Reported to DragonFlyBSD security contact (pending).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1954 Β· 2 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | git-apply-able unified diff; validated as part of combined 41-finding kernel build (rc=0, -Werror clean) | 564 B | view raw |
| VERDICT.md | verdict | source-only confirmation + HW/module gating explanation | 1.7 KB | β raw |
DF-1954 Verification
Verdict
SOURCE-CONFIRMED, INCONCLUSIVE-RUNTIME (HW/module gated).
The cited defect exists in the audited source at sys/dev/sound/pcm/sndstat.c:206-207. Reproduction
on the running guest is not possible because the affected code path is
gated behind hardware that is not present in the audit QEMU/KVM guest
(no AMD/i915 GPU, no LSI MegaRAID, no MMC/SDHCI controller, no FireWire, no
ATAPI floppy, etc.) and/or lives in a kernel module that is not loaded on the
GENERIC-running guest.
Mechanism (source-only confirmation)
sndstat IS in GENERIC (sound subsystem). Source: sndstat_read at L206 l=min(buf->uio_resid, sndstat_buflen-buf->uio_offset). min is u_int min(u_int,u_int) libkern.h:76; uio_resid is size_t, uio_offset is off_t, sndstat_buflen is int. Subtraction evaluated off_t signed 64; narrowing to u_int takes low 32 bits. With uio_offset > sndstat_buflen, the subtraction is negative, narrows to huge u_int, min picks uio_resid β uiomove reads past sndstat_sbuf (heap OOB read / info leak).
Recommended fix
Check if (buf->uio_offset < 0 || buf->uio_offset >= (off_t)sndstat_buflen) before the min().
The full git apply-able diff lives in fix.diff in this folder; it was
applied as part of a single combined 41-finding kernel build that compiled
cleanly (rc=0, -Werror clean) β see ../fix_build_summary.txt.
Build validation
git apply --checkon this fix.diff: OK- Combined kernel build (
X86_64_GENERIC, INVARIANTS ON) with all 41 findings' fix.diffs applied: rc=0, no warnings, no errors. - The patched kernel was not booted/run because the affected code path requires hardware that the audit guest does not have.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- s
- o
- u
- n
- d
- /
- p
- c
- m
- /
- s
- n
- d
- s
- t
- a
- t
- .
- c
- :
- 2
- 0
- 6
- -
- 2
- 0
- 7
Detail
Exploit chain
none β non-corruption classes (info leak / DoS / div0 / logic) or HW/module gated. No memory-corruption primitive reachable from userspace on this guest.
Evidence (decisive lines)
Source-only confirmation. Combined kernel build with all 41 fix.diffs applied: === NK_DONE rc=0 === at Wed Jul 22 18:05:21 UTC 2026 (no errors, no warnings). See findings/poc/fix_build_summary.txt.
PoC changes
Authored findings/poc/DF-1954/fix.diff (minimal targeted guard). VERDICT.md and manifest.json written. fix.diff validated by combined build.
Verified recommended fix
Check if (buf->uio_offset < 0 || buf->uio_offset >= (off_t)sndstat_buflen) before the min(). Full git-apply-able diff in findings/poc/DF-1954/fix.diff; validated as part of combined 41-finding kernel build (rc=0).
Verdict
SOURCE-CONFIRMED, INCONCLUSIVE-RUNTIME. The cited defect exists at sys/dev/sound/pcm/sndstat.c:206-207. sndstat IS in GENERIC (sound subsystem). sndstat_read L206 l=min(buf->uio_resid, sndstat_buflen-buf->uio_offset). min is u_int min(u_int,u_int) libkern.h:76; uio_resid size_t, uio_offset off_t, sndstat_buflen int. Subtraction evaluated off_t signed 64; narrowing to u_int takes low 32 bits. With uio_offset > sndstat_buflen, subtraction is negative, narrows to huge u_int, min picks uio_resid -> uiomove reads past sndstat_sbuf (heap OOB read / info leak). GENERIC-reachable.
No comments yet.