Fix overflow during *_ceil FeeRate conversions
What changed, and why it matters
This commit fixes a bug in the rust-bitcoin library where three fee-rate conversion functions would crash when given the maximum possible fee rate. The functions round fee rates up to different units, and internally they added a small number before dividing. When the input was the maximum value (the largest 64-bit unsigned integer), that addition overflowed and caused a panic. The fix uses saturating addition, which caps the value instead of overflowing, so the functions now return a sensible result rather than crashing.
Review whether callers of these ceil functions may pass FeeRate::MAX or other large values, and ensure the saturating behavior is semantically acceptable for those callers. Consider adding checked variants if exact ceil semantics are required for non-maximum values. Update tests and documentation if needed.
Security signals we found
Integer overflow in unit conversion helpers
Panic/crash on maximum fee-rate input
Use of saturating_add to prevent overflow
New regression tests for boundary value
Evidence from the diff
In units/src/fee_rate/mod.rs, the ceil conversion helpers to_sat_per_kwu_ceil, to_sat_per_vb_ceil, and to_sat_per_kvb_ceil previously computed (value + offset) / divisor using plain +. For FeeRate::MAX (u64::MAX), adding 3_999, 999_999, or 999 caused an arithmetic overflow panic in debug builds or a wrap in release. The patch replaces + with u64::saturating_add, so the intermediate sum clamps to u64::MAX and the subsequent division yields u64::MAX / divisor. Tests for FeeRate::MAX were added for all three functions.
Changed components
units/src/fee_rate/mod.rsFeeRate::to_sat_per_kwu_ceilFeeRate::to_sat_per_vb_ceilFeeRate::to_sat_per_kvb_ceilInspect captured patch +8 / −3
diff --git a/units/src/fee_rate/mod.rs b/units/src/fee_rate/mod.rs
index de8aacb1..4472e3a0 100644
--- a/units/src/fee_rate/mod.rs
+++ b/units/src/fee_rate/mod.rs
@@ -106,19 +106,19 @@ impl FeeRate {
pub const fn to_sat_per_kwu_floor(self) -> u64 { self.to_sat_per_mvb() / 4_000 }
/// Converts to sat/kwu rounding up.
- pub const fn to_sat_per_kwu_ceil(self) -> u64 { (self.to_sat_per_mvb() + 3_999) / 4_000 }
+ pub const fn to_sat_per_kwu_ceil(self) -> u64 { self.to_sat_per_mvb().saturating_add(3_999) / 4_000 }
/// Converts to sat/vB rounding down.
pub const fn to_sat_per_vb_floor(self) -> u64 { self.to_sat_per_mvb() / 1_000_000 }
/// Converts to sat/vB rounding up.
- pub const fn to_sat_per_vb_ceil(self) -> u64 { (self.to_sat_per_mvb() + 999_999) / 1_000_000 }
+ pub const fn to_sat_per_vb_ceil(self) -> u64 { self.to_sat_per_mvb().saturating_add(999_999) / 1_000_000 }
/// Converts to sat/kvb rounding down.
pub const fn to_sat_per_kvb_floor(self) -> u64 { self.to_sat_per_mvb() / 1_000 }
/// Converts to sat/kvb rounding up.
- pub const fn to_sat_per_kvb_ceil(self) -> u64 { (self.to_sat_per_mvb() + 999) / 1_000 }
+ pub const fn to_sat_per_kvb_ceil(self) -> u64 { self.to_sat_per_mvb().saturating_add(999) / 1_000 }
/// Checked multiplication.
///
@@ -400,6 +400,11 @@ mod tests {
// sat/kvb: 2_000_400 / 1_000 = 2_000.4
assert_eq!(fee_rate.to_sat_per_kvb_floor(), 2_000);
assert_eq!(fee_rate.to_sat_per_kvb_ceil(), 2_001);
+
+ let max = FeeRate::MAX;
+ assert_eq!(max.to_sat_per_kwu_ceil(), u64::MAX / 4_000);
+ assert_eq!(max.to_sat_per_vb_ceil(), u64::MAX / 1_000_000);
+ assert_eq!(max.to_sat_per_kvb_ceil(), u64::MAX / 1_000);
}
#[test]
Why this scored 38/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.