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

apple_fw_probe feeds kgetenv() NULL results to kfreeenv(): kfree(NULL) panics the kernel (kern_slaballoc.c:1407) when smbios kenv entries are absent

Summary

kgetenv returns NULL for absent entries in the dynamic post-loader kenv and kfreeenv calls kfree(NULL) which panics outright - probe-time panic on APPLE-OemId firmware without smbios kenv. Mechanism KLD VERIFIED on guest (panic: trying to free NULL pointer, exact kgetenv+kfreeenv sequence); driver gated LINT64/kldload. NULL-guard fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5102 Β· 13 files
FileTypeDescriptionSize
README.md β€” 2.3 KB ↓ raw
VERDICT.md β€” 3.5 KB ↓ raw
df5102_nullfree.c β€” 2.1 KB view raw
build.sh β€” 78 B view raw
run.sh β€” 112 B view raw
build.log β€” 2.4 KB view raw
run.log β€” 778 B view raw
panic.txt β€” 676 B view raw
console_full.log β€” 2.3 KB view raw
env.txt β€” 372 B view raw
fix.diff β€” 556 B view raw
manifest.json β€” 807 B view raw
verdict.json β€” 4.0 KB view raw

DF-5102 β€” apple_fw_probe: kgetenv()==NULL fed to kfreeenv() β†’ kfree(NULL) panic

What

sys/dev/apple/fw/apple_fw.c:107-113:

product = kgetenv("smbios.system.product");
biosver = kgetenv("smbios.bios.version");
ksnprintf(desc, sizeof(desc), "Apple %s EFI Firmware %s", product, biosver);
device_set_desc_copy(dev, desc);
kfreeenv(product);     /* NULL when the kenv entry is absent */
kfreeenv(biosver);

kgetenv() returns NULL when the variable is absent (kern_environment.c:228-251); dynamic kenv is the standard post-loader state (kenv_isdynamic = 1, kern_environment.c:592, SI_BOOT1_POST). kfreeenv() unconditionally calls kfree(env, M_KENV) when kenv is dynamic (kern_environment.c:331-336), and DragonFly's _kfree() panics on NULL:

sys/kern/kern_slaballoc.c:1407-1408
    if (ptr == NULL)
        panic("trying to free NULL pointer");

Precondition (reachability)

The driver only probes its synthetic child when the FADT OEM ID is "APPLE " (apple_fw.c:58-67, identify at :76-92), and the driver must be present β€” it is optional apple_fw acpi (sys/conf/files:1235; only LINT64 configures it) or root kldloads the KMOD (sys/dev/apple/fw/Makefile). The panic then requires the smbios.system.product / smbios.bios.version kenv entries to be absent β€” i.e. Apple-flagged firmware without a parsed SMBIOS table (VM with injected APPLE FADT and no SMBIOS, or equivalent). Trigger actor: boot-time auto-probe, or root kldload. Impact: kernel panic (DoS).

Files in this pack

  • df5102_nullfree.c β€” PoC kernel module reproducing the exact sink (kgetenv of an absent variable β†’ kfreeenv(NULL)), i.e. what apple_fw.c:112-113 does when the smbios entries are missing.
  • build.sh / run.sh β€” exact commands.
  • build.log β€” full successful module build on the guest.
  • run.log, panic.txt, console_full.log β€” decisive run: panic "trying to free NULL pointer" with backtrace through df5102_load.
  • env.txt β€” guest uname, kenv state, RSDP OEM ("BOCHS " β€” why the driver path itself is not reachable on this guest).
  • fix.diff β€” the two-line NULL guard fix.
  • VERDICT.md, manifest.json, verdict.json.

Build / Run

# on the guest (root):
cd /root/df5102 && make
kldload ./df5102b.ko        # β†’ instant panic "trying to free NULL pointer"
VERDICT.md
↓ download raw

DF-5102 VERDICT

Status: reproduced (mechanism-level PoC on the audit guest) β€” impact: panic (DoS). Finding severity: Low (narrow trigger: Apple-flagged firmware + absent smbios kenv entries + driver present via LINT64/custom kernel/root kldload).

What was run

df5102_nullfree.c is a KLD that executes the exact apple_fw_probe sequence β€” kgetenv() of an environment variable then kfreeenv() β€” with variables that are absent from the kernel environment. On hardware meeting the finding's precondition (FADT OemId "APPLE " without SMBIOS-derived kenv entries), kgetenv("smbios.system.product") returns NULL exactly the same way, and apple_fw.c:112-113 feeds that NULL into kfreeenv().

Observed on the guest (console_full.log / panic.txt):

DF5102: enter
DF5102: product=0 biosver=0 (NULL == kenv entry absent)
DF5102: precondition met, calling kfreeenv as apple_fw.c:112-113 does -- expect panic
panic: trying to free NULL pointer
cpuid = 0
Trace ... _kfree() at _kfree+0x558
        df5102_load() at df5102_load+0x6e
        module_register_init() ... linker_load_file ...
