From c35f4fd0bd93bcab01ba7704fc144514a2cc7a1b Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 23 Mar 2015 15:02:55 +0100 Subject: rlp: check top-level value sizes against input limit This is a preliminary fix for #420 (SEC-18 RLP decoder unsafe allocation). If a sane input limit is set on the rlp.Stream, it should no longer be possible to cause huge []byte allocations. --- rlp/decode_test.go | 118 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 80 insertions(+), 38 deletions(-) (limited to 'rlp/decode_test.go') diff --git a/rlp/decode_test.go b/rlp/decode_test.go index 73a31c67f..6b37ab0ad 100644 --- a/rlp/decode_test.go +++ b/rlp/decode_test.go @@ -36,7 +36,8 @@ func TestStreamKind(t *testing.T) { } for i, test := range tests { - s := NewStream(bytes.NewReader(unhex(test.input))) + // using plainReader to inhibit input limit errors. + s := NewStream(newPlainReader(unhex(test.input)), 0) kind, len, err := s.Kind() if err != nil { t.Errorf("test %d: Kind returned error: %v", i, err) @@ -70,29 +71,63 @@ func TestNewListStream(t *testing.T) { } func TestStreamErrors(t *testing.T) { + withoutInputLimit := func(b []byte) *Stream { + return NewStream(newPlainReader(b), 0) + } + withCustomInputLimit := func(limit uint64) func([]byte) *Stream { + return func(b []byte) *Stream { + return NewStream(bytes.NewReader(b), limit) + } + } + type calls []string tests := []struct { string calls + newStream func([]byte) *Stream // uses bytes.Reader if nil error }{ - {"", calls{"Kind"}, io.EOF}, - {"", calls{"List"}, io.EOF}, - {"", calls{"Uint"}, io.EOF}, - {"C0", calls{"Bytes"}, ErrExpectedString}, - {"C0", calls{"Uint"}, ErrExpectedString}, - {"81", calls{"Bytes"}, io.ErrUnexpectedEOF}, - {"81", calls{"Uint"}, io.ErrUnexpectedEOF}, - {"BFFFFFFFFFFFFFFF", calls{"Bytes"}, io.ErrUnexpectedEOF}, - {"89000000000000000001", calls{"Uint"}, errUintOverflow}, - {"00", calls{"List"}, ErrExpectedList}, - {"80", calls{"List"}, ErrExpectedList}, - {"C0", calls{"List", "Uint"}, EOL}, - {"C801", calls{"List", "Uint", "Uint"}, io.ErrUnexpectedEOF}, - {"C8C9", calls{"List", "Kind"}, ErrElemTooLarge}, - {"C3C2010201", calls{"List", "List", "Uint", "Uint", "ListEnd", "Uint"}, EOL}, - {"00", calls{"ListEnd"}, errNotInList}, - {"C40102", calls{"List", "Uint", "ListEnd"}, errNotAtEOL}, + {"C0", calls{"Bytes"}, nil, ErrExpectedString}, + {"C0", calls{"Uint"}, nil, ErrExpectedString}, + {"89000000000000000001", calls{"Uint"}, nil, errUintOverflow}, + {"00", calls{"List"}, nil, ErrExpectedList}, + {"80", calls{"List"}, nil, ErrExpectedList}, + {"C0", calls{"List", "Uint"}, nil, EOL}, + {"C8C9010101010101010101", calls{"List", "Kind"}, nil, ErrElemTooLarge}, + {"C3C2010201", calls{"List", "List", "Uint", "Uint", "ListEnd", "Uint"}, nil, EOL}, + {"00", calls{"ListEnd"}, nil, errNotInList}, + {"C401020304", calls{"List", "Uint", "ListEnd"}, nil, errNotAtEOL}, + + // Expected EOF + {"", calls{"Kind"}, nil, io.EOF}, + {"", calls{"Uint"}, nil, io.EOF}, + {"", calls{"List"}, nil, io.EOF}, + {"8105", calls{"Uint", "Uint"}, nil, io.EOF}, + {"C0", calls{"List", "ListEnd", "List"}, nil, io.EOF}, + + // Input limit errors. + {"81", calls{"Bytes"}, nil, ErrValueTooLarge}, + {"81", calls{"Uint"}, nil, ErrValueTooLarge}, + {"81", calls{"Raw"}, nil, ErrValueTooLarge}, + {"BFFFFFFFFFFFFFFFFFFF", calls{"Bytes"}, nil, ErrValueTooLarge}, + {"C801", calls{"List"}, nil, ErrValueTooLarge}, + + // Test for input limit overflow. Since we are counting the limit + // down toward zero in Stream.remaining, reading too far can overflow + // remaining to a large value, effectively disabling the limit. + {"C40102030401", calls{"Raw", "Uint"}, withCustomInputLimit(5), io.EOF}, + {"C4010203048102", calls{"Raw", "Uint"}, withCustomInputLimit(6), ErrValueTooLarge}, + + // Check that the same calls are fine without a limit. + {"C40102030401", calls{"Raw", "Uint"}, withoutInputLimit, nil}, + {"C4010203048102", calls{"Raw", "Uint"}, withoutInputLimit, nil}, + + // Unexpected EOF. This only happens when there is + // no input limit, so the reader needs to be 'dumbed down'. + {"81", calls{"Bytes"}, withoutInputLimit, io.ErrUnexpectedEOF}, + {"81", calls{"Uint"}, withoutInputLimit, io.ErrUnexpectedEOF}, + {"BFFFFFFFFFFFFFFF", calls{"Bytes"}, withoutInputLimit, io.ErrUnexpectedEOF}, + {"C801", calls{"List", "Uint", "Uint"}, withoutInputLimit, io.ErrUnexpectedEOF}, // This test verifies that the input position is advanced // correctly when calling Bytes for empty strings. Kind can be called @@ -109,12 +144,15 @@ func TestStreamErrors(t *testing.T) { "Bytes", // past final element "Bytes", // this one should fail - }, EOL}, + }, nil, EOL}, } testfor: for i, test := range tests { - s := NewStream(bytes.NewReader(unhex(test.string))) + if test.newStream == nil { + test.newStream = func(b []byte) *Stream { return NewStream(bytes.NewReader(b), 0) } + } + s := test.newStream(unhex(test.string)) rs := reflect.ValueOf(s) for j, call := range test.calls { fval := rs.MethodByName(call) @@ -124,8 +162,12 @@ testfor: err = lastret.(error).Error() } if j == len(test.calls)-1 { - if err != test.error.Error() { - t.Errorf("test %d: last call (%s) error mismatch\ngot: %s\nwant: %v", + want := "" + if test.error != nil { + want = test.error.Error() + } + if err != want { + t.Errorf("test %d: last call (%s) error mismatch\ngot: %s\nwant: %s", i, call, err, test.error) } } else if err != "" { @@ -137,7 +179,7 @@ testfor: } func TestStreamList(t *testing.T) { - s := NewStream(bytes.NewReader(unhex("C80102030405060708"))) + s := NewStream(bytes.NewReader(unhex("C80102030405060708")), 0) len, err := s.List() if err != nil { @@ -166,7 +208,7 @@ func TestStreamList(t *testing.T) { } func TestStreamRaw(t *testing.T) { - s := NewStream(bytes.NewReader(unhex("C58401010101"))) + s := NewStream(bytes.NewReader(unhex("C58401010101")), 0) s.List() want := unhex("8401010101") @@ -284,11 +326,6 @@ var decodeTests = []decodeTest{ ptr: new([5]byte), error: "rlp: input string too long for [5]uint8", }, - { - input: "850101", - ptr: new([5]byte), - error: io.ErrUnexpectedEOF.Error(), - }, // byte array reuse (should be zeroed) {input: "850102030405", ptr: &sharedByteArray, value: [5]byte{1, 2, 3, 4, 5}}, @@ -401,11 +438,17 @@ func TestDecodeWithByteReader(t *testing.T) { }) } -// dumbReader reads from a byte slice but does not -// implement ReadByte. -type dumbReader []byte +// plainReader reads from a byte slice but does not +// implement ReadByte. It is also not recognized by the +// size validation. This is useful to test how the decoder +// behaves on a non-buffered input stream. +type plainReader []byte + +func newPlainReader(b []byte) io.Reader { + return (*plainReader)(&b) +} -func (r *dumbReader) Read(buf []byte) (n int, err error) { +func (r *plainReader) Read(buf []byte) (n int, err error) { if len(*r) == 0 { return 0, io.EOF } @@ -416,15 +459,14 @@ func (r *dumbReader) Read(buf []byte) (n int, err error) { func TestDecodeWithNonByteReader(t *testing.T) { runTests(t, func(input []byte, into interface{}) error { - r := dumbReader(input) - return Decode(&r, into) + return Decode(newPlainReader(input), into) }) } func TestDecodeStreamReset(t *testing.T) { - s := NewStream(nil) + s := NewStream(nil, 0) runTests(t, func(input []byte, into interface{}) error { - s.Reset(bytes.NewReader(input)) + s.Reset(bytes.NewReader(input), 0) return s.Decode(into) }) } @@ -518,7 +560,7 @@ func ExampleDecode() { func ExampleStream() { input, _ := hex.DecodeString("C90A1486666F6F626172") - s := NewStream(bytes.NewReader(input)) + s := NewStream(bytes.NewReader(input), 0) // Check what kind of value lies ahead kind, size, _ := s.Kind() -- cgit v1.2.3 From 2750ec47b7e7ff864eaed72255581e11080907d7 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 14 Apr 2015 00:54:12 +0200 Subject: rlp: fix integer overflow in list element size validation It is not safe to add anything to s.size. --- rlp/decode_test.go | 3 +++ 1 file changed, 3 insertions(+) (limited to 'rlp/decode_test.go') diff --git a/rlp/decode_test.go b/rlp/decode_test.go index 6b37ab0ad..a64bfe3fd 100644 --- a/rlp/decode_test.go +++ b/rlp/decode_test.go @@ -112,6 +112,9 @@ func TestStreamErrors(t *testing.T) { {"BFFFFFFFFFFFFFFFFFFF", calls{"Bytes"}, nil, ErrValueTooLarge}, {"C801", calls{"List"}, nil, ErrValueTooLarge}, + // Test for list element size check overflow. + {"CD04040404FFFFFFFFFFFFFFFFFF0303", calls{"List", "Uint", "Uint", "Uint", "Uint", "List"}, nil, ErrElemTooLarge}, + // Test for input limit overflow. Since we are counting the limit // down toward zero in Stream.remaining, reading too far can overflow // remaining to a large value, effectively disabling the limit. -- cgit v1.2.3 From 6788f955c2414b025a4ea44efaf51caf50aa97f0 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 14 Apr 2015 12:28:19 +0200 Subject: rlp: fix handling of single byte zero when decoding into a pointer A single zero byte carries information and should not set the pointer to nil. This is arguably a corner case. While here, fix the comment to explain pointer reuse. --- rlp/decode_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rlp/decode_test.go') diff --git a/rlp/decode_test.go b/rlp/decode_test.go index a64bfe3fd..e5c7f3761 100644 --- a/rlp/decode_test.go +++ b/rlp/decode_test.go @@ -378,7 +378,7 @@ var decodeTests = []decodeTest{ }, // pointers - {input: "00", ptr: new(*uint), value: (*uint)(nil)}, + {input: "00", ptr: new(*uint), value: uintp(0)}, {input: "80", ptr: new(*uint), value: (*uint)(nil)}, {input: "C0", ptr: new(*uint), value: (*uint)(nil)}, {input: "07", ptr: new(*uint), value: uintp(7)}, -- cgit v1.2.3 From 6e9f8035a1a49ac096bc2eae6ec4637b48e29048 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 14 Apr 2015 16:09:06 +0200 Subject: rlp: stricter validation of canonical integer format All integers (including size information in type tags) need to be encoded using the smallest possible encoding. This commit expands the stricter validation introduced for *big.Int in commit 59597d23a5ee268 to all integer types and size tags. --- rlp/decode_test.go | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) (limited to 'rlp/decode_test.go') diff --git a/rlp/decode_test.go b/rlp/decode_test.go index e5c7f3761..aa410de92 100644 --- a/rlp/decode_test.go +++ b/rlp/decode_test.go @@ -21,16 +21,11 @@ func TestStreamKind(t *testing.T) { {"7F", Byte, 0}, {"80", String, 0}, {"B7", String, 55}, - {"B800", String, 0}, {"B90400", String, 1024}, - {"BA000400", String, 1024}, - {"BB00000400", String, 1024}, {"BFFFFFFFFFFFFFFFFF", String, ^uint64(0)}, {"C0", List, 0}, {"C8", List, 8}, {"F7", List, 55}, - {"F800", List, 0}, - {"F804", List, 4}, {"F90400", List, 1024}, {"FFFFFFFFFFFFFFFFFF", List, ^uint64(0)}, } @@ -85,7 +80,7 @@ func TestStreamErrors(t *testing.T) { string calls newStream func([]byte) *Stream // uses bytes.Reader if nil - error + error error }{ {"C0", calls{"Bytes"}, nil, ErrExpectedString}, {"C0", calls{"Uint"}, nil, ErrExpectedString}, @@ -98,6 +93,21 @@ func TestStreamErrors(t *testing.T) { {"00", calls{"ListEnd"}, nil, errNotInList}, {"C401020304", calls{"List", "Uint", "ListEnd"}, nil, errNotAtEOL}, + // Leading zero bytes are rejected when reading integers. + {"00", calls{"Uint"}, nil, ErrCanonInt}, + {"820002", calls{"Uint"}, nil, ErrCanonInt}, + + // Size tags must use the smallest possible encoding. + // Leading zero bytes in the size tag are also rejected. + {"B800", calls{"Kind"}, withoutInputLimit, ErrCanonSize}, + {"B90000", calls{"Kind"}, withoutInputLimit, ErrCanonSize}, + {"B90055", calls{"Kind"}, withoutInputLimit, ErrCanonSize}, + {"BA0002FFFF", calls{"Bytes"}, withoutInputLimit, ErrCanonSize}, + {"F800", calls{"Kind"}, withoutInputLimit, ErrCanonSize}, + {"F90000", calls{"Kind"}, withoutInputLimit, ErrCanonSize}, + {"F90055", calls{"Kind"}, withoutInputLimit, ErrCanonSize}, + {"FA0002FFFF", calls{"List"}, withoutInputLimit, ErrCanonSize}, + // Expected EOF {"", calls{"Kind"}, nil, io.EOF}, {"", calls{"Uint"}, nil, io.EOF}, @@ -170,10 +180,12 @@ testfor: want = test.error.Error() } if err != want { + t.Log(test) t.Errorf("test %d: last call (%s) error mismatch\ngot: %s\nwant: %s", i, call, err, test.error) } } else if err != "" { + t.Log(test) t.Errorf("test %d: call %d (%s) unexpected error: %q", i, j, call, err) continue testfor } @@ -289,15 +301,20 @@ var decodeTests = []decodeTest{ {input: "8405050505", ptr: new(uint32), value: uint32(0x05050505)}, {input: "850505050505", ptr: new(uint32), error: "rlp: input string too long for uint32"}, {input: "C0", ptr: new(uint32), error: "rlp: expected input string or byte for uint32"}, + {input: "00", ptr: new(uint32), error: "rlp: non-canonical integer (leading zero bytes) for uint32"}, + {input: "820004", ptr: new(uint32), error: "rlp: non-canonical integer (leading zero bytes) for uint32"}, + {input: "B8020004", ptr: new(uint32), error: "rlp: non-canonical size information for uint32"}, // slices {input: "C0", ptr: new([]uint), value: []uint{}}, {input: "C80102030405060708", ptr: new([]uint), value: []uint{1, 2, 3, 4, 5, 6, 7, 8}}, + {input: "F8020004", ptr: new([]uint), error: "rlp: non-canonical size information for []uint"}, // arrays {input: "C0", ptr: new([5]uint), value: [5]uint{}}, {input: "C50102030405", ptr: new([5]uint), value: [5]uint{1, 2, 3, 4, 5}}, {input: "C6010203040506", ptr: new([5]uint), error: "rlp: input list has too many elements for [5]uint"}, + {input: "F8020004", ptr: new([5]uint), error: "rlp: non-canonical size information for [5]uint"}, // byte slices {input: "01", ptr: new([]byte), value: []byte{1}}, @@ -352,7 +369,7 @@ var decodeTests = []decodeTest{ // big ints {input: "01", ptr: new(*big.Int), value: big.NewInt(1)}, {input: "89FFFFFFFFFFFFFFFFFF", ptr: new(*big.Int), value: veryBigInt}, - {input: "820001", ptr: new(big.Int), error: "rlp: canon int error appends zero's for *big.Int"}, + {input: "820001", ptr: new(big.Int), error: "rlp: non-canonical integer (leading zero bytes) for *big.Int"}, {input: "10", ptr: new(big.Int), value: *big.NewInt(16)}, // non-pointer also works {input: "C0", ptr: new(*big.Int), error: "rlp: expected input string or byte for *big.Int"}, @@ -366,6 +383,11 @@ var decodeTests = []decodeTest{ value: recstruct{1, &recstruct{2, &recstruct{3, nil}}}, }, + { + input: "83222222", + ptr: new(simplestruct), + error: "rlp: expected input list for rlp.simplestruct", + }, { input: "C3010101", ptr: new(simplestruct), @@ -378,7 +400,7 @@ var decodeTests = []decodeTest{ }, // pointers - {input: "00", ptr: new(*uint), value: uintp(0)}, + {input: "00", ptr: new(*[]byte), value: &[]byte{0}}, {input: "80", ptr: new(*uint), value: (*uint)(nil)}, {input: "C0", ptr: new(*uint), value: (*uint)(nil)}, {input: "07", ptr: new(*uint), value: uintp(7)}, -- cgit v1.2.3 From 1e2c93aa2da453ef9548b9957b5ed453f60ce5ca Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 16 Apr 2015 10:15:14 +0200 Subject: rlp: reject non-minimal input strings Input strings of length 1 containing a byte < 56 are non-minimal and should be encoded as a single byte instead. Reject such strings. --- rlp/decode_test.go | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) (limited to 'rlp/decode_test.go') diff --git a/rlp/decode_test.go b/rlp/decode_test.go index aa410de92..7e2ea2041 100644 --- a/rlp/decode_test.go +++ b/rlp/decode_test.go @@ -93,12 +93,16 @@ func TestStreamErrors(t *testing.T) { {"00", calls{"ListEnd"}, nil, errNotInList}, {"C401020304", calls{"List", "Uint", "ListEnd"}, nil, errNotAtEOL}, - // Leading zero bytes are rejected when reading integers. + // Non-canonical integers (e.g. leading zero bytes). {"00", calls{"Uint"}, nil, ErrCanonInt}, {"820002", calls{"Uint"}, nil, ErrCanonInt}, + {"8133", calls{"Uint"}, nil, ErrCanonSize}, + {"8156", calls{"Uint"}, nil, nil}, // Size tags must use the smallest possible encoding. // Leading zero bytes in the size tag are also rejected. + {"8100", calls{"Uint"}, nil, ErrCanonSize}, + {"8100", calls{"Bytes"}, nil, ErrCanonSize}, {"B800", calls{"Kind"}, withoutInputLimit, ErrCanonSize}, {"B90000", calls{"Kind"}, withoutInputLimit, ErrCanonSize}, {"B90055", calls{"Kind"}, withoutInputLimit, ErrCanonSize}, @@ -112,7 +116,7 @@ func TestStreamErrors(t *testing.T) { {"", calls{"Kind"}, nil, io.EOF}, {"", calls{"Uint"}, nil, io.EOF}, {"", calls{"List"}, nil, io.EOF}, - {"8105", calls{"Uint", "Uint"}, nil, io.EOF}, + {"8158", calls{"Uint", "Uint"}, nil, io.EOF}, {"C0", calls{"List", "ListEnd", "List"}, nil, io.EOF}, // Input limit errors. @@ -129,11 +133,11 @@ func TestStreamErrors(t *testing.T) { // down toward zero in Stream.remaining, reading too far can overflow // remaining to a large value, effectively disabling the limit. {"C40102030401", calls{"Raw", "Uint"}, withCustomInputLimit(5), io.EOF}, - {"C4010203048102", calls{"Raw", "Uint"}, withCustomInputLimit(6), ErrValueTooLarge}, + {"C4010203048158", calls{"Raw", "Uint"}, withCustomInputLimit(6), ErrValueTooLarge}, // Check that the same calls are fine without a limit. {"C40102030401", calls{"Raw", "Uint"}, withoutInputLimit, nil}, - {"C4010203048102", calls{"Raw", "Uint"}, withoutInputLimit, nil}, + {"C4010203048158", calls{"Raw", "Uint"}, withoutInputLimit, nil}, // Unexpected EOF. This only happens when there is // no input limit, so the reader needs to be 'dumbed down'. @@ -295,13 +299,13 @@ var decodeTests = []decodeTest{ // integers {input: "05", ptr: new(uint32), value: uint32(5)}, {input: "80", ptr: new(uint32), value: uint32(0)}, - {input: "8105", ptr: new(uint32), value: uint32(5)}, {input: "820505", ptr: new(uint32), value: uint32(0x0505)}, {input: "83050505", ptr: new(uint32), value: uint32(0x050505)}, {input: "8405050505", ptr: new(uint32), value: uint32(0x05050505)}, {input: "850505050505", ptr: new(uint32), error: "rlp: input string too long for uint32"}, {input: "C0", ptr: new(uint32), error: "rlp: expected input string or byte for uint32"}, {input: "00", ptr: new(uint32), error: "rlp: non-canonical integer (leading zero bytes) for uint32"}, + {input: "8105", ptr: new(uint32), error: "rlp: non-canonical size information for uint32"}, {input: "820004", ptr: new(uint32), error: "rlp: non-canonical integer (leading zero bytes) for uint32"}, {input: "B8020004", ptr: new(uint32), error: "rlp: non-canonical size information for uint32"}, @@ -319,10 +323,16 @@ var decodeTests = []decodeTest{ // byte slices {input: "01", ptr: new([]byte), value: []byte{1}}, {input: "80", ptr: new([]byte), value: []byte{}}, + {input: "8D6162636465666768696A6B6C6D", ptr: new([]byte), value: []byte("abcdefghijklm")}, {input: "C0", ptr: new([]byte), value: []byte{}}, {input: "C3010203", ptr: new([]byte), value: []byte{1, 2, 3}}, + { + input: "8105", + ptr: new([]byte), + error: "rlp: non-canonical size information for []uint8", + }, { input: "C3820102", ptr: new([]byte), @@ -346,10 +356,15 @@ var decodeTests = []decodeTest{ ptr: new([5]byte), error: "rlp: input string too long for [5]uint8", }, + { + input: "8105", + ptr: new([5]byte), + error: "rlp: non-canonical size information for [5]uint8", + }, // byte array reuse (should be zeroed) {input: "850102030405", ptr: &sharedByteArray, value: [5]byte{1, 2, 3, 4, 5}}, - {input: "8101", ptr: &sharedByteArray, value: [5]byte{1}}, // kind: String + {input: "01", ptr: &sharedByteArray, value: [5]byte{1}}, // kind: String {input: "850102030405", ptr: &sharedByteArray, value: [5]byte{1, 2, 3, 4, 5}}, {input: "01", ptr: &sharedByteArray, value: [5]byte{1}}, // kind: Byte {input: "C3010203", ptr: &sharedByteArray, value: [5]byte{1, 2, 3, 0, 0}}, @@ -369,9 +384,10 @@ var decodeTests = []decodeTest{ // big ints {input: "01", ptr: new(*big.Int), value: big.NewInt(1)}, {input: "89FFFFFFFFFFFFFFFFFF", ptr: new(*big.Int), value: veryBigInt}, - {input: "820001", ptr: new(big.Int), error: "rlp: non-canonical integer (leading zero bytes) for *big.Int"}, {input: "10", ptr: new(big.Int), value: *big.NewInt(16)}, // non-pointer also works {input: "C0", ptr: new(*big.Int), error: "rlp: expected input string or byte for *big.Int"}, + {input: "820001", ptr: new(big.Int), error: "rlp: non-canonical integer (leading zero bytes) for *big.Int"}, + {input: "8105", ptr: new(big.Int), error: "rlp: non-canonical size information for *big.Int"}, // structs {input: "C0", ptr: new(simplestruct), value: simplestruct{0, ""}}, @@ -404,7 +420,7 @@ var decodeTests = []decodeTest{ {input: "80", ptr: new(*uint), value: (*uint)(nil)}, {input: "C0", ptr: new(*uint), value: (*uint)(nil)}, {input: "07", ptr: new(*uint), value: uintp(7)}, - {input: "8108", ptr: new(*uint), value: uintp(8)}, + {input: "8158", ptr: new(*uint), value: uintp(0x58)}, {input: "C109", ptr: new(*[]uint), value: &[]uint{9}}, {input: "C58403030303", ptr: new(*[][]byte), value: &[][]byte{{3, 3, 3, 3}}}, -- cgit v1.2.3 From cad64fb911e7029bef876f16e0956b3b0b4bb4d0 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 17 Apr 2015 01:16:46 +0200 Subject: rlp: stricter rules for structs and pointers The rules have changed as follows: * When decoding into pointers, empty values no longer produce a nil pointer. This can be overriden for struct fields using the struct tag "nil". * When decoding into structs, the input list must contain an element for each field. --- rlp/decode_test.go | 65 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 12 deletions(-) (limited to 'rlp/decode_test.go') diff --git a/rlp/decode_test.go b/rlp/decode_test.go index 7e2ea2041..fd52bd1be 100644 --- a/rlp/decode_test.go +++ b/rlp/decode_test.go @@ -280,7 +280,7 @@ type simplestruct struct { type recstruct struct { I uint - Child *recstruct + Child *recstruct `rlp:"nil"` } var ( @@ -390,15 +390,33 @@ var decodeTests = []decodeTest{ {input: "8105", ptr: new(big.Int), error: "rlp: non-canonical size information for *big.Int"}, // structs - {input: "C0", ptr: new(simplestruct), value: simplestruct{0, ""}}, - {input: "C105", ptr: new(simplestruct), value: simplestruct{5, ""}}, - {input: "C50583343434", ptr: new(simplestruct), value: simplestruct{5, "444"}}, { - input: "C501C302C103", + input: "C50583343434", + ptr: new(simplestruct), + value: simplestruct{5, "444"}, + }, + { + input: "C601C402C203C0", ptr: new(recstruct), value: recstruct{1, &recstruct{2, &recstruct{3, nil}}}, }, + // struct errors + { + input: "C0", + ptr: new(simplestruct), + error: "rlp: too few elements for rlp.simplestruct", + }, + { + input: "C105", + ptr: new(simplestruct), + error: "rlp: too few elements for rlp.simplestruct", + }, + { + input: "C7C50583343434C0", + ptr: new([]*simplestruct), + error: "rlp: too few elements for rlp.simplestruct, decoding into ([]*rlp.simplestruct)[1]", + }, { input: "83222222", ptr: new(simplestruct), @@ -417,19 +435,15 @@ var decodeTests = []decodeTest{ // pointers {input: "00", ptr: new(*[]byte), value: &[]byte{0}}, - {input: "80", ptr: new(*uint), value: (*uint)(nil)}, - {input: "C0", ptr: new(*uint), value: (*uint)(nil)}, + {input: "80", ptr: new(*uint), value: uintp(0)}, + {input: "C0", ptr: new(*uint), error: "rlp: expected input string or byte for uint"}, {input: "07", ptr: new(*uint), value: uintp(7)}, {input: "8158", ptr: new(*uint), value: uintp(0x58)}, {input: "C109", ptr: new(*[]uint), value: &[]uint{9}}, {input: "C58403030303", ptr: new(*[][]byte), value: &[][]byte{{3, 3, 3, 3}}}, // check that input position is advanced also for empty values. - {input: "C3808005", ptr: new([]*uint), value: []*uint{nil, nil, uintp(5)}}, - - // pointer should be reset to nil - {input: "05", ptr: sharedPtr, value: uintp(5)}, - {input: "80", ptr: sharedPtr, value: (*uint)(nil)}, + {input: "C3808005", ptr: new([]*uint), value: []*uint{uintp(0), uintp(0), uintp(5)}}, // interface{} {input: "00", ptr: new(interface{}), value: []byte{0}}, @@ -599,6 +613,33 @@ func ExampleDecode() { // Decoded value: rlp.example{A:0xa, B:0x14, private:0x0, String:"foobar"} } +func ExampleDecode_structTagNil() { + // In this example, we'll use the "nil" struct tag to change + // how a pointer-typed field is decoded. The input contains an RLP + // list of one element, an empty string. + input := []byte{0xC1, 0x80} + + // This type uses the normal rules. + // The empty input string is decoded as a pointer to an empty Go string. + var normalRules struct { + String *string + } + Decode(bytes.NewReader(input), &normalRules) + fmt.Printf("normal: String = %q\n", *normalRules.String) + + // This type uses the struct tag. + // The empty input string is decoded as a nil pointer. + var withEmptyOK struct { + String *string `rlp:"nil"` + } + Decode(bytes.NewReader(input), &withEmptyOK) + fmt.Printf("with nil tag: String = %v\n", withEmptyOK.String) + + // Output: + // normal: String = "" + // with nil tag: String = +} + func ExampleStream() { input, _ := hex.DecodeString("C90A1486666F6F626172") s := NewStream(bytes.NewReader(input), 0) -- cgit v1.2.3 From 4d5a518a0ba7b0f1d42c73f3c28fe0828e7ea974 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 17 Apr 2015 02:01:38 +0200 Subject: rlp: stop accepting lists for byte slices and byte arrays --- rlp/decode_test.go | 41 +++++++---------------------------------- 1 file changed, 7 insertions(+), 34 deletions(-) (limited to 'rlp/decode_test.go') diff --git a/rlp/decode_test.go b/rlp/decode_test.go index fd52bd1be..0b69ff1f4 100644 --- a/rlp/decode_test.go +++ b/rlp/decode_test.go @@ -323,56 +323,29 @@ var decodeTests = []decodeTest{ // byte slices {input: "01", ptr: new([]byte), value: []byte{1}}, {input: "80", ptr: new([]byte), value: []byte{}}, - {input: "8D6162636465666768696A6B6C6D", ptr: new([]byte), value: []byte("abcdefghijklm")}, - {input: "C0", ptr: new([]byte), value: []byte{}}, - {input: "C3010203", ptr: new([]byte), value: []byte{1, 2, 3}}, - - { - input: "8105", - ptr: new([]byte), - error: "rlp: non-canonical size information for []uint8", - }, - { - input: "C3820102", - ptr: new([]byte), - error: "rlp: input string too long for uint8, decoding into ([]uint8)[0]", - }, + {input: "C0", ptr: new([]byte), error: "rlp: expected input string or byte for []uint8"}, + {input: "8105", ptr: new([]byte), error: "rlp: non-canonical size information for []uint8"}, // byte arrays {input: "01", ptr: new([5]byte), value: [5]byte{1}}, {input: "80", ptr: new([5]byte), value: [5]byte{}}, {input: "850102030405", ptr: new([5]byte), value: [5]byte{1, 2, 3, 4, 5}}, - {input: "C0", ptr: new([5]byte), value: [5]byte{}}, - {input: "C3010203", ptr: new([5]byte), value: [5]byte{1, 2, 3, 0, 0}}, - { - input: "C3820102", - ptr: new([5]byte), - error: "rlp: input string too long for uint8, decoding into ([5]uint8)[0]", - }, - { - input: "86010203040506", - ptr: new([5]byte), - error: "rlp: input string too long for [5]uint8", - }, - { - input: "8105", - ptr: new([5]byte), - error: "rlp: non-canonical size information for [5]uint8", - }, + // byte array errors + {input: "C0", ptr: new([5]byte), error: "rlp: expected input string or byte for [5]uint8"}, + {input: "C3010203", ptr: new([5]byte), error: "rlp: expected input string or byte for [5]uint8"}, + {input: "86010203040506", ptr: new([5]byte), error: "rlp: input string too long for [5]uint8"}, + {input: "8105", ptr: new([5]byte), error: "rlp: non-canonical size information for [5]uint8"}, // byte array reuse (should be zeroed) {input: "850102030405", ptr: &sharedByteArray, value: [5]byte{1, 2, 3, 4, 5}}, {input: "01", ptr: &sharedByteArray, value: [5]byte{1}}, // kind: String {input: "850102030405", ptr: &sharedByteArray, value: [5]byte{1, 2, 3, 4, 5}}, {input: "01", ptr: &sharedByteArray, value: [5]byte{1}}, // kind: Byte - {input: "C3010203", ptr: &sharedByteArray, value: [5]byte{1, 2, 3, 0, 0}}, - {input: "C101", ptr: &sharedByteArray, value: [5]byte{1}}, // kind: List // zero sized byte arrays {input: "80", ptr: new([0]byte), value: [0]byte{}}, - {input: "C0", ptr: new([0]byte), value: [0]byte{}}, {input: "01", ptr: new([0]byte), error: "rlp: input string too long for [0]uint8"}, {input: "8101", ptr: new([0]byte), error: "rlp: input string too long for [0]uint8"}, -- cgit v1.2.3 From 7180699d401e64b52447ccb2cfb33004b7deb672 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 17 Apr 2015 03:11:24 +0200 Subject: rlp: require declared number of input elements for array types --- rlp/decode_test.go | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) (limited to 'rlp/decode_test.go') diff --git a/rlp/decode_test.go b/rlp/decode_test.go index 0b69ff1f4..d07520bd0 100644 --- a/rlp/decode_test.go +++ b/rlp/decode_test.go @@ -290,11 +290,6 @@ var ( ) ) -var ( - sharedByteArray [5]byte - sharedPtr = new(*uint) -) - var decodeTests = []decodeTest{ // integers {input: "05", ptr: new(uint32), value: uint32(5)}, @@ -315,11 +310,16 @@ var decodeTests = []decodeTest{ {input: "F8020004", ptr: new([]uint), error: "rlp: non-canonical size information for []uint"}, // arrays - {input: "C0", ptr: new([5]uint), value: [5]uint{}}, {input: "C50102030405", ptr: new([5]uint), value: [5]uint{1, 2, 3, 4, 5}}, + {input: "C0", ptr: new([5]uint), error: "rlp: input list has too few elements for [5]uint"}, + {input: "C102", ptr: new([5]uint), error: "rlp: input list has too few elements for [5]uint"}, {input: "C6010203040506", ptr: new([5]uint), error: "rlp: input list has too many elements for [5]uint"}, {input: "F8020004", ptr: new([5]uint), error: "rlp: non-canonical size information for [5]uint"}, + // zero sized arrays + {input: "C0", ptr: new([0]uint), value: [0]uint{}}, + {input: "C101", ptr: new([0]uint), error: "rlp: input list has too many elements for [0]uint"}, + // byte slices {input: "01", ptr: new([]byte), value: []byte{1}}, {input: "80", ptr: new([]byte), value: []byte{}}, @@ -328,21 +328,17 @@ var decodeTests = []decodeTest{ {input: "8105", ptr: new([]byte), error: "rlp: non-canonical size information for []uint8"}, // byte arrays - {input: "01", ptr: new([5]byte), value: [5]byte{1}}, - {input: "80", ptr: new([5]byte), value: [5]byte{}}, + {input: "02", ptr: new([1]byte), value: [1]byte{2}}, {input: "850102030405", ptr: new([5]byte), value: [5]byte{1, 2, 3, 4, 5}}, // byte array errors + {input: "02", ptr: new([5]byte), error: "rlp: input string too short for [5]uint8"}, + {input: "80", ptr: new([5]byte), error: "rlp: input string too short for [5]uint8"}, + {input: "820000", ptr: new([5]byte), error: "rlp: input string too short for [5]uint8"}, {input: "C0", ptr: new([5]byte), error: "rlp: expected input string or byte for [5]uint8"}, {input: "C3010203", ptr: new([5]byte), error: "rlp: expected input string or byte for [5]uint8"}, {input: "86010203040506", ptr: new([5]byte), error: "rlp: input string too long for [5]uint8"}, - {input: "8105", ptr: new([5]byte), error: "rlp: non-canonical size information for [5]uint8"}, - - // byte array reuse (should be zeroed) - {input: "850102030405", ptr: &sharedByteArray, value: [5]byte{1, 2, 3, 4, 5}}, - {input: "01", ptr: &sharedByteArray, value: [5]byte{1}}, // kind: String - {input: "850102030405", ptr: &sharedByteArray, value: [5]byte{1, 2, 3, 4, 5}}, - {input: "01", ptr: &sharedByteArray, value: [5]byte{1}}, // kind: Byte + {input: "8105", ptr: new([1]byte), error: "rlp: non-canonical size information for [1]uint8"}, // zero sized byte arrays {input: "80", ptr: new([0]byte), value: [0]byte{}}, -- cgit v1.2.3