]> Untitled Git - bdk/commitdiff
fix(chain): prevent integer overflow in `SpkIterator::new_with_range`
authorElias Rohrer <dev@tnull.de>
Wed, 8 Apr 2026 09:42:39 +0000 (11:42 +0200)
committer志宇 <hello@evanlinjin.me>
Thu, 23 Apr 2026 15:35:37 +0000 (15:35 +0000)
The start and end bound calculations used unchecked addition which
overflows when given `u32::MAX`, causing the iterator to silently produce
wrong results (e.g., an empty iterator for `0..=u32::MAX` in release
mode). Use `saturating_add` to handle the boundary correctly.

Co-Authored-By: HAL 9000
Signed-off-by: Elias Rohrer <dev@tnull.de>
crates/chain/src/spk_iter.rs

index 2353807a77b6e27f84c01d4e44056b8ce327105d..32112119be3e03d773e23ec607c7904c3d7e679b 100644 (file)
@@ -67,12 +67,12 @@ where
     {
         let start = match range.start_bound() {
             Bound::Included(start) => *start,
-            Bound::Excluded(start) => *start + 1,
+            Bound::Excluded(start) => start.saturating_add(1),
             Bound::Unbounded => u32::MIN,
         };
 
         let mut end = match range.end_bound() {
-            Bound::Included(end) => *end + 1,
+            Bound::Included(end) => end.saturating_add(1),
             Bound::Excluded(end) => *end,
             Bound::Unbounded => u32::MAX,
         };
@@ -136,6 +136,8 @@ where
 #[cfg(test)]
 #[cfg_attr(coverage_nightly, coverage(off))]
 mod test {
+    use core::ops::Bound;
+
     use crate::{
         bitcoin::secp256k1::Secp256k1,
         indexer::keychain_txout::KeychainTxOutIndex,
@@ -264,6 +266,31 @@ mod test {
             None
         );
     }
+
+    #[test]
+    fn test_spkiterator_no_overflow_on_u32_max_range() {
+        let (_, external_desc, _) = init_txout_index();
+
+        // Inclusive range up to u32::MAX should not overflow and should produce items
+        // (clamped to BIP32_MAX_INDEX).
+        let mut iter = SpkIterator::new_with_range(&external_desc, 0..=u32::MAX);
+        assert!(
+            iter.next().is_some(),
+            "0..=u32::MAX range should produce items, not overflow to empty"
+        );
+
+        // Exclusive start at u32::MAX should not overflow and should produce an empty iterator
+        // (start saturates to u32::MAX which is beyond BIP32_MAX_INDEX).
+        let mut iter = SpkIterator::new_with_range(
+            &external_desc,
+            (Bound::Excluded(u32::MAX), Bound::Included(u32::MAX)),
+        );
+        assert_eq!(
+            iter.next(),
+            None,
+            "range starting after u32::MAX should be empty"
+        );
+    }
 }
 
 #[test]