From: Matt Joiner Date: Mon, 29 May 2023 09:01:01 +0000 (+1000) Subject: Fix incorrect EOF when decoding some peer protocol message types X-Git-Url: http://www.git.stargrave.org/?p=btrtrc.git;a=commitdiff_plain;h=ac086bb3bd3b8e31c117362551eb0fe295a0f78d Fix incorrect EOF when decoding some peer protocol message types Hooray for pedantic fuzz tests. This was found through peer_protocol/FuzzDecoder, when I wrote a go-fuzz-all wrapper. It probably never caused a problem in production, as EOF would be handled as stream termination anyway, but it isn't clean. --- diff --git a/peer_protocol/decoder.go b/peer_protocol/decoder.go index f4432f64..9dfe125b 100644 --- a/peer_protocol/decoder.go +++ b/peer_protocol/decoder.go @@ -11,7 +11,10 @@ import ( ) type Decoder struct { - R *bufio.Reader + R *bufio.Reader + // This must return *[]byte where the slices can fit data for piece messages. I think we store + // *[]byte in the pool to avoid an extra allocation every time we put the slice back into the + // pool. The chunk size should not change for the life of the decoder. Pool *sync.Pool MaxLength Integer // TODO: Should this include the length header or not? } @@ -35,11 +38,18 @@ func (d *Decoder) Decode(msg *Message) (err error) { length-- return d.R.ReadByte() } + // From this point onwards, EOF is unexpected + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() c, err := readByte() if err != nil { return } msg.Type = MessageType(c) + // Can return directly in cases when err is not nil, or length is known to be zero. switch msg.Type { case Choke, Unchoke, Interested, NotInterested, HaveAll, HaveNone: case Have, AllowedFast, Suggest: @@ -58,6 +68,7 @@ func (d *Decoder) Decode(msg *Message) (err error) { _, err = io.ReadFull(r, b) length = 0 msg.Bitfield = unmarshalBitfield(b) + return case Piece: for _, pi := range []*Integer{&msg.Index, &msg.Begin} { err := pi.Read(r) @@ -67,16 +78,18 @@ func (d *Decoder) Decode(msg *Message) (err error) { } length -= 8 dataLen := int64(length) - msg.Piece = *d.Pool.Get().(*[]byte) - if int64(cap(msg.Piece)) < dataLen { - return errors.New("piece data longer than expected") - } - msg.Piece = msg.Piece[:dataLen] - _, err := io.ReadFull(r, msg.Piece) - if err != nil { - return fmt.Errorf("reading piece data: %w", err) + if d.Pool == nil { + msg.Piece = make([]byte, dataLen) + } else { + msg.Piece = *d.Pool.Get().(*[]byte) + if int64(cap(msg.Piece)) < dataLen { + return errors.New("piece data longer than expected") + } + msg.Piece = msg.Piece[:dataLen] } + _, err = io.ReadFull(r, msg.Piece) length = 0 + return case Extended: var b byte b, err = readByte() @@ -86,10 +99,8 @@ func (d *Decoder) Decode(msg *Message) (err error) { msg.ExtendedID = ExtensionNumber(b) msg.ExtendedPayload = make([]byte, length) _, err = io.ReadFull(r, msg.ExtendedPayload) - if err == io.EOF { - err = io.ErrUnexpectedEOF - } length = 0 + return case Port: err = binary.Read(r, binary.BigEndian, &msg.Port) length -= 2 diff --git a/peer_protocol/fuzz_test.go b/peer_protocol/fuzz_test.go index 85fb42a6..52415048 100644 --- a/peer_protocol/fuzz_test.go +++ b/peer_protocol/fuzz_test.go @@ -17,7 +17,9 @@ func FuzzDecoder(f *testing.F) { f.Add([]byte("\x00\x00\x00\x00")) f.Add([]byte("\x00\x00\x00\x01\x00")) f.Add([]byte("\x00\x00\x00\x03\x14\x00")) + f.Add([]byte("\x00\x00\x00\x01\x07")) f.Fuzz(func(t *testing.T, b []byte) { + t.Logf("%q", b) c := qt.New(t) d := Decoder{ R: bufio.NewReader(bytes.NewReader(b)), @@ -43,7 +45,11 @@ func FuzzDecoder(f *testing.F) { for _, m := range ms { buf.Write(m.MustMarshalBinary()) } - c.Assert(buf.Bytes(), qt.DeepEquals, b) + if len(b) == 0 { + c.Assert(buf.Bytes(), qt.HasLen, 0) + } else { + c.Assert(buf.Bytes(), qt.DeepEquals, b) + } }) } diff --git a/peer_protocol/msg.go b/peer_protocol/msg.go index 23710e63..f1b1f10e 100644 --- a/peer_protocol/msg.go +++ b/peer_protocol/msg.go @@ -66,7 +66,7 @@ func (msg Message) MarshalBinary() (data []byte, err error) { } switch msg.Type { case Choke, Unchoke, Interested, NotInterested, HaveAll, HaveNone: - case Have: + case Have, AllowedFast, Suggest: err = binary.Write(&buf, binary.BigEndian, msg.Index) case Request, Cancel, Reject: for _, i := range []Integer{msg.Index, msg.Begin, msg.Length} { diff --git a/peer_protocol/testdata/fuzz/FuzzDecoder/18f327bd85f3ab06 b/peer_protocol/testdata/fuzz/FuzzDecoder/18f327bd85f3ab06 new file mode 100644 index 00000000..d214fc23 --- /dev/null +++ b/peer_protocol/testdata/fuzz/FuzzDecoder/18f327bd85f3ab06 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\x00\x00\x000\a") diff --git a/peer_protocol/testdata/fuzz/FuzzDecoder/252f96643f6de0fc b/peer_protocol/testdata/fuzz/FuzzDecoder/252f96643f6de0fc new file mode 100644 index 00000000..2d3ac2e0 --- /dev/null +++ b/peer_protocol/testdata/fuzz/FuzzDecoder/252f96643f6de0fc @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\x00\x00\x000\a00000000") diff --git a/peer_protocol/testdata/fuzz/FuzzDecoder/44a1b6410e7ce227 b/peer_protocol/testdata/fuzz/FuzzDecoder/44a1b6410e7ce227 new file mode 100644 index 00000000..a6bf5626 --- /dev/null +++ b/peer_protocol/testdata/fuzz/FuzzDecoder/44a1b6410e7ce227 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\x00\x00\x00\x05\x110000") diff --git a/peer_protocol/testdata/fuzz/FuzzDecoder/52452abe5ed3cb64 b/peer_protocol/testdata/fuzz/FuzzDecoder/52452abe5ed3cb64 new file mode 100644 index 00000000..6109993e --- /dev/null +++ b/peer_protocol/testdata/fuzz/FuzzDecoder/52452abe5ed3cb64 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\x00\x00\x000\x05") diff --git a/peer_protocol/testdata/fuzz/FuzzDecoder/9d2ec002df4eda28 b/peer_protocol/testdata/fuzz/FuzzDecoder/9d2ec002df4eda28 new file mode 100644 index 00000000..6345fd45 --- /dev/null +++ b/peer_protocol/testdata/fuzz/FuzzDecoder/9d2ec002df4eda28 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\x00\x00\x003\a\x17\b\x92\xf3\x02\xd5\x1896%~\xd2Q\x84b\x18") diff --git a/peer_protocol/testdata/fuzz/FuzzDecoder/aceaaae6cd039fb5 b/peer_protocol/testdata/fuzz/FuzzDecoder/aceaaae6cd039fb5 new file mode 100644 index 00000000..3a76846f --- /dev/null +++ b/peer_protocol/testdata/fuzz/FuzzDecoder/aceaaae6cd039fb5 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\x00\x00\x00\x01\x10") diff --git a/peer_protocol/testdata/fuzz/FuzzDecoder/eb13c84d13ebb034 b/peer_protocol/testdata/fuzz/FuzzDecoder/eb13c84d13ebb034 new file mode 100644 index 00000000..89e0e619 --- /dev/null +++ b/peer_protocol/testdata/fuzz/FuzzDecoder/eb13c84d13ebb034 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\x00\x00\x000\x14") diff --git a/test/transfer_test.go b/test/transfer_test.go index a719fcc7..683d589b 100644 --- a/test/transfer_test.go +++ b/test/transfer_test.go @@ -155,6 +155,11 @@ func testSeedAfterDownloading(t *testing.T, disableUtp bool) { cfg.Seed = true cfg.DataDir = t.TempDir() cfg.DisableUTP = disableUtp + // Make sure the leecher-leecher doesn't connect directly to the seeder. This is because I + // wanted to see if having the higher chunk-sized leecher-leecher would cause the leecher to + // error decoding. However it shouldn't because a client should only be receiving pieces sized + // to the chunk size it expects. + cfg.DisablePEX = true //cfg.Debug = true cfg.Logger = log.Default.WithContextText("leecher") leecher, err := torrent.NewClient(cfg)