Skip to content

Commit 2ee4122

Browse files
committed
Ignore unknown options or options with illegal length
According to the CoAP RFC, unknown options and options whose values have illegal lengths should be ignored (skipped). The option value parsing was refactored into the parseOptionValue function which includes the new option ID check and option value length check. This fix also mitigates a panic that was caused when trying to parsing uint fields with lengths longer than 4. The problematic packet was found with the help of afl (http://lcamtuf.coredump.cx/afl/).
1 parent 01cd1a1 commit 2ee4122

File tree

2 files changed

+97
-18
lines changed

2 files changed

+97
-18
lines changed

message.go

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,42 @@ const (
173173
Size1 OptionID = 60
174174
)
175175

176+
// Option value format (RFC7252 section 3.2)
177+
type valueFormat uint8
178+
179+
const (
180+
valueUnknown valueFormat = iota
181+
valueEmpty
182+
valueOpaque
183+
valueUint
184+
valueString
185+
)
186+
187+
type optionDef struct {
188+
valueFormat valueFormat
189+
minLen int
190+
maxLen int
191+
}
192+
193+
var optionDefs = [256]optionDef{
194+
IfMatch: optionDef{valueFormat: valueOpaque, minLen: 0, maxLen: 8},
195+
URIHost: optionDef{valueFormat: valueString, minLen: 1, maxLen: 255},
196+
ETag: optionDef{valueFormat: valueOpaque, minLen: 1, maxLen: 8},
197+
IfNoneMatch: optionDef{valueFormat: valueEmpty, minLen: 0, maxLen: 0},
198+
Observe: optionDef{valueFormat: valueUint, minLen: 0, maxLen: 3},
199+
URIPort: optionDef{valueFormat: valueUint, minLen: 0, maxLen: 2},
200+
LocationPath: optionDef{valueFormat: valueString, minLen: 0, maxLen: 255},
201+
URIPath: optionDef{valueFormat: valueString, minLen: 0, maxLen: 255},
202+
ContentFormat: optionDef{valueFormat: valueUint, minLen: 0, maxLen: 2},
203+
MaxAge: optionDef{valueFormat: valueUint, minLen: 0, maxLen: 4},
204+
URIQuery: optionDef{valueFormat: valueString, minLen: 0, maxLen: 255},
205+
Accept: optionDef{valueFormat: valueUint, minLen: 0, maxLen: 2},
206+
LocationQuery: optionDef{valueFormat: valueString, minLen: 0, maxLen: 255},
207+
ProxyURI: optionDef{valueFormat: valueString, minLen: 1, maxLen: 1034},
208+
ProxyScheme: optionDef{valueFormat: valueString, minLen: 1, maxLen: 255},
209+
Size1: optionDef{valueFormat: valueUint, minLen: 0, maxLen: 4},
210+
}
211+
176212
// MediaType specifies the content type of a message.
177213
type MediaType byte
178214

@@ -244,6 +280,33 @@ func (o option) toBytes() []byte {
244280
return encodeInt(v)
245281
}
246282

283+
func parseOptionValue(optionID OptionID, valueBuf []byte) interface{} {
284+
def := optionDefs[optionID]
285+
if def.valueFormat == valueUnknown {
286+
// Skip unrecognized options (RFC7252 section 5.4.1)
287+
return nil
288+
}
289+
if len(valueBuf) < def.minLen || len(valueBuf) > def.maxLen {
290+
// Skip options with illegal value length (RFC7252 section 5.4.3)
291+
return nil
292+
}
293+
switch def.valueFormat {
294+
case valueUint:
295+
intValue := decodeInt(valueBuf)
296+
if optionID == ContentFormat || optionID == Accept {
297+
return MediaType(intValue)
298+
} else {
299+
return intValue
300+
}
301+
case valueString:
302+
return string(valueBuf)
303+
case valueOpaque, valueEmpty:
304+
return valueBuf
305+
}
306+
// Skip unrecognized options (should never be reached)
307+
return nil
308+
}
309+
247310
type options []option
248311

249312
func (o options) Len() int {
@@ -547,27 +610,15 @@ func (m *Message) UnmarshalBinary(data []byte) error {
547610
if len(b) < length {
548611
return errors.New("truncated")
549612
}
550-
oid := OptionID(prev + delta)
551613

552-
var opval interface{} = b[:length]
553-
switch oid {
554-
case ContentFormat, Accept:
555-
opval = MediaType(decodeInt(b[:length]))
556-
case URIPort, MaxAge, Size1:
557-
opval = decodeInt(b[:length])
558-
case URIHost, LocationPath, URIPath, URIQuery, LocationQuery,
559-
ProxyURI, ProxyScheme:
560-
opval = string(b[:length])
561-
}
562-
563-
option := option{
564-
ID: oid,
565-
Value: opval,
566-
}
614+
oid := OptionID(prev + delta)
615+
opval := parseOptionValue(oid, b[:length])
567616
b = b[length:]
568-
prev = int(option.ID)
617+
prev = int(oid)
569618

570-
m.opts = append(m.opts, option)
619+
if opval != nil {
620+
m.opts = append(m.opts, option{ID: oid, Value: opval})
621+
}
571622
}
572623
m.Payload = b
573624
return nil

message_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,34 @@ func TestInvalidMessageParsing(t *testing.T) {
187187
}
188188
}
189189

190+
func TestOptionsWithIllegalLengthAreIgnoredDuringParsing(t *testing.T) {
191+
exp := Message{
192+
Type: Confirmable,
193+
Code: GET,
194+
MessageID: 0xabcd,
195+
Payload: []byte{},
196+
}
197+
msg, err := parseMessage([]byte{0x40, 0x01, 0xab, 0xcd,
198+
0x73, // URI-Port option (uint) with length 3 (valid lengths are 0-2)
199+
0x11, 0x22, 0x33, 0xff})
200+
if err != nil {
201+
t.Fatalf("Error parsing message: %v", err)
202+
}
203+
if fmt.Sprintf("%#v", exp) != fmt.Sprintf("%#v", msg) {
204+
t.Errorf("Expected\n%#v\ngot\n%#v", exp, msg)
205+
}
206+
207+
msg, err = parseMessage([]byte{0x40, 0x01, 0xab, 0xcd,
208+
0xd5, 0x01, // Max-Age option (uint) with length 5 (valid lengths are 0-4)
209+
0x11, 0x22, 0x33, 0x44, 0x55, 0xff})
210+
if err != nil {
211+
t.Fatalf("Error parsing message: %v", err)
212+
}
213+
if fmt.Sprintf("%#v", exp) != fmt.Sprintf("%#v", msg) {
214+
t.Errorf("Expected\n%#v\ngot\n%#v", exp, msg)
215+
}
216+
}
217+
190218
func TestDecodeMessageSmallWithPayload(t *testing.T) {
191219
input := []byte{
192220
0x40, 0x1, 0x30, 0x39, 0x21, 0x3,

0 commit comments

Comments
 (0)