Skip to content

Commit 843a162

Browse files
jasnelladuh95
authored andcommitted
dns: validate resolve inputs for null bytes
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode PR-URL: nodejs-private/node-private#984 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> CVE-ID: CVE-2026-105482
1 parent 96c3ccb commit 843a162

5 files changed

Lines changed: 102 additions & 5 deletions

File tree

‎lib/internal/dns/callback_resolver.js‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const {
2020

2121
const {
2222
validateFunction,
23-
validateString,
23+
validateStringWithoutNullBytes,
2424
} = require('internal/validators');
2525

2626
const {
@@ -59,7 +59,7 @@ function resolver(bindingName) {
5959
callback = arguments[2];
6060
}
6161

62-
validateString(name, 'name');
62+
validateStringWithoutNullBytes(name, 'name');
6363
validateFunction(callback, 'callback');
6464

6565
const req = new QueryReqWrap();

‎lib/internal/dns/promises.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ function createResolverPromise(resolver, bindingName, hostname, ttl) {
340340

341341
function resolver(bindingName) {
342342
function query(name, options) {
343-
validateString(name, 'name');
343+
validateStringWithoutNullBytes(name, 'name');
344344

345345
const ttl = !!(options?.ttl);
346346
return createResolverPromise(this, bindingName, name, ttl);

‎lib/internal/dns/utils.js‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const {
2626
validateOneOf,
2727
validatePort,
2828
validateString,
29+
validateStringWithoutNullBytes,
2930
validateUint32,
3031
} = require('internal/validators');
3132
let binding;
@@ -178,10 +179,10 @@ class ResolverBase {
178179

179180

180181
setLocalAddress(ipv4, ipv6) {
181-
validateString(ipv4, 'ipv4');
182+
validateStringWithoutNullBytes(ipv4, 'ipv4');
182183

183184
if (ipv6 !== undefined) {
184-
validateString(ipv6, 'ipv6');
185+
validateStringWithoutNullBytes(ipv6, 'ipv6');
185186
}
186187

187188
this._handle.setLocalAddress(ipv4, ipv6);
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
'use strict';
2+
const common = require('../common');
3+
4+
// Names containing null bytes must be rejected by the resolver methods.
5+
// c-ares receives the name as a C string, so everything after the first
6+
// null byte would otherwise be silently dropped, e.g. resolving
7+
// 'example.com\0.trusted.corp' would actually query 'example.com'.
8+
9+
const assert = require('assert');
10+
const dns = require('dns');
11+
const dnsPromises = dns.promises;
12+
13+
const name = 'example.com\u0000.trusted.corp';
14+
const ip = '127.0.0.1\u0000.trusted.corp';
15+
16+
const err = {
17+
code: 'ERR_INVALID_ARG_VALUE',
18+
name: 'TypeError',
19+
message: /^The argument 'name' must be a string without null bytes\. Received /,
20+
};
21+
22+
const methods = [
23+
'resolve',
24+
'resolve4',
25+
'resolve6',
26+
'resolveAny',
27+
'resolveCaa',
28+
'resolveCname',
29+
'resolveMx',
30+
'resolveNaptr',
31+
'resolveNs',
32+
'resolvePtr',
33+
'resolveSoa',
34+
'resolveSrv',
35+
'resolveTlsa',
36+
'resolveTxt',
37+
];
38+
39+
const rrtypes = [
40+
'A',
41+
'AAAA',
42+
'ANY',
43+
'CAA',
44+
'CNAME',
45+
'MX',
46+
'NAPTR',
47+
'NS',
48+
'PTR',
49+
'SOA',
50+
'SRV',
51+
'TLSA',
52+
'TXT',
53+
];
54+
55+
// Callback-based API: the default resolver and a Resolver instance.
56+
for (const resolver of [dns, new dns.Resolver()]) {
57+
for (const method of methods) {
58+
assert.throws(() => resolver[method](name, common.mustNotCall()), err);
59+
}
60+
for (const rrtype of rrtypes) {
61+
assert.throws(() => resolver.resolve(name, rrtype, common.mustNotCall()), err);
62+
}
63+
assert.throws(() => resolver.reverse(ip, common.mustNotCall()), err);
64+
}
65+
66+
// Promise-based API: the default resolver and a Resolver instance.
67+
for (const resolver of [dnsPromises, new dnsPromises.Resolver()]) {
68+
for (const method of methods) {
69+
assert.throws(() => resolver[method](name), err);
70+
}
71+
for (const rrtype of rrtypes) {
72+
assert.throws(() => resolver.resolve(name, rrtype), err);
73+
}
74+
assert.throws(() => resolver.reverse(ip), err);
75+
}

‎test/parallel/test-dns-setlocaladdress.js‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,24 @@ const promiseResolver = new dns.promises.Resolver();
3838
promiseResolver.setLocalAddress();
3939
}, Error);
4040
}
41+
42+
// Verify that setLocalAddress rejects addresses containing null bytes instead
43+
// of silently truncating them at the first null byte
44+
{
45+
for (const r of [resolver, promiseResolver]) {
46+
assert.throws(() => {
47+
r.setLocalAddress('127.0.0.1\u0000junk');
48+
}, {
49+
code: 'ERR_INVALID_ARG_VALUE',
50+
name: 'TypeError',
51+
message: /^The argument 'ipv4' must be a string without null bytes\. Received /,
52+
});
53+
assert.throws(() => {
54+
r.setLocalAddress('127.0.0.1', '::1\u0000junk');
55+
}, {
56+
code: 'ERR_INVALID_ARG_VALUE',
57+
name: 'TypeError',
58+
message: /^The argument 'ipv6' must be a string without null bytes\. Received /,
59+
});
60+
}
61+
}

0 commit comments

Comments
 (0)