Commit a3709662825 for nodejs
commit a37096628256c0d4b4abbdf548e4d5e339e4f04f
Author: James M Snell <jasnell@gmail.com>
Date: Fri Sep 18 21:58:43 2026 +0000
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
diff --git a/lib/internal/dns/callback_resolver.js b/lib/internal/dns/callback_resolver.js
index f634f452e91..68822ea291b 100644
--- a/lib/internal/dns/callback_resolver.js
+++ b/lib/internal/dns/callback_resolver.js
@@ -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();
diff --git a/lib/internal/dns/promises.js b/lib/internal/dns/promises.js
index 1dd6417d753..aaa8a03be76 100644
--- a/lib/internal/dns/promises.js
+++ b/lib/internal/dns/promises.js
@@ -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);
diff --git a/lib/internal/dns/utils.js b/lib/internal/dns/utils.js
index 50ec10d3b04..dcc2828b0f9 100644
--- a/lib/internal/dns/utils.js
+++ b/lib/internal/dns/utils.js
@@ -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);
diff --git a/test/parallel/test-dns-resolve-null-bytes.js b/test/parallel/test-dns-resolve-null-bytes.js
new file mode 100644
index 00000000000..ef091d06a0d
--- /dev/null
+++ b/test/parallel/test-dns-resolve-null-bytes.js
@@ -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);
+}
diff --git a/test/parallel/test-dns-setlocaladdress.js b/test/parallel/test-dns-setlocaladdress.js
index 25bece328f4..b47916115a2 100644
--- a/test/parallel/test-dns-setlocaladdress.js
+++ b/test/parallel/test-dns-setlocaladdress.js
@@ -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 /,
+ });
+ }
+}