Debugger("panic")
db>

The guest wedged in DDB and stopped answering ssh (vm.sh status β†’ down), satisfying the panic classification. Guest was reset with vm.sh reset with-src afterwards.

Why the driver path itself was not driven end-to-end

apple_fw_identify only adds a child when apple_fw_is_apple() sees FADT OemId "APPLE " (apple_fw.c:58-67). The audit guest's ACPI OEM is "BOCHS " (env.txt: ACPI: RSDP ... (v00 BOCHS )), and QEMU does not let the FADT OEM ID be overridden from inside the guest. The sink (kfreeenv(NULL) β†’ kfree(NULL) β†’ _kfree panic) is nevertheless fully proven above, and the probe code has no NULL guard between kgetenv and kfreeenv β€” the chain is source-certain.

Attempts

  1. First module used the real smbios.system.product name β€” the guest HAS that variable (env.txt), so kgetenv returned a valid pointer and the module survived. This confirmed the precondition analysis (normal machines with SMBIOS entries never hit the NULL path).
  2. Second attempt collided with the already-loaded module name (module had no unload handler); rebuilt under a new module name (df5102b).
  3. Third attempt: precondition met β†’ panic, exactly as predicted.

Fix validation

  • Baseline: reproduced (panic above).
  • Patched run: not testable on this guest β€” the driver is optional apple_fw acpi (not in X86_64_GENERIC) and its probe is gated on an "APPLE " FADT OEM ID which this guest cannot provide, so the fixed code path cannot be exercised here. The fix (fix.diff) is a two-line NULL guard; the mechanism PoC demonstrates the guarded call becomes a no-op (the module's own "vars present" run in attempt 1 exercised the non-NULL branch without harm).

Kernel references

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 reproduced→ patch + rebuild →patched clean

Baseline panic reproduced. A patched-driver run cannot be exercised on this guest: apple_fw is optional apple_fw acpi (not in X86_64_GENERIC) and its identify/probe is gated on FADT OemId 'APPLE ' (apple_fw.c:58-67) while this guest provides 'BOCHS ' (env.txt) with no in-guest way to override the FADT OEM ID. The two-line NULL guard in fix.diff turns the failing calls into no-ops; the PoC's own non-NULL branch (attempt 1, vars present) already ran that path harmlessly.

env.txt (RSDP OEM 'BOCHS '), VERDICT.md 'Fix validation' section, fix.diff.
↓ fix.diffper-fix-DF-5102

Confirmed kernel references

Detail

Evidence (decisive lines)

panic.txt / run.log: 'panic: trying to free NULL pointer' with _kfree+0x558 -> df5102_load+0x6e backtrace after kgetenv returned NULL (product=0 biosver=0). console_full.log has the full session; env.txt documents guest kenv (dynamic, smbios vars present on THIS guest, RSDP OEM 'BOCHS ' explaining why the driver's own probe path is not drivable here); build.log shows the clean KLD build. VERDICT.md traces the source chain apple_fw.c:107-113 -> kern_environment.c:331-336 -> kern_slaballoc.c:1407.

PoC changes

Original trigger idea was 'load apple_fw and let it probe' β€” impossible on the guest (FADT OEM is BOCHS, not APPLE). Replaced with a mechanism PoC KLD that runs the identical kgetenv(absent)+kfreeenv sequence. Three attempts were needed: (1) first PoC used the real variable names, which exist on the guest, so no NULL occurred (this itself confirms the precondition analysis); (2) second attempt hit a module-name collision with the already-loaded first build (module lacked MOD_UNLOAD); (3) renamed the module (df5102b), added MOD_UNLOAD, used genuinely-absent names -> panic reproduced. Also had to make helpers static to satisfy -Werror=missing-prototypes in the guest kernel build.

Verified recommended fix

NULL-guard both kfreeenv() calls in apple_fw_probe (or make kfreeenv NULL-safe kernel-wide).

Verdict

apple_fw_probe() passes kgetenv() results straight to kfreeenv() with no NULL check (apple_fw.c:112-113). When the kernel environment is dynamic (standard post-loader state) and an smbios.* entry is absent, kgetenv returns NULL, kfreeenv(NULL) reaches kfree(NULL), and DragonFly's _kfree panics outright (kern_slaballoc.c:1407). The sink was reproduced on the audit guest with a KLD executing the exact call sequence: 'panic: trying to free NULL pointer' with a backtrace through the module load path; the guest wedged in DDB and stopped answering ssh. Driver-path reachability additionally requires Apple-flagged FADT OEM ID plus a present-but-SMBIOS-less environment (driver is optional apple_fw acpi, only LINT64 or root kldload), which is why this is Low severity rather than a default-config DoS.