primitives: use saturating add in WitnessDecoder
What changed, and why it matters
This commit fixes a potential integer overflow when calculating how much memory to reserve while decoding Bitcoin transaction witness data. The overflow could only happen on 16-bit machines, which are extremely uncommon today. The fix replaces regular addition with saturating addition, so the value stops growing at the maximum instead of wrapping around. This is a defensive hardening change rather than a confirmed exploitable vulnerability on typical hardware.
Treat as low-priority hardening. Review reserve_batch's behavior when passed a saturated length to confirm no downstream safety issue. No urgent patch cycle is warranted for mainstream 64-bit deployments, but the fix should be included in the next release.
Security signals we found
Integer overflow in buffer-size calculation
Use of saturating_add as hardening measure
16-bit architecture-specific concern
Memory allocation sizing bug in decoder
Witness data parsing code
Evidence from the diff
In primitives/src/witness.rs, two required_len calculations in WitnessDecoder::update() used unchecked + operations. On 16-bit architectures, self.cursor (a usize) plus bytes.len() or encoded_size + element_length could overflow, leading to a small required_len being passed to reserve_batch(). The patch replaces these with saturating_add(). The actual safety depends on how reserve_batch behaves when given a truncated length; the diff alone does not show whether this causes an out-of-bounds write, an allocation failure, or merely a decoding error. The commit message explicitly frames this as a 16-bit overflow concern.
Changed components
primitives/src/witness.rsWitnessDecoder::update()reserve_batch() callersInspect captured patch +2 / −2
diff --git a/primitives/src/witness.rs b/primitives/src/witness.rs
index 76f116f9..5642a28c 100644
--- a/primitives/src/witness.rs
+++ b/primitives/src/witness.rs
@@ -403,7 +403,7 @@ impl Decoder for WitnessDecoder {
// If we have some bytes to read, then reading element data.
// Else we are reading the element's length.
if let Some(bytes_to_read) = self.element_bytes_remaining {
- let required_len = self.cursor + bytes.len().min(bytes_to_read);
+ let required_len = self.cursor.saturating_add(bytes.len().min(bytes_to_read));
let actual_len = self.reserve_batch(required_len);
let available_space = actual_len.saturating_sub(self.cursor);
@@ -441,7 +441,7 @@ impl Decoder for WitnessDecoder {
// Re-encode the length back into the buffer.
let encoded_size = CompactSizeEncoder::encoded_size(element_length);
- let required_len = self.cursor + encoded_size + element_length;
+ let required_len = self.cursor.saturating_add(encoded_size).saturating_add(element_length);
self.reserve_batch(required_len);
let encoded_compact_size = crate::compact_size_encode(element_length);
self.content[self.cursor..self.cursor + encoded_size]
Why this scored 32/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.