Skip to content

Commit 22efc05

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 5112315 commit 22efc05

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
@@ -207,6 +207,23 @@ Local<Array> AddrTTLToArray(
207207
return Array::New(env->isolate(), ttls.out(), naddrttls);
208208
}
209209

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

10701087
/* Parse A records or CNAME records */
1071-
ares_addrttl addrttls[256];
1072-
int naddrttls = arraysize(addrttls);
1088+
std::vector<ares_addrttl> addrttls =
1089+
MakeAddrTTLBuffer<ares_addrttl>(buf, len);
1090+
int naddrttls = static_cast<int>(addrttls.size());
10731091

10741092
type = ns_t_cname_or_a;
1075-
if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls, &naddrttls)
1093+
if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls.data(), &naddrttls)
10761094
.To(&status)) {
10771095
return Nothing<int>();
10781096
}
@@ -1114,11 +1132,13 @@ Maybe<int> AnyTraits::Parse(QueryAnyWrap* wrap,
11141132
}
11151133

11161134
/* Parse AAAA records */
1117-
ares_addr6ttl addr6ttls[256];
1118-
int naddr6ttls = arraysize(addr6ttls);
1135+
std::vector<ares_addr6ttl> addr6ttls =
1136+
MakeAddrTTLBuffer<ares_addr6ttl>(buf, len);
1137+
int naddr6ttls = static_cast<int>(addr6ttls.size());
11191138

11201139
type = ns_t_aaaa;
1121-
if (!ParseGeneralReply(env, buf, len, &type, ret, addr6ttls, &naddr6ttls)
1140+
if (!ParseGeneralReply(
1141+
env, buf, len, &type, ret, addr6ttls.data(), &naddr6ttls)
11221142
.To(&status)) {
11231143
return Nothing<int>();
11241144
}
@@ -1270,20 +1290,22 @@ Maybe<int> ATraits::Parse(QueryAWrap* wrap,
12701290
HandleScope handle_scope(env->isolate());
12711291
Context::Scope context_scope(env->context());
12721292

1273-
ares_addrttl addrttls[256];
1274-
int naddrttls = arraysize(addrttls), status;
1293+
std::vector<ares_addrttl> addrttls =
1294+
MakeAddrTTLBuffer<ares_addrttl>(buf, len);
1295+
int naddrttls = static_cast<int>(addrttls.size()), status;
12751296
Local<Array> ret = Array::New(env->isolate());
12761297

12771298
int type = ns_t_a;
1278-
if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls, &naddrttls)
1299+
if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls.data(), &naddrttls)
12791300
.To(&status)) {
12801301
return Nothing<int>();
12811302
}
12821303
if (status != ARES_SUCCESS) {
12831304
return Just<int>(status);
12841305
}
12851306

1286-
Local<Array> ttls = AddrTTLToArray<ares_addrttl>(env, addrttls, naddrttls);
1307+
Local<Array> ttls =
1308+
AddrTTLToArray<ares_addrttl>(env, addrttls.data(), naddrttls);
12871309

12881310
wrap->CallOnComplete(ret, ttls);
12891311
return Just<int>(ARES_SUCCESS);
@@ -1302,20 +1324,22 @@ Maybe<int> AaaaTraits::Parse(QueryAaaaWrap* wrap,
13021324
HandleScope handle_scope(env->isolate());
13031325
Context::Scope context_scope(env->context());
13041326

1305-
ares_addr6ttl addrttls[256];
1306-
int naddrttls = arraysize(addrttls), status;
1327+
std::vector<ares_addr6ttl> addrttls =
1328+
MakeAddrTTLBuffer<ares_addr6ttl>(buf, len);
1329+
int naddrttls = static_cast<int>(addrttls.size()), status;
13071330
Local<Array> ret = Array::New(env->isolate());
13081331

13091332
int type = ns_t_aaaa;
1310-
if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls, &naddrttls)
1333+
if (!ParseGeneralReply(env, buf, len, &type, ret, addrttls.data(), &naddrttls)
13111334
.To(&status)) {
13121335
return Nothing<int>();
13131336
}
13141337
if (status != ARES_SUCCESS) {
13151338
return Just<int>(status);
13161339
}
13171340

1318-
Local<Array> ttls = AddrTTLToArray<ares_addr6ttl>(env, addrttls, naddrttls);
1341+
Local<Array> ttls =
1342+
AddrTTLToArray<ares_addr6ttl>(env, addrttls.data(), naddrttls);
13191343

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