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

it_generic_stemp missing (int8_t) sign conversion for IT87xx temperature registers β€” sub-zero temps published as +128..+255 C under hw.sensors

Summary

it_readreg returns u_int8_t but the registers are two's-complement degrees - every negative temp reports as +128..255C (up to ~528K); comment also mislabels the framework unit as Fahrenheit (it is micro-Kelvin). OpenBSD 2007 ancestor bug inherited unchanged. Data-correctness only; module absent from GENERIC and guest (verified). One-line (int8_t) cast fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5031 Β· 8 files
FileTypeDescriptionSize
README.md β€” 2.7 KB ↓ raw
VERDICT.md β€” 4.0 KB ↓ raw
reachability.sh β€” 688 B view raw
reachability.log β€” 391 B view raw
fix.diff β€” 529 B view raw
manifest.json β€” 866 B view raw
verdict.json β€” 2.8 KB view raw
env.txt β€” 150 B view raw

DF-5031 β€” it.c: missing signed conversion of IT87xx temperature registers

File: sys/dev/powermng/it/it.c Severity: Info (sensor-data correctness; no memory-safety impact) Confidence: certain (code inspection; chip semantics from IT87xx datasheet family)

What the code does

void
it_generic_stemp(struct it_softc *sc, struct ksensor *sensors)
{
    int i, sdata;

    for (i = 0; i < 3; i++) {
        sdata = it_readreg(sc, ITD_SENSORTEMPBASE + i);   /* u_int8_t */
        /* Convert temperature to Fahrenheit degres */
        sensors[i].value = sdata * 1000000 + 273150000;   /* it.c:261 */
    }
}

it_readreg() returns u_int8_t (it.c:192-197), so sdata is 0..255. IT87xx temperature data registers (0x29-0x2B here) are signed two's complement degrees Celsius. The formula degC * 1e6 + 273150000 is the correct Β΅K conversion used across the DragonFly hw.sensors framework, but without an (int8_t) cast every sub-zero reading (0x80..0xFF = -128..-1 Β°C) is reported as +128 .. +255 Β°C (up to ~528 K) under hw.sensors.it0.tempN. The stale comment also says "Fahrenheit" β€” the framework unit is micro-Kelvin.

Impact

Wrong monitoring data only. An overheating-vs-frozen misread is an operational hazard (a chilled sensor reads as scorching), but there is no memory-safety, privilege, or DoS consequence: sensors[i].value is int64_t (sys/sys/sensors.h:146) and the arithmetic stays far below overflow even with the sign bug (255e6 + 273.15e6 β‰ˆ 5.3e8 < 2^63).

Why it is only Info

The driver requires two admin gates before it exists at runtime: sys/conf/files:2601 gates it behind optional it isa (not in X86_64_GENERIC β€” guest-verified 0 matches; no it.ko shipped or loaded), and the ISA device needs an explicit port hint before probe/attach run. Once attached, the parsed data comes from the monitored chip's own registers, not from any unprivileged user input; unprivileged users can only read the resulting sysctl values.

Reproduce (reachability, read-only)

reachability.sh / reachability.log β€” on the QEMU guest: device it absent from the config, no it.ko, not loaded. No runtime PoC possible (no IT87xx hardware behind QEMU).

--- a/sys/dev/powermng/it/it.c
+++ b/sys/dev/powermng/it/it.c
@@ -258,7 +258,8 @@ it_generic_stemp(struct it_softc *sc, struct ksensor *sensors)
    int i, sdata;

    for (i = 0; i < 3; i++) {
-       sdata = it_readreg(sc, ITD_SENSORTEMPBASE + i);
-       /* Convert temperature to Fahrenheit degres */
+       /* temp registers are signed two's complement, degC */
+       sdata = (int8_t)it_readreg(sc, ITD_SENSORTEMPBASE + i);
+       /* Convert temperature to micro-Kelvin */
        sensors[i].value = sdata * 1000000 + 273150000;
    }
 }

(Full context in fix.diff; never applied to sys/.)

VERDICT.md
↓ download raw

DF-5031 VERDICT β€” untested (Phase V skipped: Info-severity; no IT87xx hardware on guest)

Bottom line

it_generic_stemp() (sys/dev/powermng/it/it.c:253-263) reads the IT87xx temperature registers through it_readreg() which returns u_int8_t (it.c:192-197) and never applies the signed conversion the registers require. Sub-zero temperatures (0x80..0xFF, i.e. -128..-1 Β°C) are published to the hw.sensors framework as +128..+255 Β°C. Pure sensor-data correctness defect inherited from the 2007 OpenBSD port ($OpenBSD: it.c,v 1.22): the framework unit is Β΅K (degC*1e6 + 273150000), the in-tree comment mislabels it "Fahrenheit". No memory-safety impact: the destination is int64_t and the math cannot overflow.

Why no runtime PoC (status untested)

