Skip to content

Commit 9d0d36c

Browse files
committed
dns: handle large resolveAny address replies
Refs: https://hackerone.com/reports/3795657 Signed-off-by: RafaelGSS <rafael.nunu@hotmail.com> PR-URL: nodejs-private/node-private#929 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> CVE-ID: CVE-2026-58042
1 parent af9ff04 commit 9d0d36c

2 files changed

Lines changed: 107 additions & 14 deletions

File tree

src/cares_wrap.cc

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,23 @@ Local<Array> AddrTTLToArray(
209209
return Array::New(env->isolate(), ttls.out(), naddrttls);
210210
}
211211

212+
int GetAnswerCountForTTLBuffer(const unsigned char* buf, int len) {
213+
static constexpr int kDNSAnswerCountOffset = 6;
214+
static constexpr int kAresDefaultTTLBufferLength = 256;
215+
if (len <= kDNSAnswerCountOffset + 1) {
216+
return kAresDefaultTTLBufferLength;
217+
}
218+
219+
const int answer_count = (static_cast<int>(buf[kDNSAnswerCountOffset]) << 8) |
220+
static_cast<int>(buf[kDNSAnswerCountOffset + 1]);
221+
return answer_count == 0 ? 1 : answer_count;
222+
}
223+
224+
template <typename T>
225+
std::vector<T> MakeAddrTTLBuffer(const unsigned char* buf, int len) {
226+
return std::vector<T>(GetAnswerCountForTTLBuffer(buf, len));
227+
}
228+
212229
Maybe<int> ParseGeneralReply(Environment* env,
213230
const unsigned char* buf,
214231
int len,
@@ -1105,11 +1122,12 @@ Maybe<int> AnyTraits::Parse(QueryAnyWrap* wrap,
11051122
int type, status, old_count;
11061123

11071124
/* Parse A records or CNAME records */
1108-
ares_addrttl addrttls[256];
1109-
int naddrttls = arraysize(addrttls);
1125+
std::vector<ares_addrttl> addrttls =
1126+
MakeAddrTTLBuffer<ares_addrttl>(buf, len);
1127+
int naddrttls = static_cast<int>(addrttls.size());
11101128

11111129
type = ns_t_cname_or_a;
1112-
if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls, &naddrttls)
1130+
if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls.data(), &naddrttls)
11131131
.To(&status)) {
11141132
return Nothing<int>();
11151133
}
@@ -1182,11 +1200,13 @@ Maybe<int> AnyTraits::Parse(QueryAnyWrap* wrap,
11821200
}
11831201

11841202
/* Parse AAAA records */
1185-
ares_addr6ttl addr6ttls[256];
1186-
int naddr6ttls = arraysize(addr6ttls);
1203+
std::vector<ares_addr6ttl> addr6ttls =
1204+
MakeAddrTTLBuffer<ares_addr6ttl>(buf, len);
1205+
int naddr6ttls = static_cast<int>(addr6ttls.size());
11871206

11881207
type = ns_t_aaaa;
1189-
if (!ParseGeneralReply(env, buf, len, &type, ret, addr6ttls, &naddr6ttls)
1208+
if (!ParseGeneralReply(
1209+
env, buf, len, &type, ret, addr6ttls.data(), &naddr6ttls)
11901210
.To(&status)) {
11911211
return Nothing<int>();
11921212
}
@@ -1374,20 +1394,22 @@ Maybe<int> ATraits::Parse(QueryAWrap* wrap,
13741394
HandleScope handle_scope(env->isolate());
13751395
Context::Scope context_scope(env->context());
13761396

1377-
ares_addrttl addrttls[256];
1378-
int naddrttls = arraysize(addrttls), status;
1397+
std::vector<ares_addrttl> addrttls =
1398+
MakeAddrTTLBuffer<ares_addrttl>(buf, len);
1399+
int naddrttls = static_cast<int>(addrttls.size()), status;
13791400
Local<Array> ret = Array::New(env->isolate());
13801401

13811402
int type = ns_t_a;
1382-
if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls, &naddrttls)
1403+
if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls.data(), &naddrttls)
13831404
.To(&status)) {
13841405
return Nothing<int>();
13851406
}
13861407
if (status != ARES_SUCCESS) {
13871408
return Just<int>(status);
13881409
}
13891410

