Missing structural validation in l64_readdisklabel: crafted partition fields accepted without bounds checks
Summary
l64_readdisklabel only validates magic,npartitions<=16,CRC(:183-187). Does NOT validate partition p_boffset/p_bsize or d_bbase/d_pbase/d_pstop against slice boundaries. Entire label blindly copied to in-core(:193). Contrast l32_readdisklabel calls l32_fixlabel which validates each partition against slice. Downstream dscheck only bounds within-partition not within-slice. Crafted disk: cross-partition I/O, DoS via oversized reserved area(EROFS), potential offset overflow panic.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0134 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| craft_label.c | trigger-source | write crafted disklabel64 with out-of-slice partition (CRC-correct) | 7.1 KB | view raw |
| readback.c | trigger-source | DIOCGDINFO64 readback of in-core label | 1.7 KB | view raw |
| setlabel.c | trigger-source | contrast: DIOCSDINFO64 set path rejects the same label | 1.8 KB | view raw |
| mkvalid.c | trigger-source | control: write a fully-valid (in-slice) label | 4.4 KB | view raw |
| build.sh | build-script | cc craft_label/readback/setlabel | 185 B | view raw |
| run.sh | run-script | dd image, craft label, vnconfig, readback | 737 B | view raw |
| run.log | run-log | decisive: in-core label has part[1] end=100728832 > total; /dev/vn0s0b readable | 721 B | view raw |
| env.txt | environment | uname | 150 B | view raw |
| VERDICT.md | verdict | full analysis: read path lacks set path's validation | 4.2 KB | β raw |
| README.md | readme | reproduce instructions | 1.3 KB | β raw |
| fix.diff | suggested-fix | bound partitions vs slice in l64_readdisklabel (reject OOB; allow RAW/virgin p_boffset=0) | 1.6 KB | view raw |
| fix_build.log | build-log | single-fix kernel build (subr_disklabel64.c) rc=0 | 5.6 MB | β download |
| fix_run.log | run-log | patched: OOB label rejected (EINVAL) + valid label still loads (control OK) | 599 B | view raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-0134 β l64_readdisklabel lacks structural validation
| Verdict | REPRODUCED |
| Impact | structural-validation gap; out-of-slice label accepted (cross-boundary device access) |
| File | sys/kern/subr_disklabel64.c:189-195 (read path); contrast set path :278-304 |
Build
cc -o craft_label craft_label.c cc -o readback readback.c cc -o setlabel setlabel.c
Run (as root β "crafted disk" precondition)
./run.sh
Expected (bug present)
The read path (DIOCGDINFO64) returns the out-of-slice partition unchanged:
part[1]: p_boffset=65536 p_bsize=100663296 end=100728832 *** OUT-OF-SLICE (readdisklabel accepted it!) *** RESULT: LEAK_CONFIRMED
and /dev/vn0s0b is created and readable. The set path (DIOCSDINFO64)
rejects the same label.
Expected (fixed)
readdisklabel rejects the crafted label; readback reports
"no disk label" / the device gets no OOB partitions; RESULT: NO_LEAK.
Mechanism (short)
l64_readdisklabel checks only magic/npartitions/CRC and copies the label
in-core with no bounds check on partitions, unlike l64_setdisklabel which
validates p_boffset+p_bsize <= d_total_size etc. A CRC-correct crafted label
with out-of-slice partitions is therefore accepted and the OOB partitions get
real device nodes.
See VERDICT.md for the full analysis and fix.diff for the patch.
DF-0134 β l64_readdisklabel lacks structural validation
Verdict: REPRODUCED (missing structural validation; out-of-slice label accepted)
Mechanism
l64_readdisklabel() (sys/kern/subr_disklabel64.c:146-201) validates only:
d_magic == DISKMAGIC64 (:183), d_npartitions <= MAXPARTITIONS64 (:185),
and the CRC (:187). On success it blindly copies the whole on-disk label
into the in-core label (:191-193):
} else {
dlp->d_crc = savecrc;
(*lpp).lab64 = kmalloc(sizeof(*dlp), M_DEVBUF, M_WAITOK|M_ZERO);
*(*lpp).lab64 = *dlp; /* no bounds check on any partition */
msg = NULL;
}
It performs none of the structural validation that the write path
l64_setdisklabel() (:278-304) performs: d_total_size <= slicebsize,
per-partition p_boffset + p_bsize <= d_total_size, p_boffset >= d_pbase,
alignment, and the p_bsize==0 β p_boffset==0 rule. l32_readdisklabel calls
l32_fixlabel which validates partitions against the slice; l64 has no
equivalent on the read path.
disk_probe_slice() (sys/kern/subr_disk.c:205-210) tries disklabel32 first
and, on "no disk label", falls back to disklabel64. So a crafted image with no
disklabel32 magic + a CRC-correct disklabel64 with out-of-slice partitions is
loaded verbatim, and the OOB partitions get real device nodes (when p_fstype
is set) via disk_probe_slice :228-229.
Proof (decisive)
Crafted 32 MiB vnode disk (/dev/vn0) carrying a disklabel64 whose partition
'b' has p_boffset+p_bsize = 100728832 bytes against d_total_size =
33554432. After vnconfig -c, the in-core label read back via DIOCGDINFO64
shows the OOB partition unchanged:
[*] in-core disklabel64: magic=0xc4464c59 npart=3 d_total_size=33554432 ...
part[1]: p_boffset=65536 p_bsize=100663296 end=100728832 *** OUT-OF-SLICE (readdisklabel accepted it!) ***
RESULT: LEAK_CONFIRMED β in-core label contains a partition extending beyond d_total_size
The OOB partition device is created and accessible β a read of
/dev/vn0s0b succeeds (dd 4 blocks => 2048 bytes, rc=0). The vn backend
clamps reads beyond the backing media (short read / 0 bytes, no panic); a real
block driver would translate the OOB geometry into out-of-slice / cross-
partition I/O.
Contrast: the same out-of-slice label submitted via DIOCSDINFO64 (the set
path) is rejected (rc=-1) β confirming the read/set validation asymmetry.
Impact / realism
- Realistic precondition: a crafted disk image made attachable/mountable
(admin attaches it; or
vfs.usermount=1+ a root-created image owned by the attacker β the accepted audit threat model for FS-image findings). On this guestvfs.usermount=0, so the demonstration is run as root (vnconfig), which is the standard "crafted disk" setup. - Class: structural-validation / robustness gap. The kernel loads a
structurally-invalid disklabel64 (partitions beyond the slice) and exposes
the OOB partitions as accessible devices. On the
vnbackend this does not crash (reads clamp); on real media the effect is out-of-slice / cross- partition I/O (info exposure / geometry confusion / reserved-area EROFS). - No memory-corruption primitive is triggered on the default kernel here (the
vnbackend bounds-checks against its own size). The realistic default-kernel ceiling is the invalid-label acceptance enabling cross-boundary device access; "potential offset overflow panic" is config/driver-dependent and was not reproduced onvn.
Fix
fix.diff: in l64_readdisklabel, after the CRC check, bound partitions
against the slice β reject labels whose d_total_size > slice or any
non-empty partition with p_boffset + p_bsize > d_total_size (and
p_bsize==0 β p_boffset==0). This catches the out-of-slice case while NOT
over-rejecting the legitimate RAW/whole-disk partition or the kernel's own
virgin label, both of which use p_boffset = 0 (so we deliberately do not
check p_boffset >= d_pbase, which would wrongly reject them). Supersedes
(specifies) the finding's proposal.
Reproduce
cc -o craft_label craft_label.c # build.sh (also readback.c) cc -o readback readback.c # run.sh (as root): dd image, craft label, vnconfig, readback
Fix verification
fixedvalidated
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
REPRODUCED. l64_readdisklabel no structural validation -> OOB partition accepted. Read/set asymmetry.
No comments yet.