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

PTIOCSETTIMEOUT signed-multiply overflow wraps softc->io_timeout negative (UB), consumed as u_int32_t CDB timeout

Summary

Only the lower bound is validated before io_timeout = val*1000: >=2147484 overflows to negative (observable via GETTIMEOUT returning 0), consumed as a u_int32_t CCB timeout. Root-gated self-corruption, no memory safety. Guest-reproduced on the ptsim rig (2147484 -> GET=0); EINVAL-upper-bound fix validated. Same code upstream.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5463 Β· 10 files
FileTypeDescriptionSize
README.md β€” 1.1 KB ↓ raw
VERDICT.md β€” 2.1 KB ↓ raw
to5463.c β€” 1.5 KB view raw
build.sh β€” 138 B view raw
run.sh β€” 107 B view raw
run.log β€” 209 B view raw
run.patched.log β€” 168 B view raw
fix.diff β€” 379 B view raw
verdict.json β€” 2.3 KB view raw
manifest.json β€” 837 B view raw

DF-5463 β€” PTIOCSETTIMEOUT signed-multiply overflow

ptioctl() (sys/bus/cam/scsi/scsi_pt.c:648-656) stores

softc->io_timeout = *(int *)addr * 1000;      /* :654 */

with no upper bound. For *(int*)addr > 2147483 the int multiply overflows (UB) and io_timeout goes negative; PTIOCGETTIMEOUT (:642-646) maps anything < 1000 ms to 0, so the wrap is directly observable from userland. The negative value is later consumed as a u_int32_t CDB timeout in ptstart() :483 β†’ scsi_send_receive() :691 (cam_fill_csio timeout) β€” a garbage (negative-wrapped) timeout instead of a rejection.

Root-gated knob self-corruption (no memory-safety impact): hardening / correctness finding.

Prereq

Guest kernel with device pt (see ../DF-5462/README.md) and the ptsim rig attached (pt0 present).

Reproduce (guest, root)

./build.sh
./run.sh

Expected

set=100000     -> get=100000 (expect 100000)
set=2147483    -> get=2147483 (expect 2147483)
set=2147484    -> get=0      (expect 2147484; 0 == OVERFLOWED to negative)
OVERFLOW-MANIFEST: io_timeout wrapped negative
VERDICT.md
↓ download raw

DF-5463 VERDICT

Status: REPRODUCED (deterministic), fix validated on the guest

Root cause

ptioctl() PTIOCSETTIMEOUT (sys/bus/cam/scsi/scsi_pt.c:648-656):

if (*(int *)addr < 1) {          /* only the LOWER bound is checked */
        error = EINVAL;
        break;
}
softc->io_timeout = *(int *)addr * 1000;   /* :654 signed overflow */

For *(int *)addr >= 2147484 the int multiply wraps (UB) and softc->io_timeout goes negative. It is consumed as a u_int32_t millisecond CDB timeout by ptstart() :483 β†’ scsi_send_receive() :691 (cam_fill_csio timeout) β€” e.g. 2147484 becomes -2147483296 β†’ 0x8007A120 as u_int32 (a ~35.8 minute-wall-clock... as far in the wrong direction as the value allows; the point is the driver accepted and silently mangled it). PTIOCGETTIMEOUT (:642-646) maps any io_timeout < 1000 to 0, making the wrap directly observable:

Reproduced (guest, root, EVILPT kernel #0 / unfixed)

run.log:

set=100000     -> get=100000
set=2147483    -> get=2147483
set=2147484    -> get=0          <-- wrapped negative
OVERFLOW-MANIFEST: io_timeout wrapped negative

Impact ceiling

Root-gated knob self-corruption (the caller already passed caps_priv_check_self(SYSCAP_RESTRICTEDROOT) at open): no memory safety impact, no cross-privilege effect. Signed-overflow UB / silent-misconfiguration hardening finding (Info).

Fix validated (guest, kernel #1 22:30:30 with fix.diff)

fix.diff rejects *(int *)addr > 2147483 (INT_MAX/1000) with EINVAL. run.patched.log:

set=100000     -> get=100000
set=2147483    -> get=2147483
set=2147484    -> get=2147483    <-- rejected, previous value kept;
                                     no OVERFLOW-MANIFEST line

The overflow manifestation is gone; in-range behavior unchanged.

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

With fix.diff applied and the kernel rebuilt, PTIOCSETTIMEOUT(2147484) returns EINVAL instead of wrapping (GET yields the previous valid 2147483, OVERFLOW-MANIFEST line gone); in-range values 100000 and 2147483 behave identically to baseline.

run.patched.log vs run.log; fix.diff
↓ fix.diffper-fix-DF-5463

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (baseline: set=2147484 -> get=0 + OVERFLOW-MANIFEST), run.patched.log (fixed kernel: set=2147484 -> get=2147483, rejected), VERDICT.md

PoC changes

Authored from scratch (no seed). Requires a pt device: created by the DF-5462 ptsim rig (T_PROCESSOR fake SIM) plus a guest kernel built with 'device pt' (not in X86_64_GENERIC).

Verified recommended fix

Reject (int)addr > INT_MAX/1000 in PTIOCSETTIMEOUT (see fix.diff)

Verdict

Deterministic userland demonstration on the guest: PTIOCSETTIMEOUT with 2147484 makes ptioctl's (int)addr*1000 signed-multiply overflow, io_timeout wraps negative (observable as PTIOCGETTIMEOUT returning 0 for a set value of 2147484 while 2147483 round-trips exactly), and the negative value is consumed as a u_int32_t CDB timeout in ptstart/scsi_send_receive. Root-gated knob self-corruption; no memory-safety impact. Fix validated on a rebuilt guest kernel: the offending value is rejected with EINVAL and the manifest line disappears.