Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #173 #174

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions doc/compression
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

DNS Packet Compression Algorithm
================================

Original algo was not performant enough and caused vulnerability to denials of
service. Specifically, building rbtree for a DNS request containing many lables
is a bad idea because 1/ the tree is built pro-actively, even before the actual
need to compress anything; 2/ rbtree is inadequate choice because adding
elements leeds to re-balancing, which, in due turn, calls many comparisons.

ldns_llnode_t type is added to host2wire.h, and implementation of
ldns_dname2buffer_wire_compress() function is re-designed. The new algo is lazy.

Consider five DNAMEs being put on the wire:


start
^ o #
| o #
| o x #
D o x---------C
N o x
A o x ~
M o----A * ~
E o * ~
| o---------B ~
| o ~
------------------------------>
packet on the wire end


Every time we add another dname, we move left to right measuring size of match
(som) from the end between the dname being added and a dname seen earlier. We
consider early dname only if the current som equates to the one stored for that
early dname (refer to A and C being such stitching points). Thus iterating, we
seek the greatest som and remember corresponding dname. Finally, we just splice
the beginning (minus som) of the dname being added with the reference to the
early dname remembered. Also adding another node to the linked list
(ldns_llnode_t) containing the som/dname having just been processed.

Vitaly Zuevsky
130 changes: 59 additions & 71 deletions host2wire.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,10 @@ ldns_dname2buffer_wire(ldns_buffer *buffer, const ldns_rdf *name)
}

ldns_status
ldns_dname2buffer_wire_compress(ldns_buffer *buffer, const ldns_rdf *name, ldns_rbtree_t *compression_data)
ldns_dname2buffer_wire_compress(ldns_buffer *buffer, const ldns_rdf *name, ldns_llnode_t *compression_data)
{
ldns_rbnode_t *node;
uint8_t *data;
size_t size;
ldns_rdf *label;
ldns_rdf *rest;
ldns_status s;
ldns_llnode_t *node_ref, *node = compression_data;
size_t len, som = 0; /* size of match from the end */

/* If no tree, just add the data */
if(!compression_data)
Expand All @@ -42,67 +38,62 @@ ldns_dname2buffer_wire_compress(ldns_buffer *buffer, const ldns_rdf *name, ldns_
return ldns_buffer_status(buffer);
}

/* No labels left, write final zero */
if(ldns_dname_label_count(name)==0)
/* Not sure if "owner" below is guaranteed of TYPE_DNAME */
if (ldns_rdf_get_type(name) != LDNS_RDF_TYPE_DNAME) return LDNS_STATUS_INVALID_RDF_TYPE;

if (ldns_rdf_size(name) > 256) return LDNS_STATUS_DOMAINNAME_OVERFLOW;

for (ldns_llnode_t *n; (n = node->next) != NULL; node = n)
{
if(ldns_buffer_reserve(buffer,1))
char *lp1, *lp2, *lps;

if (node->som != som) continue; /* som is a stitching point */

/* for an early dname we first scan backward from the som point */
lp1 = lps = node->dname + node->dsize - som;

lp2 = (char*) ldns_rdf_data(name) + ldns_rdf_size(name) - som;

while (node->dname <= --lp1 && (char*) ldns_rdf_data(name) <= --lp2)
{
ldns_buffer_write_u8(buffer, 0);
if (LDNS_DNAME_NORMALIZE((int) *lp1) !=
LDNS_DNAME_NORMALIZE((int) *lp2)) break;
}
return ldns_buffer_status(buffer);
}
lp1++;

/* Can we find the name in the tree? */
if((node = ldns_rbtree_search(compression_data, name)) != NULL)
{
/* Found */
uint16_t position = (uint16_t) (intptr_t) node->data | 0xC000;
if (ldns_buffer_reserve(buffer, 2))
/* secondly, we parse early dname forward to get label boundary right */
for (lp2 = node->dname; lp2 < lp1; ) lp2 += *(uint8_t*) lp2 + 1;

if (lps - lp2 > 1) /* growing som: update */
{
ldns_buffer_write_u16(buffer, position);
som += lps - lp2;
node_ref = node;
}
return ldns_buffer_status(buffer);
}
else
{
/* Not found. Write cache entry, take off first label, write it, */
/* try again with the rest of the name. */
if (ldns_buffer_position(buffer) < 16384) {
ldns_rdf *key;

node = LDNS_MALLOC(ldns_rbnode_t);
if(!node)
{
return LDNS_STATUS_MEM_ERR;
}
/* populate the node, and the buffer */
node->som = som;
node->dsize = ldns_rdf_size(name);
node->buffer_start = ldns_buffer_position(buffer);
memcpy(node->dname, ldns_rdf_data(name), ldns_rdf_size(name));

key = ldns_rdf_clone(name);
if (!key) {
LDNS_FREE(node);
return LDNS_STATUS_MEM_ERR;
}
node->key = key;
node->data = (void *) (intptr_t) ldns_buffer_position(buffer);
if(!ldns_rbtree_insert(compression_data,node))
{
/* fprintf(stderr,"Name not found but now it's there?\n"); */
ldns_rdf_deep_free(key);
LDNS_FREE(node);
}
}
label = ldns_dname_label(name, 0);
rest = ldns_dname_left_chop(name);
size = ldns_rdf_size(label) - 1; /* Don't want the final zero */
data = ldns_rdf_data(label);
if(ldns_buffer_reserve(buffer, size))
if ( (node->next = LDNS_CALLOC(ldns_llnode_t, 1)) == NULL) return LDNS_STATUS_MEM_ERR;

len = ldns_rdf_size(name) - som;
if (ldns_buffer_reserve(buffer, len))
{
/* if som = 0 => final 0 is included */
ldns_buffer_write(buffer, ldns_rdf_data(name), len);
}
if (som)
{
uint16_t position = (uint16_t) (node_ref->buffer_start +
node_ref->dsize - som) | 0xC000;
if (ldns_buffer_reserve(buffer, 2))
{
ldns_buffer_write(buffer, data, size);
ldns_buffer_write_u16(buffer, position);
}
ldns_rdf_deep_free(label);
s = ldns_dname2buffer_wire_compress(buffer, rest, compression_data);
ldns_rdf_deep_free(rest);
return s;
}
return ldns_buffer_status(buffer);
}

ldns_status
Expand All @@ -112,7 +103,7 @@ ldns_rdf2buffer_wire(ldns_buffer *buffer, const ldns_rdf *rdf)
}

