test: dns-packet: pin suffix compression past the pointer range
The existing case only covered a fully-uncompressed re-occurrence. Every DNS-SD name shares an in-range suffix, so also pin that a name first seen beyond the pointer range still compresses from its mapped suffix -- one label plus a two-byte pointer per occurrence, map unchanged, and the pointer addressing the suffix's prior occurrence rather than any in-range octet. A padding helper puts the next append at a chosen offset, for the two sides of the boundary: at 0x3FFF the name is still mapped and re-occurs as a pointer whose wire bytes are checked independently of the parser; one past it, it is not mapped.
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
#include "log.h"
|
||||
#include "stdio-util.h"
|
||||
#include "tests.h"
|
||||
#include "unaligned.h"
|
||||
|
||||
#define BIT_QR (1 << 7)
|
||||
#define BIT_AA (1 << 2)
|
||||
@@ -1302,20 +1303,6 @@ TEST(packet_append_key_name_too_long) {
|
||||
ASSERT_EQ(packet->size, 12U);
|
||||
}
|
||||
|
||||
/* Pad the packet to exactly `target` bytes, so the next append starts at a chosen offset. Opaque
|
||||
* filler: nothing parses it, and it leaves the compression map empty. */
|
||||
static void pad_packet_to(DnsPacket *p, size_t target) {
|
||||
_cleanup_free_ void *filler = NULL;
|
||||
size_t gap;
|
||||
|
||||
ASSERT_GT(target, p->size);
|
||||
gap = target - p->size;
|
||||
ASSERT_NOT_NULL(filler = malloc0(gap));
|
||||
ASSERT_OK(dns_packet_append_blob(p, filler, gap, NULL));
|
||||
ASSERT_EQ(p->size, target);
|
||||
ASSERT_EQ(hashmap_size(p->names), 0u);
|
||||
}
|
||||
|
||||
/* The reader's label-type dispatch. RFC 1035 section 4.1.4 gives a length octet the top bits 00 and
|
||||
* a compression pointer 11, and reserves 01 and 10: it takes both bits to make a pointer. The
|
||||
* reserved octet points at a real name here, so a dispatch accepting either bit follows it and
|
||||
@@ -1345,6 +1332,14 @@ TEST(packet_read_name_rejects_reserved_label_types) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Every offset in the compression map must be pointer-expressible. */
|
||||
static void assert_offsets_in_pointer_range(const DnsPacket *p) {
|
||||
void *v;
|
||||
|
||||
HASHMAP_FOREACH(v, p->names)
|
||||
ASSERT_LE(PTR_TO_SIZE(v), (size_t) DNS_COMPRESSION_OFFSET_MAX);
|
||||
}
|
||||
|
||||
TEST(packet_append_name_beyond_compression_pointer_range) {
|
||||
_cleanup_(dns_packet_unrefp) DnsPacket *packet = NULL;
|
||||
size_t start;
|
||||
@@ -1382,6 +1377,7 @@ TEST(packet_append_name_beyond_compression_pointer_range) {
|
||||
/* allow_compression= */ true, /* canonical_candidate= */ false,
|
||||
NULL));
|
||||
ASSERT_EQ(hashmap_size(packet->names), n_mapped);
|
||||
assert_offsets_in_pointer_range(packet);
|
||||
|
||||
/* Names first seen within pointer range still compress: re-appending one takes exactly the
|
||||
* two bytes of a compression pointer. */
|
||||
@@ -1402,6 +1398,115 @@ TEST(packet_append_name_beyond_compression_pointer_range) {
|
||||
dns_packet_rewind(packet, before);
|
||||
ASSERT_OK(dns_packet_read_name(packet, &parsed, /* allow_compression= */ true, NULL));
|
||||
ASSERT_STREQ(parsed, "filler0.example.com");
|
||||
|
||||
/* A name beyond the pointer range whose suffix IS mapped in range -- the shape every DNS-SD
|
||||
* name hits via ".local" -- must still compress from that suffix: one label plus a two-byte
|
||||
* pointer, on every occurrence, with the map unchanged. */
|
||||
n_mapped = hashmap_size(packet->names);
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
size_t at, occurrence_start = packet->size;
|
||||
|
||||
ASSERT_OK(dns_packet_append_name(packet, "far.example.com",
|
||||
/* allow_compression= */ true,
|
||||
/* canonical_candidate= */ false,
|
||||
&at));
|
||||
ASSERT_GT(at, (size_t) DNS_COMPRESSION_OFFSET_MAX);
|
||||
ASSERT_EQ(packet->size, occurrence_start + 1 + STRLEN("far") + 2);
|
||||
ASSERT_EQ(hashmap_size(packet->names), n_mapped);
|
||||
assert_offsets_in_pointer_range(packet);
|
||||
|
||||
/* The pointer must address the suffix's mapped prior occurrence, not just any
|
||||
* in-range length octet (RFC 1035 section 4.1.4): compare it with the map. */
|
||||
size_t suffix = PTR_TO_SIZE(hashmap_get(packet->names, "example.com"));
|
||||
size_t pointer_at = occurrence_start + 1 + STRLEN("far");
|
||||
ASSERT_GT(suffix, 0u);
|
||||
ASSERT_LT(suffix, occurrence_start);
|
||||
ASSERT_EQ(unaligned_read_be16(DNS_PACKET_DATA(packet) + pointer_at),
|
||||
(uint16_t) (DNS_COMPRESSION_POINTER_FLAG | suffix));
|
||||
|
||||
parsed = mfree(parsed);
|
||||
dns_packet_rewind(packet, at);
|
||||
ASSERT_OK(dns_packet_read_name(packet, &parsed, /* allow_compression= */ true, NULL));
|
||||
ASSERT_STREQ(parsed, "far.example.com");
|
||||
}
|
||||
}
|
||||
|
||||
/* Pad the packet to exactly `target` bytes, so the next append starts at a chosen offset. Opaque
|
||||
* filler: nothing parses it, and it leaves the compression map empty. */
|
||||
static void pad_packet_to(DnsPacket *p, size_t target) {
|
||||
_cleanup_free_ void *filler = NULL;
|
||||
size_t gap;
|
||||
|
||||
ASSERT_GT(target, p->size);
|
||||
gap = target - p->size;
|
||||
ASSERT_NOT_NULL(filler = malloc0(gap));
|
||||
ASSERT_OK(dns_packet_append_blob(p, filler, gap, NULL));
|
||||
ASSERT_EQ(p->size, target);
|
||||
ASSERT_EQ(hashmap_size(p->names), 0u);
|
||||
}
|
||||
|
||||
TEST(packet_append_name_at_compression_offset_max) {
|
||||
_cleanup_(dns_packet_unrefp) DnsPacket *packet = NULL;
|
||||
size_t start;
|
||||
|
||||
/* A name whose first label starts at exactly the last pointer-expressible offset must be
|
||||
* mapped and referenced by pointer... */
|
||||
ASSERT_OK(dns_packet_new(&packet, DNS_PROTOCOL_DNS, 0, DNS_PACKET_SIZE_MAX));
|
||||
pad_packet_to(packet, DNS_COMPRESSION_OFFSET_MAX);
|
||||
|
||||
ASSERT_OK(dns_packet_append_name(packet, "edge.example.org",
|
||||
/* allow_compression= */ true, /* canonical_candidate= */ false,
|
||||
&start));
|
||||
ASSERT_EQ(start, (size_t) DNS_COMPRESSION_OFFSET_MAX);
|
||||
/* Only the whole name is mapped: its suffixes start past the bound. */
|
||||
ASSERT_EQ(hashmap_size(packet->names), 1u);
|
||||
|
||||
size_t before = packet->size;
|
||||
ASSERT_OK(dns_packet_append_name(packet, "edge.example.org",
|
||||
/* allow_compression= */ true, /* canonical_candidate= */ false,
|
||||
NULL));
|
||||
ASSERT_EQ(packet->size, before + 2);
|
||||
/* The pointer's wire bytes, independent of the parser: offset 0x3FFF under the tag bits. */
|
||||
ASSERT_EQ(DNS_PACKET_DATA(packet)[before], 0xFFu);
|
||||
ASSERT_EQ(DNS_PACKET_DATA(packet)[before + 1], 0xFFu);
|
||||
|
||||
_cleanup_free_ char *parsed = NULL;
|
||||
dns_packet_rewind(packet, before);
|
||||
ASSERT_OK(dns_packet_read_name(packet, &parsed, /* allow_compression= */ true, NULL));
|
||||
ASSERT_STREQ(parsed, "edge.example.org");
|
||||
|
||||
/* The same two bytes must be refused, not followed, where compression is not allowed. */
|
||||
parsed = mfree(parsed);
|
||||
dns_packet_rewind(packet, before);
|
||||
ASSERT_ERROR(dns_packet_read_name(packet, &parsed, /* allow_compression= */ false, NULL),
|
||||
EBADMSG);
|
||||
}
|
||||
|
||||
TEST(packet_append_name_past_compression_offset_max) {
|
||||
_cleanup_(dns_packet_unrefp) DnsPacket *packet = NULL;
|
||||
_cleanup_free_ char *parsed = NULL;
|
||||
size_t start, before;
|
||||
|
||||
/* One byte past the last pointer-expressible offset, a name stays out of the map and every
|
||||
* occurrence is re-emitted as labels. */
|
||||
ASSERT_OK(dns_packet_new(&packet, DNS_PROTOCOL_DNS, 0, DNS_PACKET_SIZE_MAX));
|
||||
pad_packet_to(packet, DNS_COMPRESSION_OFFSET_MAX + 1);
|
||||
|
||||
ASSERT_OK(dns_packet_append_name(packet, "past.example.org",
|
||||
/* allow_compression= */ true, /* canonical_candidate= */ false,
|
||||
&start));
|
||||
ASSERT_EQ(start, (size_t) DNS_COMPRESSION_OFFSET_MAX + 1);
|
||||
ASSERT_EQ(hashmap_size(packet->names), 0u);
|
||||
|
||||
before = packet->size;
|
||||
ASSERT_OK(dns_packet_append_name(packet, "past.example.org",
|
||||
/* allow_compression= */ true, /* canonical_candidate= */ false,
|
||||
NULL));
|
||||
ASSERT_EQ(packet->size, before + STRLEN("past.example.org") + 2);
|
||||
|
||||
dns_packet_rewind(packet, before);
|
||||
ASSERT_OK(dns_packet_read_name(packet, &parsed, /* allow_compression= */ true, NULL));
|
||||
ASSERT_STREQ(parsed, "past.example.org");
|
||||
}
|
||||
|
||||
/* The same case via dns_packet_append_rr(), where the name append is nested inside the rdlength
|
||||
|
||||
Reference in New Issue
Block a user