[review] resolv: Implement trust-ad option for /etc/resolv.conf [BZ #20358]

Message ID gerrit.1572453956000.Ibfe0f7c73ea221c35979842c5c3b6ed486495ccc@gnutoolchain-gerrit.osci.io
State Superseded
Headers

Commit Message

Simon Marchi (Code Review) Oct. 30, 2019, 4:45 p.m. UTC
  Change URL: https://gnutoolchain-gerrit.osci.io/r/c/glibc/+/461
......................................................................

resolv: Implement trust-ad option for /etc/resolv.conf [BZ #20358]

This introduces a concept of trusted name servers, for which the
AD bit is passed through to applications.  For untrusted name
servers (the default), the AD bit in responses are cleared, to
provide a safe default.

This approach is very similar to the one suggested by Pavel Šimerda
in <https://bugzilla.redhat.com/show_bug.cgi?id=1164339#c15>.

The DNS test framework in support/ is enhanced with support for
setting the AD bit in responses.

Tested on x86_64-linux-gnu.

Change-Id: Ibfe0f7c73ea221c35979842c5c3b6ed486495ccc
---
M NEWS
M resolv/Makefile
M resolv/res_debug.c
M resolv/res_init.c
M resolv/res_mkquery.c
M resolv/res_send.c
M resolv/resolv.h
M resolv/tst-resolv-res_init-skeleton.c
A resolv/tst-resolv-trustad.c
M support/resolv_test.c
M support/resolv_test.h
11 files changed, 253 insertions(+), 1 deletion(-)
  

Comments

Simon Marchi (Code Review) Nov. 27, 2019, 7:29 p.m. UTC | #1
Carlos O'Donell has posted comments on this change.

Change URL: https://gnutoolchain-gerrit.osci.io/r/c/glibc/+/461
......................................................................


Patch Set 2: Code-Review+2

(15 comments)

The solution here is similar to what is documented in the upstream wiki design:
https://sourceware.org/glibc/wiki/DNSSEC

That is we are implementing the ad-flag/RES_AD_FLAG option. I think RES_TRUSTAD is a better name since it captures what the flag really means that you trust the resolvers setup in /etc/resolv.conf.

OK for master!

Reviewed-by: Carlos O'Donell <carlos@redhat.com>

| --- NEWS
| +++ NEWS
| @@ -34,17 +34,26 @@ * The gettimeofday function will no longer report information about a
|    will always receive a 'struct timezone' whose tz_minuteswest and
|    tz_dsttime fields are zero.
|  
|  * The function pthread_clockjoin_np has been added, enabling join with a
|    terminated thread with a specific clock.  It allows waiting against
|    CLOCK_MONOTONIC and CLOCK_REALTIME.  This function is a GNU extension.
|  
|  * New locale added: mnw_MM (Mon language spoken in Myanmar).
|  
| +* The DNS stub resolver will optionally send the AD (authenticated data) bit
| +  in queries if the trust-ad option is set via the options directive in
| +  /etc/resolv.conf (or if RES_TRUSTAD is set in _res.options).  In this
| +  mode, the AD bit, as provided by the name server, is available to
| +  applications which call res_search and related functions.  In the default
| +  mode, the AD bit is not set in queries, and it is automatically cleared in
| +  responses, indicating a lack of DNSSEC validation.  (Therefore, the name
| +  servers and the network path to them are treated as untrusted.)

PS2, Line 50:

OK. Agreed, this is the best case design we could come up with at the
time. Thanks for realizing this implementation.

| +
|  Deprecated and removed features, and other changes affecting compatibility:
|  
|  * The totalorder and totalordermag functions, and the corresponding
|    functions for other floating-point types, now take pointer arguments to
|    avoid signaling NaNs possibly being converted to quiet NaNs in argument
|    passing.  This is in accordance with the resolution of Clarification
|    Request 25 to TS 18661-1, as applied for C2X.  Existing binaries that pass
|    floating-point arguments directly will continue to work.
| --- resolv/Makefile
| +++ resolv/Makefile
| @@ -62,18 +62,19 @@ tests += \
|    tst-resolv-trailing \
|  
|  # These tests need libdl.
|  ifeq (yes,$(build-shared))
|  tests += \
|    tst-resolv-ai_idn \
|    tst-resolv-ai_idn-latin1 \
|    tst-resolv-ai_idn-nolibidn2 \
|    tst-resolv-canonname \
| +  tst-resolv-trustad \

PS2, Line 71:

OK. Test case added.

|  
|  # Needs resolv_context.
|  tests-internal += \
|    tst-resolv-res_init \
|    tst-resolv-res_init-thread \
|    tst-resolv-res_ninit \
|    tst-resolv-threads \
|  
|  # Used by tst-resolv-ai_idn-nolibidn2 to disable libidn2 (by not

 ...

| @@ -193,16 +194,17 @@ $(objpfx)tst-resolv-res_init-thread: $(libdl) $(objpfx)libresolv.so \
|  $(objpfx)tst-resolv-nondecimal: $(objpfx)libresolv.so $(shared-thread-library)
|  $(objpfx)tst-resolv-qtypes: $(objpfx)libresolv.so $(shared-thread-library)
|  $(objpfx)tst-resolv-rotate: $(objpfx)libresolv.so $(shared-thread-library)
|  $(objpfx)tst-resolv-search: $(objpfx)libresolv.so $(shared-thread-library)
|  $(objpfx)tst-resolv-trailing: $(objpfx)libresolv.so $(shared-thread-library)
|  $(objpfx)tst-resolv-threads: \
|    $(libdl) $(objpfx)libresolv.so $(shared-thread-library)
|  $(objpfx)tst-resolv-canonname: \
|    $(libdl) $(objpfx)libresolv.so $(shared-thread-library)
| +$(objpfx)tst-resolv-trustad: $(objpfx)libresolv.so $(shared-thread-library)

PS2, Line 203:

OK. Link with libresolv.so.

|  
|  $(objpfx)tst-ns_name: $(objpfx)libresolv.so
|  $(objpfx)tst-ns_name.out: tst-ns_name.data
|  $(objpfx)tst-ns_name_compress: $(objpfx)libresolv.so
|  $(objpfx)tst-ns_name_pton: $(objpfx)libresolv.so
|  $(objpfx)tst-res_hnok: $(objpfx)libresolv.so
|  $(objpfx)tst-p_secstodate: $(objpfx)libresolv.so
| --- resolv/res_debug.c
| +++ resolv/res_debug.c
| @@ -606,18 +606,19 @@ p_option(u_long option) {
|  	case RES_DNSRCH:	return "dnsrch";
|  	case RES_NOALIASES:	return "noaliases";
|  	case RES_ROTATE:	return "rotate";
|  	case RES_USE_EDNS0:	return "edns0";
|  	case RES_SNGLKUP:	return "single-request";
|  	case RES_SNGLKUPREOP:	return "single-request-reopen";
|  	case RES_USE_DNSSEC:	return "dnssec";
|  	case RES_NOTLDQUERY:	return "no-tld-query";
|  	case RES_NORELOAD:	return "no-reload";
| +	case RES_TRUSTAD:	return "trust-ad";

PS2, Line 615:

OK, add new RES_TRUSTAD/trust-ad mnemonic mapping.

|  				/* XXX nonreentrant */
|  	default:		sprintf(nbuf, "?0x%lx?", (u_long)option);
|  				return (nbuf);
|  	}
|  }
|  libresolv_hidden_def (p_option)
|  
|  /*
|   * Return a mnemonic for a time to live.
| --- resolv/res_init.c
| +++ resolv/res_init.c
| @@ -675,17 +675,18 @@ #define STRnLEN(str) str, sizeof (str) - 1
|              { STRnLEN ("rotate"), 0, RES_ROTATE },
|              { STRnLEN ("edns0"), 0, RES_USE_EDNS0 },
|              { STRnLEN ("single-request-reopen"), 0, RES_SNGLKUPREOP },
|              { STRnLEN ("single-request"), 0, RES_SNGLKUP },
|              { STRnLEN ("no_tld_query"), 0, RES_NOTLDQUERY },
|              { STRnLEN ("no-tld-query"), 0, RES_NOTLDQUERY },
|              { STRnLEN ("no-reload"), 0, RES_NORELOAD },
| -            { STRnLEN ("use-vc"), 0, RES_USEVC }
| +            { STRnLEN ("use-vc"), 0, RES_USEVC },
| +            { STRnLEN ("trust-ad"), 0, RES_TRUSTAD },

PS2, Line 683:

OK. Parse RES_TRUSTAD for trust-ad.

|            };
|  #define noptions (sizeof (options) / sizeof (options[0]))
|            for (int i = 0; i < noptions; ++i)
|              if (strncmp (cp, options[i].str, options[i].len) == 0)
|                {
|                  if (options[i].clear)
|                    parser->template.options &= options[i].flag;
|                  else
|                    parser->template.options |= options[i].flag;
| --- resolv/res_mkquery.c
| +++ resolv/res_mkquery.c
| @@ -113,17 +113,19 @@ __res_context_mkquery (struct resolv_context *ctx, int op, const char *dname,
|      return -1;
|    memset (buf, 0, HFIXEDSZ);
|    hp = (HEADER *) buf;
|    /* We randomize the IDs every time.  The old code just incremented
|       by one after the initial randomization which still predictable if
|       the application does multiple requests.  */
|    hp->id = random_bits ();
|    hp->opcode = op;
| +  if (ctx->resp->options & RES_TRUSTAD)
| +    hp->ad = 1;

PS2, Line 122:

OK, if RES_TRUSTAD is set then set ad to 1.

|    hp->rd = (ctx->resp->options & RES_RECURSE) != 0;
|    hp->rcode = NOERROR;
|    cp = buf + HFIXEDSZ;
|    buflen -= HFIXEDSZ;
|    dpp = dnptrs;
|    *dpp++ = buf;
|    *dpp++ = NULL;
|    lastdnptr = dnptrs + sizeof dnptrs / sizeof dnptrs[0];
|  
| --- resolv/res_send.c
| +++ resolv/res_send.c
| @@ -332,10 +332,19 @@ nameserver_offset (struct __res_state *statp)
|      }
|  }
|  
| +/* Clear the AD bit unless the trust-ad option was specified in the
| +   resolver configuration.  */
| +static void
| +mask_ad_bit (struct resolv_context *ctx, void *buf)
| +{
| +  if (!(ctx->resp->options & RES_TRUSTAD))
| +    ((HEADER *) buf)->ad = 0;

PS2, Line 341:

OK, if RES_TRUSTAD is not set then clear ad.

| +}
| +
|  /* int
|   * res_queriesmatch(buf1, eom1, buf2, eom2)
|   *	is there a 1:1 mapping of (name,type,class)
|   *	in (buf1,eom1) and (buf2,eom2)?
|   * returns:
|   *	-1 : format error
|   *	0  : not a 1:1 mapping

 ...

| @@ -519,17 +528,29 @@ __res_context_send (struct resolv_context *ctx,
|  				goto next_ns;
|  			if (v_circuit)
|  			  // XXX Check whether both requests failed or
|  			  // XXX whether one has been answered successfully
|  				goto same_ns;
|  		}
|  
|  		resplen = n;
|  
| +		/* Mask the AD bit in both responses unless it is
| +		   marked trusted.  */
| +		if (resplen > HFIXEDSZ)
| +		  {
| +		    if (ansp != NULL)
| +		      mask_ad_bit (ctx, *ansp);
| +		    else
| +		      mask_ad_bit (ctx, ans);
| +		  }
| +		if (resplen2 != NULL && *resplen2 > HFIXEDSZ)
| +		  mask_ad_bit (ctx, *ansp2);