ldns_status
ldns_rdf2buffer_wire_compress(ldns_buffer *buffer, const ldns_rdf *rdf, ldns_rbtree_t *compression_data)
ldns_rdf2buffer_wire_compress(ldns_buffer *buffer, const ldns_rdf *rdf, ldns_llnode_t *compression_data)
{
/* If it's a DNAME, call that function to get compression */
if(compression_data && ldns_rdf_get_type(rdf) == LDNS_RDF_TYPE_DNAME)
Expand Down Expand Up @@ -247,7 +238,7 @@ ldns_rr2buffer_wire(ldns_buffer *buffer, const ldns_rr *rr, int section)
}

ldns_status
ldns_rr2buffer_wire_compress(ldns_buffer *buffer, const ldns_rr *rr, int section, ldns_rbtree_t *compression_data)
ldns_rr2buffer_wire_compress(ldns_buffer *buffer, const ldns_rr *rr, int section, ldns_llnode_t *compression_data)
{
uint16_t i;
uint16_t rdl_pos = 0;
Expand Down Expand Up @@ -364,30 +355,27 @@ ldns_hdr2buffer_wire(ldns_buffer *buffer, const ldns_pkt *packet)
return ldns_buffer_status(buffer);
}

static void
compression_node_free(ldns_rbnode_t *node, void *arg)
{
(void)arg; /* Yes, dear compiler, it is used */
ldns_rdf_deep_free((ldns_rdf *)node->key);
LDNS_FREE(node);
}

