block: close the range-clamp overflow — a confined caller could wrap into the neighbour

The naive bound `lba + count > r.count` wraps for an lba near u64 max: the
sum overflows to a small value, sails under the check, and `base + lba`
wraps to an absolute block OUTSIDE the range. Calibrated, it is a real
confinement escape — a process confined to [1,3) reads absolute block 0
(the boot sector) with lba = maxInt(u64), since base + lba wraps to 0.

The bound is rewritten as two subtractions that cannot overflow: lba within
the range, and count within what remains. block-range gains a wrap-refused
assertion calibrated to be exploitable against the naive form — it FAILS
against the old bound (reads block 0) and passes against the fix (verified
by reverting the clamp). Caught pre-emptively before the V2 boundary review.
This commit is contained in:
Daniel Samson
2026-08-09 17:40:45 +01:00
parent 7af65697cc
commit 89d4592777
3 changed files with 15 additions and 1 deletions
+1
View File
@@ -1246,6 +1246,7 @@ CASES = [
"timeout": 150,
"expect": r"(?s)(?=.*block-range: ok in-range-read)"
r"(?=.*block-range: ok out-of-range-refused)"
r"(?=.*block-range: ok wrap-refused)"
r"(?=.*block-range: ok geometry-is-confined)"
r"(?=.*block-range: ok confined-cannot-redefine)"
r"(?=.*block-range: VERDICT done)",
@@ -133,6 +133,12 @@ pub fn main(init: process.Init) void {
// range, and must be refused.
verdict(device.read(0, 1, bounce.physical), "in-range-read");
verdict(!device.read(2, 1, bounce.physical), "out-of-range-refused");
// The wrap attack, calibrated to be exploitable against a naive bound: this
// process is confined with base 1, so a volume-relative LBA of maxInt(u64)
// makes base + lba wrap to absolute block 0 — a real, readable block OUTSIDE
// the range (the boot sector). A naive `lba + count > count` check also
// wraps to 0 and waves it through; the overflow-safe bound refuses it.
verdict(!device.read(std.math.maxInt(u64), 1, bounce.physical), "wrap-refused");
// Geometry now reports the CONFINED size, not the device's.
const confined = device.geometry() orelse {