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

bios_sigsearch(9) performs no parameter sanity checks: paralen==0 infinite-loops the kernel, negative paralen/sigofs walk outside the BIOS scan window (kernel-API robustness; all in-tree callers pass constants)

Summary

Only the start address is validated; the int parameters are trusted blindly: paralen==0 never advances (infinite loop), paralen<0 walks the pointer backwards while the loop condition stays true (unbounded OOB read down the kernel map), sigofs<0 reads before the window, siglen<=0 degenerates bcmp into a false match. Pure defense-in-depth: the sole in-tree caller (ipmi_smbios) passes compile-time constants and ipmi is module-only on GENERIC (live-verified: symbol present, function never executes at boot, window contains only SeaBIOS firmware bytes). Entry-guard fix proposed; introspection-only evidence (a root-triggerable kernel hang is not a vulnerability).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5666 Β· 8 files
FileTypeDescriptionSize
introspect.sh β€” 2.7 KB view raw
introspection.log β€” 1.3 KB view raw
README.md β€” 3.6 KB ↓ raw
VERDICT.md β€” 5.1 KB ↓ raw
env.txt β€” 595 B view raw
fix.diff β€” 667 B view raw
manifest.json β€” 1.2 KB view raw
verdict.json β€” 4.2 KB view raw

DF-5666 β€” bios_sigsearch(9) lacks parameter sanity checks (API robustness, Info)

File: sys/platform/pc64/x86_64/bios.c (94 LOC, pass 2 GLM 5.3) Severity: Info (hardening; no reachable impact with in-tree callers) Evidence class: guest-live introspection + code reading (per reservation DF-5666..DF-5678)

What this is

bios.c on pc64 provides exactly ONE live symbol: bios_sigsearch (bios.c:66-94) β€” a read-only scan of the 128 KB BIOS ROM window (physical 0xe0000-0xfffff, accessed via BIOS_PADDRTOVADDR(x) = x + KERNBASE, pc64/include/pc/bios.h:36). The bios16/bios32 trampoline machinery is explicitly absent (bios.c:30-34); guest nm /boot/kernel/kernel confirms only bios_sigsearch exists (ffffffff80c19220 T bios_sigsearch).

The hardening gap

The function validates only its start argument (bios.c:72-78, accepting [0xe0000, 0x100000] inclusive β€” start == 0x100000 degenerates to zero iterations, harmless). It performs no validation of siglen, paralen, sigofs:

  • paralen == 0 β†’ sp += paralen (bios.c:91) never advances β†’ infinite kernel loop if no match exists.
  • paralen < 0 β†’ sp walks backwards out of the window, below KERNBASE+0xe0000; the loop condition (sp + sigofs + siglen) < end (bios.c:84) stays true while the pointer decreases β†’ unbounded OOB read below the window until an unmapped kernel VA faults (panic).
  • sigofs < 0 β†’ bcmp(sp + sigofs, …) (bios.c:87) reads before the window.
  • siglen <= 0 β†’ degenerate bcmp (len 0 "matches" instantly β€” first paragraph returned).

Why this is Info and not higher

  • Sole in-tree caller is ipmi_smbios.c:186: bios_sigsearch(SMBIOS_START, SMBIOS_SIG, SMBIOS_LEN, SMBIOS_STEP, SMBIOS_OFF) = (0xf0000, "_SM_", 4, 0x10, 0) β€” all compile-time constants (pc64/include/pc/bios.h:56-60). No user-controlled value reaches any argument, ever.
  • Guest-verified reachability: ipmi(4) is NOT compiled into X86_64_GENERIC (0 matches in the config); it exists only as /boot/kernel/ipmi.ko and is not loaded on the audit guest β†’ the function does not execute at all in the default configuration. Loading it requires root.
  • The data scanned is firmware-written ROM (attacker model = hostile firmware = ring -2, out of scope); consequences of hostile SMBIOS tables in the downstream parser were already filed as DF-2107 (sys/dev/misc/ipmi/ipmi_smbios.c, Medium) β€” not this file's bug.

Reproduce the introspection (not a crash PoC)

Phase V crash PoC is skipped per contract for Info hardening findings β€” "triggering" the gap requires a root-loaded kernel module passing paralen=0, which is not a threat model (root can halt the box anyway). The reserved evidence class for this file is guest-live introspection:

# guest up (dfbsd-qemu/vm.sh status)
dfbsd-qemu/vm.sh run_root "$(cat introspect.sh)"