PS2, Line 547:

OK, we are in res_send.c:__res_context_send, which is some very
complicated code, but here we check in both possible responses and
potentially clear the ad bit.

| +
|  		/*
|  		 * If we have temporarily opened a virtual circuit,
|  		 * or if we haven't been asked to keep a socket open,
|  		 * close the socket.
|  		 */
|  		if ((v_circuit && (statp->options & RES_USEVC) == 0) ||
|  		    (statp->options & RES_STAYOPEN) == 0) {
|  			__res_iclose(statp, false);
| --- resolv/resolv.h
| +++ resolv/resolv.h
| @@ -125,18 +125,19 @@ #define	RES_BLAST \
|    __glibc_macro_warning ("RES_BLAST is deprecated") 0x00020000
|  #define RES_USE_EDNS0	0x00100000	/* Use EDNS0.  */
|  #define RES_SNGLKUP	0x00200000	/* one outstanding request at a time */
|  #define RES_SNGLKUPREOP	0x00400000	/* -"-, but open new socket for each
|  					   request */
|  #define RES_USE_DNSSEC	0x00800000	/* use DNSSEC using OK bit in OPT */
|  #define RES_NOTLDQUERY	0x01000000	/* Do not look up unqualified name
|  					   as a TLD.  */
|  #define RES_NORELOAD    0x02000000 /* No automatic configuration reload.  */
| +#define RES_TRUSTAD     0x04000000 /* Request AD bit, keep it in responses.  */

PS2, Line 134:

OK, new constant.

|  
|  #define RES_DEFAULT	(RES_RECURSE|RES_DEFNAMES|RES_DNSRCH)
|  
|  /*
|   * Resolver "pfcode" values.  Used by dig.
|   */
|  #define RES_PRF_STATS	0x00000001
|  #define RES_PRF_UPDATE	0x00000002
|  #define RES_PRF_CLASS   0x00000004
| --- resolv/tst-resolv-res_init-skeleton.c
| +++ resolv/tst-resolv-res_init-skeleton.c
| @@ -121,18 +121,19 @@ print_resp (FILE *fp, res_state resp)
|          print_option_flag (fp, &options, RES_USEVC, "use-vc");
|          print_option_flag (fp, &options, RES_ROTATE, "rotate");
|          print_option_flag (fp, &options, RES_USE_EDNS0, "edns0");
|          print_option_flag (fp, &options, RES_SNGLKUP,
|                             "single-request");
|          print_option_flag (fp, &options, RES_SNGLKUPREOP,
|                             "single-request-reopen");
|          print_option_flag (fp, &options, RES_NOTLDQUERY, "no-tld-query");
|          print_option_flag (fp, &options, RES_NORELOAD, "no-reload");
| +        print_option_flag (fp, &options, RES_TRUSTAD, "trust-ad");

PS2, Line 130:

OK, print the new constant.

|          fputc ('\n', fp);
|          if (options != 0)
|            fprintf (fp, "; error: unresolved option bits: 0x%x\n", options);
|        }
|    }
|  
|    /* The search and domain directives.  */
|    if (resp->dnsrch[0] != NULL)
|      {

 ...

| @@ -705,18 +706,27 @@ #define D63 "this-domain-name-is-as-long-as-the-previous-name--63-characters"
|       .expected = "search example.com " H63 "." D63 ".example.org\n"
|       "; search[0]: example.com\n"
|       "; search[1]: " H63 "." D63 ".example.org\n"
|       "; search[2]: " H63 "." D63 ".example.net\n"
|  #undef H63
|  #undef D63
|       "nameserver 192.0.2.1\n"
|       "; nameserver[0]: [192.0.2.1]:53\n"
|      },
| +    {.name = "trust-ad flag",
| +     .conf = "options trust-ad\n"
| +     "nameserver 192.0.2.1\n",
| +     .expected = "options trust-ad\n"
| +     "search example.com\n"
| +     "; search[0]: example.com\n"
| +     "nameserver 192.0.2.1\n"
| +     "; nameserver[0]: [192.0.2.1]:53\n"
| +    },

