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

Signed integer overflow in amdsb_watchdog() timeout calc programs watchdog count=0 -> deterministic reboot

Summary

amdsb_watchdog computes hardware tick count as (period*1000)/sc->ms_per_tick in plain int arithmetic. period is arbitrary int from kern.watchdog.period sysctl root-writable no period_max clamp. For period~=4294967 period*1000 overflows INT_MAX wraps to negative divides to 0 hardware count register programmed to 0 while function returns original huge period causing reload callout scheduled ~24 days out. AMD southbridge watchdog fires on near-zero count reboots machine. Deterministic local DoS via arithmetic logic bug. Clamp at :218-219 only caps FINAL value cannot undo overflow. Negative period even simpler. Root-only sysctl gated.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2540 Β· 8 files
FileTypeDescriptionSize
build.sh build-script no binary to build (HW-gated) 250 B view raw
run.sh run-script HW-gate check + arithmetic summary 1002 B view raw
df2540_arith.py trigger-source verifies signed-overflow produces count=0 for period~4294967 1.3 KB view raw
arith_output.txt run-log full period -> count table showing count=0 window 1.8 KB view raw
VERDICT.md verdict analysis: overflow math, HW-gating, fix 4.2 KB ↓ raw
fix.diff suggested-fix cast period to int64_t before multiplication 425 B view raw
fix_build.log build-log amdsbwd.ko rebuild with fix (rc=0) 6.9 KB view raw
env.txt environment QEMU chipset, /dev/amdsbwd absent 458 B view raw
VERDICT.md verdict analysis: overflow math, HW-gating, fix
↓ download raw

DF-2540 β€” Signed integer overflow in amdsb_watchdog() timeout calculation

Verdict

NOT REPRODUCED on this guest (HW-gated β€” no AMD SB600/SB7xx southbridge in QEMU). However, the code bug is CONFIRMED by source tracing and arithmetic verification: the signed integer overflow at line 217 produces a watchdog count of 0 for specific period values, bypassing the max-ticks clamp and programming the hardware to fire immediately.

Bug analysis

amdsb_watchdog() (sys/dev/misc/amdsbwd/amdsbwd.c:211-228):

static int
amdsb_watchdog(void *arg, int period)
{
    unsigned int timeout;
    struct amdsbwd_softc *sc = arg;

    timeout = (period * 1000) / sc->ms_per_tick;   // line 217: SIGNED overflow
    if (timeout > sc->max_ticks)                    // line 218: clamp β€” can't undo overflow
        timeout = sc->max_ticks;
    ...
    amdsbwd_tmr_set(sc, timeout);                   // programs hardware count register
    return period;
}

period is int (from kern.watchdog.period sysctl). period * 1000 is int Γ— int β†’ int. For period > INT_MAX/1000 = 2147483, this overflows signed int β€” undefined behavior in C. On two's-complement hardware (gcc 8.3 default), it wraps.

With sc->ms_per_tick = 1000 (the only value assigned, amdsbwd.c:472):

period period*1000 as int32 /1000 (C trunc toward 0) unsigned int > max_ticks? HW count
2147483 2147483000 2147483 2147483 yes β†’ 65535 65535 (safe)
2147484 -2147483296 -2147483 4292819813 yes β†’ 65535 65535 (safe)
4294967 -296 0 0 NO 0 β†’ FIRE!
4294966 -1296 -1 4294967295 yes β†’ 65535 65535 (safe)

period = 4294967: 4294967 * 1000 = 4294967000, which as int32 wraps to -296. Then -296 / 1000 = 0 (C integer division truncates toward zero, and |-296| < 1000). timeout = (unsigned int)0 = 0. Since 0 <= max_ticks(65535), the clamp does NOT fire. amdsbwd_tmr_set(sc, 0) programs the AMD SB watchdog count register to 0, causing the watchdog to fire immediately β†’ deterministic system reboot.

The finding's analysis is correct: the signed overflow produces count=0 for period β‰ˆ 4294967, bypassing the clamp and causing an immediate reboot on real AMD SB hardware.

Why it can't be reproduced on this guest

The amdsbwd device only attaches when AMD SB600/SB7xx/SB8x hardware is present (amdsbwd_identify checks pci_find_bsf(0, 20, 0) for AMD SMBus device IDs at amdsbwd.c:245-251). The QEMU guest uses an Intel 440FX / PIIX3 chipset β€” no AMD SB device exists:

