primitives: Avoid allocating claimed size when decoding witness length
What changed, and why it matters
This change fixes a memory allocation quirk in how Bitcoin transaction witness data is decoded. Previously, when reading a witness element, the decoder would reserve memory equal to the full claimed size of the element immediately, even though no actual element bytes had been read yet. The patch makes it reserve only a small minimum amount and let the buffer grow as real data arrives. This reduces the risk of an attacker causing large memory reservations by supplying a misleading length prefix, though the actual allocation still cannot exceed a built-in maximum.
Treat as a low-to-moderate hardening fix. Review whether MAX_VECTOR_ALLOCATE and MIN_VECTOR_ALLOCATE constants are documented and whether other decoders in the crate use the same pattern. No immediate emergency response is indicated, but backporting to stable branches is reasonable if witness parsing is exposed to untrusted network data.
Security signals we found
Memory allocation based on attacker-controlled length prefix
Decoder reserves claimed full size before payload bytes arrive
Potential memory pressure / small-allocation DoS vector
Bounds/vector allocation hardening in deserialization code
Evidence from the diff
In primitives/src/witness.rs, the WitnessDecoder::decode logic previously computed required_len as cursor + encoded_size + element_length and called reserve_batch(required_len). Because element_length comes from a decoded CompactSize length prefix, a malicious or malformed input could claim a very large witness element and cause the decoder to reserve up to MAX_VECTOR_ALLOCATE bytes before any element payload bytes were validated or consumed. The patch changes the reservation to cursor + encoded_size + element_length.min(MIN_VECTOR_ALLOCATE), so only a small initial buffer is reserved and the content vector grows incrementally as bytes are actually decoded. This is a hardening fix against memory-pressure / DoS via premature large allocation.
Changed components
primitives/src/witness.rsWitnessDecoder::decodeBitcoin transaction witness deserializationInspect captured patch +1 / −2
diff --git a/primitives/src/witness.rs b/primitives/src/witness.rs
index 2362eb48..32bd1e3c 100644
--- a/primitives/src/witness.rs
+++ b/primitives/src/witness.rs
@@ -468,8 +468,7 @@ impl encoding::Decoder for WitnessDecoder {
// Re-encode the length back into the buffer.
let encoded_size = CompactSizeEncoder::encoded_size(element_length);
- let required_len =
- self.cursor.saturating_add(encoded_size).saturating_add(element_length);
+ let required_len = self.cursor.saturating_add(encoded_size).saturating_add(element_length.min(MIN_VECTOR_ALLOCATE));
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 51/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.