PS2, Line 723:

OK, in this test we set options trust-ad.

|      { NULL }
|    };
|  
|  /* Run the indicated test case.  This function assumes that the chroot
|     contents has already been set up.  */
|  static void
|  test_file_contents (const struct test_case *t)
|  {
|  #if TEST_THREAD
| --- /dev/null
| +++ resolv/tst-resolv-trustad.c
| @@ -1,0 +87,28 @@ do_test (void)
| +  struct resolv_test *aux = resolv_test_start
| +    ((struct resolv_redirect_config)
| +     {
| +       .response_callback = response,
| +     });
| +
| +  /* By default, the resolver is not trusted, and the AD bit is
| +     cleared.  */
| +
| +  static const unsigned char hand_crafted_query[] =
| +    {
| +     10, 11,                    /* Transaction ID.  */
| +     1, 0x20,                   /* Query with RD, AD flags.  */
| +     0, 1,                      /* One question.  */
| +     0, 0, 0, 0, 0, 0,          /* The other sections are empty.  */
| +     3, 'w', 'w', 'w', 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0,
| +     0, T_A,                    /* A query.  */
| +     0, 1,                      /* Class IN.  */
| +    };

PS2, Line 105:

OK, default test.

| +
| +  ++response_number;
| +  response_ad_bit = false;
| +
| +  unsigned char buffer[512];
| +  memset (buffer, 255, sizeof (buffer));
| +  query_ad_bit = true;
| +  int ret = res_send (hand_crafted_query, sizeof (hand_crafted_query),
| +                      buffer, sizeof (buffer));

 ...

| @@ -1,0 +170,19 @@ do_test (void)
| +  check_answer (buffer, ret, false);
| +
| +  response_ad_bit = true;
| +
| +  ++response_number;
| +  memset (buffer, 0, sizeof (buffer));
| +  ret = res_send (hand_crafted_query, sizeof (hand_crafted_query),
| +                  buffer, sizeof (buffer));
| +  TEST_VERIFY (ret > 0);
| +  check_answer (buffer, ret, true);

PS2, Line 179:

OK, here we expect an ad enabled answer.

| +
| +  ++response_number;
| +  memset (buffer, 0, sizeof (buffer));
| +  ret = res_query ("www.example", C_IN, T_A, buffer, sizeof (buffer));
| +  TEST_VERIFY (ret > 0);
| +  check_answer (buffer, ret, true);
| +
| +  /* AD bit set in generated queries.  */
| +  memset (buffer, 0, sizeof (buffer));
| --- support/resolv_test.c
| +++ support/resolv_test.c
| @@ -177,17 +177,19 @@ resolv_response_init (struct resolv_response_builder *b,
|    b->buffer[1] = b->query_buffer[1];
|  
|    /* Initialize the flags.  */
|    b->buffer[2] = 0x80;                       /* Mark as response.   */
|    b->buffer[2] |= b->query_buffer[2] & 0x01; /* Copy the RD bit.  */
|    if (flags.tc)
|      b->buffer[2] |= 0x02;
|    b->buffer[3] = 0x80 | flags.rcode; /* Always set RA.  */
| +  if (flags.ad)
| +    b->buffer[3] |= 0x20;

PS2, Line 186:

OK, set bit.

|  
|    /* Fill in the initial section count values.  */
|    b->buffer[4] = flags.qdcount >> 8;
|    b->buffer[5] = flags.qdcount;
|    b->buffer[6] = flags.ancount >> 8;
|    b->buffer[7] = flags.ancount;
|    b->buffer[8] = flags.nscount >> 8;
|    b->buffer[9] = flags.nscount;
|    b->buffer[10] = flags.adcount >> 8;
| --- support/resolv_test.h
| +++ support/resolv_test.h
| @@ -129,16 +129,19 @@ /* Special settings for constructing responses from the callback.  */
|  struct resolv_response_flags
|  {
|    /* 4-bit response code to incorporate into the response. */
|    unsigned char rcode;
|  
|    /* If true, the TC (truncation) flag will be set.  */
|    bool tc;
|  
| +  /* If true, the AD (authenticated data) flag will be set.  */
| +  bool ad;

PS2, Line 138:

OK.

| +
|    /* Initial section count values.  Can be used to artificially
|       increase the counts, for malformed packet testing.*/
|    unsigned short qdcount;
|    unsigned short ancount;
|    unsigned short nscount;
|    unsigned short adcount;
|  };
|
  

Patch

diff --git a/NEWS b/NEWS
index d728684..a6221a3 100644
--- a/NEWS
+++ b/NEWS
@@ -21,6 +21,15 @@ 
   18661-1:2014 and TS 18661-3:2015 as amended by the resolution of
   Clarification Request 13 to TS 18661-3.
 
+* The DNS stub resolver will optionally send the AD (authenticated data) bit
+  in queries if the trust-ad option is set via the options directive in
+  /etc/resolv.conf (or if RES_TRUSTAD is set in _res.options).  In this
+  mode, the AD bit, as provided by the name server, is available to
+  applications which call res_search and related functions.  In the default
+  mode, the AD bit is not set in queries, and it is automatically cleared in
+  responses, indicating a lack of DNSSEC validation.  (Therefore, the name
+  servers and the network path to them are treated as untrusted.)
+
 Deprecated and removed features, and other changes affecting compatibility:
 
 * The totalorder and totalordermag functions, and the corresponding
diff --git a/resolv/Makefile b/resolv/Makefile
index 3cb64e6..cd097bd 100644
--- a/resolv/Makefile
+++ b/resolv/Makefile
@@ -68,6 +68,7 @@ 
   tst-resolv-ai_idn-latin1 \
   tst-resolv-ai_idn-nolibidn2 \
   tst-resolv-canonname \
+  tst-resolv-trustad \
 
 # Needs resolv_context.
 tests-internal += \
@@ -199,6 +200,7 @@ 
   $(libdl) $(objpfx)libresolv.so $(shared-thread-library)
 $(objpfx)tst-resolv-canonname: \
   $(libdl) $(objpfx)libresolv.so $(shared-thread-library)
+$(objpfx)tst-resolv-trustad: $(objpfx)libresolv.so $(shared-thread-library)
 
 $(objpfx)tst-ns_name: $(objpfx)libresolv.so
 $(objpfx)tst-ns_name.out: tst-ns_name.data
diff --git a/resolv/res_debug.c b/resolv/res_debug.c
index 4dac71f..844269c 100644
--- a/resolv/res_debug.c
+++ b/resolv/res_debug.c
@@ -612,6 +612,7 @@ 
 	case RES_USE_DNSSEC:	return "dnssec";
 	case RES_NOTLDQUERY:	return "no-tld-query";
 	case RES_NORELOAD:	return "no-reload";
+	case RES_TRUSTAD:	return "trust-ad";
 				/* XXX nonreentrant */
 	default:		sprintf(nbuf, "?0x%lx?", (u_long)option);
 				return (nbuf);
diff --git a/resolv/res_init.c b/resolv/res_init.c
index 1b8e062..c2f2c7b 100644
--- a/resolv/res_init.c
+++ b/resolv/res_init.c
@@ -679,7 +679,8 @@ 
             { STRnLEN ("no_tld_query"), 0, RES_NOTLDQUERY },
             { STRnLEN ("no-tld-query"), 0, RES_NOTLDQUERY },
             { STRnLEN ("no-reload"), 0, RES_NORELOAD },
-            { STRnLEN ("use-vc"), 0, RES_USEVC }
+            { STRnLEN ("use-vc"), 0, RES_USEVC },
+            { STRnLEN ("trust-ad"), 0, RES_TRUSTAD },
           };
 #define noptions (sizeof (options) / sizeof (options[0]))
           for (int i = 0; i < noptions; ++i)
diff --git a/resolv/res_mkquery.c b/resolv/res_mkquery.c
index cf0c9e2..0d39353 100644
--- a/resolv/res_mkquery.c
+++ b/resolv/res_mkquery.c
@@ -118,6 +118,8 @@ 
      the application does multiple requests.  */
   hp->id = random_bits ();
   hp->opcode = op;
+  if (ctx->resp->options & RES_TRUSTAD)
+    hp->ad = 1;
   hp->rd = (ctx->resp->options & RES_RECURSE) != 0;
   hp->rcode = NOERROR;
   cp = buf + HFIXEDSZ;
diff --git a/resolv/res_send.c b/resolv/res_send.c
index 6b9c73f..0545d58 100644
--- a/resolv/res_send.c
+++ b/resolv/res_send.c
@@ -337,6 +337,15 @@ 
     }
 }
 
