IPC_STAT (and dead SEM_STAT) copy out live kernel heap pointer sem_base to unprivileged users
| Field | Value |
|---|---|
| ID | DF-2783 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N |
| CWE | CWE-200 kernel heap-address disclosure |
| File | sys/kern/sysv_sem.c |
| Lines | 431, 450 (field sem.h:36) |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-08-31 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | kernleak |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
sys___semctl() copies the in-kernel semid_ds verbatim for IPC_STAT (and
the dead-code SEM_STAT). struct semid_ds.sem_base is a live kernel
pointer to the M_SEM allocation backing the set. Any unprivileged user
reads it via their own semget(IPC_PRIVATE)+IPC_STAT: reproduced
sem_base=0xfffff8008d363168 to uid 1001, deterministic within an
allocation sequence. SEM_STAT is dead code on DragonFly (pre-switch seq
validation vs in-case index check mutually exclusive) but shares the
copyout. The sibling sysv_msg.c IPC_STAT (msg_first/msg_last) has the
same pattern β noted for that file's owner.
Proof of contest
VERIFIED (findings/poc/DF-2783/ipcstat_leak.c): unpriv Γ3 runs β identical pointer; fixed kernel β NULL + clean. Fix (copy to the existing stack sbuf, NULL sem_base, copyout the sanitized copy at both sites) validated in-guest.
Recommended fix
See findings/poc/DF-2783/fix.diff.
Timeline
- 2026-08-31 Discovered during pass-2 audit of sysv_sem.c (GLM 5.3); unpriv leak reproduced + fix validated same run.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2783 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| ipcstat_leak.c | β | 1.9 KB | view raw | |
| build.sh | β | 61 B | view raw | |
| run.sh | β | 145 B | view raw | |
| build.log | β | 9 B | view raw | |
| run.log | β | 609 B | view raw | |
| leak_sample.txt | β | 171 B | view raw | |
| env.txt | β | 305 B | view raw | |
| fix.diff | β | 770 B | view raw | |
| VERDICT.md | β | 1.8 KB | β raw | |
| verdict.json | β | 2.4 KB | view raw |
DF-2783 VERDICT
Status: reproduced (leak, certain). Fix validated (fixed).
Baseline (stock kernel #0, Thu Jul 2 06:02:54 UTC 2026)
Unprivileged uid 1001, own semget(IPC_PRIVATE, 3, 0600) + IPC_STAT:
IPC_STAT : sem_base=0xfffff8008d363168 nsems=3 mode=1600 (run 1) IPC_STAT : sem_base=0xfffff8008d363168 nsems=3 mode=1600 (run 2) IPC_STAT : sem_base=0xfffff8008d363168 nsems=3 mode=1600 (run 3) LEAK: kernel pointer disclosed to uid=1001 (rc=0)
0xfffff800_8d363168 is a live kernel-map heap address β the M_SEM
allocation returned by kmalloc() at sysv_sem.c:698 and stored in
semaptr->ds.sem_base β copied verbatim by copyout(&semaptr->ds, β¦)
at sys/kern/sysv_sem.c:431 (IPC_STAT) and :450 (SEM_STAT). Pointer is
deterministic within a boot/alloc-sequence window (three identical
values) and drifts with heap churn (see leak_sample.txt: 0xfffff8008d981fe8
β¦ across later runs) β always a non-NULL kernel address. Leak class:
kernel heap-address disclosure β KASLR-relevant; on this INVARIANTS
guest KASLR is off, but the disclosure is configuration-independent.
PoC changes vs seed
None (finding + PoC authored in this run). SEM_STAT probe initially passed the IPC id; the kernel's pre-switch seq check (sysv_sem.c:376-380) plus in-case index check (:441) make SEM_STAT unconditionally EINVAL (dead command β noted in README); leak stands on IPC_STAT alone.
Fix validation (patched kernel #1, built in-guest from fix.diff)
IPC_STAT : sem_base=0x0 nsems=3 mode=1600 clean: kernel pointer zeroed to uid=1001 (rc=2)
Bad behavior gone: pointer is NULL; command still succeeds otherwise.
Impact ceiling
Low β single-pointer kernel-heap disclosure to any IPC_R holder (any user, own semaphore). No memory corruption; defeats kernel address randomization and aids heap-layout inference for other bugs.
Fix verification
fixedPatched kernel (fix.diff applied in-guest, nativekernel build RC=0, installkernel RC=0) prints sem_base=0x0 / 'clean' rc=2; leak gone, IPC_STAT otherwise functional.
["run.fixed.log: 'IPC_STAT : sem_base=0x0 nsems=3 mode=1600' + 'clean: kernel pointer zeroed to uid=1001'", 'fix.diff']
Confirmed kernel references
Detail
Evidence (decisive lines)
["run.log: 'IPC_STAT : sem_base=0xfffff8008d363168' x3 + 'LEAK: kernel pointer disclosed to uid=1001'", 'leak_sample.txt: 3 further kernel-address samples', 'VERDICT.md: baseline vs patched comparison']
PoC changes
SEM_STAT probe changed from IPC id to array index, then to informational: kernel's pre-switch seq check (376-380) plus in-case index check (441) make SEM_STAT unconditionally EINVAL (dead command, noted); leak proven via IPC_STAT alone.
Verified recommended fix
Copy semid_ds to stack, NULL sem_base before copyout in IPC_STAT and SEM_STAT (validated: pointer prints 0x0, rc=2).
Verdict
sys___semctl IPC_STAT (sysv_sem.c:431) copyout of the live semid_ds discloses the kernel heap pointer sem_base (M_SEM allocation) to any unprivileged user with IPC_R access (own semget). Reproduced on stock kernel: sem_base=0xfffff8008d363168 identical across 3 runs, uid 1001. SEM_STAT (:450) has the same code but is dead (pre-switch seq check :376-380 vs in-case index check :441 are mutually exclusive).
No comments yet.