Skip to content

Commit f054cad

Browse files
author
Joonas Koivunen
authored
Merge pull request #715 from whalelephant/stream-fuzz-iter_frames
Stream fuzzing fixes
2 parents aa02b57 + a691723 commit f054cad

File tree

2 files changed

+94
-21
lines changed

2 files changed

+94
-21
lines changed

Diff for: crates/interledger-stream/fuzz/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ libfuzzer-sys = "0.4"
1414

1515
[dependencies.interledger-stream]
1616
path = ".."
17+
features = ["strict"]
1718

1819
# Prevent this from interfering with workspaces
1920
[workspace]

Diff for: crates/interledger-stream/src/packet.rs

+93-21
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ impl StreamPacket {
155155
/// # Errors
156156
/// 1. If the version of Stream Protocol doesn't match the hardcoded [stream version](constant.STREAM_VERSION.html)
157157
/// 1. If the decrypted bytes cannot be parsed to an unencrypted [Stream Packet](./struct.StreamPacket.html)
158-
fn from_bytes_unencrypted(buffer_unencrypted: BytesMut) -> Result<Self, ParseError> {
158+
fn from_bytes_unencrypted(mut buffer_unencrypted: BytesMut) -> Result<Self, ParseError> {
159159
// TODO don't copy the whole packet again
160160
let mut reader = &buffer_unencrypted[..];
161161
let version = reader.read_u8()?;
@@ -173,13 +173,28 @@ impl StreamPacket {
173173
let num_frames = reader.read_var_uint()?;
174174
let frames_offset = buffer_unencrypted.len() - reader.len();
175175

176-
// Try reading through all the frames to make sure they can be parsed correctly
176+
let mut reader = &buffer_unencrypted[frames_offset..];
177+
for _ in 0..num_frames {
178+
// First byte is the frame type
179+
reader.skip(1)?;
180+
reader.skip_var_octet_string()?;
181+
}
182+
183+
let junk_data_len = reader.len();
184+
185+
if junk_data_len > 0 {
186+
// trailing bytes are supported for future compatibility, see
187+
// https://github.com/interledger/rfcs/blob/master/0029-stream/0029-stream.md#52-stream-packet
188+
let _ = buffer_unencrypted.split_off(buffer_unencrypted.len() - junk_data_len);
189+
}
190+
177191
if num_frames
178192
== (FrameIterator {
179193
buffer: &buffer_unencrypted[frames_offset..],
180194
})
181195
.count() as u64
182196
{
197+
// Try reading through all the frames to make sure they can be parsed correctly
183198
Ok(StreamPacket {
184199
buffer_unencrypted,
185200
sequence,
@@ -305,14 +320,15 @@ impl<'a> Iterator for FrameIterator<'a> {
305320
type Item = Frame<'a>;
306321

307322
fn next(&mut self) -> Option<Self::Item> {
308-
while !self.buffer.is_empty() {
309-
// TODO don't ignore errors if the packet is just invalid
323+
if !self.buffer.is_empty() {
310324
match self.try_read_next_frame() {
311325
Ok(frame) => return Some(frame),
312-
Err(err) => warn!("Error reading STREAM frame: {:?}", err),
326+
Err(err) => {
327+
warn!("Error reading STREAM frame: {:?}", err);
328+
return None;
329+
}
313330
}
314331
}
315-
316332
None
317333
}
318334
}
@@ -868,23 +884,25 @@ fn saturating_read_var_uint<'a>(reader: &mut impl BufOerExt<'a>) -> Result<u64,
868884
#[cfg(test)]
869885
mod fuzzing {
870886
use super::{StreamPacket, StreamPacketBuilder};
887+
use bytes::{Buf, BytesMut};
871888

872889
#[test]
873-
#[ignore]
874890
fn fuzzed_0_extra_trailer_bytes() {
875-
// junk trailer mentioned in RFC might be applicable here, see
876-
// fuzzed_1_unknown_frames_dont_roundtrip
877-
roundtrip(&[
878-
1, 14, 3, 19, 5, 3, 1, 14, 3, 0, 0, 0, 0, 14, 3, 0, 14, 5, 17,
879-
]);
880-
881-
// roundtripped version looks like:
882-
//
883-
// [1, 14, 3, 19, 5, 3, 1, 14, 1, 0]
884-
// ^
885-
// differs, was 3
891+
// From the [RFC]'s it sounds like the at least trailer junk should be kept around,
892+
// but only under the condition that they are zero-bytes and MUST be ignored in parsing.
893+
// For Fuzzing, at least, we remove the extra bytes for roundtripping support.
886894
//
887-
// which is almost &input[0..10] except
895+
// [RFC]: https://github.com/interledger/rfcs/blob/master/0029-stream/0029-stream.md#52-stream-packet
896+
#[rustfmt::skip]
897+
let input: &[u8] = &[
898+
// Version, packet type, sequence and prepare amount
899+
1, 14, 1, 0, 1, 0,
900+
// num frames
901+
1, 0,
902+
// junk data since zero frames
903+
0, 31, 31, 31, 0, 96, 17];
904+
905+
roundtrip(input);
888906
}
889907

890908
#[test]
@@ -906,9 +924,63 @@ mod fuzzing {
906924
// which is &input[0..12]
907925
}
908926

909-
fn roundtrip(input: &[u8]) {
910-
use bytes::{Buf, BytesMut};
927+
#[test]
928+
fn fuzzed_2_frame_data_with_parse_error_should_error() {
929+
// TODO: The input for this test is from a test
930+
// (fuzzed_1_unknown_frames_dont_roundtrip)
931+
// identifying a roundtripping problem when Unknown frames are in the packet data.
932+
// That problem is still present but needs new set of data
933+
934+
#[rustfmt::skip]
935+
let input: &[u8] = &[
936+
// Version
937+
1,
938+
// Packet type
939+
14,
940+
// len + Sequence
941+
3, 5, 0, 0,
942+
// len + Prepare amount
943+
3, 5, 14, 9,
944+
// len + num frame
945+
1, 3,
946+
// frame type - ConnectionClose
947+
1,
948+
// len + frame content
949+
// This content does not parse successfully as ConnectionCloseFrame
950+
3, 0, 4, 20,
951+
// rest of frames and junk data
952+
3, 7, 14, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
953+
];
954+
let b = BytesMut::from(input);
955+
let pkt = StreamPacket::from_decrypted(b);
911956

957+
assert_eq!(
958+
"Invalid Packet: Incorrect number of frames or unable to parse all frames",
959+
format!("{}", pkt.unwrap_err())
960+
);
961+
}
962+
963+
#[test]
964+
#[ignore]
965+
fn fuzzed_3() {
966+
#[rustfmt::skip]
967+
let input: &[u8] = &[
968+
// Version, packet type, sequence and prepare amount
969+
1, 14, 1, 14, 1, 14,
970+
// num frames
971+
1, 1,
972+
// frame data (frame type + content)
973+
1, 14, 1, 0, 1, 1, 4, 0, 255, 255, 255, 255, 255, 128, 255, 128
974+
];
975+
976+
// roundtrip result
977+
// [1, 14, 1, 14, 1, 14, 1, 1, 1, 2, 1, 0]
978+
// ^ ----^
979+
// Suspect due to content prefix length
980+
roundtrip(input);
981+
}
982+
983+
fn roundtrip(input: &[u8]) {
912984
// this started off as almost copy of crate::fuzz_decrypted_stream_packet but should be
913985
// extended if necessary
914986
let b = BytesMut::from(input);

0 commit comments

Comments
 (0)