mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
dns: validate resolve inputs for null bytes
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode PR-URL: https://github.com/nodejs-private/node-private/pull/984 Fixes: https://hackerone.com/reports/4038342 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> CVE-ID: CVE-2026-105482
This commit is contained in:
1 parent
b85dd8021c
commit
a370966282
5 files changed
+102
-5
No files matched your search
@@ -20,7 +20,7 @@ const {
|
||||
|
||||
const {
|
||||
validateFunction,
|
||||
validateString,
|
||||
validateStringWithoutNullBytes,
|
||||
} = require('internal/validators');
|
||||
|
||||
const {
|
||||
@@ -59,7 +59,7 @@ function resolver(bindingName) {
|
||||
callback = arguments[2];
|
||||
}
|
||||
|
||||
validateString(name, 'name');
|
||||
validateStringWithoutNullBytes(name, 'name');
|
||||
validateFunction(callback, 'callback');
|
||||
|
||||
const req = new QueryReqWrap();
|
||||
|
||||
@@ -339,7 +339,7 @@ function createResolverPromise(resolver, bindingName, hostname, ttl) {
|
||||
|
||||
function resolver(bindingName) {
|
||||
function query(name, options) {
|
||||
validateString(name, 'name');
|
||||
validateStringWithoutNullBytes(name, 'name');
|
||||
|
||||
const ttl = !!(options?.ttl);
|
||||
return createResolverPromise(this, bindingName, name, ttl);
|
||||
|
||||
@@ -26,6 +26,7 @@ const {
|
||||
validateOneOf,
|
||||
validatePort,
|
||||
validateString,
|
||||
validateStringWithoutNullBytes,
|
||||
validateUint32,
|
||||
} = require('internal/validators');
|
||||
let binding;
|
||||
@@ -178,10 +179,10 @@ class ResolverBase {
|
||||
|
||||
|
||||
setLocalAddress(ipv4, ipv6) {
|
||||
validateString(ipv4, 'ipv4');
|
||||
validateStringWithoutNullBytes(ipv4, 'ipv4');
|
||||
|
||||
if (ipv6 !== undefined) {
|
||||
validateString(ipv6, 'ipv6');
|
||||
validateStringWithoutNullBytes(ipv6, 'ipv6');
|
||||
}
|
||||
|
||||
this._handle.setLocalAddress(ipv4, ipv6);
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
|
||||
// Names containing null bytes must be rejected by the resolver methods.
|
||||
// c-ares receives the name as a C string, so everything after the first
|
||||
// null byte would otherwise be silently dropped, e.g. resolving
|
||||
// 'example.com\0.trusted.corp' would actually query 'example.com'.
|
||||
|
||||
const assert = require('assert');
|
||||
const dns = require('dns');
|
||||
const dnsPromises = dns.promises;
|
||||
|
||||
const name = 'example.com\u0000.trusted.corp';
|
||||
const ip = '127.0.0.1\u0000.trusted.corp';
|
||||
|
||||
const err = {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
message: /^The argument 'name' must be a string without null bytes\. Received /,
|
||||
};
|
||||
|
||||
const methods = [
|
||||
'resolve',
|
||||
'resolve4',
|
||||
'resolve6',
|
||||
'resolveAny',
|
||||
'resolveCaa',
|
||||
'resolveCname',
|
||||
'resolveMx',
|
||||
'resolveNaptr',
|
||||
'resolveNs',
|
||||
'resolvePtr',
|
||||
'resolveSoa',
|
||||
'resolveSrv',
|
||||
'resolveTlsa',
|
||||
'resolveTxt',
|
||||
];
|
||||
|
||||
const rrtypes = [
|
||||
'A',
|
||||
'AAAA',
|
||||
'ANY',
|
||||
'CAA',
|
||||
'CNAME',
|
||||
'MX',
|
||||
'NAPTR',
|
||||
'NS',
|
||||
'PTR',
|
||||
'SOA',
|
||||
'SRV',
|
||||
'TLSA',
|
||||
'TXT',
|
||||
];
|
||||
|
||||
// Callback-based API: the default resolver and a Resolver instance.
|
||||
for (const resolver of [dns, new dns.Resolver()]) {
|
||||
for (const method of methods) {
|
||||
assert.throws(() => resolver[method](name, common.mustNotCall()), err);
|
||||
}
|
||||
for (const rrtype of rrtypes) {
|
||||
assert.throws(() => resolver.resolve(name, rrtype, common.mustNotCall()), err);
|
||||
}
|
||||
assert.throws(() => resolver.reverse(ip, common.mustNotCall()), err);
|
||||
}
|
||||
|
||||
// Promise-based API: the default resolver and a Resolver instance.
|
||||
for (const resolver of [dnsPromises, new dnsPromises.Resolver()]) {
|
||||
for (const method of methods) {
|
||||
assert.throws(() => resolver[method](name), err);
|
||||
}
|
||||
for (const rrtype of rrtypes) {
|
||||
assert.throws(() => resolver.resolve(name, rrtype), err);
|
||||
}
|
||||
assert.throws(() => resolver.reverse(ip), err);
|
||||
}
|
||||
@@ -38,3 +38,24 @@ const promiseResolver = new dns.promises.Resolver();
|
||||
promiseResolver.setLocalAddress();
|
||||
}, Error);
|
||||
}
|
||||
|
||||
// Verify that setLocalAddress rejects addresses containing null bytes instead
|
||||
// of silently truncating them at the first null byte
|
||||
{
|
||||
for (const r of [resolver, promiseResolver]) {
|
||||
assert.throws(() => {
|
||||
r.setLocalAddress('127.0.0.1\u0000junk');
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
message: /^The argument 'ipv4' must be a string without null bytes\. Received /,
|
||||
});
|
||||
assert.throws(() => {
|
||||
r.setLocalAddress('127.0.0.1', '::1\u0000junk');
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
message: /^The argument 'ipv6' must be a string without null bytes\. Received /,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user