The it driver is not present on the audit guest at all (guest-verified: no device it in X86_64_GENERIC, no it.ko in /boot/kernel, nothing loaded β€” reachability.log), and even with the module hand-built it needs an ISA port hint plus real IT8705-class Super-I/O silicon to produce a reading; QEMU exposes no such device. There is no unprivileged input path into this code in any case β€” the only user-facing surface is reading the (wrong) values back via sysctl.

Evidence

  • reachability.log β€” guest output: device-it-in-config: 0, it.ko … NONE, it-loaded: 0; only coretemp from the powermng family is in the default config.
  • Source: it.c:259-261 (the unsigned read + Β΅K conversion), it.c:192-197 (it_readreg returns u_int8_t), itvar.h:62 (temp base 0x29), sys/sys/sensors.h:146 (value int64_t), sys/conf/files:2601 (optional it isa).

Other classes checked and killed in this file (pass 2)

  • Fan divisor div-by-zero (it.c:288-313): impossible β€” sdata == 0 and 0xff are handled before 1350000 / (sdata << (divisor & 7)) at it.c:308; the divisor argument is β‰₯ 1 for every surviving path.
  • Volt scaling overflow (it.c:266-285): max intermediate 4080 Γ— 51500 = 2.10e8 < INT32_MAX, stored in int64_t; (sdata<<4)-4096 can be negative by design (negative rails) β€” values stay sane.
  • Sensor array bounds (it.c:154-156, 323-325 vs itvar.h:32,86): fan 0-2, volt 3-11, temp 12-14 in sensors[15]; refresh passes matching bases.
  • Chip-ID bypass (it.c:125-130): exact equality on 0x90; no mask, no bypass; single-register probe is the standard IT87xx EC ID check.
  • Detach UAF vs sensor task (it.c:175-190): killed by kern_sensors.c β€” st->func(st->arg) executes under thr->lock (kern_sensors.c:334) and sensor_task_unregister takes the same lock before clearing running (kern_sensors.c:243-248); the task entry is framework-owned and freed by the sensors thread (kern_sensors.c:326-331) without touching arg.
  • Uninitialized sensor values via sysctl: newbus softc is allocated with M_INTWAIT | M_ZERO (kern/subr_bus.c:1952-1953), and sysctl_handle_sensor copies explicit fields only (kern_sensors.c:484-493).
  • Port-range OOB: only offsets 5/6 (ITC_ADDR/ITC_DATA, itvar.h:39-40) of the 8 allocated ports are ever touched (it.c:115-116, 143-144, 195-203); register indices like ITD_CHIPID 0x58 are address-port values, not port offsets.
  • Probe/attach interleaving: sensor_task_register at it.c:158 may fire before monitoring enable at 161-163, but sc_iot/sc_ioh are set at 149-150 before registration; a garbage first sample is harmless.
  • Probe returns 1 instead of ENXIO (it.c:119,130) β€” style only; any non-zero probe return means no match in newbus.

Fix validation

Not performed β€” no runtime baseline exists on the guest (driver absent, hardware absent). fix.diff is the reviewer-authored one-line sign fix, untested at runtime by construction.

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

Fix validation not applicable: Info-severity correctness finding, no runtime baseline exists on the guest (driver not compiled, hardware absent), Phase V skipped per contract.

n/a (no fix build performed)
↓ fix.diffper-fix-DF-5031

Confirmed kernel references

Detail

Evidence (decisive lines)

reachability.log (guest: device-it-in-config 0, it.ko NONE, it-loaded 0); it.c:259-261, it.c:192-197, itvar.h:62, sys/sys/sensors.h:146, sys/conf/files:2601; VERDICT.md (full negative-result list)

PoC changes

No PoC built: driver not compiled into the default kernel (optional it isa) and no IT87xx hardware exists behind QEMU. reachability.sh replaces the trigger.

Verified recommended fix

Cast the temperature register read to signed in it_generic_stemp: sdata = (int8_t)it_readreg(sc, ITD_SENSORTEMPBASE + i); and fix the unit comment (fix.diff).

Verdict

it_generic_stemp (sys/dev/powermng/it/it.c:259-261) reads IT87xx temperature registers via it_readreg() which returns u_int8_t (it.c:192-197) and never applies the required (int8_t) signed conversion, so sub-zero temperatures (0x80..0xFF = -128..-1 degC) are published under hw.sensors as +128..+255 degC; the in-tree comment also mislabels the framework unit (Β΅K) as Fahrenheit. Pure sensor-data correctness defect inherited from the OpenBSD ancestor; no memory-safety impact (value is int64_t, arithmetic bounded at ~5.3e8). Driver absent on the audit guest (no 'device it' in X86_64_GENERIC, no it.ko shipped/loaded, no IT87xx silicon in QEMU) and gated behind admin-only config, so Phase V runtime PoC skipped per contract.