ldns_status
ldns_pkt2buffer_wire(ldns_buffer *buffer, const ldns_pkt *packet)
{
ldns_status status;
ldns_rbtree_t *compression_data = ldns_rbtree_create((int (*)(const void *, const void *))ldns_dname_compare);

ldns_llnode_t *compression_data = LDNS_CALLOC(ldns_llnode_t, 1);
if (!compression_data) return LDNS_STATUS_MEM_ERR;

status = ldns_pkt2buffer_wire_compress(buffer, packet, compression_data);

ldns_traverse_postorder(compression_data,compression_node_free,NULL);
ldns_rbtree_free(compression_data);
for (ldns_llnode_t *ptr; compression_data; compression_data = ptr)
{
ptr = compression_data->next;
LDNS_FREE(compression_data);
}

return status;
}

ldns_status
ldns_pkt2buffer_wire_compress(ldns_buffer *buffer, const ldns_pkt *packet, ldns_rbtree_t *compression_data)
ldns_pkt2buffer_wire_compress(ldns_buffer *buffer, const ldns_pkt *packet, ldns_llnode_t *compression_data)
{
ldns_rr_list *rr_list;
uint16_t i;
Expand Down
26 changes: 22 additions & 4 deletions ldns/host2wire.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,24 @@
extern "C" {
#endif

/**
* Linked list node holding state for compression
* Order of dnames matters but not controlled, so
* single walk in the order will suffice for this algo
*/
typedef
struct ldns_llnode_t
{
struct ldns_llnode_t *next;

size_t som; /* size of match from the end */
size_t buffer_start; /* offset of the start */

size_t dsize;
char dname[256]; /* size limit per RFC1035 */
}
ldns_llnode_t;

/**
* Copies the dname data to the buffer in wire format
* \param[out] *buffer buffer to append the result to
Expand All @@ -46,7 +64,7 @@ ldns_status ldns_dname2buffer_wire(ldns_buffer *buffer, const ldns_rdf *name);
* \param[out] *compression_data data structure holding state for compression
* \return ldns_status
*/
ldns_status ldns_dname2buffer_wire_compress(ldns_buffer *buffer, const ldns_rdf *name, ldns_rbtree_t *compression_data);
ldns_status ldns_dname2buffer_wire_compress(ldns_buffer *buffer, const ldns_rdf *name, ldns_llnode_t *compression_data);

/**
* Copies the rdata data to the buffer in wire format
Expand All @@ -63,7 +81,7 @@ ldns_status ldns_rdf2buffer_wire(ldns_buffer *output, const ldns_rdf *rdf);
* \param[out] *compression_data data structure holding state for compression
* \return ldns_status
*/
ldns_status ldns_rdf2buffer_wire_compress(ldns_buffer *output, const ldns_rdf *rdf, ldns_rbtree_t *compression_data);
ldns_status ldns_rdf2buffer_wire_compress(ldns_buffer *output, const ldns_rdf *rdf, ldns_llnode_t *compression_data);

/**
* Copies the rdata data to the buffer in wire format
Expand Down Expand Up @@ -100,7 +118,7 @@ ldns_status ldns_rr2buffer_wire(ldns_buffer *output,
ldns_status ldns_rr2buffer_wire_compress(ldns_buffer *output,
const ldns_rr *rr,
int section,
ldns_rbtree_t *compression_data);
ldns_llnode_t *compression_data);

/**
* Copies the rr data to the buffer in wire format, in canonical format
Expand Down Expand Up @@ -153,7 +171,7 @@ ldns_status ldns_pkt2buffer_wire(ldns_buffer *output, const ldns_pkt *pkt);
* \param[out] *compression_data data structure holding state for compression
* \return ldns_status
*/
ldns_status ldns_pkt2buffer_wire_compress(ldns_buffer *output, const ldns_pkt *pkt, ldns_rbtree_t *compression_data);
ldns_status ldns_pkt2buffer_wire_compress(ldns_buffer *output, const ldns_pkt *pkt, ldns_llnode_t *compression_data);

/**
* Copies the rr_list data to the buffer in wire format
Expand Down