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)
PoC verification
Evidence pack
findings/poc/DF-5102 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 throughdf5102_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"
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
- First module used the real
smbios.system.productname β 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). - Second attempt collided with the already-loaded module name (module had
no unload handler); rebuilt under a new module name (
df5102b). - 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
- sys/dev/apple/fw/apple_fw.c:107-113 (the missing NULL check)
- sys/kern/kern_environment.c:228-251 (kgetenv NULL return), 331-336 (kfreeenv unconditional kfree)
- sys/kern/kern_slaballoc.c:1407-1408 (panic on free(NULL))
- sys/kern/subr_prf.c:750-753 (ksnprintf %s NULL β "(null)" β why the ksnprintf call itself is safe)
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_testableBaseline 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.
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.
No comments yet.