Phase 3 — measure calculator overflow (category A3)¶
Parent plan: PLAN-fuzzing-bugs.md
Goal¶
Make each measure_<format> calculator surface
MeasureError::Overflow (instead of returning a MeasureOutput
whose required + fully_allocated overflows u64) so the
fifteen queued reproducers stop tripping the harness assert.
Closes: #337, #333, #329, #327, #320, #316, #312, #307,
305, #303, #296, #294, #291, #290, #289 (of which #333, #305,¶
290 are autofix-failed).¶
Planning effort¶
Medium. There are four calculators (measure_qcow2,
measure_vhd, measure_vhdx, measure_vmdk) in
src/crates/measure/src/ — the bug shape is the same in each
(unchecked sums of cluster/block-table overhead against an
adversarial input virtual size). Need to read each calculator
to find the unguarded sums; mechanical from there.
Investigation¶
The harness assert (fuzz_measure_calc.rs:144):
assert!(
m.required.checked_add(m.fully_allocated).is_some(),
"required + fully_allocated overflows u64"
);
is fired when the calculator returns (required, fully_allocated)
whose individual values are valid u64s but whose sum overflows.
The condition is itself an invariant the harness chose to assert
because MeasureOutput's consumers (e.g. the JSON printer)
add the two fields together when reporting to qemu-img-parity
output.
The fix lives inside each measure_* function. There are two
common patterns to audit:
requiredderived fromvirtual_size + header_overheadwherevirtual_sizeis already nearu64::MAX. Replacea + bwitha.checked_add(b).ok_or(MeasureError::Overflow)?.fully_allocatedderived fromclusters * cluster_size + metadatawhere the multiply overflows. Replace witha.checked_mul(b).and_then(|p| p.checked_add(m)) .ok_or(MeasureError::Overflow)?.
After the per-field guards are in, add a final guard before
returning MeasureOutput:
This makes the invariant the harness is asserting an explicit contract of the calculator function, not an implicit side-effect of the per-field checks.
Implementation¶
In src/crates/measure/src/ (one calculator per file or all in
lib.rs depending on the layout — grep for pub fn measure_):
- For each of
measure_qcow2,measure_vhd,measure_vhdx,measure_vmdk: - Walk every arithmetic operation that contributes to
requiredorfully_allocated. - Convert raw
*/+/<<tochecked_*and route toMeasureError::Overflow. - Add the final
required.checked_add(fully_allocated)guard. - Update or add unit tests per calculator that exercise the
overflow path with
virtual_size = u64::MAX.
The fuzz harness covers all four target selectors
(target_sel = data[0] % 4 in the harness header). The 15
issues are spread across the four — re-examine the reproducer
inputs after the fix to confirm at least one input per
calculator is exercised in the regression set.
Verification¶
- Re-run each filed reproducer: for each of the fifteen hashes. None should crash.
- Run a 10-minute campaign:
cargo fuzz run fuzz_measure_calc -- -max_total_time=600. make test-rust— in particular themeasurecrate tests and themeasureintegration tests.make test-integrationwith the measure subset — confirm the cross-version baselines intests/baselines/measure/are unchanged (none of these adversarial sizes appear in real fixtures).
Steps¶
| Step | Effort | Model | Isolation | Brief |
|---|---|---|---|---|
| 3a | medium | sonnet | none | In src/crates/measure/src/, convert all unchecked arithmetic in measure_qcow2, measure_vhd, measure_vhdx, measure_vmdk to checked_* operations routed to MeasureError::Overflow. Add a final required.checked_add(fully_allocated).ok_or(Overflow)? guard before each function returns its MeasureOutput. Keep no_std compatibility. |
| 3b | low | sonnet | none | Add per-calculator unit tests using virtual_size = u64::MAX and (where applicable) extreme cluster_size / grain_size / block_size values. |
| 3c | low | sonnet | none | Verify the fifteen reproducers pass and run a 10-minute fuzz campaign. |
| 3d | low | sonnet | none | Close the fifteen issues with gh issue close <n> -c "Fixed in <sha>. Root cause: measure_* calculator returned outputs whose required + fully_allocated overflowed u64; see PLAN-fuzzing-bugs-phase-03-measure-calc.md.". |
Commit shape¶
One commit for steps 3a + 3b ("measure: guard calculator arithmetic against u64 overflow"). Optionally split into one commit per calculator if the diff is large — each calculator edit is self-contained.