Expected decisive output (see introspection.log):

ffffffff80c19220 T bios_sigsearch          <- live symbol
ipmi NOT compiled into X86_64_GENERIC ...  <- never runs at boot
000159c0  5f 53 4d 5f 28 1f 02 08 ...      <- "_SM_" anchor at phys 0xf59c0

Fix

One entry guard in bios_sigsearch (see fix.diff); not applied to sys/ (read-only audit tree), not kernel-rebuild-validated (fix_status: not_testable β€” no triggerable behavior to A/B for an Info finding).

Artifacts

  • introspect.sh + introspection.log β€” the full guest-live evidence
  • env.txt β€” guest environment
  • VERDICT.md β€” full narrative
  • fix.diff β€” git-apply-able hardening guard
  • manifest.json, verdict.json
VERDICT.md
↓ download raw

DF-5666 β€” VERDICT

Status: untested (Phase V crash PoC skipped per contract for Info hardening findings; guest-live introspection performed instead β€” it is the evidence class reserved for this file) Reproduced: no (nothing user-reachable to reproduce) Impact: none Confidence: certain (about the facts: the gap exists; the reachability analysis is code- and guest-verified)

The four checklist questions, answered with evidence

(a) What does this file actually provide on pc64?

Exactly one function: bios_sigsearch (sys/platform/pc64/x86_64/bios.c:66-94). The file header states it outright: "We cannot make bios16 nor bios32 calls … However, searching for bios rom signatures can be useful" (bios.c:30-34). Guest proof (introspection.log):

ffffffff80c19220 T bios_sigsearch
(bios16/bios32: none)

The header (sys/platform/pc64/include/pc/bios.h:34-37) declares only bios_sigsearch plus the BIOS_PADDRTOVADDR/BIOS_VADDRTOPADDR KERNBASE-offset macros and SMBIOS constants/structs. There is no pc32 twin in this tree (sys/platform/ contains only pc64 and vkernel64).

(b) Who reaches it, and when?

One in-tree caller: sys/dev/misc/ipmi/ipmi_smbios.c:186, inside ipmi_smbios_probe(), reached from ipmi_smbios_identify() (ipmi_smbios.c:222-250, results cached via the ipmi_probed state machine) called by the ipmi(4) identify/probe routines: ipmi_isa.c:58, ipmi_pci.c:98, ipmi_smbus.c:60,81. Every argument is a compile-time constant from machine/pc/bios.h:56-60 (0xf0000, "_SM_", 4, 0x10, 0).

Guest proof: ipmi has 0 matches in /usr/src/sys/config/X86_64_GENERIC, kldstat -v shows it not loaded, /boot/kernel/ipmi.ko exists as a module only, and there is no /dev/ipmi*. So on a stock system the function never executes; when it does (root kldload ipmi or a custom kernel), it runs once per boot at driver-identify time with firmware data as its only input.

(c) Buffer math

  • Loop bound (sp + sigofs + siglen) < end (bios.c:84) is strict β€” the last byte compared is at end-2, i.e. the scan stays in bounds and is in fact one byte more conservative than necessary (a signature ending exactly at 0x100000 would be missed; for SMBIOS this is physically moot since the 31-byte EPS cannot fit there). No OOB read on any in-tree path.
  • start is validated to [0xe0000, 0x100000] (bios.c:72-78); start == 0 maps to BIOS_START; anything else returns 0. start == 0x100000 degenerates to zero iterations. Pointer range KERNBASE+0xe0000 .. KERNBASE+0x100000 β€” mapped (guest /dev/mem dump of the window succeeded and returned firmware bytes), return conversion BIOS_VADDRTOPADDR fits u_int32_t.
  • The gap: siglen/paralen/sigofs are trusted ints. paralen==0 β†’ infinite loop (bios.c:91); paralen<0 β†’ backwards OOB walk below the window (bios.c:84 condition stays true as sp decreases); sigofs<0 β†’ reads before the window (bios.c:87); siglen<=0 β†’ degenerate bcmp match. No in-tree caller can pass such values β€” kernel-API hardening only.

(d) Boot-time attacker model

The scanned window is firmware-written (BIOS ROM / reserved area). Guest dump shows SeaBIOS's SMBIOS 2.8 EPS: _SM_ at physical 0xf59c0 (16-byte aligned), _DMI_ intermediate anchor, structure table at 0xf59e0, 10 structures β€” i.e., had ipmi.ko been loaded, bios_sigsearch would have matched at 0xf59c0 and handed that address to the downstream parser. A hostile firmware controlling that data is ring -2 compromise (out of model), and the downstream table-walk risk was already filed as DF-2107 against sys/dev/misc/ipmi/ipmi_smbios.c (Medium) in this audit β€” deliberately not re-filed here (different file, pass-2 discipline).