$ ls /dev/amdsbwd*          β†’ No such file or directory
$ pciconf -lv | grep AMD    β†’ (nothing β€” Intel 440FX/PIIX3/PIIX4 only)

Without the device, amdsb_watchdog is never registered as a watchdog_list callback, so kern.watchdog.period changes never reach the vulnerable code. The bug is latent on this guest β€” real on physical AMD SB hardware, unreachable in QEMU.

Impact

  • CVSS PR:H β€” kern.watchdog.period requires root to set.
  • HW-gated β€” requires AMD SB600/SB7xx/SB8x southbridge.
  • On real hardware: root sets kern.watchdog.period=4294967 β†’ AMD SB watchdog count=0 β†’ immediate reboot. Local DoS via arithmetic logic bug.
  • Not remotely exploitable; not unprivileged.

Fix

Cast period to int64_t before multiplication to eliminate the signed overflow:

-   timeout = (period * 1000) / sc->ms_per_tick;
+   timeout = ((int64_t)period * 1000) / sc->ms_per_tick;

With the fix, period=4294967 β†’ (int64_t)4294967 * 1000 = 4294967000 (fits in int64), /1000 = 4294967, > max_ticks(65535) β†’ clamped to 65535. Correct.

Validated: amdsbwd.ko rebuilt with the fix (make KERNCONF=X86_64_GENERIC in sys/dev/misc/amdsbwd/, rc=0). Behavioral testing not possible (no AMD SB hardware in QEMU); fix correctness is verified by arithmetic.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

not_testable: amdsbwd.ko rebuilt with int64_t cast fix (rc=0, compiles cleanly) but device cannot attach without AMD SB600/SB7xx hardware (absent in QEMU). Fix correctness verified by arithmetic: ((int64_t)4294967*1000)/1000=4294967 -> clamped to max_ticks(65535), NOT 0. Diff applies and compiles.

fix_build.log: amdsbwd.ko link -> rc=0. Arithmetic: patched timeout=((int64_t)4294967*1000)/1000=4294967 > 65535 -> clamped to 65535 (correct). Unpatched: timeout=(int)4294967*1000/1000 -> -296/1000=0 -> count=0 (bug). Cannot live-test: no AMD SB HW.
↓ fix.diffDragonFly 6.5-DEVELOPMENT (amdsbwd.ko rebuilt with fix, rc=0; not loadable without AMD SB HW)

Confirmed kernel references

Detail

Exploit chain

none (HW-gated, not memory corruption). On real AMD SB hardware: root sets kern.watchdog.period=4294967 -> amdsb_watchdog computes timeout=0 via signed overflow -> wdcount_write(0) -> AMD SB watchdog fires immediately -> deterministic reboot. Local DoS (PR:H, HW-gated).

Evidence (decisive lines)

QEMU HW-gate: /dev/amdsbwd absent; pciconf shows Intel 440FX/PIIX3/PIIX4 only (no AMD). Arithmetic: period=4294967 -> int32(4294967*1000)=-296 -> -296/1000=0 -> count=0 -> FIRE (count=0 window confirmed for period 4294967-4294968).

PoC changes

Created df2540_arith.py (simulates int32 overflow arithmetic, generates full period->count table), run.sh (HW-gate check + summary), build.sh, VERDICT.md, fix.diff.

Verified recommended fix

Cast period to int64_t before multiplication: timeout=((int64_t)period1000)/sc->ms_per_tick at amdsbwd.c:217. Eliminates signed overflow; period=4294967 -> (int64_t)42949671000/1000=4294967 -> clamped to max_ticks(65535). Correct. Matches finding proposal intent. Full diff in findings/poc/DF-2540/fix.diff.

Verdict

NOT REPRODUCED on this guest (HW-gated). The amdsbwd device requires AMD SB600/SB7xx/SB8x southbridge hardware; the QEMU guest uses Intel 440FX/PIIX3 -- no AMD SMBus PCI device at 0:20:0, so amdsbwd_identify returns without attaching, amdsb_watchdog never registered, /dev/amdsbwd absent. HOWEVER code bug CONFIRMED by source tracing and arithmetic verification: amdsb_watchdog (amdsbwd.c:217) computes timeout=(period1000)/ms_per_tick in int arithmetic. With ms_per_tick=1000 (only value, amdsbwd.c:472) and period=4294967: int32(42949671000)=-296, -296/1000=0 (C truncation toward zero), timeout=0, 0<=max_ticks so NOT clamped -> hardware watchdog count register programmed to 0 -> immediate reboot. Finding's analysis correct.