peer_protocol/decoder.go | 35 +++++++++++++++++++++++------------ peer_protocol/fuzz_test.go | 8 +++++++- peer_protocol/msg.go | 2 +- peer_protocol/testdata/fuzz/FuzzDecoder/18f327bd85f3ab06 | 2 ++ peer_protocol/testdata/fuzz/FuzzDecoder/252f96643f6de0fc | 2 ++ peer_protocol/testdata/fuzz/FuzzDecoder/44a1b6410e7ce227 | 2 ++ peer_protocol/testdata/fuzz/FuzzDecoder/52452abe5ed3cb64 | 2 ++ peer_protocol/testdata/fuzz/FuzzDecoder/9d2ec002df4eda28 | 2 ++ peer_protocol/testdata/fuzz/FuzzDecoder/aceaaae6cd039fb5 | 2 ++ peer_protocol/testdata/fuzz/FuzzDecoder/eb13c84d13ebb034 | 2 ++ test/transfer_test.go | 5 +++++ diff --git a/peer_protocol/decoder.go b/peer_protocol/decoder.go index f4432f64d6d1750afe4c90b184557cf535b933cd..9dfe125b1a45e2960e8f746df1fc95ad17bb67d6 100644 --- a/peer_protocol/decoder.go +++ b/peer_protocol/decoder.go @@ -11,7 +11,10 @@ "github.com/pkg/errors" ) 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 @@ readByte := func() (byte, 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 @@ b := make([]byte, length) _, 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 @@ } } 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") + 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] } - msg.Piece = msg.Piece[:dataLen] - _, err := io.ReadFull(r, msg.Piece) - if err != nil { - return fmt.Errorf("reading piece data: %w", err) - } + _, err = io.ReadFull(r, msg.Piece) length = 0 + return case Extended: var b byte b, err = readByte() @@ -86,10 +99,8 @@ } 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 85fb42a66809e826b98c2c5447313a434b9e8eac..5241504853dfe90f4b4d904395ff943d87de704b 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 @@ var buf bytes.Buffer 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 23710e6339d4cd93b79071cdb4c17b0165fbf5cb..f1b1f10e836af5890f5070eaf1fb0a0c2c0ee219 100644 --- a/peer_protocol/msg.go +++ b/peer_protocol/msg.go @@ -66,7 +66,7 @@ return } 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 0000000000000000000000000000000000000000..d214fc23aa99d3e4ae3f8c5d34911b3046e7d791 --- /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 0000000000000000000000000000000000000000..2d3ac2e08962ad72bcbf68c598d559b4406241ba --- /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 0000000000000000000000000000000000000000..a6bf562659b731356990e8af3ce5168b0b45b437 --- /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 0000000000000000000000000000000000000000..6109993e8bce61df43529c3d3739d42208977c0d --- /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 0000000000000000000000000000000000000000..6345fd45affb018ccf24b94a9345d8d64fe47136 --- /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 0000000000000000000000000000000000000000..3a76846fa26ed0f1fa19734c2f6e1d093d34cc2f --- /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 0000000000000000000000000000000000000000..89e0e619ee902010c8a60828183e995b702e3eb4 --- /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 a719fcc73f7c4de4d0b946b8f432054881ce8079..683d589b897059c1b76b24491c66bedecdcc7773 100644 --- a/test/transfer_test.go +++ b/test/transfer_test.go @@ -155,6 +155,11 @@ cfg = torrent.TestingConfig(t) 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)