1390-
Local<Array> ttls = AddrTTLToArray<ares_addrttl>(env, addrttls, naddrttls);
1411+
Local<Array> ttls =
1412+
AddrTTLToArray<ares_addrttl>(env, addrttls.data(), naddrttls);
13911413

13921414
wrap->CallOnComplete(ret, ttls);
13931415
return Just<int>(ARES_SUCCESS);
@@ -1406,20 +1428,22 @@ Maybe<int> AaaaTraits::Parse(QueryAaaaWrap* wrap,
14061428
HandleScope handle_scope(env->isolate());
14071429
Context::Scope context_scope(env->context());
14081430

1409-
ares_addr6ttl addrttls[256];
1410-
int naddrttls = arraysize(addrttls), status;
1431+
std::vector<ares_addr6ttl> addrttls =
1432+
MakeAddrTTLBuffer<ares_addr6ttl>(buf, len);
1433+
int naddrttls = static_cast<int>(addrttls.size()), status;
14111434
Local<Array> ret = Array::New(env->isolate());
14121435

14131436
int type = ns_t_aaaa;
1414-
if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls, &naddrttls)
1437+
if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls.data(), &naddrttls)
14151438
.To(&status)) {
14161439
return Nothing<int>();
14171440
}
14181441
if (status != ARES_SUCCESS) {
14191442
return Just<int>(status);
14201443
}
14211444

1422-
Local<Array> ttls = AddrTTLToArray<ares_addr6ttl>(env, addrttls, naddrttls);
1445+
Local<Array> ttls =
1446+
AddrTTLToArray<ares_addr6ttl>(env, addrttls.data(), naddrttls);
14231447

14241448
wrap->CallOnComplete(ret, ttls);
14251449
return Just<int>(ARES_SUCCESS);
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
'use strict';
2+
const common = require('../common');
3+
const dnstools = require('../common/dns');
4+
const assert = require('assert');
5+
const dgram = require('dgram');
6+
const dns = require('dns');
7+
8+
const dnsPromises = dns.promises;
9+
10+
const kRecordCount = 257;
11+
const kADomain = 'many-a.example.org';
12+
13+
const server = dgram.createSocket('udp4');
14+
15+
server.on('message', common.mustCall((msg, { address, port }) => {
16+
const parsed = dnstools.parseDNSPacket(msg);
17+
const question = parsed.questions[0];
18+
const { domain } = question;
19+
20+
assert.strictEqual(question.type, 'ANY');
21+
assert.strictEqual(domain, kADomain);
22+
23+
server.send(dnstools.writeDNSPacket({
24+
id: parsed.id,
25+
questions: parsed.questions,
26+
answers: createARecords(domain),
27+
}), port, address);
28+
}, 2));
29+
30+
server.bind(0, common.mustCall(async () => {
31+
const { port } = server.address();
32+
const callbackResolver = new dns.Resolver({ timeout: 1000, tries: 1 });
33+
const promiseResolver = new dnsPromises.Resolver({ timeout: 1000, tries: 1 });
34+
callbackResolver.setServers([`127.0.0.1:${port}`]);
35+
promiseResolver.setServers([`127.0.0.1:${port}`]);
36+
37+
validateRecords(await promiseResolver.resolveAny(kADomain), 'A');
38+
validateRecords(await resolveAny(callbackResolver, kADomain), 'A');
39+
40+
server.close();
41+
}));
42+
43+
function createARecords(domain) {
44+
return Array.from({ length: kRecordCount }, (_, i) => ({
45+
type: 'A',
46+
address: `10.0.${i >> 8}.${i & 0xff}`,
47+
ttl: 60 + i,
48+
domain,
49+
}));
50+
}
51+
52+
function resolveAny(resolver, domain) {
53+
return new Promise((resolve) => {
54+
resolver.resolveAny(domain, common.mustSucceed(resolve));
55+
});
56+
}
57+
58+
function validateRecords(records, type) {
59+
assert.strictEqual(records.length, kRecordCount);
60+
for (const record of records) {
61+
assert.strictEqual(record.type, type);
62+
}
63+
64+
assert.strictEqual(records[0].ttl, 60);
65+
assert.strictEqual(records[255].ttl, 315);
66+
assert.strictEqual(records[256].ttl, 316);
67+
assert.strictEqual(records[0].address, '10.0.0.0');
68+
assert.strictEqual(records[256].address, '10.0.1.0');
69+
}

0 commit comments

Comments
 (0)