+/* Clear the AD bit unless the trust-ad option was specified in the
+   resolver configuration.  */
+static void
+mask_ad_bit (struct resolv_context *ctx, void *buf)
+{
+  if (!(ctx->resp->options & RES_TRUSTAD))
+    ((HEADER *) buf)->ad = 0;
+}
+
 /* int
  * res_queriesmatch(buf1, eom1, buf2, eom2)
  *	is there a 1:1 mapping of (name,type,class)
@@ -530,6 +539,18 @@ 
 
 		resplen = n;
 
+		/* Mask the AD bit in both responses unless it is
+		   marked trusted.  */
+		if (resplen > HFIXEDSZ)
+		  {
+		    if (ansp != NULL)
+		      mask_ad_bit (ctx, *ansp);
+		    else
+		      mask_ad_bit (ctx, ans);
+		  }
+		if (resplen2 != NULL && *resplen2 > HFIXEDSZ)
+		  mask_ad_bit (ctx, *ansp2);
+
 		/*
 		 * If we have temporarily opened a virtual circuit,
 		 * or if we haven't been asked to keep a socket open,
diff --git a/resolv/resolv.h b/resolv/resolv.h
index 7a8023a..a039a9e 100644
--- a/resolv/resolv.h
+++ b/resolv/resolv.h
@@ -131,6 +131,7 @@ 
 #define RES_NOTLDQUERY	0x01000000	/* Do not look up unqualified name
 					   as a TLD.  */
 #define RES_NORELOAD    0x02000000 /* No automatic configuration reload.  */
