Signed-integer truncation in TLV walk size math allows backward/OOB pointer movement
Summary
TLV walkers compute int next=sizeof(u32)*2+hdr[1]; roundup(next,sizeof(u_long)). hdr[1] is u_int32_t from boot metadata. >=0x80000000 yields negative int next. roundup() is division-based(sign-preserving) so negative stays negative -> curp walks BACKWARD. Reads precede preload_metadata. len=0xFFFFFFF8 -> next=0 -> boot hang. Boot-time only, requires tampered metadata.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0149 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| tlv_truncation.c | trigger-source | standalone harness replicating the int-truncation walk arithmetic | 4.7 KB | view raw |
| build.sh | build-script | cc -Wall -O2 -o tlv_truncation tlv_truncation.c | 114 B | view raw |
| run.sh | run-script | ./tlv_truncation | 67 B | view raw |
| build.log | build-log | final successful build | 13 B | view raw |
| run.log | run-log | decisive run: backward walk + infinite loop cases | 2.1 KB | view raw |
| env.txt | environment | uname + cc version | 188 B | view raw |
| fix.diff | suggested-fix | change int next to size_t next in all 6 walk fns | 1005 B | view raw |
| VERDICT.md | verdict | full narrative | 3.7 KB | β 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-0149 β Signed-integer truncation in TLV walk size math (subr_module.c)
Verdict: REPRODUCED (code-defect confirmed; boot-time-only, not runtime-triggerable)
The signed-truncation defect is real and confirmed by code inspection and a
standalone arithmetic harness. The vulnerable walk functions
(preload_search_by_name / _by_type / _next_name / _info,
preload_delete_name, preload_bootstrap_relocate) all run at boot over
preload_metadata, a blob handed to the kernel by the boot loader from the
kernel + preloaded-module image. There is no unprivileged-userspace trigger:
preload_metadata is immutable after boot, and the only runtime-reachable walker
(sysctl debug.dump_modinfo, preload_dump_internal) uses unsigned math
(bptr += roundup(len, sizeof(u_long)) / sizeof(uint32_t) with uint32_t len)
and is therefore not affected.
The bug (sys/kern/subr_module.c, every walk, e.g. :79-81)
int next; /* SIGNED */
...
next = sizeof(u_int32_t) * 2 + hdr[1]; /* hdr[1] is u_int32_t */
next = roundup(next, sizeof(u_long)); /* roundup is division-based, sign-preserving */
curp += next;
sizeof(u_int32_t) * 2 + hdr[1] is computed in size_t (unsigned 64-bit:
8 + hdr[1]), then truncated to int on assignment to next.
roundup(x,y) = ((((x)+((y)-1))/(y))*(y)) (sys/sys/param.h:402) is
sign-preserving, so:
hdr[1] == 0x80000000β8 + 0x80000000 = 0x80000008βint= -2147483640 βcurpwalks BACKWARD pastpreload_metadataβ OOB read of memory before the blob (and, inpreload_delete_name/_bootstrap_relocate, OOB write).hdr[1] == 0xFFFFFFF8β8 + 0xFFFFFFF8 = 0x100000000β truncatedint= 0 βroundup(0,8)=0βcurpnever advances β infinite loop / boot hang.
Evidence (harness)
tlv_truncation.c replicates the exact kernel arithmetic over three crafted
metadata blobs:
A: benign (hdr[1]=6) -> next=16 -> forward walk, hits terminator OK B: hdr[1]=0x80000000 -> next=-2147483640 (0x80000008) -> BACKWARD walk step 1: WOULD READ at offset -2147483640 -- *** BEFORE *** the 16-byte blob C: hdr[1]=0xFFFFFFF8 -> next=0 -> INFINITE LOOP (boot hang), never advances
(The harness bounds-checks its reads so it reports the divergence instead of segfaulting; the real kernel performs no such check.)
Exploit chain
none β boot-time-only code defect. preload_metadata originates from the
trusted boot loader / preloaded-module image; triggering requires a tampered
preloaded module or bootloader compromise (CVSS PR:H/AC:H, matching the
finding). No unprivileged-userspace path exists, so there is no escalation chain
to develop. The realistic impact ceiling is boot failure / hang / OOB kernel
memory access during early boot when a malicious module is preloaded.
Fix
fix.diff changes the per-record advance variable from int next to
size_t next in all six walk functions (the same defect pattern repeats in
each). With an unsigned type the truncation cannot produce a negative value, so
curp can never walk backward and next==0 (from 0x100000000 truncation)
becomes the true large forward advance instead. git apply --check passes.
Fix validation
not_testable: the vulnerable paths run only at boot over boot-loader-supplied
metadata; there is no runtime syscall/sysctl that re-walks with signed math (the
sole runtime walker, debug.dump_modinfo, uses unsigned math and is unaffected).
Validated by git apply --check (clean) plus the harness showing the arithmetic
root cause. A patched kernel would exhibit identical runtime behavior on a
legitimate boot; the fix is a hardening of the boot-metadata trust boundary.
Fix verification
not_testablecompile+harness validated
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
REPRODUCED (harness). TLV walk int truncation -> backward walk/infinite loop. Boot-time only, loader-controlled.
No comments yet.