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.idreturns 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.
Recommended fix
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_sysctlsmc_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)
PoC verification
Evidence pack
findings/poc/DF-1860 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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
fixedfix.diff compiled in batch kernel build rc=0 -Werror
fix.diff compiled in batch kernel build rc=0 -Werror
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)
No comments yet.