+#define RES_TRUSTAD     0x04000000 /* Request AD bit, keep it in responses.  */
 
 #define RES_DEFAULT	(RES_RECURSE|RES_DEFNAMES|RES_DNSRCH)
 
diff --git a/resolv/tst-resolv-res_init-skeleton.c b/resolv/tst-resolv-res_init-skeleton.c
index d4da8b6..9934d23 100644
--- a/resolv/tst-resolv-res_init-skeleton.c
+++ b/resolv/tst-resolv-res_init-skeleton.c
@@ -127,6 +127,7 @@ 
                            "single-request-reopen");
         print_option_flag (fp, &options, RES_NOTLDQUERY, "no-tld-query");
         print_option_flag (fp, &options, RES_NORELOAD, "no-reload");
+        print_option_flag (fp, &options, RES_TRUSTAD, "trust-ad");
         fputc ('\n', fp);
         if (options != 0)
           fprintf (fp, "; error: unresolved option bits: 0x%x\n", options);
@@ -711,6 +712,15 @@ 
      "nameserver 192.0.2.1\n"
      "; nameserver[0]: [192.0.2.1]:53\n"
     },
+    {.name = "trust-ad flag",
+     .conf = "options trust-ad\n"
+     "nameserver 192.0.2.1\n",
+     .expected = "options trust-ad\n"
+     "search example.com\n"
+     "; search[0]: example.com\n"
+     "nameserver 192.0.2.1\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
+    },
     { NULL }
   };
 