Why not a crash PoC

The only way to exercise the unvalidated-parameter paths is a kernel module calling bios_sigsearch(0xf0000, "_SM_", 4, 0, 0) β€” loading it requires root, and a root-triggerable kernel hang is not a vulnerability. Per contract, Info hardening findings skip Phase V; the guest-live introspection above is the verification for this file's reservation.

Fix validation

fix.diff adds an entry guard (sig==NULL / siglen<=0 / paralen<=0 / sigofs<0 β†’ return 0). Not kernel-rebuild-tested: fix_status not_testable β€” there is no triggerable misbehavior on a stock guest to A/B against (the guard is pure defense-in-depth for future callers).

Bottom line

bios.c is a 94-line, correct-on-every-in-tree-path, boot-time, firmware-data-only signature scan. The sole reportable item is the missing API parameter validation β€” filed as Info hardening. Everything else considered (bounds math, entry validation, mapping, TOCTOU/locking β€” none applicable, no locks, boot-time identify only, caller caches results) is negative; see the JSON result block's negative_notes.

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

not_testable
baseline no→ patch + rebuild →patched clean

fix.diff adds the parameter guard at bios_sigsearch entry. Not kernel-rebuild-validated: there is no triggerable misbehavior on a stock guest (no in-tree caller can pass bad parameters; ipmi(4) is module-only), so no baseline-vs-patched A/B exists for an Info hardening change.

findings/poc/DF-5666/fix.diff (git-apply-able unified diff against sys/platform/pc64/x86_64/bios.c; never applied to the read-only audit tree).
↓ fix.diffper-fix-DF-5666

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-5666/introspection.log β€” decisive lines: 'ffffffff80c19220 T bios_sigsearch' (live symbol, no bios16/32), 'ipmi NOT compiled into X86_64_GENERIC => bios_sigsearch does not run at boot' (reachability), hexdump line '000159c0 5f 53 4d 5f 28 1f 02 08' = _SM_ SMBIOS 2.8 EPS at phys 0xf59c0 (firmware-only input, window mapped). Code: bios.c:72-78 start validation vs. absent siglen/paralen/sigofs validation; bios.c:84/87/91 the trusting arithmetic. VERDICT.md has the full narrative.

PoC changes

No crash PoC authored (Info hardening; Phase V PoC skipped per contract). Verification artifact is introspect.sh β€” exact guest-live introspection commands (symbol table, config/module reachability, /dev/mem window dump) β€” executed verbatim via vm.sh run_root; log in introspection.log.

Verified recommended fix

Add an entry guard to bios_sigsearch: reject sig==NULL, siglen<=0, paralen<=0, sigofs<0 (return 0) before computing sp β€” see fix.diff.

Verdict

bios.c on pc64 provides exactly one live symbol, bios_sigsearch (bios.c:66-94), a boot-time read-only scan of the firmware-controlled 128KB BIOS window (phys 0xe0000-0xfffff via KERNBASE offset). The bounds math is correct on every in-tree path: the strict loop bound (sp+sigofs+siglen) < end (bios.c:84) keeps every compared byte inside the window, start is validated to [0xe0000,0x100000] (bios.c:72-78), and the sole in-tree caller (ipmi_smbios.c:186) passes only compile-time constants (0xf0000, "SM", 4, 0x10, 0 from machine/pc/bios.h:56-60). Guest-live introspection confirms: symbol live at ffffffff80c19220; ipmi(4) not compiled into X86_64_GENERIC and not loaded (module-only, /boot/kernel/ipmi.ko), so the function does not even execute in the default configuration; the /dev/mem dump of the window shows SeaBIOS's SMBIOS 2.8 SM anchor at physical 0xf59c0, i.e. firmware data is the only input. The reportable gap is API robustness only: siglen/paralen/sigofs are trusted ints, so a future kernel caller passing paralen==0 gets an infinite kernel loop (bios.c:91) and paralen<0 or sigofs<0 walks outside the window (bios.c:84/87). No user-reachable trigger exists; crash PoC skipped per contract for Info hardening findings, with guest introspection (the reserved evidence class) captured in introspection.log.