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

REL/RELA sh_info accepted unvalidated β†’ findbase()==0 β†’ panic("lost base for reltab/relatab") on crafted module (also via the boot preload path)

Field Value
ID DF-2772
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H
CWE CWE-20 / CWE-617
File sys/kern/link_elf_obj.c
Lines 775/791 (store), 977/1004/1245/1266 (panics), preload 348/355/368
Area kern
Confidence certain
Discovered 2026-08-30
Pass 2 (GLM 5.3 second pass)
Bucket base:kern
Reported pending
Known CVE none
CVE match novel

Summary

The section scan stores each REL/RELA section's sh_info verbatim as the relocation target section without checking it names a section that actually gets a progtab entry. If sh_info points at the NULL section, a STRTAB/SYMTAB, an index β‰₯ e_shnum, or a size-0 PROGBITS section (skipped from progtab), findbase() returns 0 and both relocation drivers panic instead of rejecting the file β€” reached from kldload and from boot preload (making a corrupted preloaded boot module a boot-time brick). Root-supplied-input class; deterministic panic; no write primitive.

Proof of contest

VERIFIED 2/2 on fresh-reset stock guests (findings/poc/DF-2772/): crafted .ko β†’ panic: lost base for relatab with backtrace link_elf_obj_reloc_local ← link_elf_obj_load_file ← linker_load_module ← sys_kldload. Patched kernel: clean ENOEXEC rejection, guest survives.

Validate sh_info at scan time (target must be a non-empty PROGBITS/NOBITS section within e_shnum) and convert the four panics to ENOEXEC returns β€” validated fix.diff in the pack.

Timeline

  • 2026-08-30 Discovered during pass-2 audit of link_elf_obj.c (GLM 5.3); panic reproduced 2Γ— + fix validated same run.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2772 Β· 11 files
FileTypeDescriptionSize
README.md β€” 1.6 KB ↓ raw
VERDICT.md β€” 3.8 KB ↓ raw
gen_module.py β€” 3.7 KB view raw
build.sh β€” 53 B view raw
run.sh β€” 266 B view raw
run.log β€” 1.4 KB view raw
panic.txt β€” 658 B view raw
build.log β€” 5.7 MB ↓ download
env.txt β€” 191 B view raw
fix.diff β€” 5.6 KB view raw
verdict.json β€” 4.0 KB view raw

DF-2772 β€” findbase()==0 β†’ panic("lost base for relatab") on REL/RELA whose sh_info doesn't name a loaded section

Build (host)

python3 gen_module.py df2772.ko panic-rela

Run (guest, root β€” kldload is SYSCAP_NOKLD-gated)

scp -F dfbsd-qemu/config df2772.ko dfbsd:/tmp/
dfbsd-qemu/vm.sh run_root 'kldload /tmp/df2772.ko'

Expected β€” stock kernel (baseline, reproduced twice)

Serial console (panic.txt):

panic: lost base for relatab
link_elf_obj_reloc_local() at link_elf_obj_reloc_local+0x28f
link_elf_obj_load_file() at link_elf_obj_load_file+0xb6f
linker_load_file.part.3() ... sys_kldload() ...
Debugger("panic")

The module's single .rela section carries sh_info = 0 (the SHT_NULL section). The scan stores it verbatim (sec = sh_info, link_elf_obj.c:355/:775), and since section 0 never becomes a progtab entry, findbase() returns 0 and link_elf_obj_reloc_local (called during load, :818β†’:1243-1245 / during boot for preloads :368) executes panic("lost base for relatab") instead of rejecting the file. Same shape at :975-977/:1002-1004 in relocate_file.

