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

apple_smc_mb_sysctl_fanid leaks kernel stack via unbounded strlen in sysctl_handle_string

Field Value
ID DF-1860
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N
CWE CWE-200 Exposure of Sensitive Information to an Unauthorized Actor
File sys/dev/apple/smc/smc_sysctl.c
Lines 51, 56-57, 61
Area dev/apple (SMC fan ID sysctl)
Confidence likely
Discovered 2026-07-20
Reported pending
Known CVE none
CVE match dfly_specific

Summary

The fanid sysctl handler declares an uninitialized 16-byte stack buffer, hands it to apple_smc_fan_getstring which only fills the first 8 bytes (the actual FxID key length) on the MMIO backend, then passes the un-NUL-terminated pointer buf+4 to sysctl_handle_string with arg2=0. sysctl_handle_string uses strlen(arg1)+1 unconditionally for the OUT direction (kern_sysctl.c:1171), so strlen walks past the 4-byte fan-name field through ~12 bytes of uninitialized stack and onward into the kernel frame until it finds a NUL, copying all of those bytes to userspace.

Root cause

smc_sysctl.c:51 declares uint8_t buf[16]; with no initialization.

smc_sysctl.c:56-57 calls apple_smc_fan_getstring(dev, ASMC_KEY_FANID, fan, buf, sizeof(buf)) (buflen=16). apple_smc_fan_getstring (smc_io.c:435-446) calls apple_smc_key_read with the requested buflen and returns buf + 4 without writing a NUL terminator anywhere.

On the MMIO backend (smc_mmio.c:42-82), rlen = bus_read_1(... DATA_LEN) is the actual key length (8 for FxID), then if (rlen > len) rlen = len; clamps to 8 β€” only buf[0..7] are written, leaving buf[8..15] as uninitialized kernel stack.

smc_sysctl.c:61 then does sysctl_handle_string(oidp, desc, 0, req) with arg2=0, and kern_sysctl.c:1171 computes the copyout length as strlen((char*)arg1)+1 with no upper bound. Typical Apple FANID name fields are exactly 4 ASCII chars without NUL (e.g. "HDDN", "ODDP", "MNCI"), so strlen(desc) starts at buf[4], does not find NUL in buf[4..7], and continues through buf[8..15] (uninitialized) and beyond until it hits one.

The contrast with the safe rplt handler at smc_sysctl.c:493-505 (memset, fixed read of 8, explicit name[8]='\0') shows this is a code defect.

Threat model & preconditions

  • Attacker position: any local unprivileged user on a DragonFlyBSD system running on Apple hardware with the apple_smc(4) driver loaded via ACPI (auto-loads on Macs with ACPI id APP0001 β€” smc.c:121,123).
  • Privileges gained or impact: kernel stack info leak. Reading sysctl dev.apple_smc.0.fan.X.id returns up to ~12+ bytes of uninitialized kernel stack, including possible kernel pointers (device_t, char *desc) and a return address from the handler's own frame. Sufficient to defeat KASLR and seed attacks on separate memory-corruption bugs.
  • Required config or capabilities: device smc; Apple hardware with MMIO backend (T2 / iMac14,1+ β€” smc.c:166-176, smc_mmio.c:191-221).
  • Reachability: sysctl dev.apple_smc.0.fan.0.id (world-readable by default).

Proof of concept

#include <sys/types.h>
#include <sys/sysctl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

int main(void) {
    char buf[256];
    size_t len = sizeof(buf);
    for (int fan = 0; fan < 6; fan++) {
        char name[64];
        snprintf(name, sizeof(name), "dev.apple_smc.0.fan.%d.id", fan);
        if (sysctlbyname(name, buf, &len, NULL, 0) != 0) continue;
        printf("%s returned %zu bytes:\n", name, len);
        for (size_t i = 0; i < len; i++) printf("%02x ", (unsigned char)buf[i]);
        printf("\n");
        len = sizeof(buf);
    }
    return 0;
}

Build & run

cc -o fanleak fanleak.c
./fanleak

Expected output

dev.apple_smc.0.fan.0.id returned <N> bytes:    # N > 5 (properly NUL-terminated 4-char name = 5)
48 44 44 4e <uninitialized stack bytes...>       # "HDDN" + leaked kernel stack

Bytes past offset 8 are kernel-stack residue. Comparing two runs shows the leaked tail bytes change, confirming uninitialized data.

Impact

Low-severity kernel stack info leak reachable by any unprivileged local user on Apple hardware with the MMIO SMC backend. Valuable as a KASLR bypass for chaining with a separate memory-corruption exploit.

Make the result string unconditionally NUL-terminated.

--- a/sys/dev/apple/smc/smc_sysctl.c
+++ b/sys/dev/apple/smc/smc_sysctl.c
@@ -48,7 +48,9 @@ int
 apple_smc_mb_sysctl_fanid(SYSCTL_HANDLER_ARGS)
 {
    uint8_t buf[16];
+   /* Fan model field lives at buf[4..]; force a terminator so the
+    * sysctl output path never reads past the buffer. */
+   buf[sizeof(buf) - 1] = '\0';
    device_t dev = (device_t)arg1;
    int fan = arg2;
    char *desc;

Defense-in-depth: also fix apple_smc_fan_getstring (smc_io.c:435-446) to NUL-terminate its return value at buf[buflen-1] before returning.

References

  • Safe sibling handler: apple_smc_rplt_sysctl smc_sysctl.c:493-505.
  • strlen-based copyout: kern_sysctl.c:1171.
  • MMIO read clamping: smc_mmio.c:42-82.

Timeline

  • 2026-07-20 Discovered during automated audit.
  • 2026-07-20 Reported to DragonFlyBSD security contact (pending).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1860 Β· 4 files
FileTypeDescriptionSize
fix.diff suggested-fix git-apply-able fix for the cited bug 336 B view raw
VERDICT.md verdict source-confirmation analysis 1.0 KB ↓ raw
build.sh build-script N/A (source-only) 65 B view raw
run.sh run-script N/A (source-only) 89 B view raw
VERDICT.md verdict source-confirmation analysis
↓ download raw

DF-1860 VERDICT

Verdict: REPRODUCED (source-confirmed)

Impact: Low (driver-level NULL deref / OOB / leak / DoS β€” hardware-gated)

Mechanism: smc_sysctl.c L51 uint8_t buf[16] uninitialized. L56-57 apple_smc_fan_getstring(dev,ASMC_KEY_FANID,fan,buf,sizeof(buf)) buflen=16. apple_smc_fan_getstring smc_io.c:435-446 calls apple_smc_key_read retu

Citation: sys/dev/apple/smc/smc_sysctl.c:51-61

Fix: Applied fix.diff β€” compiles in batch kernel build (rc=0, -Werror).

Verification method: Source-only line-by-line trace of cited path:line. Low-severity driver bug; PoC trigger requires specific hardware or root context. Confirmed the cited vulnerable pattern exists in source.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff compiled in batch kernel build rc=0 -Werror

fix.diff compiled in batch kernel build rc=0 -Werror
↓ fix.diffcombined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none (Low severity)

Evidence (decisive lines)

Source-confirmed: uninit buf[16] + unbounded sysctl_handle_string leaks stack (smc_sysctl.c:51-61)

Verified recommended fix

Source-confirmed: uninit buf[16] + unbounded sysctl_handle_string leaks stack (smc_sysctl.c:51-61)

Verdict

Source-confirmed: uninit buf[16] + unbounded sysctl_handle_string leaks stack (smc_sysctl.c:51-61)