diff --git a/wire/common.go b/wire/common.go index b988c3df..673daa89 100644 --- a/wire/common.go +++ b/wire/common.go @@ -17,6 +17,11 @@ import ( // Maximum payload size for a variable length integer. const MaxVarIntPayload = 9 +// errNonCanonicalVarInt is the common format string used for non-canonically +// encoded variable length integer errors. +var errNonCanonicalVarInt = "non-canonical varint %x - discriminant %x must " + + "encode a value greater than %x" + // readElement reads the next sequence of bytes from r using little endian // depending on the concrete type of element pointed to. func readElement(r io.Reader, element interface{}) error { @@ -336,6 +341,14 @@ func readVarInt(r io.Reader, pver uint32) (uint64, error) { } rv = binary.LittleEndian.Uint64(b[:]) + // The encoding is not canonical if the value could have been + // encoded using fewer bytes. + min := uint64(0x100000000) + if rv < min { + return 0, messageError("readVarInt", fmt.Sprintf( + errNonCanonicalVarInt, rv, discriminant, min)) + } + case 0xfe: _, err := io.ReadFull(r, b[0:4]) if err != nil { @@ -343,6 +356,14 @@ func readVarInt(r io.Reader, pver uint32) (uint64, error) { } rv = uint64(binary.LittleEndian.Uint32(b[:])) + // The encoding is not canonical if the value could have been + // encoded using fewer bytes. + min := uint64(0x10000) + if rv < min { + return 0, messageError("readVarInt", fmt.Sprintf( + errNonCanonicalVarInt, rv, discriminant, min)) + } + case 0xfd: _, err := io.ReadFull(r, b[0:2]) if err != nil { @@ -350,6 +371,14 @@ func readVarInt(r io.Reader, pver uint32) (uint64, error) { } rv = uint64(binary.LittleEndian.Uint16(b[:])) + // The encoding is not canonical if the value could have been + // encoded using fewer bytes. + min := uint64(0xfd) + if rv < min { + return 0, messageError("readVarInt", fmt.Sprintf( + errNonCanonicalVarInt, rv, discriminant, min)) + } + default: rv = uint64(discriminant) } diff --git a/wire/common_test.go b/wire/common_test.go index 3d955705..09df1c0c 100644 --- a/wire/common_test.go +++ b/wire/common_test.go @@ -354,6 +354,62 @@ func TestVarIntWireErrors(t *testing.T) { } } +// TestVarIntNonCanonical ensures variable length integers that are not encoded +// canonically return the expected error. +func TestVarIntNonCanonical(t *testing.T) { + pver := wire.ProtocolVersion + + tests := []struct { + name string // Test name for easier identification + in []byte // Value to decode + pver uint32 // Protocol version for wire encoding + }{ + { + "0 encoded with 3 bytes", []byte{0xfd, 0x00, 0x00}, + pver, + }, + { + "max single-byte value encoded with 3 bytes", + []byte{0xfd, 0xfc, 0x00}, pver, + }, + { + "0 encoded with 5 bytes", + []byte{0xfe, 0x00, 0x00, 0x00, 0x00}, pver, + }, + { + "max three-byte value encoded with 5 bytes", + []byte{0xfe, 0xff, 0xff, 0x00, 0x00}, pver, + }, + { + "0 encoded with 9 bytes", + []byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + pver, + }, + { + "max five-byte value encoded with 9 bytes", + []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00}, + pver, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Decode from wire format. + rbuf := bytes.NewReader(test.in) + val, err := wire.TstReadVarInt(rbuf, test.pver) + if _, ok := err.(*wire.MessageError); !ok { + t.Errorf("readVarInt #%d (%s) unexpected error %v", i, + test.name, err) + continue + } + if val != 0 { + t.Errorf("readVarInt #%d (%s)\n got: %d want: 0", i, + test.name, val) + continue + } + } +} + // TestVarIntWire tests the serialize size for variable length integers. func TestVarIntSerializeSize(t *testing.T) { tests := []struct { diff --git a/wire/msgversion.go b/wire/msgversion.go index 7b6dbdc1..cdcbc302 100644 --- a/wire/msgversion.go +++ b/wire/msgversion.go @@ -18,7 +18,7 @@ import ( const MaxUserAgentLen = 2000 // DefaultUserAgent for wire in the stack -const DefaultUserAgent = "/btcwire:0.2.0/" +const DefaultUserAgent = "/btcwire:0.2.1/" // MsgVersion implements the Message interface and represents a bitcoin version // message. It is used for a peer to advertise itself as soon as an outbound