Expected β€” patched kernel (fix.diff applied, kernel #1 Sep 1 2026)

kldload: an error occurred while loading module /tmp/df2772.ko ...
RC=1
dmesg: linker_load_file: Unsupported file type

Module rejected at scan time by elf_obj_reloc_target_ok(), no panic, guest up.

Threat model

Root-supplied-input class (SYSCAP_NOKLD), like DF-0040/0041/0042. The same panic is also reachable from the preload path (boot-time module metadata), so a corrupted/substituted preloaded module bricks the boot.

VERDICT.md
↓ download raw

DF-2772 β€” VERDICT

REPRODUCED (panic / DoS, root-gated).

REL/RELA sections are recorded with their raw sh_info as the target-section number, with no validation that the target is (or will be) a loaded PROGBITS/NOBITS section:

772:        case SHT_REL:
773:            ef->reltab[rl].rel = kmalloc(shdr[i].sh_size, ...);
774:            ef->reltab[rl].nrel = shdr[i].sh_size / sizeof(Elf_Rel);
775:            ef->reltab[rl].sec = shdr[i].sh_info;      /* raw, unchecked */
...
791:            ef->relatab[ra].sec = shdr[i].sh_info;      /* raw, unchecked */

Later, findbase() (:940-953) returns 0 unless some progtab entry carries that exact section index, and both relocation drivers turn 0 into a panic:

975:        base = findbase(ef, ef->reltab[i].sec);
976:        if (base == 0)
977:            panic("lost base for reltab");
...
1002:       base = findbase(ef, ef->relatab[i].sec);
1003:       if (base == 0)
1004:           panic("lost base for relatab");

and identically in link_elf_obj_reloc_local (:1243-1245, :1265-1267), which runs from link_elf_obj_load_file:818 (kldload path) and link_elf_obj_preload_file:368 (boot path). Any crafted module with a nonempty REL/RELA section whose sh_info names the NULL section (0), a STRTAB/SYMTAB section, an index β‰₯ e_shnum, or a size-0 PROGBITS section (size-0 sections are never entered into progtab, :518-519/:697-698) panics the kernel instead of being rejected with an error.

Reproduction (baseline, stock kernel)

Guest: DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 … X86_64_GENERIC, securelevel -1. gen_module.py df2772.ko panic-rela emits a 514-byte ET_REL object: .text, .symtab (1 null sym), .strtab, .shstrtab, and one .rela (24 bytes, sh_info=0). kldload /tmp/df2772.ko β†’ ssh hangs; serial console (panic.txt):

panic: lost base for relatab
link_elf_obj_reloc_local() at link_elf_obj_reloc_local+0x28f
link_elf_obj_load_file() at link_elf_obj_load_file+0xb6f
linker_load_file.part.3() at linker_load_file.part.3+0x92
linker_load_module() at linker_load_module+0x116
sys_kldload() at sys_kldload+0xc6
Debugger("panic")

Reproduced twice on fresh vm.sh reset with-src boots (first run, then a re-run on a fresh reset to capture the clean excerpt).

Impact ceiling

Deterministic kernel panic from a crafted module β€” a robustness/DoS defect, not a corruption primitive (nothing is written; the panic is the whole effect). kldload is root-only (caps_priv_check_self(SYSCAP_NOKLD), kern_linker.c:792; blocked at securelevel>0), so unprivileged reach is nil; the interesting real-world framing is a corrupted or substituted module (incl. a preloaded one, which converts this into a boot-time brick). Low severity, consistent with DF-0040/0041/0042 calibration.

Fix validation

fix.diff (same combined patch as DF-2771/DF-2773): new elf_obj_reloc_target_ok() validates sh_info < e_shnum + target type PROGBITS/NOBITS (+ nonempty for the file case) in both scan loops (preload :234-239, file :530-535) before the section is counted, and the four panic("lost base …") sites become return (ENOEXEC) with the error propagated (link_elf_obj_reloc_local becomes int-returning; callers :368 and :818 check it). Rebuilt kernel (make -j6 nativekernel KERNCONF=X86_64_GENERIC, build.log, -Werror clean), installed as #1: Tue Sep 1 01:04:57 UTC 2026, rebooted. Same PoC:

kldload: an error occurred while loading module /tmp/df2772.ko. Please check dmesg(8)
dmesg: linker_load_file: Unsupported file type
RC=1, guest stays up (ALL-SURVIVED), 0 panics

Baseline panic gone β†’ fix_status=fixed.

Note on scope

The scan-time rejection intentionally runs before the nprogtab/nsym checks; the goto-out it takes was made safe by the DF-2773 unload guard in the same patch (previously a scan-time rejection with nrelatab>0 and relatab==NULL would itself have NULL-derefed in unload).

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Patched kernel rejects the identical module with ENOEXEC ('linker_load_file: Unsupported file type' in dmesg, RC=1), no panic, guest stays up; baseline kernel #0 panics on the same input.

run.log (baseline vs patched), build.log (kernel #1 build), dmesg 'linker_load_file: Unsupported file type'
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Tue Sep 1 01:04:57 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Exploit chain

root crafts module (gen_module.py mode=panic-rela, .rela sh_info=0) -> kldload -> link_elf_obj_load_file -> link_elf_obj_reloc_local -> findbase()==0 -> panic('lost base for relatab') -> kernel halt in ddb. No memory corruption and no unprivileged reach (SYSCAP_NOKLD); ceiling is deterministic panic/DoS.

Evidence (decisive lines)

['panic.txt β€” baseline serial console: panic: lost base for relatab with full backtrace through link_elf_obj_load_file and sys_kldload', 'run.log β€” baseline (2 runs, ssh hang + panic) vs patched (clean rejection RC=1) transcripts', 'gen_module.py β€” trigger generator (mode panic-rela): shdr[5] type=SHT_RELA size=0x18 link=2 info=0', 'build.log β€” full untrimmed nativekernel build of the fix (-Werror clean)', 'VERDICT.md β€” path:line trace incl. preload reachability and all four panic sites', 'fix.diff β€” scan-time sh_info validation + panic-to-error conversion, baseline-vs-patched validated']

PoC changes

n/a β€” trigger authored fresh (hand-crafted ET_REL); no prior seed for this finding

Verified recommended fix

Validate REL/RELA sh_info at scan time (index < e_shnum and target is a to-be-loaded PROGBITS/NOBITS section) and return ENOEXEC instead of panic()ing at the findbase() call sites.

Verdict

link_elf_obj stores each REL/RELA section's sh_info verbatim as the relocation target (sys/kern/link_elf_obj.c:775/:791) and later turns any target that is not a loaded PROGBITS/NOBITS section into panic('lost base for reltab/relatab') (:975-977, :1002-1004, and link_elf_obj_reloc_local :1243-1245/:1265-1267, reached from kldload via :818 and from boot preload via :368) instead of rejecting the file. A 514-byte crafted ET_REL module with one .rela whose sh_info=0 panics the stock guest deterministically (reproduced twice on fresh resets) β€” panic backtrace link_elf_obj_reloc_local <- link_elf_obj_load_file <- sys_kldload. Robustness/DoS only (no write primitive); trigger is root-supplied (SYSCAP_NOKLD), and the same panic from the preload path converts a corrupted boot module into a boot-time brick. fix.diff validates sh_info at scan time (elf_obj_reloc_target_ok) and converts the panics to ENOEXEC error returns; on rebuilt kernel #1 the same module is cleanly rejected ('Unsupported file type', RC=1, no panic, guest up).