]> Sergey Matveev's repositories - btrtrc.git/commitdiff
Fix incorrect EOF when decoding some peer protocol message types
authorMatt Joiner <anacrolix@gmail.com>
Mon, 29 May 2023 09:01:01 +0000 (19:01 +1000)
committerMatt Joiner <anacrolix@gmail.com>
Mon, 29 May 2023 09:01:01 +0000 (19:01 +1000)
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.

peer_protocol/decoder.go
peer_protocol/fuzz_test.go
peer_protocol/msg.go
peer_protocol/testdata/fuzz/FuzzDecoder/18f327bd85f3ab06 [new file with mode: 0644]
peer_protocol/testdata/fuzz/FuzzDecoder/252f96643f6de0fc [new file with mode: 0644]
peer_protocol/testdata/fuzz/FuzzDecoder/44a1b6410e7ce227 [new file with mode: 0644]
peer_protocol/testdata/fuzz/FuzzDecoder/52452abe5ed3cb64 [new file with mode: 0644]
peer_protocol/testdata/fuzz/FuzzDecoder/9d2ec002df4eda28 [new file with mode: 0644]
peer_protocol/testdata/fuzz/FuzzDecoder/aceaaae6cd039fb5 [new file with mode: 0644]
peer_protocol/testdata/fuzz/FuzzDecoder/eb13c84d13ebb034 [new file with mode: 0644]
test/transfer_test.go

index f4432f64d6d1750afe4c90b184557cf535b933cd..9dfe125b1a45e2960e8f746df1fc95ad17bb67d6 100644 (file)
@@ -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
index 85fb42a66809e826b98c2c5447313a434b9e8eac..5241504853dfe90f4b4d904395ff943d87de704b 100644 (file)
@@ -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)
+               }
        })
 }
 
index 23710e6339d4cd93b79071cdb4c17b0165fbf5cb..f1b1f10e836af5890f5070eaf1fb0a0c2c0ee219 100644 (file)
@@ -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 (file)
index 0000000..d214fc2
--- /dev/null
@@ -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 (file)
index 0000000..2d3ac2e
--- /dev/null
@@ -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 (file)
index 0000000..a6bf562
--- /dev/null
@@ -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 (file)
index 0000000..6109993
--- /dev/null
@@ -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 (file)
index 0000000..6345fd4
--- /dev/null
@@ -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 (file)
index 0000000..3a76846
--- /dev/null
@@ -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 (file)
index 0000000..89e0e61
--- /dev/null
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("\x00\x00\x000\x14")
index a719fcc73f7c4de4d0b946b8f432054881ce8079..683d589b897059c1b76b24491c66bedecdcc7773 100644 (file)
@@ -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)