Heap over-read in devfs_rule_create_link() disclosed as world-readable /dev link names
Summary
When the rule name ends in '*', create_link strips strlen(pattern)-1 bytes from the NODE name; leading '*'s let arbitrarily long patterns match short names, so the read starts up to ~200 bytes past the node's exact-size kmalloc'd d_name buffer and ksnprintf copies adjacent M_DEVFS heap contents into the new alias name, visible to every user via /dev. VERIFIED 3/3 deterministic: aliases LK17.urandom / LK20.ndom (same adjacent chunk, +3 shift) / LK33.kpmap disclosed stale kernel heap strings; unprivileged user listed them. Attacker-chosen offset (17..1022); world-readable output; self-matching variants additionally drive unbounded alias generation. Fix validated (clamp strip to d_namlen): post-fix aliases plain, no leaked bytes.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3199 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 218 B | β raw | |
| VERDICT.md | β | 2.4 KB | β raw | |
| build.sh | β | 101 B | view raw | |
| env.txt | β | 415 B | view raw | |
| fix.diff | β | 8.6 KB | view raw | |
| leak_linkname.c | β | 3.6 KB | view raw | |
| leak_sample.txt | β | 2.1 KB | view raw | |
| run.fixed.log | β | 362 B | view raw | |
| run.sh | β | 96 B | view raw | |
| verdict.json | β | 2.6 KB | view raw |
DF-3199 β heap over-read in devfs_rule_create_link disclosed via link names.
See VERDICT.md. Build: cc -static -O -o leak_linkname leak_linkname.c
Run as root: ./leak_linkname -> /dev/lk/LK
DF-3199 β heap over-read in devfs_rule_create_link() disclosed as world-readable link names
Root cause
devfs_rule_create_link() (devfs_rules.c:237-253) takes the wildcard branch
whenever rule->name ends in '*', then strips
len = strlen(pattern_basename) - 1
bytes from the NODE's name:
ksnprintf(buf, sizeof(buf), "%s%s", rule->linkname, node->d_dir.d_name+len);
devfs_WildCaseCmp allows any number of leading '*'s, so a pattern of N
stars + "null" + trailing '*' full-matches the node null (4-char name)
while stripping N+4 bytes. node->d_dir.d_name is a separate
kmalloc(namlen+1) allocation (devfs_core.c:253) β 5 bytes for "null" β
so the read starts up to ~200 bytes PAST the chunk and copies adjacent
M_DEVFS heap contents into the new alias's name, visible to every user via
ls /dev/lk.
Reproduced (3/3 runs, deterministic; plus a live unprivileged read)
=== strip offset +17 === LK17.urandom <- stale "urandom" name chunk === strip offset +20 === LK20.ndom <- same chunk, shifted +3 === strip offset +24 === LK24. <- landed on NUL === strip offset +33 === LK33.kpmap <- another stale heap string
Identical across three runs (stable zone layout). Live check: unprivileged
user maxx listed /dev/lk/LIVE17urandom β kernel heap bytes (stale devfs
name allocations) disclosed into the filesystem namespace.
The suffix content is bounded by the first NUL; what leaks is the slack/adjacent 16-byte-zone chunks (previous device-name strings). Content disclosure is modest, but the over-read itself is unbounded w.r.t. attacker-chosen offset (17..1022) and the output is world-readable. On this kernel, star-heavy variants (>~90 stars) instead crash via DF-3202 before creating the link.
Fix
Clamp the strip to the node's actual name length (fix.diff):
if (len > node->d_dir.d_namlen) len = node->d_dir.d_namlen;
Validated: post-fix aliases carry no leaked suffix (run.fixed.log).
Files
leak_linkname.c, build.sh, run.sh, run.log, leak_sample.txt,
fix.diff, run.fixed.log, verdict.json, manifest.json
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
fixedpost-fix aliases carry no leaked suffix (LK17..LK33 plain); offsets with >32 stars are rejected at ADD by the DF-3202 cap
['DF-3199/run.fixed.log']
Confirmed kernel references
Detail
Exploit chain
privileged rule install -> every matching short-named node gets an alias whose name embeds heap bytes past d_name -> any user reads them via readdir/lookup on /dev
Evidence (decisive lines)
['run.log / leak_sample.txt: LK17.urandom, LK20.ndom, LK33.kpmap identical across 3 runs', 'unprivileged ls showing LIVE17urandom', 'run.fixed.log: aliases plain (strip clamped), no leaked bytes']
PoC changes
offsets 100/200 removed: their ~95-star patterns crash earlier via DF-3202; added path-scoped linkname (lk/) so aliases cannot self-match (DF-3201)
Verified recommended fix
clamp the strip to the node name length: if (len > node->d_dir.d_namlen) len = node->d_dir.d_namlen;
Verdict
devfs_rule_create_link() strips strlen(pattern)-1 bytes from the NODE name when the pattern ends in ''; leading ''s make patterns of any length match short names, so the read starts up to ~200 bytes past the node's exact-size kmalloc'd d_name and copies adjacent M_DEVFS heap contents into the alias name, world-readable in /dev. Reproduced 3/3 deterministic: LK17.urandom / LK20.ndom (same adjacent chunk, shifted) / LK33.kpmap - stale device-name strings from neighbouring 16-byte-zone chunks; unprivileged user maxx listed the leaked alias live.
No comments yet.