X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fresolve%2Fresolved-dns-packet.c;h=21756f566fb0674b8c6948f07de98a8c6e460acf;hp=05c62a497f256596d922d8e785cfcfa97753b62d;hb=2eec67acbb00593e414549a7e5b35eb7dd776b1b;hpb=6a6fc3df74c8ed91d4ea1813b3f6a6c3bf48a288 diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index 05c62a497..21756f566 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -22,6 +22,7 @@ #include "utf8.h" #include "util.h" #include "strv.h" +#include "unaligned.h" #include "resolved-dns-domain.h" #include "resolved-dns-packet.h" @@ -311,8 +312,7 @@ int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start) { if (r < 0) return r; - ((uint8_t*) d)[0] = (uint8_t) (v >> 8); - ((uint8_t*) d)[1] = (uint8_t) v; + unaligned_write_be16(d, v); return 0; } @@ -327,10 +327,7 @@ int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start) { if (r < 0) return r; - ((uint8_t*) d)[0] = (uint8_t) (v >> 24); - ((uint8_t*) d)[1] = (uint8_t) (v >> 16); - ((uint8_t*) d)[2] = (uint8_t) (v >> 8); - ((uint8_t*) d)[3] = (uint8_t) v; + unaligned_write_be32(d, v); return 0; } @@ -377,7 +374,8 @@ int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start return 0; } -int dns_packet_append_name(DnsPacket *p, const char *name, size_t *start) { +int dns_packet_append_name(DnsPacket *p, const char *name, + bool allow_compression, size_t *start) { size_t saved_size; int r; @@ -389,9 +387,11 @@ int dns_packet_append_name(DnsPacket *p, const char *name, size_t *start) { while (*name) { _cleanup_free_ char *s = NULL; char label[DNS_LABEL_MAX]; - size_t n; + size_t n = 0; + int k; - n = PTR_TO_SIZE(hashmap_get(p->names, name)); + if (allow_compression) + n = PTR_TO_SIZE(hashmap_get(p->names, name)); if (n > 0) { assert(n < p->size); @@ -414,19 +414,32 @@ int dns_packet_append_name(DnsPacket *p, const char *name, size_t *start) { if (r < 0) goto fail; - r = dns_packet_append_label(p, label, r, &n); - if (r < 0) + if (p->protocol == DNS_PROTOCOL_DNS) + k = dns_label_apply_idna(label, r, label, sizeof(label)); + else + k = dns_label_undo_idna(label, r, label, sizeof(label)); + if (k < 0) { + r = k; goto fail; + } + if (k > 0) + r = k; - r = hashmap_ensure_allocated(&p->names, dns_name_hash_func, dns_name_compare_func); + r = dns_packet_append_label(p, label, r, &n); if (r < 0) goto fail; - r = hashmap_put(p->names, s, SIZE_TO_PTR(n)); - if (r < 0) - goto fail; + if (allow_compression) { + r = hashmap_ensure_allocated(&p->names, &dns_name_hash_ops); + if (r < 0) + goto fail; + + r = hashmap_put(p->names, s, SIZE_TO_PTR(n)); + if (r < 0) + goto fail; - s = NULL; + s = NULL; + } } r = dns_packet_append_uint8(p, 0, NULL); @@ -453,7 +466,7 @@ int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, size_t *start) saved_size = p->size; - r = dns_packet_append_name(p, DNS_RESOURCE_KEY_NAME(k), NULL); + r = dns_packet_append_name(p, DNS_RESOURCE_KEY_NAME(k), true, NULL); if (r < 0) goto fail; @@ -499,11 +512,27 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { + case DNS_TYPE_SRV: + r = dns_packet_append_uint16(p, rr->srv.priority, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, rr->srv.weight, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, rr->srv.port, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_name(p, rr->srv.name, true, NULL); + break; + case DNS_TYPE_PTR: case DNS_TYPE_NS: case DNS_TYPE_CNAME: case DNS_TYPE_DNAME: - r = dns_packet_append_name(p, rr->ptr.name, NULL); + r = dns_packet_append_name(p, rr->ptr.name, true, NULL); break; case DNS_TYPE_HINFO: @@ -518,10 +547,19 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star case DNS_TYPE_TXT: { char **s; - STRV_FOREACH(s, rr->txt.strings) { - r = dns_packet_append_string(p, *s, NULL); + if (strv_isempty(rr->txt.strings)) { + /* RFC 6763, section 6.1 suggests to generate + * single empty string for an empty array. */ + + r = dns_packet_append_string(p, "", NULL); if (r < 0) goto fail; + } else { + STRV_FOREACH(s, rr->txt.strings) { + r = dns_packet_append_string(p, *s, NULL); + if (r < 0) + goto fail; + } } r = 0; @@ -537,11 +575,11 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star break; case DNS_TYPE_SOA: - r = dns_packet_append_name(p, rr->soa.mname, NULL); + r = dns_packet_append_name(p, rr->soa.mname, true, NULL); if (r < 0) goto fail; - r = dns_packet_append_name(p, rr->soa.rname, NULL); + r = dns_packet_append_name(p, rr->soa.rname, true, NULL); if (r < 0) goto fail; @@ -569,7 +607,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (r < 0) goto fail; - r = dns_packet_append_name(p, rr->mx.exchange, NULL); + r = dns_packet_append_name(p, rr->mx.exchange, true, NULL); break; case DNS_TYPE_LOC: @@ -589,19 +627,81 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (r < 0) goto fail; - r = dns_packet_append_uint16(p, rr->loc.latitude, NULL); + r = dns_packet_append_uint32(p, rr->loc.latitude, NULL); if (r < 0) goto fail; - r = dns_packet_append_uint16(p, rr->loc.longitude, NULL); + r = dns_packet_append_uint32(p, rr->loc.longitude, NULL); if (r < 0) goto fail; - r = dns_packet_append_uint16(p, rr->loc.altitude, NULL); + r = dns_packet_append_uint32(p, rr->loc.altitude, NULL); break; - case DNS_TYPE_SRV: case DNS_TYPE_SSHFP: + r = dns_packet_append_uint8(p, rr->sshfp.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->sshfp.fptype, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->sshfp.key, rr->sshfp.key_size, NULL); + break; + + case DNS_TYPE_DNSKEY: + r = dns_packet_append_uint16(p, dnskey_to_flags(rr), NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, 3u, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->dnskey.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->dnskey.key, rr->dnskey.key_size, NULL); + break; + + case DNS_TYPE_RRSIG: + r = dns_packet_append_uint16(p, rr->rrsig.type_covered, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->rrsig.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->rrsig.labels, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->rrsig.original_ttl, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->rrsig.expiration, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->rrsig.inception, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->rrsig.key_tag, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_name(p, rr->rrsig.signer, false, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->rrsig.signature, rr->rrsig.signature_size, NULL); + break; + case _DNS_TYPE_INVALID: /* unparseable */ default: @@ -699,8 +799,8 @@ int dns_packet_read_uint16(DnsPacket *p, uint16_t *ret, size_t *start) { if (r < 0) return r; - *ret = (((uint16_t) ((uint8_t*) d)[0]) << 8) | - ((uint16_t) ((uint8_t*) d)[1]); + *ret = unaligned_read_be16(d); + return 0; } @@ -714,10 +814,7 @@ int dns_packet_read_uint32(DnsPacket *p, uint32_t *ret, size_t *start) { if (r < 0) return r; - *ret = (((uint32_t) ((uint8_t*) d)[0]) << 24) | - (((uint32_t) ((uint8_t*) d)[1]) << 16) | - (((uint32_t) ((uint8_t*) d)[2]) << 8) | - ((uint32_t) ((uint8_t*) d)[3]); + *ret = unaligned_read_be32(d); return 0; } @@ -770,8 +867,9 @@ fail: return r; } -int dns_packet_read_name(DnsPacket *p, char **_ret, size_t *start) { - size_t saved_rindex, after_rindex = 0; +int dns_packet_read_name(DnsPacket *p, char **_ret, + bool allow_compression, size_t *start) { + size_t saved_rindex, after_rindex = 0, jump_barrier; _cleanup_free_ char *ret = NULL; size_t n = 0, allocated = 0; bool first = true; @@ -781,6 +879,7 @@ int dns_packet_read_name(DnsPacket *p, char **_ret, size_t *start) { assert(_ret); saved_rindex = p->rindex; + jump_barrier = p->rindex; for (;;) { uint8_t c, d; @@ -815,10 +914,10 @@ int dns_packet_read_name(DnsPacket *p, char **_ret, size_t *start) { else first = false; - memcpy(ret + n, t, c); + memcpy(ret + n, t, r); n += r; continue; - } else if ((c & 0xc0) == 0xc0) { + } else if (allow_compression && (c & 0xc0) == 0xc0) { uint16_t ptr; /* Pointer */ @@ -827,7 +926,7 @@ int dns_packet_read_name(DnsPacket *p, char **_ret, size_t *start) { goto fail; ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d; - if (ptr < DNS_PACKET_HEADER_SIZE || ptr >= saved_rindex) { + if (ptr < DNS_PACKET_HEADER_SIZE || ptr >= jump_barrier) { r = -EBADMSG; goto fail; } @@ -835,9 +934,13 @@ int dns_packet_read_name(DnsPacket *p, char **_ret, size_t *start) { if (after_rindex == 0) after_rindex = p->rindex; + /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */ + jump_barrier = ptr; p->rindex = ptr; - } else + } else { + r = -EBADMSG; goto fail; + } } if (!GREEDY_REALLOC(ret, allocated, n + 1)) { @@ -875,7 +978,7 @@ int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) { saved_rindex = p->rindex; - r = dns_packet_read_name(p, &name, NULL); + r = dns_packet_read_name(p, &name, true, NULL); if (r < 0) goto fail; @@ -905,6 +1008,43 @@ fail: return r; } +static int dns_packet_read_public_key(DnsPacket *p, size_t length, + void **dp, size_t *lengthp, + size_t *start) { + int r; + const void *d; + void *d2; + + r = dns_packet_read(p, length, &d, NULL); + if (r < 0) + return r; + + d2 = memdup(d, length); + if (!d2) + return -ENOMEM; + + *dp = d2; + *lengthp = length; + return 0; +} + +static bool loc_size_ok(uint8_t size) { + uint8_t m = size >> 4, e = size & 0xF; + + return m <= 9 && e <= 9 && (m > 0 || e == 0); +} + +static int dnskey_parse_flags(DnsResourceRecord *rr, uint16_t flags) { + assert(rr); + + if (flags & ~(DNSKEY_FLAG_SEP | DNSKEY_FLAG_ZONE_KEY)) + return -EBADMSG; + + rr->dnskey.zone_key_flag = flags & DNSKEY_FLAG_ZONE_KEY; + rr->dnskey.sep_flag = flags & DNSKEY_FLAG_SEP; + return 0; +} + int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; @@ -951,11 +1091,24 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { switch (rr->key->type) { + case DNS_TYPE_SRV: + r = dns_packet_read_uint16(p, &rr->srv.priority, NULL); + if (r < 0) + goto fail; + r = dns_packet_read_uint16(p, &rr->srv.weight, NULL); + if (r < 0) + goto fail; + r = dns_packet_read_uint16(p, &rr->srv.port, NULL); + if (r < 0) + goto fail; + r = dns_packet_read_name(p, &rr->srv.name, true, NULL); + break; + case DNS_TYPE_PTR: case DNS_TYPE_NS: case DNS_TYPE_CNAME: case DNS_TYPE_DNAME: - r = dns_packet_read_name(p, &rr->ptr.name, NULL); + r = dns_packet_read_name(p, &rr->ptr.name, true, NULL); break; case DNS_TYPE_HINFO: @@ -967,22 +1120,31 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { break; case DNS_TYPE_SPF: /* exactly the same as TXT */ - case DNS_TYPE_TXT: { - char *s; + case DNS_TYPE_TXT: + if (rdlength <= 0) { + /* RFC 6763, section 6.1 suggests to treat + * empty TXT RRs as equivalent to a TXT record + * with a single empty string. */ - while (p->rindex < offset + rdlength) { - r = dns_packet_read_string(p, &s, NULL); + r = strv_extend(&rr->txt.strings, ""); if (r < 0) goto fail; + } else { + while (p->rindex < offset + rdlength) { + char *s; - r = strv_consume(&rr->txt.strings, s); - if (r < 0) - goto fail; + r = dns_packet_read_string(p, &s, NULL); + if (r < 0) + goto fail; + + r = strv_consume(&rr->txt.strings, s); + if (r < 0) + goto fail; + } } r = 0; break; - } case DNS_TYPE_A: r = dns_packet_read_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL); @@ -993,11 +1155,11 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { break; case DNS_TYPE_SOA: - r = dns_packet_read_name(p, &rr->soa.mname, NULL); + r = dns_packet_read_name(p, &rr->soa.mname, true, NULL); if (r < 0) goto fail; - r = dns_packet_read_name(p, &rr->soa.rname, NULL); + r = dns_packet_read_name(p, &rr->soa.rname, true, NULL); if (r < 0) goto fail; @@ -1025,7 +1187,7 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { if (r < 0) goto fail; - r = dns_packet_read_name(p, &rr->mx.exchange, NULL); + r = dns_packet_read_name(p, &rr->mx.exchange, true, NULL); break; case DNS_TYPE_LOC: { @@ -1043,14 +1205,29 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { if (r < 0) goto fail; + if (!loc_size_ok(rr->loc.size)) { + r = -EBADMSG; + goto fail; + } + r = dns_packet_read_uint8(p, &rr->loc.horiz_pre, NULL); if (r < 0) goto fail; + if (!loc_size_ok(rr->loc.horiz_pre)) { + r = -EBADMSG; + goto fail; + } + r = dns_packet_read_uint8(p, &rr->loc.vert_pre, NULL); if (r < 0) goto fail; + if (!loc_size_ok(rr->loc.vert_pre)) { + r = -EBADMSG; + goto fail; + } + r = dns_packet_read_uint32(p, &rr->loc.latitude, NULL); if (r < 0) goto fail; @@ -1067,13 +1244,96 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { } else { dns_packet_rewind(p, pos); rr->unparseable = true; - /* fall through */ + goto unparseable; } } - case DNS_TYPE_SRV: case DNS_TYPE_SSHFP: + r = dns_packet_read_uint8(p, &rr->sshfp.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->sshfp.fptype, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_public_key(p, rdlength - 2, + &rr->sshfp.key, &rr->sshfp.key_size, + NULL); + break; + + case DNS_TYPE_DNSKEY: { + uint16_t flags; + uint8_t proto; + + r = dns_packet_read_uint16(p, &flags, NULL); + if (r < 0) + goto fail; + + r = dnskey_parse_flags(rr, flags); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &proto, NULL); + if (r < 0) + goto fail; + + /* protocol is required to be always 3 */ + if (proto != 3) { + r = -EBADMSG; + goto fail; + } + + r = dns_packet_read_uint8(p, &rr->dnskey.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_public_key(p, rdlength - 4, + &rr->dnskey.key, &rr->dnskey.key_size, + NULL); + break; + } + + case DNS_TYPE_RRSIG: + r = dns_packet_read_uint16(p, &rr->rrsig.type_covered, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->rrsig.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->rrsig.labels, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->rrsig.original_ttl, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->rrsig.expiration, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->rrsig.inception, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint16(p, &rr->rrsig.key_tag, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_name(p, &rr->rrsig.signer, false, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_public_key(p, offset + rdlength - p->rindex, + &rr->rrsig.signature, &rr->rrsig.signature_size, + NULL); + break; + default: + unparseable: r = dns_packet_read(p, rdlength, &d, NULL); if (r < 0) goto fail; @@ -1113,6 +1373,9 @@ int dns_packet_extract(DnsPacket *p) { unsigned n, i; int r; + if (p->extracted) + return 0; + saved_rindex = p->rindex; dns_packet_rewind(p, DNS_PACKET_HEADER_SIZE); @@ -1164,6 +1427,8 @@ int dns_packet_extract(DnsPacket *p) { p->answer = answer; answer = NULL; + p->extracted = true; + r = 0; finish: @@ -1199,3 +1464,15 @@ static const char* const dns_protocol_table[_DNS_PROTOCOL_MAX] = { [DNS_PROTOCOL_LLMNR] = "llmnr", }; DEFINE_STRING_TABLE_LOOKUP(dns_protocol, DnsProtocol); + +static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = { + [DNSSEC_ALGORITHM_RSAMD5] = "RSAMD5", + [DNSSEC_ALGORITHM_DH] = "DH", + [DNSSEC_ALGORITHM_DSA] = "DSA", + [DNSSEC_ALGORITHM_ECC] = "ECC", + [DNSSEC_ALGORITHM_RSASHA1] = "RSASHA1", + [DNSSEC_ALGORITHM_INDIRECT] = "INDIRECT", + [DNSSEC_ALGORITHM_PRIVATEDNS] = "PRIVATEDNS", + [DNSSEC_ALGORITHM_PRIVATEOID] = "PRIVATEOID", +}; +DEFINE_STRING_TABLE_LOOKUP(dnssec_algorithm, int);