Skip to content

Commit 3e6cca0

Browse files
committed
dns: handle large resolveAny address replies
Refs: https://hackerone.com/reports/3795657 Signed-off-by: RafaelGSS <rafael.nunu@hotmail.com> CVE-ID: CVE-2026-58042 PR-URL: #64820 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Aviv Keller <me@aviv.sh> Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
1 parent 7c87c37 commit 3e6cca0

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
@@ -249,6 +249,23 @@ std::vector<std::pair<std::string, int>> ParseServersCsv(const char* csv) {
249249
return servers;
250250
}
251251

252+
int GetAnswerCountForTTLBuffer(const unsigned char* buf, int len) {
253+
static constexpr int kDNSAnswerCountOffset = 6;
254+
static constexpr int kAresDefaultTTLBufferLength = 256;
255+
if (len <= kDNSAnswerCountOffset + 1) {
256+
return kAresDefaultTTLBufferLength;
257+
}
258+
259+
const int answer_count = (static_cast<int>(buf[kDNSAnswerCountOffset]) << 8) |
260+
static_cast<int>(buf[kDNSAnswerCountOffset + 1]);
261+
return answer_count == 0 ? 1 : answer_count;
262+
}
263+
264+
template <typename T>
265+
std::vector<T> MakeAddrTTLBuffer(const unsigned char* buf, int len) {
266+
return std::vector<T>(GetAnswerCountForTTLBuffer(buf, len));
267+
}
268+
252269
Maybe<int> ParseGeneralReply(Environment* env,
253270
const unsigned char* buf,
254271
int len,
@@ -1217,11 +1234,12 @@ Maybe<int> AnyTraits::Parse(QueryAnyWrap* wrap,
12171234
int type, status, old_count;
12181235

12191236
/* Parse A records or CNAME records */
1220-
ares_addrttl addrttls[256];
1221-
int naddrttls = arraysize(addrttls);
1237+
std::vector<ares_addrttl> addrttls =
1238+
MakeAddrTTLBuffer<ares_addrttl>(buf, len);
1239+
int naddrttls = static_cast<int>(addrttls.size());
12221240

12231241
type = ns_t_cname_or_a;
1224-
if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls, &naddrttls)
1242+
if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls.data(), &naddrttls)
12251243
.To(&status)) {
12261244
return Nothing<int>();
12271245
}
@@ -1294,11 +1312,13 @@ Maybe<int> AnyTraits::Parse(QueryAnyWrap* wrap,
12941312
}
12951313

12961314
/* Parse AAAA records */
1297-
ares_addr6ttl addr6ttls[256];
1298-
int naddr6ttls = arraysize(addr6ttls);
1315+
std::vector<ares_addr6ttl> addr6ttls =
1316+
MakeAddrTTLBuffer<ares_addr6ttl>(buf, len);
1317+
int naddr6ttls = static_cast<int>(addr6ttls.size());
12991318

13001319
type = ns_t_aaaa;
1301-
if (!ParseGeneralReply(env, buf, len, &type, ret, addr6ttls, &naddr6ttls)
1320+
if (!ParseGeneralReply(
1321+
env, buf, len, &type, ret, addr6ttls.data(), &naddr6ttls)
13021322
.To(&status)) {
13031323
return Nothing<int>();
13041324
}
@@ -1486,20 +1506,22 @@ Maybe<int> ATraits::Parse(QueryAWrap* wrap,
14861506
HandleScope handle_scope(env->isolate());
14871507
Context::Scope context_scope(env->context());
14881508

1489-
ares_addrttl addrttls[256];
1490-
int naddrttls = arraysize(addrttls), status;
1509+
std::vector<ares_addrttl> addrttls =
1510+
MakeAddrTTLBuffer<ares_addrttl>(buf, len);
1511+
int naddrttls = static_cast<int>(addrttls.size()), status;
14911512
Local<Array> ret = Array::New(env->isolate());
14921513

14931514
int type = ns_t_a;
1494-
if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls, &naddrttls)
1515+
if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls.data(), &naddrttls)
14951516
.To(&status)) {
14961517
return Nothing<int>();
14971518
}
14981519
if (status != ARES_SUCCESS) {
14991520
return Just<int>(status);
15001521
}
15011522

1502-
Local<Array> ttls = AddrTTLToArray<ares_addrttl>(env, addrttls, naddrttls);
1523+
Local<Array> ttls =
1524+
AddrTTLToArray<ares_addrttl>(env, addrttls.data(), naddrttls);
15031525

15041526
wrap->CallOnComplete(ret, ttls);
15051527
return Just<int>(ARES_SUCCESS);
@@ -1518,20 +1540,22 @@ Maybe<int> AaaaTraits::Parse(QueryAaaaWrap* wrap,
15181540
HandleScope handle_scope(env->isolate());
15191541
Context::Scope context_scope(env->context());
15201542

1521-
ares_addr6ttl addrttls[256];
1522-
int naddrttls = arraysize(addrttls), status;
1543+
std::vector<ares_addr6ttl> addrttls =
1544+
MakeAddrTTLBuffer<ares_addr6ttl>(buf, len);
1545+
int naddrttls = static_cast<int>(addrttls.size()), status;
15231546
Local<Array> ret = Array::New(env->isolate());
15241547

15251548
int type = ns_t_aaaa;
1526-
if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls, &naddrttls)
1549+
if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls.data(), &naddrttls)
15271550
.To(&status)) {
15281551
return Nothing<int>();
15291552
}
15301553
if (status != ARES_SUCCESS) {
15311554
return Just<int>(status);
15321555
}
15331556

1534-
Local<Array> ttls = AddrTTLToArray<ares_addr6ttl>(env, addrttls, naddrttls);
1557+
Local<Array> ttls =
1558+
AddrTTLToArray<ares_addr6ttl>(env, addrttls.data(), naddrttls);
15351559

15361560
wrap->CallOnComplete(ret, ttls);
15371561
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)