mirror of
https://sourceware.org/git/glibc.git
synced 2024-11-21 12:30:06 +00:00
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
This commit is contained in:
parent
4a2ab5843a
commit
446997ff14
9
NEWS
9
NEWS
@ -40,6 +40,15 @@ Major new features:
|
||||
|
||||
* 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.)
|
||||
|
||||
Deprecated and removed features, and other changes affecting compatibility:
|
||||
|
||||
* The totalorder and totalordermag functions, and the corresponding
|
||||
|
@ -68,6 +68,7 @@ tests += \
|
||||
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 @@ $(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)
|
||||
|
||||
$(objpfx)tst-ns_name: $(objpfx)libresolv.so
|
||||
$(objpfx)tst-ns_name.out: tst-ns_name.data
|
||||
|
@ -612,6 +612,7 @@ p_option(u_long option) {
|
||||
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);
|
||||
|
@ -679,7 +679,8 @@ res_setoptions (struct resolv_conf_parser *parser, const char *options)
|
||||
{ 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)
|
||||
|
@ -118,6 +118,8 @@ __res_context_mkquery (struct resolv_context *ctx, int op, const char *dname,
|
||||
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;
|
||||
|
@ -332,6 +332,15 @@ 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;
|
||||
}
|
||||
|
||||
/* int
|
||||
* res_queriesmatch(buf1, eom1, buf2, eom2)
|
||||
* is there a 1:1 mapping of (name,type,class)
|
||||
@ -525,6 +534,18 @@ __res_context_send (struct resolv_context *ctx,
|
||||
|
||||
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,
|
||||
|
@ -131,6 +131,7 @@ struct res_sym {
|
||||
#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)
|
||||
|
||||
|
@ -127,6 +127,7 @@ print_resp (FILE *fp, res_state resp)
|
||||
"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 @@ struct test_case test_cases[] =
|
||||
"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 }
|
||||
};
|
||||
|
||||
|
200
resolv/tst-resolv-trustad.c
Normal file
200
resolv/tst-resolv-trustad.c
Normal file
@ -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>
|
@ -182,6 +182,8 @@ resolv_response_init (struct resolv_response_builder *b,
|
||||
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;
|
||||
|
@ -134,6 +134,9 @@ struct resolv_response_flags
|
||||
/* 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;
|
||||
|
Loading…
Reference in New Issue
Block a user