From b3678dff520a18cc482e99b4d3e8a9242a4e812d Mon Sep 17 00:00:00 2001 From: XadillaX Date: Wed, 15 Nov 2017 15:09:33 +0800 Subject: [PATCH] dns: fix crash while setting server during query MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix this issue follow these two points: 1. Keep track of how many queries are currently open. If `setServers()` is called while there are open queries, error out. 2. For `Resolver` instances, use option 1. For dns.setServers(), just create a fresh new default channel every time it is called, and then set its servers list. PR-URL: https://github.com/nodejs/node/pull/14891 Fixes: https://github.com/nodejs/node/issues/14734 Reviewed-By: Anna Henningsen Reviewed-By: Tobias Nießen --- lib/dns.js | 50 +++++++++++++------ src/cares_wrap.cc | 31 ++++++++++-- ...t-dns-setserver-in-callback-of-resolve4.js | 3 ++ .../test-dns-setserver-when-querying.js | 31 ++++++++++++ 4 files changed, 94 insertions(+), 21 deletions(-) create mode 100644 test/parallel/test-dns-setserver-when-querying.js diff --git a/lib/dns.js b/lib/dns.js index b8c98e712540c5..92b55f03595f45 100644 --- a/lib/dns.js +++ b/lib/dns.js @@ -380,28 +380,44 @@ function setServers(servers) { } } -const defaultResolver = new Resolver(); +let defaultResolver = new Resolver(); + +const resolverKeys = [ + 'getServers', + 'resolve', + 'resolveAny', + 'resolve4', + 'resolve6', + 'resolveCname', + 'resolveMx', + 'resolveNs', + 'resolveTxt', + 'resolveSrv', + 'resolvePtr', + 'resolveNaptr', + 'resolveSoa', + 'reverse' +]; + +function setExportsFunctions() { + resolverKeys.forEach((key) => { + module.exports[key] = defaultResolver[key].bind(defaultResolver); + }); +} + +function defaultResolverSetServers(servers) { + const resolver = new Resolver(); + resolver.setServers(servers); + defaultResolver = resolver; + setExportsFunctions(); +} module.exports = { lookup, lookupService, Resolver, - getServers: defaultResolver.getServers.bind(defaultResolver), - setServers: defaultResolver.setServers.bind(defaultResolver), - resolve: defaultResolver.resolve.bind(defaultResolver), - resolveAny: defaultResolver.resolveAny.bind(defaultResolver), - resolve4: defaultResolver.resolve4.bind(defaultResolver), - resolve6: defaultResolver.resolve6.bind(defaultResolver), - resolveCname: defaultResolver.resolveCname.bind(defaultResolver), - resolveMx: defaultResolver.resolveMx.bind(defaultResolver), - resolveNs: defaultResolver.resolveNs.bind(defaultResolver), - resolveTxt: defaultResolver.resolveTxt.bind(defaultResolver), - resolveSrv: defaultResolver.resolveSrv.bind(defaultResolver), - resolvePtr: defaultResolver.resolvePtr.bind(defaultResolver), - resolveNaptr: defaultResolver.resolveNaptr.bind(defaultResolver), - resolveSoa: defaultResolver.resolveSoa.bind(defaultResolver), - reverse: defaultResolver.reverse.bind(defaultResolver), + setServers: defaultResolverSetServers, // uv_getaddrinfo flags ADDRCONFIG: cares.AI_ADDRCONFIG, @@ -433,3 +449,5 @@ module.exports = { ADDRGETNETWORKPARAMS: 'EADDRGETNETWORKPARAMS', CANCELLED: 'ECANCELLED' }; + +setExportsFunctions(); diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index 3fb98c293c23d7..80335f2bea6cf0 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -83,6 +83,7 @@ inline uint32_t cares_get_32bit(const unsigned char* p) { const int ns_t_cname_or_a = -1; +#define DNS_ESETSRVPENDING -1000 inline const char* ToErrorCodeString(int status) { switch (status) { #define V(code) case ARES_##code: return #code; @@ -150,6 +151,8 @@ class ChannelWrap : public AsyncWrap { void EnsureServers(); void CleanupTimer(); + void ModifyActivityQueryCount(int count); + inline uv_timer_t* timer_handle() { return timer_handle_; } inline ares_channel cares_channel() { return channel_; } inline bool query_last_ok() const { return query_last_ok_; } @@ -158,6 +161,7 @@ class ChannelWrap : public AsyncWrap { inline void set_is_servers_default(bool is_default) { is_servers_default_ = is_default; } + inline int active_query_count() { return active_query_count_; } inline node_ares_task_list* task_list() { return &task_list_; } size_t self_size() const override { return sizeof(*this); } @@ -170,6 +174,7 @@ class ChannelWrap : public AsyncWrap { bool query_last_ok_; bool is_servers_default_; bool library_inited_; + int active_query_count_; node_ares_task_list task_list_; }; @@ -180,7 +185,8 @@ ChannelWrap::ChannelWrap(Environment* env, channel_(nullptr), query_last_ok_(true), is_servers_default_(true), - library_inited_(false) { + library_inited_(false), + active_query_count_(0) { MakeWeak(this); Setup(); @@ -545,6 +551,11 @@ void ChannelWrap::CleanupTimer() { timer_handle_ = nullptr; } +void ChannelWrap::ModifyActivityQueryCount(int count) { + active_query_count_ += count; + if (active_query_count_ < 0) active_query_count_ = 0; +} + /** * This function is to check whether current servers are fallback servers @@ -682,6 +693,7 @@ class QueryWrap : public AsyncWrap { CaresAsyncCb)); wrap->channel_->set_query_last_ok(status != ARES_ECONNREFUSED); + wrap->channel_->ModifyActivityQueryCount(-1); async_handle->data = data; uv_async_send(async_handle); } @@ -1802,9 +1814,12 @@ static void Query(const FunctionCallbackInfo& args) { Wrap* wrap = new Wrap(channel, req_wrap_obj); node::Utf8Value name(env->isolate(), string); + channel->ModifyActivityQueryCount(1); int err = wrap->Send(*name); - if (err) + if (err) { + channel->ModifyActivityQueryCount(-1); delete wrap; + } args.GetReturnValue().Set(err); } @@ -2081,6 +2096,10 @@ void SetServers(const FunctionCallbackInfo& args) { ChannelWrap* channel; ASSIGN_OR_RETURN_UNWRAP(&channel, args.Holder()); + if (channel->active_query_count()) { + return args.GetReturnValue().Set(DNS_ESETSRVPENDING); + } + CHECK(args[0]->IsArray()); Local arr = Local::Cast(args[0]); @@ -2161,11 +2180,13 @@ void Cancel(const FunctionCallbackInfo& args) { ares_cancel(channel->cares_channel()); } - +const char EMSG_ESETSRVPENDING[] = "There are pending queries."; void StrError(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - const char* errmsg = ares_strerror(args[0]->Int32Value(env->context()) - .FromJust()); + int code = args[0]->Int32Value(env->context()).FromJust(); + const char* errmsg = (code == DNS_ESETSRVPENDING) ? + EMSG_ESETSRVPENDING : + ares_strerror(code); args.GetReturnValue().Set(OneByteString(env->isolate(), errmsg)); } diff --git a/test/internet/test-dns-setserver-in-callback-of-resolve4.js b/test/internet/test-dns-setserver-in-callback-of-resolve4.js index 2ef7e91f701efd..58b3327efe9475 100644 --- a/test/internet/test-dns-setserver-in-callback-of-resolve4.js +++ b/test/internet/test-dns-setserver-in-callback-of-resolve4.js @@ -13,3 +13,6 @@ dns.resolve4( common.mustCall(function(/* err, nameServers */) { dns.setServers([ addresses.DNS4_SERVER ]); })); + +// Test https://github.com/nodejs/node/issues/14734 +dns.resolve4(addresses.INET4_HOST, common.mustCall()); diff --git a/test/parallel/test-dns-setserver-when-querying.js b/test/parallel/test-dns-setserver-when-querying.js new file mode 100644 index 00000000000000..205a5b6c8585d0 --- /dev/null +++ b/test/parallel/test-dns-setserver-when-querying.js @@ -0,0 +1,31 @@ +'use strict'; + +const common = require('../common'); + +const dns = require('dns'); + +const goog = [ + '8.8.8.8', + '8.8.4.4', +]; + +{ + // Fix https://github.com/nodejs/node/issues/14734 + + { + const resolver = new dns.Resolver(); + resolver.resolve('localhost', common.mustCall()); + + common.expectsError(resolver.setServers.bind(resolver, goog), { + code: 'ERR_DNS_SET_SERVERS_FAILED', + message: /^c-ares failed to set servers: "There are pending queries\." \[.+\]$/g + }); + } + + { + dns.resolve('localhost', common.mustCall()); + + // should not throw + dns.setServers(goog); + } +}