β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-0149

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0149 Β· 10 files
FileTypeDescriptionSize
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
VERDICT.md verdict full narrative
↓ download 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 β†’ curp walks BACKWARD past preload_metadata β†’ OOB read of memory before the blob (and, in preload_delete_name/_bootstrap_relocate, OOB write).
  • hdr[1] == 0xFFFFFFF8 β†’ 8 + 0xFFFFFFF8 = 0x100000000 β†’ truncated int = 0 β†’ roundup(0,8)=0 β†’ curp never 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_testable

compile+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.