diff --git a/resolv/tst-resolv-trustad.c b/resolv/tst-resolv-trustad.c
new file mode 100644
index 0000000..fc9ae54
--- /dev/null
+++ b/resolv/tst-resolv-trustad.c
@@ -0,0 +1,200 @@ 
+/* Test the behavior of the trust-ad option.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <resolv.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/check_nss.h>
+#include <support/resolv_test.h>
+#include <support/support.h>
+
+/* This controls properties of the response.  volatile because
+   __res_send is incorrectly declared as __THROW.  */
+static volatile unsigned char response_number;
+static volatile bool response_ad_bit;
+static volatile bool query_ad_bit;
+
+static void
+response (const struct resolv_response_context *ctx,
+          struct resolv_response_builder *b,
+          const char *qname, uint16_t qclass, uint16_t qtype)
+{
+  TEST_COMPARE (qclass, C_IN);
+  TEST_COMPARE (qtype, T_A);
+  TEST_COMPARE_STRING (qname, "www.example");
+
+  HEADER header;
+  memcpy (&header, ctx->query_buffer, sizeof (header));
+  TEST_COMPARE (header.ad, query_ad_bit);
+
+  struct resolv_response_flags flags = { .ad = response_ad_bit, };
+  resolv_response_init (b, flags);
+  resolv_response_add_question (b, qname, qclass, qtype);
+  resolv_response_section (b, ns_s_an);
+  resolv_response_open_record (b, qname, qclass, T_A, 0x12345678);
+  char addr[4] = { 192, 0, 2, response_number };
+  resolv_response_add_data (b, addr, sizeof (addr));
+  resolv_response_close_record (b);
+}
+
+static void
+check_answer (const unsigned char *buffer, size_t buffer_length,
+              bool expected_ad)
+{
+  HEADER header;
+  TEST_VERIFY (buffer_length > sizeof (header));
+  memcpy (&header, buffer, sizeof (header));
+  TEST_COMPARE (0, header.aa);
+  TEST_COMPARE (expected_ad, header.ad);
+  TEST_COMPARE (0, header.opcode);
+  TEST_COMPARE (1, header.qr);
+  TEST_COMPARE (0, header.rcode);
+  TEST_COMPARE (1, header.rd);
+  TEST_COMPARE (0, header.tc);
+  TEST_COMPARE (1, ntohs (header.qdcount));
+  TEST_COMPARE (1, ntohs (header.ancount));
+  TEST_COMPARE (0, ntohs (header.nscount));
+  TEST_COMPARE (0, ntohs (header.arcount));
+
+  char *description = xasprintf ("response=%d ad=%d",
+                                 response_number, expected_ad);
+  char *expected = xasprintf ("name: www.example\n"
+                              "address: 192.0.2.%d\n", response_number);
+  check_dns_packet (description, buffer, buffer_length, expected);
+  free (expected);
+  free (description);
+}
+
+static int
+do_test (void)
+{
+  struct resolv_test *aux = resolv_test_start
+    ((struct resolv_redirect_config)
+     {
+       .response_callback = response,
+     });
+
+  /* By default, the resolver is not trusted, and the AD bit is
+     cleared.  */
+
+  static const unsigned char hand_crafted_query[] =
+    {
+     10, 11,                    /* Transaction ID.  */
+     1, 0x20,                   /* Query with RD, AD flags.  */
+     0, 1,                      /* One question.  */
+     0, 0, 0, 0, 0, 0,          /* The other sections are empty.  */
+     3, 'w', 'w', 'w', 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0,
+     0, T_A,                    /* A query.  */
+     0, 1,                      /* Class IN.  */
+    };
+
+  ++response_number;
+  response_ad_bit = false;
+
+  unsigned char buffer[512];
+  memset (buffer, 255, sizeof (buffer));
+  query_ad_bit = true;
+  int ret = res_send (hand_crafted_query, sizeof (hand_crafted_query),
+                      buffer, sizeof (buffer));
+  TEST_VERIFY (ret > 0);
+  check_answer (buffer, ret, false);
+
+  ++response_number;
+  memset (buffer, 255, sizeof (buffer));
+  query_ad_bit = false;
+  ret = res_query ("www.example", C_IN, T_A, buffer, sizeof (buffer));
+  TEST_VERIFY (ret > 0);
+  check_answer (buffer, ret, false);
+  response_ad_bit = true;
+
+  response_ad_bit = true;
+
+  ++response_number;
+  query_ad_bit = true;
+  ret = res_send (hand_crafted_query, sizeof (hand_crafted_query),
+                  buffer, sizeof (buffer));
+  TEST_VERIFY (ret > 0);
+  check_answer (buffer, ret, false);
+
+  ++response_number;
+  memset (buffer, 255, sizeof (buffer));
+  query_ad_bit = false;
+  ret = res_query ("www.example", C_IN, T_A, buffer, sizeof (buffer));
+  TEST_VERIFY (ret > 0);
+  check_answer (buffer, ret, false);
+
+  /* No AD bit set in generated queries.  */
+  memset (buffer, 255, sizeof (buffer));
+  ret = res_mkquery (QUERY, "www.example", C_IN, T_A,
+                     (const unsigned char *) "", 0, NULL,
+                     buffer, sizeof (buffer));
+  HEADER header;
+  memcpy (&header, buffer, sizeof (header));
+  TEST_VERIFY (!header.ad);
+
+  /* With RES_TRUSTAD, the AD bit is passed through if it set in the
+     response.  It is also included in queries.  */
+
+  _res.options |= RES_TRUSTAD;
+  query_ad_bit = true;
+
+  response_ad_bit = false;
+
+  ++response_number;
+  memset (buffer, 255, sizeof (buffer));
+  ret = res_send (hand_crafted_query, sizeof (hand_crafted_query),
+                  buffer, sizeof (buffer));
+  TEST_VERIFY (ret > 0);
+  check_answer (buffer, ret, false);
+
+  ++response_number;
+  memset (buffer, 255, sizeof (buffer));
+  ret = res_query ("www.example", C_IN, T_A, buffer, sizeof (buffer));
+  TEST_VERIFY (ret > 0);
+  check_answer (buffer, ret, false);
+
+  response_ad_bit = true;
+
+  ++response_number;
+  memset (buffer, 0, sizeof (buffer));
+  ret = res_send (hand_crafted_query, sizeof (hand_crafted_query),
+                  buffer, sizeof (buffer));
+  TEST_VERIFY (ret > 0);
+  check_answer (buffer, ret, true);
+
+  ++response_number;
+  memset (buffer, 0, sizeof (buffer));
+  ret = res_query ("www.example", C_IN, T_A, buffer, sizeof (buffer));
+  TEST_VERIFY (ret > 0);
+  check_answer (buffer, ret, true);
+
+  /* AD bit set in generated queries.  */
+  memset (buffer, 0, sizeof (buffer));
+  ret = res_mkquery (QUERY, "www.example", C_IN, T_A,
+                     (const unsigned char *) "", 0, NULL,
+                     buffer, sizeof (buffer));
+  memcpy (&header, buffer, sizeof (header));
+  TEST_VERIFY (header.ad);
+
+  resolv_test_end (aux);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/support/resolv_test.c b/support/resolv_test.c
index aeca519..b1c745d 100644
--- a/support/resolv_test.c
+++ b/support/resolv_test.c
@@ -182,6 +182,8 @@ 
   if (flags.tc)
     b->buffer[2] |= 0x02;
   b->buffer[3] = 0x80 | flags.rcode; /* Always set RA.  */
+  if (flags.ad)
+    b->buffer[3] |= 0x20;
 
   /* Fill in the initial section count values.  */
   b->buffer[4] = flags.qdcount >> 8;
diff --git a/support/resolv_test.h b/support/resolv_test.h
index e36e3e3..02a11bd 100644
--- a/support/resolv_test.h
+++ b/support/resolv_test.h
@@ -134,6 +134,9 @@ 
   /* If true, the TC (truncation) flag will be set.  */
   bool tc;
 
+  /* If true, the AD (authenticated data) flag will be set.  */
+  bool ad;
+
   /* Initial section count values.  Can be used to artificially
      increase the counts, for malformed packet testing.*/
   unsigned short qdcount;