Skip to content

Commit 4f3c6a5

Browse files
committed
Check CBOR well-formedness in SimpleValue.UnmarshalCBOR
When SimpleValue.UnmarshalCBOR() is called by codec (normal case), the codec will first check if data is well-formed before calling SimpleValue.UnmarshalCBOR(data). However, it can also be called by user app (not intended use) and user apps might not check if data is well-formed. In such cases, this function can panic if given malformed data. This commit updates SimpleValue.UnmarshalCBOR() to check for well-formedness inside the function, so it behaves the same whether it is called by codec internally or by user app. Unfortunately, this approach means the same data is checked twice for the normal case of the codec using Unmarshal(data, *SimpleValue). This can be revisited and maybe optimized in the future.
1 parent 621c666 commit 4f3c6a5

File tree

2 files changed

+136
-0
lines changed

2 files changed

+136
-0
lines changed

simplevalue.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,21 @@ func (sv *SimpleValue) UnmarshalCBOR(data []byte) error {
5555

5656
d := decoder{data: data, dm: defaultDecMode}
5757

58+
// Check well-formedness of CBOR data item.
59+
// NOTE: well-formedness check here is redundant when
60+
// Unmarshal() invokes SimpleValue.UnmarshalCBOR().
61+
// However, SimpleValue.UnmarshalCBOR() is exported, so
62+
// the codec needs to support same behavior for:
63+
// - Unmarshal(data, *SimpleValue)
64+
// - SimpleValue.UnmarshalCBOR(data)
65+
err := d.wellformed(false, false)
66+
if err != nil {
67+
return err
68+
}
69+
70+
// Restore decoder offset after well-formedness check.
71+
d.off = 0
72+
5873
typ, ai, val := d.getHead()
5974

6075
if typ != cborTypePrimitives {

simplevalue_test.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ package cbor
55

66
import (
77
"bytes"
8+
"io"
89
"reflect"
10+
"strings"
911
"testing"
1012
)
1113

@@ -51,6 +53,125 @@ func TestUnmarshalSimpleValue(t *testing.T) {
5153
})
5254
}
5355

56+
func TestUnmarshalSimpleValueOnBadData(t *testing.T) {
57+
testCases := []struct {
58+
name string
59+
data []byte
60+
errMsg string
61+
}{
62+
// Empty data
63+
{
64+
name: "nil data",
65+
data: nil,
66+
errMsg: io.EOF.Error(),
67+
},
68+
{
69+
name: "empty data",
70+
data: []byte{},
71+
errMsg: io.EOF.Error(),
72+
},
73+
74+
// Wrong CBOR types
75+
{
76+
name: "uint type",
77+
data: hexDecode("01"),
78+
errMsg: "cbor: cannot unmarshal positive integer into Go value of type SimpleValue",
79+
},
80+
{
81+
name: "int type",
82+
data: hexDecode("20"),
83+
errMsg: "cbor: cannot unmarshal negative integer into Go value of type SimpleValue",
84+
},
85+
{
86+
name: "byte string type",
87+
data: hexDecode("40"),
88+
errMsg: "cbor: cannot unmarshal byte string into Go value of type SimpleValue",
89+
},
90+
{
91+
name: "string type",
92+
data: hexDecode("60"),
93+
errMsg: "cbor: cannot unmarshal UTF-8 text string into Go value of type SimpleValue",
94+
},
95+
{
96+
name: "array type",
97+
data: hexDecode("80"),
98+
errMsg: "cbor: cannot unmarshal array into Go value of type SimpleValue",
99+
},
100+
{
101+
name: "map type",
102+
data: hexDecode("a0"),
103+
errMsg: "cbor: cannot unmarshal map into Go value of type SimpleValue",
104+
},
105+
{
106+
name: "tag type",
107+
data: hexDecode("c074323031332d30332d32315432303a30343a30305a"),
108+
errMsg: "cbor: cannot unmarshal tag into Go value of type SimpleValue",
109+
},
110+
{
111+
name: "float type",
112+
data: hexDecode("f90000"),
113+
errMsg: "cbor: cannot unmarshal primitives into Go value of type SimpleValue",
114+
},
115+
116+
// Truncated CBOR data
117+
{
118+
name: "truncated head",
119+
data: hexDecode("18"),
120+
errMsg: io.ErrUnexpectedEOF.Error(),
121+
},
122+
123+
// Truncated CBOR simple value
124+
{
125+
name: "truncated simple value",
126+
data: hexDecode("f8"),
127+
errMsg: io.ErrUnexpectedEOF.Error(),
128+
},
129+
130+
// Invalid simple value
131+
{
132+
name: "invalid simple value",
133+
data: hexDecode("f800"),
134+
errMsg: "cbor: invalid simple value 0 for type primitives",
135+
},
136+
137+
// Extraneous CBOR data
138+
{
139+
name: "extraneous data",
140+
data: hexDecode("f4f5"),
141+
errMsg: "cbor: 1 bytes of extraneous data starting at index 1",
142+
},
143+
}
144+
145+
for _, tc := range testCases {
146+
t.Run(tc.name, func(t *testing.T) {
147+
// Test SimpleValue.UnmarshalCBOR(data)
148+
{
149+
var v SimpleValue
150+
151+
err := v.UnmarshalCBOR(tc.data)
152+
if err == nil {
153+
t.Errorf("UnmarshalCBOR(%x) didn't return error", tc.data)
154+
}
155+
if !strings.HasPrefix(err.Error(), tc.errMsg) {
156+
t.Errorf("UnmarshalCBOR(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
157+
}
158+
}
159+
// Test Unmarshal(data, *SimpleValue), which calls SimpleValue.UnmarshalCBOR() under the hood
160+
{
161+
var v SimpleValue
162+
163+
err := Unmarshal(tc.data, &v)
164+
if err == nil {
165+
t.Errorf("UnmarshalCBOR(%x) didn't return error", tc.data)
166+
}
167+
if !strings.HasPrefix(err.Error(), tc.errMsg) {
168+
t.Errorf("UnmarshalCBOR(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
169+
}
170+
}
171+
})
172+
}
173+
}
174+
54175
func testUnmarshalInvalidSimpleValueToEmptyInterface(t *testing.T, data []byte) {
55176
var v any
56177
if err := Unmarshal(data, v); err == nil {

0 commit comments

Comments
 (0)