Fix read bug in io::decode_from_read_unbuffered_with
What changed, and why it matters
This commit fixes a bug in a Rust Bitcoin library function that reads and decodes data. Previously, when reading data in chunks, the code would pass a chunk to the decoder once and then discard any leftover bytes the decoder did not fully consume. This could cause incomplete decoding, corrupted results, or unexpected failures when processing Bitcoin data streams. The fix repeatedly feeds the same chunk to the decoder until it is fully consumed.
Treat this as a correctness fix with possible security implications. Review callers of decode_from_read_unbuffered_with to determine whether truncated input could lead to consensus-relevant parsing errors, transaction malleability, or denial-of-service. Apply the patch and add regression tests covering decoders that consume input in multiple push_bytes calls.
Security signals we found
Data truncation / loss of unconsumed decoder input
Inconsistent decoding behavior across decoder implementations
Potential for malformed parsed structures or denial-of-service via incomplete reads
Fix pattern mirrors a prior fix in encoding::decode_from_hex, suggesting same bug class
Evidence from the diff
In io::decode_from_read_unbuffered_with, after a successful read into a clamped buffer, the original code called decoder.push_bytes once with the filled slice and ignored any unconsumed remainder. Because some decoders only consume part of the input in a single push_bytes call, the remaining bytes were effectively dropped. The patch introduces a loop that continues calling push_bytes on the remaining slice until to_push.is_empty() or the decoder signals readiness, matching the behavior already used in encoding::decode_from_hex.
Changed components
rust-bitcoin/io/src/lib.rsio::decode_from_read_unbuffered_withInspect captured patch +5 / −6
diff --git a/io/src/lib.rs b/io/src/lib.rs
index dfd3f3c5..f51e8b79 100644
--- a/io/src/lib.rs
+++ b/io/src/lib.rs
@@ -631,12 +631,11 @@ where
return decoder.end().map_err(ReadError::Decode);
}
Ok(bytes_read) => {
- if decoder
- .push_bytes(&mut &clamped_buffer[..bytes_read])
- .map_err(ReadError::Decode)?
- .is_ready()
- {
- return decoder.end().map_err(ReadError::Decode);
+ let mut to_push = &clamped_buffer[..bytes_read];
+ while !to_push.is_empty() {
+ if decoder.push_bytes(&mut to_push).map_err(ReadError::Decode)?.is_ready() {
+ return decoder.end().map_err(ReadError::Decode);
+ }
}
}
Err(ref e) if e.kind() == ErrorKind::Interrupted => {
Why this scored 61/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.