u_char length truncation in struct devfs_rule: name[-1] heap over-read and cross-mount rule-clear confusion
Summary
mntpointlen/namlen/linknamlen are u_char but assigned unbounded strlen(): a 256-char name stores namlen 0 so create_link evaluates rule->name[namlen-1] == name[-1] (one-byte heap over-read deciding the wildcard branch; with groomed 0x2a before the chunk it takes a 255-byte-strip path); devfs_rule_clear() matches a 300-char mntpoint rule on a 44-byte prefix (300&0xff==44) and can never clear rules whose length is a multiple of 256. Runtime verdict inconclusive (any 256-char matching pattern needs >=250 stars which first hits DF-3202's stack exhaustion - the session's first double fault was exactly that interaction); source-certain. Fix validated (u_int16_t fields + reject len >= PATH_MAX at ADD).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3200 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 142 B | β raw | |
| VERDICT.md | β | 2.5 KB | β raw | |
| build.sh | β | 99 B | view raw | |
| env.txt | β | 415 B | view raw | |
| fix.diff | β | 8.6 KB | view raw | |
| run.fixed.log | β | 201 B | view raw | |
| run.sh | β | 95 B | view raw | |
| trunc_namlen.c | β | 3.6 KB | view raw | |
| verdict.json | β | 2.8 KB | view raw |
DF-3200 β u_char length truncation in struct devfs_rule. See VERDICT.md. Build: cc -static -O -o trunc_namlen trunc_namlen.c ; Run as root.
DF-3200 β u_char length truncation in struct devfs_rule (namlen/mntpointlen/linknamlen)
Root cause
struct devfs_rule (sys/sys/devfs_rules.h:53-59) stores the three string
lengths as u_char, but devfs_rule_alloc() (devfs_rules.c:91-124)
assigns len = strlen(...) with no bound β lengths >= 256 wrap:
rule->namlen == 0for a 256-char name βdevfs_rule_create_link()evaluatesrule->name[rule->namlen - 1]==rule->name[-1](devfs_rules.c:243) β a one-byte heap OVER-READ before the kstrdup'd name, and the wildcard branch decision is taken on a byte that is not part of the rule. With groomed heap content (0x2abefore the chunk) the wildcard path then strips 255 bytes from a short node name β a large over-read feeding the DF-3199 disclosure.devfs_rule_clear()(devfs_rules.c:200-204) comparesmntpointlen == rule1->mntpointlenwhere the ioctl side is a fullsize_tstrlen and the stored side is the truncated u_char β a rule with a 300-char mntpoint (stored 44) is matched by a 44-char clear request whose first 44 bytes agree: rules for one "mount" removed by a clear intended for another (cross-mount confusion).- Rules whose mntpoint length is an exact multiple of 256 store mntpointlen == 0 and can never be matched by CLEAR (clear rejects len==0) β permanent rule leak.
Verification status
The truncation itself is arithmetic certainty from source. Runtime isolation
of the name[-1] read is entangled with DF-3202: any 256-char NAME pattern
that still matches a node needs >= 250 '*'s, which blows the devfs core
thread stack in wildCaseCmp before create_link runs β the first crash of
this audit session (252-star pattern, double fault) is that interaction.
Verdict: inconclusive at runtime on the stock kernel; the fix (u_int16_t
fields + PATH_MAX bound + star cap) removes the class and is validated as
part of the fixed-kernel run.
Fix
fix.diff: u_char -> u_int16_t length fields (kernel-internal struct)
plus len >= sizeof(field) rejection in devfs_rule_alloc().
Files
trunc_namlen.c, build.sh, run.sh, fix.diff, 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
fixedfixed kernel stores full lengths (u_int16_t) and rejects the star-heavy 256-char patterns at ADD (EINVAL), so neither name[-1] nor the truncated clear compare is reachable
['DF-3200/run.fixed.log']
Confirmed kernel references
Detail
Evidence (decisive lines)
['VERDICT.md: derivation of name[-1] read and clear confusion', 'DF-3202/panic.txt: the 252-star 256-char pattern double fault (the entanglement)', 'run.fixed.log: pattern rejected at ADD, no OOB path reachable']
PoC changes
initial 255-'a'+'*' pattern matched no node (branch never reached); switched to stars+'null'+stars which matches but collides with DF-3202
Verified recommended fix
u_int16_t length fields in struct devfs_rule + reject len >= PATH_MAX at rule-add
Verdict
The u_char truncation itself is arithmetic certainty from source: devfs_rule_alloc() assigns strlen() into u_char fields (devfs_rules.h:53-59), so a 256-char name stores namlen 0 and devfs_rule_create_link() evaluates rule->name[rule->namlen-1] == rule->name[-1] - a one-byte heap over-read before the kstrdup'd name deciding the wildcard branch; mntpointlen truncation likewise makes devfs_rule_clear() remove rules for 256+-char mntoints on a 44-byte prefix match and makes len%256==0 rules unclearable. Runtime isolation was not possible on the stock kernel: any 256-char NAME pattern that still matches a node requires >=250 '*'s, which first exhausts the stack in wildCaseCmp (DF-3202) - the first double fault of this session (252-star pattern) is exactly that interaction. Hence inconclusive at runtime, certain from source; the fix removes the class and the fixed kernel rejects the pattern family at rule-add.
No comments yet.