From b6f32f5f90d614f02696ce77afc7f1ae4c302807 Mon Sep 17 00:00:00 2001 From: RafaelGSS Date: Sun, 19 Jul 2026 11:38:25 -0300 Subject: [PATCH] dns: handle large resolveAny address replies Refs: https://hackerone.com/reports/3795657 Signed-off-by: RafaelGSS PR-URL: https://github.com/nodejs-private/node-private/pull/929 Reviewed-By: Matteo Collina CVE-ID: CVE-2026-58042 --- src/cares_wrap.cc | 52 ++++++++++---- .../test-dns-resolveany-ttl-overflow.js | 69 +++++++++++++++++++ 2 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 test/parallel/test-dns-resolveany-ttl-overflow.js diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index eb7d1079f67330..0408cf119d02ae 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -249,6 +249,23 @@ std::vector> ParseServersCsv(const char* csv) { return servers; } +int GetAnswerCountForTTLBuffer(const unsigned char* buf, int len) { + static constexpr int kDNSAnswerCountOffset = 6; + static constexpr int kAresDefaultTTLBufferLength = 256; + if (len <= kDNSAnswerCountOffset + 1) { + return kAresDefaultTTLBufferLength; + } + + const int answer_count = (static_cast(buf[kDNSAnswerCountOffset]) << 8) | + static_cast(buf[kDNSAnswerCountOffset + 1]); + return answer_count == 0 ? 1 : answer_count; +} + +template +std::vector MakeAddrTTLBuffer(const unsigned char* buf, int len) { + return std::vector(GetAnswerCountForTTLBuffer(buf, len)); +} + Maybe ParseGeneralReply(Environment* env, const unsigned char* buf, int len, @@ -1217,11 +1234,12 @@ Maybe AnyTraits::Parse(QueryAnyWrap* wrap, int type, status, old_count; /* Parse A records or CNAME records */ - ares_addrttl addrttls[256]; - int naddrttls = arraysize(addrttls); + std::vector addrttls = + MakeAddrTTLBuffer(buf, len); + int naddrttls = static_cast(addrttls.size()); type = ns_t_cname_or_a; - if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls, &naddrttls) + if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls.data(), &naddrttls) .To(&status)) { return Nothing(); } @@ -1294,11 +1312,13 @@ Maybe AnyTraits::Parse(QueryAnyWrap* wrap, } /* Parse AAAA records */ - ares_addr6ttl addr6ttls[256]; - int naddr6ttls = arraysize(addr6ttls); + std::vector addr6ttls = + MakeAddrTTLBuffer(buf, len); + int naddr6ttls = static_cast(addr6ttls.size()); type = ns_t_aaaa; - if (!ParseGeneralReply(env, buf, len, &type, ret, addr6ttls, &naddr6ttls) + if (!ParseGeneralReply( + env, buf, len, &type, ret, addr6ttls.data(), &naddr6ttls) .To(&status)) { return Nothing(); } @@ -1486,12 +1506,13 @@ Maybe ATraits::Parse(QueryAWrap* wrap, HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); - ares_addrttl addrttls[256]; - int naddrttls = arraysize(addrttls), status; + std::vector addrttls = + MakeAddrTTLBuffer(buf, len); + int naddrttls = static_cast(addrttls.size()), status; Local ret = Array::New(env->isolate()); int type = ns_t_a; - if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls, &naddrttls) + if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls.data(), &naddrttls) .To(&status)) { return Nothing(); } @@ -1499,7 +1520,8 @@ Maybe ATraits::Parse(QueryAWrap* wrap, return Just(status); } - Local ttls = AddrTTLToArray(env, addrttls, naddrttls); + Local ttls = + AddrTTLToArray(env, addrttls.data(), naddrttls); wrap->CallOnComplete(ret, ttls); return Just(ARES_SUCCESS); @@ -1518,12 +1540,13 @@ Maybe AaaaTraits::Parse(QueryAaaaWrap* wrap, HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); - ares_addr6ttl addrttls[256]; - int naddrttls = arraysize(addrttls), status; + std::vector addrttls = + MakeAddrTTLBuffer(buf, len); + int naddrttls = static_cast(addrttls.size()), status; Local ret = Array::New(env->isolate()); int type = ns_t_aaaa; - if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls, &naddrttls) + if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls.data(), &naddrttls) .To(&status)) { return Nothing(); } @@ -1531,7 +1554,8 @@ Maybe AaaaTraits::Parse(QueryAaaaWrap* wrap, return Just(status); } - Local ttls = AddrTTLToArray(env, addrttls, naddrttls); + Local ttls = + AddrTTLToArray(env, addrttls.data(), naddrttls); wrap->CallOnComplete(ret, ttls); return Just(ARES_SUCCESS); diff --git a/test/parallel/test-dns-resolveany-ttl-overflow.js b/test/parallel/test-dns-resolveany-ttl-overflow.js new file mode 100644 index 00000000000000..c8cc4fbc95515d --- /dev/null +++ b/test/parallel/test-dns-resolveany-ttl-overflow.js @@ -0,0 +1,69 @@ +'use strict'; +const common = require('../common'); +const dnstools = require('../common/dns'); +const assert = require('assert'); +const dgram = require('dgram'); +const dns = require('dns'); + +const dnsPromises = dns.promises; + +const kRecordCount = 257; +const kADomain = 'many-a.example.org'; + +const server = dgram.createSocket('udp4'); + +server.on('message', common.mustCall((msg, { address, port }) => { + const parsed = dnstools.parseDNSPacket(msg); + const question = parsed.questions[0]; + const { domain } = question; + + assert.strictEqual(question.type, 'ANY'); + assert.strictEqual(domain, kADomain); + + server.send(dnstools.writeDNSPacket({ + id: parsed.id, + questions: parsed.questions, + answers: createARecords(domain), + }), port, address); +}, 2)); + +server.bind(0, common.mustCall(async () => { + const { port } = server.address(); + const callbackResolver = new dns.Resolver({ timeout: 1000, tries: 1 }); + const promiseResolver = new dnsPromises.Resolver({ timeout: 1000, tries: 1 }); + callbackResolver.setServers([`127.0.0.1:${port}`]); + promiseResolver.setServers([`127.0.0.1:${port}`]); + + validateRecords(await promiseResolver.resolveAny(kADomain), 'A'); + validateRecords(await resolveAny(callbackResolver, kADomain), 'A'); + + server.close(); +})); + +function createARecords(domain) { + return Array.from({ length: kRecordCount }, (_, i) => ({ + type: 'A', + address: `10.0.${i >> 8}.${i & 0xff}`, + ttl: 60 + i, + domain, + })); +} + +function resolveAny(resolver, domain) { + return new Promise((resolve) => { + resolver.resolveAny(domain, common.mustSucceed(resolve)); + }); +} + +function validateRecords(records, type) { + assert.strictEqual(records.length, kRecordCount); + for (const record of records) { + assert.strictEqual(record.type, type); + } + + assert.strictEqual(records[0].ttl, 60); + assert.strictEqual(records[255].ttl, 315); + assert.strictEqual(records[256].ttl, 316); + assert.strictEqual(records[0].address, '10.0.0.0'); + assert.strictEqual(records[256].address, '10.0.1.0'); +}