resolved: rework how we handle truncation in the stub resolver

When we a reply message gets longer than the client supports we need to
truncate the response and set the TC bit, and we already do that.
However, we are not supposed to send incomplete RRs in that case, but
instead truncate right at a record boundary. Do that.

This fixes the "Message parser reports malformed message packet."
warning the venerable "host" tool outputs when a very large response is
requested.

See: #6520
This commit is contained in:
Lennart Poettering 2017-10-04 12:35:48 +02:00
parent 9886b6b13c
commit 5102765695
9 changed files with 69 additions and 37 deletions

View File

@ -357,7 +357,7 @@ static int output_rr_packet(const void *d, size_t l, int ifindex) {
int r;
char ifname[IF_NAMESIZE] = "";
r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0);
r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0, DNS_PACKET_SIZE_MAX);
if (r < 0)
return log_oom();

View File

@ -43,11 +43,20 @@ static void rewind_dns_packet(DnsPacketRewinder *rewinder) {
#define INIT_REWINDER(rewinder, p) do { rewinder.packet = p; rewinder.saved_rindex = p->rindex; } while (0)
#define CANCEL_REWINDER(rewinder) do { rewinder.packet = NULL; } while (0)
int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t min_alloc_dsize) {
int dns_packet_new(
DnsPacket **ret,
DnsProtocol protocol,
size_t min_alloc_dsize,
size_t max_size) {
DnsPacket *p;
size_t a;
assert(ret);
assert(max_size >= DNS_PACKET_HEADER_SIZE);
if (max_size > DNS_PACKET_SIZE_MAX)
max_size = DNS_PACKET_SIZE_MAX;
/* The caller may not check what is going to be truly allocated, so do not allow to
* allocate a DNS packet bigger than DNS_PACKET_SIZE_MAX.
@ -70,8 +79,8 @@ int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t min_alloc_dsize
a = PAGE_ALIGN(ALIGN(sizeof(DnsPacket)) + a) - ALIGN(sizeof(DnsPacket));
/* make sure we never allocate more than useful */
if (a > DNS_PACKET_SIZE_MAX)
a = DNS_PACKET_SIZE_MAX;
if (a > max_size)
a = max_size;
p = malloc0(ALIGN(sizeof(DnsPacket)) + a);
if (!p)
@ -79,6 +88,7 @@ int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t min_alloc_dsize
p->size = p->rindex = DNS_PACKET_HEADER_SIZE;
p->allocated = a;
p->max_size = max_size;
p->protocol = protocol;
p->opt_start = p->opt_size = (size_t) -1;
p->n_ref = 1;
@ -144,7 +154,7 @@ int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t min_alloc
assert(ret);
r = dns_packet_new(&p, protocol, min_alloc_dsize);
r = dns_packet_new(&p, protocol, min_alloc_dsize, DNS_PACKET_SIZE_MAX);
if (r < 0)
return r;
@ -313,11 +323,13 @@ static int dns_packet_extend(DnsPacket *p, size_t add, void **ret, size_t *start
assert(p);
if (p->size + add > p->allocated) {
size_t a;
size_t a, ms;
a = PAGE_ALIGN((p->size + add) * 2);
if (a > DNS_PACKET_SIZE_MAX)
a = DNS_PACKET_SIZE_MAX;
ms = dns_packet_size_max(p);
if (a > ms)
a = ms;
if (p->size + add > a)
return -EMSGSIZE;

View File

@ -73,7 +73,7 @@ struct DnsPacketHeader {
struct DnsPacket {
int n_ref;
DnsProtocol protocol;
size_t size, allocated, rindex;
size_t size, allocated, rindex, max_size;
void *_data; /* don't access directly, use DNS_PACKET_DATA()! */
Hashmap *names; /* For name compression */
size_t opt_start, opt_size;
@ -187,7 +187,7 @@ static inline unsigned DNS_PACKET_RRCOUNT(DnsPacket *p) {
(unsigned) DNS_PACKET_ARCOUNT(p);
}
int dns_packet_new(DnsPacket **p, DnsProtocol protocol, size_t min_alloc_dsize);
int dns_packet_new(DnsPacket **p, DnsProtocol protocol, size_t min_alloc_dsize, size_t max_size);
int dns_packet_new_query(DnsPacket **p, DnsProtocol protocol, size_t min_alloc_dsize, bool dnssec_checking_disabled);
void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated);
@ -303,3 +303,13 @@ static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family,
return f;
}
}
static inline size_t dns_packet_size_max(DnsPacket *p) {
assert(p);
/* Why not insist on a fully initialized max_size during DnsPacket construction? Well, this way it's easy to
* allocate a transient, throw-away DnsPacket on the stack by simple zero initialization, without having to
* deal with explicit field initialization. */
return p->max_size != 0 ? p->max_size : DNS_PACKET_SIZE_MAX;
}

View File

@ -632,7 +632,7 @@ int dns_scope_make_reply_packet(
dns_answer_isempty(soa))
return -EINVAL;
r = dns_packet_new(&p, s->protocol, 0);
r = dns_packet_new(&p, s->protocol, 0, DNS_PACKET_SIZE_MAX);
if (r < 0)
return r;
@ -818,7 +818,7 @@ static int dns_scope_make_conflict_packet(
assert(rr);
assert(ret);
r = dns_packet_new(&p, s->protocol, 0);
r = dns_packet_new(&p, s->protocol, 0, DNS_PACKET_SIZE_MAX);
if (r < 0)
return r;

View File

@ -260,7 +260,7 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use
ssize_t ss;
if (!s->read_packet) {
r = dns_packet_new(&s->read_packet, s->protocol, be16toh(s->read_size));
r = dns_packet_new(&s->read_packet, s->protocol, be16toh(s->read_size), DNS_PACKET_SIZE_MAX);
if (r < 0)
return dns_stream_complete(s, -r);

View File

@ -30,9 +30,12 @@ static int manager_dns_stub_tcp_fd(Manager *m);
static int dns_stub_make_reply_packet(
DnsPacket **p,
size_t max_size,
DnsQuestion *q,
DnsAnswer *answer) {
DnsAnswer *answer,
bool *ret_truncated) {
bool truncated = false;
DnsResourceRecord *rr;
unsigned c = 0;
int r;
@ -43,7 +46,7 @@ static int dns_stub_make_reply_packet(
* roundtrips aren't expensive. */
if (!*p) {
r = dns_packet_new(p, DNS_PROTOCOL_DNS, 0);
r = dns_packet_new(p, DNS_PROTOCOL_DNS, 0, max_size);
if (r < 0)
return r;
@ -71,12 +74,21 @@ static int dns_stub_make_reply_packet(
continue;
add:
r = dns_packet_append_rr(*p, rr, 0, NULL, NULL);
if (r == -EMSGSIZE) {
truncated = true;
break;
}
if (r < 0)
return r;
c++;
}
if (ret_truncated)
*ret_truncated = truncated;
else if (truncated)
return -EMSGSIZE;
DNS_PACKET_HEADER(*p)->ancount = htobe16(be16toh(DNS_PACKET_HEADER(*p)->ancount) + c);
return 0;
@ -86,6 +98,7 @@ static int dns_stub_finish_reply_packet(
DnsPacket *p,
uint16_t id,
int rcode,
bool tc, /* set the Truncated bit? */
bool add_opt, /* add an OPT RR to this packet? */
bool edns0_do, /* set the EDNS0 DNSSEC OK bit? */
bool ad) { /* set the DNSSEC authenticated data bit? */
@ -110,14 +123,14 @@ static int dns_stub_finish_reply_packet(
DNS_PACKET_HEADER(p)->id = id;
DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS(
1 /* qr */,
0 /* opcode */,
0 /* aa */,
0 /* tc */,
1 /* rd */,
1 /* ra */,
1 /* qr */,
0 /* opcode */,
0 /* aa */,
tc /* tc */,
1 /* rd */,
1 /* ra */,
ad /* ad */,
0 /* cd */,
0 /* cd */,
rcode));
if (add_opt) {
@ -149,12 +162,6 @@ static int dns_stub_send(Manager *m, DnsStream *s, DnsPacket *p, DnsPacket *repl
else {
int fd;
/* Truncate the message to the right size */
if (reply->size > DNS_PACKET_PAYLOAD_SIZE_MAX(p)) {
dns_packet_truncate(reply, DNS_PACKET_UNICAST_SIZE_MAX);
DNS_PACKET_HEADER(reply)->flags = htobe16(be16toh(DNS_PACKET_HEADER(reply)->flags) | DNS_PACKET_FLAG_TC);
}
fd = manager_dns_stub_udp_fd(m);
if (fd < 0)
return log_debug_errno(fd, "Failed to get reply socket: %m");
@ -178,11 +185,11 @@ static int dns_stub_send_failure(Manager *m, DnsStream *s, DnsPacket *p, int rco
assert(m);
assert(p);
r = dns_stub_make_reply_packet(&reply, p->question, NULL);
r = dns_stub_make_reply_packet(&reply, DNS_PACKET_PAYLOAD_SIZE_MAX(p), p->question, NULL, NULL);
if (r < 0)
return log_debug_errno(r, "Failed to make failure packet: %m");
r = dns_stub_finish_reply_packet(reply, DNS_PACKET_ID(p), rcode, !!p->opt, DNS_PACKET_DO(p), authenticated);
r = dns_stub_finish_reply_packet(reply, DNS_PACKET_ID(p), rcode, false, !!p->opt, DNS_PACKET_DO(p), authenticated);
if (r < 0)
return log_debug_errno(r, "Failed to build failure packet: %m");
@ -197,9 +204,10 @@ static void dns_stub_query_complete(DnsQuery *q) {
switch (q->state) {
case DNS_TRANSACTION_SUCCESS:
case DNS_TRANSACTION_SUCCESS: {
bool truncated;
r = dns_stub_make_reply_packet(&q->reply_dns_packet, q->question_idna, q->answer);
r = dns_stub_make_reply_packet(&q->reply_dns_packet, DNS_PACKET_PAYLOAD_SIZE_MAX(q->request_dns_packet), q->question_idna, q->answer, &truncated);
if (r < 0) {
log_debug_errno(r, "Failed to build reply packet: %m");
break;
@ -221,6 +229,7 @@ static void dns_stub_query_complete(DnsQuery *q) {
q->reply_dns_packet,
DNS_PACKET_ID(q->request_dns_packet),
q->answer_rcode,
truncated,
!!q->request_dns_packet->opt,
DNS_PACKET_DO(q->request_dns_packet),
dns_query_fully_authenticated(q));
@ -231,6 +240,7 @@ static void dns_stub_query_complete(DnsQuery *q) {
(void) dns_stub_send(q->manager, q->request_dns_stream, q->request_dns_packet, q->reply_dns_packet);
break;
}
case DNS_TRANSACTION_RCODE_FAILURE:
(void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, q->answer_rcode, dns_query_fully_authenticated(q));

View File

@ -723,7 +723,7 @@ int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) {
if (ms < 0)
return ms;
r = dns_packet_new(&p, protocol, ms);
r = dns_packet_new(&p, protocol, ms, DNS_PACKET_SIZE_MAX);
if (r < 0)
return r;

View File

@ -75,7 +75,7 @@ static void test_packet_from_file(const char* filename, bool canonical) {
assert_se(packet_size > 0);
assert_se(offset + 8 + packet_size <= data_size);
assert_se(dns_packet_new(&p, DNS_PROTOCOL_DNS, 0) >= 0);
assert_se(dns_packet_new(&p, DNS_PROTOCOL_DNS, 0, DNS_PACKET_SIZE_MAX) >= 0);
assert_se(dns_packet_append_blob(p, data + offset + 8, packet_size, NULL) >= 0);
assert_se(dns_packet_read_rr(p, &rr, NULL, NULL) >= 0);
@ -90,7 +90,7 @@ static void test_packet_from_file(const char* filename, bool canonical) {
assert_se(dns_resource_record_to_wire_format(rr, canonical) >= 0);
assert_se(dns_packet_new(&p2, DNS_PROTOCOL_DNS, 0) >= 0);
assert_se(dns_packet_new(&p2, DNS_PROTOCOL_DNS, 0, DNS_PACKET_SIZE_MAX) >= 0);
assert_se(dns_packet_append_blob(p2, rr->wire_format, rr->wire_format_size, NULL) >= 0);
assert_se(dns_packet_read_rr(p2, &rr2, NULL, NULL) >= 0);

View File

@ -27,7 +27,7 @@ static void test_dns_packet_new(void) {
for (i = 0; i <= DNS_PACKET_SIZE_MAX; i++) {
_cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
assert_se(dns_packet_new(&p, DNS_PROTOCOL_DNS, i) == 0);
assert_se(dns_packet_new(&p, DNS_PROTOCOL_DNS, i, DNS_PACKET_SIZE_MAX) == 0);
log_debug("dns_packet_new: %zu → %zu", i, p->allocated);
assert_se(p->allocated >= MIN(DNS_PACKET_SIZE_MAX, i));
@ -36,7 +36,7 @@ static void test_dns_packet_new(void) {
i = MIN(i * 2, DNS_PACKET_SIZE_MAX - 10);
}
assert_se(dns_packet_new(&p2, DNS_PROTOCOL_DNS, DNS_PACKET_SIZE_MAX + 1) == -EFBIG);
assert_se(dns_packet_new(&p2, DNS_PROTOCOL_DNS, DNS_PACKET_SIZE_MAX + 1, DNS_PACKET_SIZE_MAX) == -EFBIG);
}
int main(int argc, char **argv) {