nodejs / node

Node.js JavaScript runtime ✨🐢🚀✨
https://nodejs.org
Other
106.73k stars 29.12k forks source link

dns.resolveSoa returns EBADRESP if hostname has a CNAME record #34612

Open jlchristi opened 4 years ago

jlchristi commented 4 years ago

What steps will reproduce the bug?

const dns = require('dns');
const resolver = new dns.promises.Resolver();

// **************************************************************

async function test(hostname) {
  let data = '';

  console.log('hostname:', hostname);

  try {
    data = await resolver.resolveCname(hostname);
    console.log('CNAME result:', data);
  } catch (error) {
    console.log('CNAME result:', error.message);
  }

  try {
    data = await resolver.resolveSoa(hostname);
    console.log('SOA result:', JSON.stringify(data));
  } catch (error) {
    console.log('SOA result:', error.message);
  }

  console.log();
}

// **************************************************************

(async function main() {
  await test('support.microsoft.com');
})();

Actual Results

hostname: support.microsoft.com CNAME result: [ 'ev.support.microsoft.com.edgekey.net' ] SOA result: querySoa EBADRESP support.microsoft.com

Expected Results

hostname: support.microsoft.com CNAME result: [ 'ev.support.microsoft.com.edgekey.net' ] SOA result: querySoa ENODATA support.microsoft.com

Additional information

This seems to happen for any hostname with a CNAME record.

Another example:

hostname: store.gocomics.com CNAME result: [ 'gocomicsstore.wpengine.com' ] SOA result: querySoa EBADRESP store.gocomics.com

I would expect to get an 'ENODATA' instead of 'EBADRESP', as with the other resolveXXX() calls.

For a hostname with an SOA record but no CNAME, you get:

hostname: microsoft.com CNAME result: queryCname ENODATA microsoft.com SOA result: {"nsname":"ns1-205.azure-dns.com","hostmaster":"azuredns- hostmaster.microsoft.com","serial":1,"refresh":3600,"retry":300,"expire":2419200,"minttl":300}

bnoordhuis commented 4 years ago

Thanks for the bug report. At first glance this appears to be a bug in our response parsing logic.


https://github.com/nodejs/node/blob/74df7496ff6d899e4b8ceed9505a641e79ad6366/src/cares_wrap.cc#L1078-L1080

@addaleax @XadillaX Maybe not the root cause but those bounds checks look like UB to me.

Pointers in C and C++ are allowed to point one element past the end of an array and no more. The check should look like this:

diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc
index 73a0ac6b33..778e0821d7 100644
--- a/src/cares_wrap.cc
+++ b/src/cares_wrap.cc
@@ -1075,7 +1075,7 @@ int ParseSoaReply(Environment* env,
     return status == ARES_EBADNAME ? ARES_EBADRESP : status;
   }

-  if (ptr + temp_len + NS_QFIXEDSZ > buf + len) {
+  if (temp_len > LONG_MAX - NS_QFIXEDSZ || temp_len + NS_QFIXEDSZ > len) {
     return ARES_EBADRESP;
   }
   ptr += temp_len + NS_QFIXEDSZ;
AasthaGupta commented 3 years ago

@bnoordhuis I'd like to work on this.

AasthaGupta commented 3 years ago

@bnoordhuis - I tried tracing down function calls and I don't think the problem exists in parseSoaReply. Instead, the Parse function of QuerySoaWrap is invoked.

This Parse function handles the parsing when the ares_query invokes its callback with a success status. In the case when SOA response doesn't exist, the ares_query function invokes the callback with a success status instead of ENODATA.

At this point, I think the problem exists in the ares_query library function itself.

Also while working on this, I discovered a bug related to free call on garbage pointer. #35502

nordluf commented 8 months ago

v20.11.0 the bug is still there