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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy