Skip to content

Commit c82f244

Browse files
bnoordhuisaddaleax
authored andcommitted
dns: use IDNA 2008 to encode non-ascii hostnames
Before this commit, Node.js left it up to the system resolver or c-ares. Leaving it to the system resolver introduces platform differences because: * some support IDNA 2008 * some only IDNA 2003 (glibc until 2.28), and * some don't support IDNA at all (musl libc) c-ares doesn't support IDNA either although curl does, by virtue of linking against libidn2. Upgrading from libidn1 to libidn2 in order to get proper IDNA 2008 support was the fix for curl's CVE-2016-8625. libidn2 is not an option (incompatible license) but ICU has an IDNA API and we already use that in one place. For non-ICU builds, we fall back to the bundled punycode.js that also supports IDNA 2008. Fixes: https://github.com/nodejs-private/security/issues/97 Fixes: #25558 PR-URL: #25679 Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com> Reviewed-By: Saúl Ibarra Corretgé <saghul@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
1 parent 6d9af41 commit c82f244

File tree

7 files changed

+51
-9
lines changed

7 files changed

+51
-9
lines changed

lib/dns.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
'use strict';
2323

2424
const cares = internalBinding('cares_wrap');
25+
const { toASCII } = require('internal/idna');
2526
const { isIP, isIPv4, isLegalPort } = require('internal/net');
2627
const { customPromisifyArgs } = require('internal/util');
2728
const errors = require('internal/errors');
@@ -139,7 +140,7 @@ function lookup(hostname, options, callback) {
139140
req.hostname = hostname;
140141
req.oncomplete = all ? onlookupall : onlookup;
141142

142-
var err = cares.getaddrinfo(req, hostname, family, hints, verbatim);
143+
var err = cares.getaddrinfo(req, toASCII(hostname), family, hints, verbatim);
143144
if (err) {
144145
process.nextTick(callback, dnsException(err, 'getaddrinfo', hostname));
145146
return {};
@@ -219,7 +220,7 @@ function resolver(bindingName) {
219220
req.hostname = name;
220221
req.oncomplete = onresolve;
221222
req.ttl = !!(options && options.ttl);
222-
var err = this._handle[bindingName](req, name);
223+
var err = this._handle[bindingName](req, toASCII(name));
223224
if (err) throw dnsException(err, bindingName, name);
224225
return req;
225226
}

lib/internal/dns/promises.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const {
66
emitInvalidHostnameWarning,
77
} = require('internal/dns/utils');
88
const { codes, dnsException } = require('internal/errors');
9+
const { toASCII } = require('internal/idna');
910
const { isIP, isIPv4, isLegalPort } = require('internal/net');
1011
const {
1112
getaddrinfo,
@@ -86,7 +87,7 @@ function createLookupPromise(family, hostname, all, hints, verbatim) {
8687
req.resolve = resolve;
8788
req.reject = reject;
8889

89-
const err = getaddrinfo(req, hostname, family, hints, verbatim);
90+
const err = getaddrinfo(req, toASCII(hostname), family, hints, verbatim);
9091

9192
if (err) {
9293
reject(dnsException(err, 'getaddrinfo', hostname));
@@ -184,7 +185,7 @@ function createResolverPromise(resolver, bindingName, hostname, ttl) {
184185
req.reject = reject;
185186
req.ttl = ttl;
186187

187-
const err = resolver._handle[bindingName](req, hostname);
188+
const err = resolver._handle[bindingName](req, toASCII(hostname));
188189

189190
if (err)
190191
reject(dnsException(err, bindingName, hostname));

lib/internal/idna.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use strict';
2+
3+
if (process.binding('config').hasIntl) {
4+
const { toASCII, toUnicode } = internalBinding('icu');
5+
module.exports = { toASCII, toUnicode };
6+
} else {
7+
const { toASCII, toUnicode } = require('punycode');
8+
module.exports = { toASCII, toUnicode };
9+
}

lib/url.js

+1-4
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,8 @@
2121

2222
'use strict';
2323

24-
const { toASCII } = internalBinding('config').hasIntl ?
25-
internalBinding('icu') : require('punycode');
26-
24+
const { toASCII } = require('internal/idna');
2725
const { hexTable } = require('internal/querystring');
28-
2926
const { SafeSet } = require('internal/safe_globals');
3027

3128
const {

node.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
'lib/internal/fs/utils.js',
128128
'lib/internal/fs/watchers.js',
129129
'lib/internal/http.js',
130+
'lib/internal/idna.js',
130131
'lib/internal/inspector_async_hook.js',
131132
'lib/internal/js_stream_socket.js',
132133
'lib/internal/linkedlist.js',

test/internet/test-dns-idna2008.js

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use strict';
2+
3+
// Verify that non-ASCII hostnames are handled correctly as IDNA 2008.
4+
//
5+
// * Tests will fail with NXDOMAIN when UTF-8 leaks through to a getaddrinfo()
6+
// that doesn't support IDNA at all.
7+
//
8+
// * "straße.de" will resolve to the wrong address when the resolver supports
9+
// only IDNA 2003 (e.g., glibc until 2.28) because it encodes it wrong.
10+
11+
const { mustCall } = require('../common');
12+
const assert = require('assert');
13+
const dns = require('dns');
14+
15+
const [host, expectedAddress] = ['straße.de', '81.169.145.78'];
16+
17+
dns.lookup(host, mustCall((err, address) => {
18+
assert.ifError(err);
19+
assert.strictEqual(address, expectedAddress);
20+
}));
21+
22+
dns.promises.lookup(host).then(mustCall(({ address }) => {
23+
assert.strictEqual(address, expectedAddress);
24+
}));
25+
26+
dns.resolve4(host, mustCall((err, addresses) => {
27+
assert.ifError(err);
28+
assert.deepStrictEqual(addresses, [expectedAddress]);
29+
}));
30+
31+
new dns.promises.Resolver().resolve4(host).then(mustCall((addresses) => {
32+
assert.deepStrictEqual(addresses, [expectedAddress]);
33+
}));

test/parallel/test-bootstrap-modules.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const assert = require('assert');
1010

1111
const isMainThread = common.isMainThread;
1212
const kCoverageModuleCount = process.env.NODE_V8_COVERAGE ? 1 : 0;
13-
const kMaxModuleCount = (isMainThread ? 64 : 84) + kCoverageModuleCount;
13+
const kMaxModuleCount = (isMainThread ? 65 : 85) + kCoverageModuleCount;
1414

1515
assert(list.length <= kMaxModuleCount,
1616
`Total length: ${list.length}\n` + list.join('\n')

0 commit comments

Comments
 (0)