Skip to content

Commit 1d42aad

Browse files
authored
Make name.decode stricter (#79)
* Add tests for name decoding corner cases * Modify name.decode to throw an error in the following cases: * Not enough data for reading the full label * The label is too long (over 253 characters when dots are included) * A label must be either <= 63 bytes or a pointer * Pointers can only point to prior data (see RFC 1035, section 4.1.4) In addition pointer jumps don't add extra dots in the names anymore. * Make name_decoding tests more specific * Make name.decode non-recursive * Ensure name.decode can read the label header * Fix name.decode error messages
1 parent 8e6d91c commit 1d42aad

File tree

2 files changed

+86
-22
lines changed

2 files changed

+86
-22
lines changed

index.js

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -46,33 +46,53 @@ name.decode = function (buf, offset) {
4646
if (!offset) offset = 0
4747

4848
const list = []
49-
const oldOffset = offset
50-
let len = buf[offset++]
51-
52-
if (len === 0) {
53-
name.decode.bytes = 1
54-
return '.'
55-
}
56-
if (len >= 0xc0) {
57-
const res = name.decode(buf, buf.readUInt16BE(offset - 1) - 0xc000)
58-
name.decode.bytes = 2
59-
return res
60-
}
49+
let oldOffset = offset
50+
let totalLength = 0
51+
let consumedBytes = 0
52+
let jumped = false
53+
54+
while (true) {
55+
if (offset >= buf.length) {
56+
throw new Error('Cannot decode name (buffer overflow)')
57+
}
58+
const len = buf[offset++]
59+
consumedBytes += jumped ? 0 : 1
6160

62-
while (len) {
63-
if (len >= 0xc0) {
64-
list.push(name.decode(buf, buf.readUInt16BE(offset - 1) - 0xc000))
65-
offset++
61+
if (len === 0) {
6662
break
63+
} else if ((len & 0xc0) === 0) {
64+
if (offset + len > buf.length) {
65+
throw new Error('Cannot decode name (buffer overflow)')
66+
}
67+
totalLength += len + 1
68+
if (totalLength > 254) {
69+
throw new Error('Cannot decode name (name too long)')
70+
}
71+
list.push(buf.toString('utf-8', offset, offset + len))
72+
offset += len
73+
consumedBytes += jumped ? 0 : len
74+
} else if ((len & 0xc0) === 0xc0) {
75+
if (offset + 1 > buf.length) {
76+
throw new Error('Cannot decode name (buffer overflow)')
77+
}
78+
const jumpOffset = buf.readUInt16BE(offset - 1) - 0xc000
79+
if (jumpOffset >= oldOffset) {
80+
// Allow only pointers to prior data. RFC 1035, section 4.1.4 states:
81+
// "[...] an entire domain name or a list of labels at the end of a domain name
82+
// is replaced with a pointer to a prior occurance (sic) of the same name."
83+
throw new Error('Cannot decode name (bad pointer)')
84+
}
85+
offset = jumpOffset
86+
oldOffset = jumpOffset
87+
consumedBytes += jumped ? 0 : 1
88+
jumped = true
89+
} else {
90+
throw new Error('Cannot decode name (bad label)')
6791
}
68-
69-
list.push(buf.toString('utf-8', offset, offset + len))
70-
offset += len
71-
len = buf[offset++]
7292
}
7393

74-
name.decode.bytes = offset - oldOffset
75-
return list.join('.')
94+
name.decode.bytes = consumedBytes
95+
return list.length === 0 ? '.' : list.join('.')
7696
}
7797

7898
name.decode.bytes = 0

test.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,50 @@ tape('name_encoding', function (t) {
304304
t.end()
305305
})
306306

307+
tape('name_decoding', function (t) {
308+
// The two most significant bits of a valid label header must be either both zero or both one
309+
t.throws(function () { packet.name.decode(Buffer.from([0x80])) }, /Cannot decode name \(bad label\)$/)
310+
t.throws(function () { packet.name.decode(Buffer.from([0xb0])) }, /Cannot decode name \(bad label\)$/)
311+
312+
// Ensure there's enough buffer to read
313+
t.throws(function () { packet.name.decode(Buffer.from([])) }, /Cannot decode name \(buffer overflow\)$/)
314+
t.throws(function () { packet.name.decode(Buffer.from([0x01, 0x00])) }, /Cannot decode name \(buffer overflow\)$/)
315+
t.throws(function () { packet.name.decode(Buffer.from([0x01])) }, /Cannot decode name \(buffer overflow\)$/)
316+
t.throws(function () { packet.name.decode(Buffer.from([0xc0])) }, /Cannot decode name \(buffer overflow\)$/)
317+
318+
// Allow only pointers backwards
319+
t.throws(function () { packet.name.decode(Buffer.from([0xc0, 0x00])) }, /Cannot decode name \(bad pointer\)$/)
320+
t.throws(function () { packet.name.decode(Buffer.from([0xc0, 0x01])) }, /Cannot decode name \(bad pointer\)$/)
321+
322+
// A name can be only 253 characters (when connected with dots)
323+
const maxLength = Buffer.alloc(255)
324+
maxLength.fill(Buffer.from([0x01, 0x61]), 0, 254)
325+
t.ok(packet.name.decode(maxLength) === new Array(127).fill('a').join('.'))
326+
327+
const tooLong = Buffer.alloc(256)
328+
tooLong.fill(Buffer.from([0x01, 0x61]))
329+
t.throws(function () { packet.name.decode(tooLong) }, /Cannot decode name \(name too long\)$/)
330+
331+
// Ensure jumps don't reset the total length counter
332+
const tooLongWithJump = Buffer.alloc(403)
333+
tooLongWithJump.fill(Buffer.from([0x01, 0x61]), 0, 200)
334+
tooLongWithJump.fill(Buffer.from([0x01, 0x61]), 201, 401)
335+
tooLongWithJump.set([0xc0, 0x00], 401)
336+
t.throws(function () { packet.name.decode(tooLongWithJump, 201) }, /Cannot decode name \(name too long\)$/)
337+
338+
// Ensure a jump to a null byte doesn't add extra dots
339+
t.ok(packet.name.decode(Buffer.from([0x00, 0x01, 0x61, 0xc0, 0x00]), 1) === 'a')
340+
341+
// Ensure deeply nested pointers don't cause "Maximum call stack size exceeded" errors
342+
const buf = Buffer.alloc(16386)
343+
for (let i = 0; i < 16384; i += 2) {
344+
buf.writeUInt16BE(0xc000 | i, i + 2)
345+
}
346+
t.ok(packet.name.decode(buf, 16384) === '.')
347+
348+
t.end()
349+
})
350+
307351
tape('stream', function (t) {
308352
const val = {
309353
type: 'query',

0 commit comments

Comments
 (0)