Fix decoder bug when ending before decoding prefix
What changed, and why it matters
This commit fixes a bug in a Bitcoin data decoder. Previously, if you stopped decoding early—before the full length prefix was read—the decoder would incorrectly report success with an empty result instead of reporting an error. The fix makes the decoder correctly return an 'unexpected end of data' error in those cases. This could matter for anyone parsing Bitcoin protocol data from partial or truncated inputs.
Review callers of ByteVecDecoder::end() to confirm they now handle UnexpectedEof correctly, and verify no code relied on the prior empty-Vec success behavior. Consider whether this bug could have caused silent truncation in any protocol parsing paths and add regression tests if not already present.
Security signals we found
Incorrect success on truncated/partial input
Missing input validation in decoder finalization
Potential for empty-vector misinterpretation by downstream callers
Fix adds explicit UnexpectedEof error path
Evidence from the diff
In rust-bitcoin’s consensus_encoding crate, ByteVecDecoder.end() previously returned Ok(empty Vec) when called before the variable-length prefix had been fully decoded. The patch adds an early check: if self.prefix_decoder is still present (meaning the prefix was not fully consumed), end() now returns ByteVecDecoderErrorInner::UnexpectedEof with missing set to prefix_decoder.read_limit(). Tests are added for empty input and for incomplete 0xfd, 0xfe, and 0xff multi-byte length prefixes.
Changed components
consensus_encoding/src/decode/decoders.rsByteVecDecoderByteVecDecoder::end()Inspect captured patch +65 / −0
diff --git a/consensus_encoding/src/decode/decoders.rs b/consensus_encoding/src/decode/decoders.rs
index 0a7e3b71..d5b3178e 100644
--- a/consensus_encoding/src/decode/decoders.rs
+++ b/consensus_encoding/src/decode/decoders.rs
@@ -107,6 +107,12 @@ impl Decoder for ByteVecDecoder {
fn end(self) -> Result<Self::Output, Self::Error> {
use {ByteVecDecoderError as E, ByteVecDecoderErrorInner as Inner};
+
+ if let Some(ref prefix_decoder) = self.prefix_decoder {
+ return Err(E(Inner::UnexpectedEof(UnexpectedEofError {
+ missing: prefix_decoder.read_limit(),
+ })));
+ }
if self.bytes_written == self.bytes_expected {
Ok(self.buffer)
@@ -1208,6 +1214,65 @@ mod tests {
decode_byte_vec_multi_byte_length_prefix, [0xff; 256], two_fifty_six_bytes_encoded();
}
+ #[test]
+ #[cfg(feature = "alloc")]
+ fn byte_vec_decoder_decode_empty_slice() {
+ let mut decoder = ByteVecDecoder::new();
+ let data = [];
+ let _ = decoder.push_bytes(&mut data.as_slice());
+ let err = decoder.end().unwrap_err();
+
+ if let ByteVecDecoderErrorInner::UnexpectedEof(e) = err.0 {
+ assert_eq!(e.missing, 1);
+ } else {
+ panic!("Expected UnexpectedEof error");
+ }
+ }
+
+ #[test]
+ #[cfg(feature = "alloc")]
+ fn byte_vec_decoder_incomplete_0xfd_prefix() {
+ let mut decoder = ByteVecDecoder::new();
+ let data = [0xFD];
+ let _ = decoder.push_bytes(&mut data.as_slice());
+ let err = decoder.end().unwrap_err();
+
+ if let ByteVecDecoderErrorInner::UnexpectedEof(e) = err.0 {
+ assert_eq!(e.missing, 2);
+ } else {
+ panic!("Expected UnexpectedEof error");
+ }
+ }
+
+ #[test]
+ #[cfg(feature = "alloc")]
+ fn byte_vec_decoder_incomplete_0xfe_prefix() {
+ let mut decoder = ByteVecDecoder::new();
+ let data = [0xFE];
+ let _ = decoder.push_bytes(&mut data.as_slice());
+ let err = decoder.end().unwrap_err();
+
+ if let ByteVecDecoderErrorInner::UnexpectedEof(e) = err.0 {
+ assert_eq!(e.missing, 4);
+ } else {
+ panic!("Expected UnexpectedEof error");
+ }
+ }
+
+ #[test]
+ #[cfg(feature = "alloc")]
+ fn byte_vec_decoder_incomplete_0xff_prefix() {
+ let mut decoder = ByteVecDecoder::new();
+ let data = [0xFF];
+ let _ = decoder.push_bytes(&mut data.as_slice());
+ let err = decoder.end().unwrap_err();
+
+ if let ByteVecDecoderErrorInner::UnexpectedEof(e) = err.0 {
+ assert_eq!(e.missing, 8);
+ } else {
+ panic!("Expected UnexpectedEof error");
+ }
+ }
#[test]
#[cfg(feature = "alloc")]
fn byte_vec_decoder_reserves_in_batches() {
Why this scored 50/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.