From de1cd3865c28441d8ef06d92c3850da8f8ea22d6 Mon Sep 17 00:00:00 2001 From: Martin Mirchev Date: Tue, 27 Oct 2026 21:09:32 +0800 Subject: [PATCH] dns: validate both addresses before applying them in setLocalAddress SetLocalAddress() applied the first address to the c-ares channel with ares_set_local_ip4()/ares_set_local_ip6() before parsing and validating the second one. A call that then threw ERR_INVALID_ARG_VALUE, because both addresses were of the same family or the second address was invalid, left the first address in effect, so later queries were sent from an address the caller had been told was rejected. Parse and validate both arguments first and only then apply them. With a single argument the other family is still reset to "any", as before. Fixes: https://github.com/nodejs/node/issues/67049 Signed-off-by: Martin Mirchev --- src/cares_wrap.cc | 24 +++++----- .../test-dns-setlocaladdress-rejected.js | 45 +++++++++++++++++++ 2 files changed, 56 insertions(+), 13 deletions(-) create mode 100644 test/parallel/test-dns-setlocaladdress-rejected.js diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index 5cfae82f47b1..900c1a1e66f5 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -2332,16 +2332,16 @@ void SetLocalAddress(const FunctionCallbackInfo& args) { // to 0 (any). if (uv_inet_pton(AF_INET, *ip0, &addr0) == 0) { - ares_set_local_ip4(channel->cares_channel(), nbytes::ReadUint32BE(addr0)); type0 = 4; } else if (uv_inet_pton(AF_INET6, *ip0, &addr0) == 0) { - ares_set_local_ip6(channel->cares_channel(), addr0); type0 = 6; } else { THROW_ERR_INVALID_ARG_VALUE(env, "Invalid IP address."); return; } + // Validate the second argument before applying anything, so that a call + // that throws leaves the channel unchanged. if (!args[1]->IsUndefined()) { CHECK(args[1]->IsString()); node::Utf8Value ip1(isolate, args[1]); @@ -2350,16 +2350,11 @@ void SetLocalAddress(const FunctionCallbackInfo& args) { if (type0 == 4) { THROW_ERR_INVALID_ARG_VALUE(env, "Cannot specify two IPv4 addresses."); return; - } else { - ares_set_local_ip4(channel->cares_channel(), - nbytes::ReadUint32BE(addr1)); } } else if (uv_inet_pton(AF_INET6, *ip1, &addr1) == 0) { if (type0 == 6) { THROW_ERR_INVALID_ARG_VALUE(env, "Cannot specify two IPv6 addresses."); return; - } else { - ares_set_local_ip6(channel->cares_channel(), addr1); } } else { THROW_ERR_INVALID_ARG_VALUE(env, "Invalid IP address."); @@ -2367,12 +2362,15 @@ void SetLocalAddress(const FunctionCallbackInfo& args) { } } else { // No second arg specified - if (type0 == 4) { - memset(&addr1, 0, sizeof(addr1)); - ares_set_local_ip6(channel->cares_channel(), addr1); - } else { - ares_set_local_ip4(channel->cares_channel(), 0); - } + memset(&addr1, 0, sizeof(addr1)); + } + + if (type0 == 4) { + ares_set_local_ip4(channel->cares_channel(), nbytes::ReadUint32BE(addr0)); + ares_set_local_ip6(channel->cares_channel(), addr1); + } else { + ares_set_local_ip6(channel->cares_channel(), addr0); + ares_set_local_ip4(channel->cares_channel(), nbytes::ReadUint32BE(addr1)); } } diff --git a/test/parallel/test-dns-setlocaladdress-rejected.js b/test/parallel/test-dns-setlocaladdress-rejected.js new file mode 100644 index 000000000000..e0a316fb66d2 --- /dev/null +++ b/test/parallel/test-dns-setlocaladdress-rejected.js @@ -0,0 +1,45 @@ +'use strict'; +const common = require('../common'); + +// Verifies that a call to resolver.setLocalAddress() that throws leaves the +// previously configured local address in place. + +// Binding to 127.0.0.0/8 addresses other than 127.0.0.1 works out of the box +// on Linux only. +if (!common.isLinux) + common.skip('requires the whole 127.0.0.0/8 range on the loopback interface'); + +const assert = require('assert'); +const dgram = require('dgram'); +const { Resolver } = require('dns'); + +const resolver = new Resolver({ timeout: 1000, tries: 1 }); +const server = dgram.createSocket('udp4'); + +server.on('message', common.mustCall((msg, rinfo) => { + assert.strictEqual(rinfo.address, '127.0.0.2'); + server.close(); + resolver.cancel(); +})); + +server.bind(0, '127.0.0.1', common.mustCall(() => { + resolver.setServers([`127.0.0.1:${server.address().port}`]); + resolver.setLocalAddress('127.0.0.2'); + + assert.throws(() => { + resolver.setLocalAddress('127.0.0.3', '127.0.0.4'); + }, { + code: 'ERR_INVALID_ARG_VALUE', + message: 'Cannot specify two IPv4 addresses.', + }); + assert.throws(() => { + resolver.setLocalAddress('127.0.0.5', 'bad'); + }, { + code: 'ERR_INVALID_ARG_VALUE', + message: 'Invalid IP address.', + }); + + resolver.resolve4('example.test', common.mustCall((err) => { + assert.strictEqual(err.code, 'ECANCELLED'); + })); +}));