2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-22 18:19:42 +00:00

Fuzzing and benchmarking for dns_name_fromwire()

Since this is very sensitive code which has often had security
problems in many DNS implementations, it needs a decent amount of
validation. This fuzzer ensures that the new code has the same output
as the old code, and that it doesn't take longer than a second.

The benchmark uses the fuzzer's copy of the old dns_name_fromwire()
code to compare a number of scenarios: many compression pointers, many
labels, long labels, random data, with/without downcasing.
This commit is contained in:
Tony Finch 2022-11-07 16:22:48 +00:00 committed by Tony Finch
parent 1c0f607811
commit 04f3000dfc
55 changed files with 517 additions and 1 deletions

1
fuzz/.gitignore vendored
View File

@ -4,6 +4,7 @@
/dns_message_checksig
/dns_message_parse
/dns_name_fromtext_target
/dns_name_fromwire
/dns_rdata_fromtext
/dns_rdata_fromwire_text
/isc_lex_getmastertoken

View File

@ -26,6 +26,7 @@ check_PROGRAMS = \
dns_message_checksig \
dns_message_parse \
dns_name_fromtext_target \
dns_name_fromwire \
dns_rdata_fromtext \
dns_rdata_fromwire_text \
isc_lex_getmastertoken \
@ -36,11 +37,17 @@ EXTRA_DIST = \
dns_message_checksig.in \
dns_message_parse.in \
dns_name_fromtext_target.in \
dns_name_fromwire.in \
dns_rdata_fromtext.in \
dns_rdata_fromwire_text.in \
isc_lex_getmastertoken.in \
isc_lex_gettoken.in
dns_name_fromwire_SOURCES = \
dns_name_fromwire.c \
old.c \
old.h
TESTS = $(check_PROGRAMS)
if HAVE_FUZZ_LOG_COMPILER

104
fuzz/dns_name_fromwire.c Normal file
View File

@ -0,0 +1,104 @@
/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#include <isc/ascii.h>
#include <isc/buffer.h>
#include <isc/util.h>
#include <dns/compress.h>
#include <dns/fixedname.h>
#include <dns/name.h>
#include "fuzz.h"
#include "old.h"
bool debug = false;
int
LLVMFuzzerInitialize(int *argc __attribute__((unused)),
char ***argv __attribute__((unused))) {
return (0);
}
int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
isc_result_t new_result;
isc_result_t old_result;
dns_fixedname_t new_fixed;
dns_fixedname_t old_fixed;
dns_name_t *new_name = dns_fixedname_initname(&new_fixed);
dns_name_t *old_name = dns_fixedname_initname(&old_fixed);
uint8_t *new_offsets;
uint8_t *old_offsets;
dns_decompress_t dctx = DNS_DECOMPRESS_PERMITTED;
isc_buffer_t new_buf;
isc_buffer_t old_buf;
/*
* Output buffers may be partially used or undersized.
*/
if (size > 0) {
uint8_t add = *data++;
size--;
isc_buffer_add(&new_fixed.buffer, add);
isc_buffer_add(&old_fixed.buffer, add);
}
/*
* timeout faster if we hit a pointer loop
*/
alarm(1);
/*
* We shift forward by half the input data to make an area
* that pointers can refer back to.
*/
isc_buffer_constinit(&new_buf, data, size);
isc_buffer_add(&new_buf, size);
isc_buffer_setactive(&new_buf, size);
isc_buffer_forward(&new_buf, size / 2);
new_result = dns_name_fromwire(new_name, &new_buf, dctx, 0, NULL);
isc_buffer_constinit(&old_buf, data, size);
isc_buffer_add(&old_buf, size);
isc_buffer_setactive(&old_buf, size);
isc_buffer_forward(&old_buf, size / 2);
old_result = old_name_fromwire(old_name, &old_buf, dctx, 0, NULL);
REQUIRE(new_result == old_result);
REQUIRE(dns_name_equal(new_name, old_name));
REQUIRE(new_name->labels == old_name->labels);
new_offsets = new_name->offsets;
old_offsets = old_name->offsets;
REQUIRE(new_offsets != NULL && old_offsets != NULL);
REQUIRE(memcmp(new_offsets, old_offsets, old_name->labels) == 0);
REQUIRE(new_fixed.buffer.current == old_fixed.buffer.current);
REQUIRE(new_fixed.buffer.active == old_fixed.buffer.active);
REQUIRE(new_fixed.buffer.used == old_fixed.buffer.used);
REQUIRE(new_fixed.buffer.length == old_fixed.buffer.length);
REQUIRE(new_buf.base == old_buf.base);
REQUIRE(new_buf.current == old_buf.current);
REQUIRE(new_buf.active == old_buf.active);
REQUIRE(new_buf.used == old_buf.used);
REQUIRE(new_buf.length == old_buf.length);
return (0);
}

View File

@ -0,0 +1 @@
<EFBFBD>0

View File

@ -0,0 +1 @@
<EFBFBD>0<EFBFBD>w

View File

@ -0,0 +1 @@
0<EFBFBD>

View File

@ -0,0 +1,2 @@
<EFBFBD><EFBFBD>
<EFBFBD>

View File

@ -0,0 +1 @@

223
fuzz/old.c Normal file
View File

@ -0,0 +1,223 @@
/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
#include <isc/ascii.h>
#include <isc/buffer.h>
#include <isc/result.h>
#include <isc/types.h>
#include <isc/util.h>
#include <dns/compress.h>
#include <dns/types.h>
/*
*/
#include "old.h"
/*
* code copied from lib/dns/name.c as of commit
* 6967973568fe80b03e1729259f8907ce8792be34
*/
typedef enum { fw_start = 0, fw_ordinary, fw_newcurrent } fw_state;
#define VALID_NAME(n) ISC_MAGIC_VALID(n, DNS_NAME_MAGIC)
#define INIT_OFFSETS(name, var, default_offsets) \
if ((name)->offsets != NULL) \
var = (name)->offsets; \
else \
var = (default_offsets);
#define MAKE_EMPTY(name) \
do { \
name->ndata = NULL; \
name->length = 0; \
name->labels = 0; \
name->attributes.absolute = false; \
} while (0)
#define BINDABLE(name) (!name->attributes.readonly && !name->attributes.dynamic)
isc_result_t
old_name_fromwire(dns_name_t *name, isc_buffer_t *source, dns_decompress_t dctx,
unsigned int options, isc_buffer_t *target) {
unsigned char *cdata, *ndata;
unsigned int cused; /* Bytes of compressed name data used */
unsigned int nused, labels, n, nmax;
unsigned int current, new_current, biggest_pointer;
bool done;
fw_state state = fw_start;
unsigned int c;
unsigned char *offsets;
dns_offsets_t odata;
bool downcase;
bool seen_pointer;
/*
* Copy the possibly-compressed name at source into target,
* decompressing it. Loop prevention is performed by checking
* the new pointer against biggest_pointer.
*/
REQUIRE(VALID_NAME(name));
REQUIRE((target != NULL && ISC_BUFFER_VALID(target)) ||
(target == NULL && ISC_BUFFER_VALID(name->buffer)));
downcase = ((options & DNS_NAME_DOWNCASE) != 0);
if (target == NULL && name->buffer != NULL) {
target = name->buffer;
isc_buffer_clear(target);
}
REQUIRE(BINDABLE(name));
INIT_OFFSETS(name, offsets, odata);
/*
* Make 'name' empty in case of failure.
*/
MAKE_EMPTY(name);
/*
* Initialize things to make the compiler happy; they're not required.
*/
n = 0;
new_current = 0;
/*
* Set up.
*/
labels = 0;
done = false;
ndata = isc_buffer_used(target);
nused = 0;
seen_pointer = false;
/*
* Find the maximum number of uncompressed target name
* bytes we are willing to generate. This is the smaller
* of the available target buffer length and the
* maximum legal domain name length (255).
*/
nmax = isc_buffer_availablelength(target);
if (nmax > DNS_NAME_MAXWIRE) {
nmax = DNS_NAME_MAXWIRE;
}
cdata = isc_buffer_current(source);
cused = 0;
current = source->current;
biggest_pointer = current;
/*
* Note: The following code is not optimized for speed, but
* rather for correctness. Speed will be addressed in the future.
*/
while (current < source->active && !done) {
c = *cdata++;
current++;
if (!seen_pointer) {
cused++;
}
switch (state) {
case fw_start:
if (c < 64) {
offsets[labels] = nused;
labels++;
if (nused + c + 1 > nmax) {
goto full;
}
nused += c + 1;
*ndata++ = c;
if (c == 0) {
done = true;
}
n = c;
state = fw_ordinary;
} else if (c >= 192) {
/*
* 14-bit compression pointer
*/
if (!dns_decompress_getpermitted(dctx)) {
return (DNS_R_DISALLOWED);
}
new_current = c & 0x3F;
state = fw_newcurrent;
} else {
return (DNS_R_BADLABELTYPE);
}
break;
case fw_ordinary:
if (downcase) {
c = isc_ascii_tolower(c);
}
*ndata++ = c;
n--;
if (n == 0) {
state = fw_start;
}
break;
case fw_newcurrent:
new_current *= 256;
new_current += c;
if (new_current >= biggest_pointer) {
return (DNS_R_BADPOINTER);
}
biggest_pointer = new_current;
current = new_current;
cdata = (unsigned char *)source->base + current;
seen_pointer = true;
state = fw_start;
break;
default:
FATAL_ERROR("Unknown state %d", state);
/* Does not return. */
}
}
if (!done) {
return (ISC_R_UNEXPECTEDEND);
}
name->ndata = (unsigned char *)target->base + target->used;
name->labels = labels;
name->length = nused;
name->attributes.absolute = true;
isc_buffer_forward(source, cused);
isc_buffer_add(target, name->length);
return (ISC_R_SUCCESS);
full:
if (nmax == DNS_NAME_MAXWIRE) {
/*
* The name did not fit even though we had a buffer
* big enough to fit a maximum-length name.
*/
return (DNS_R_NAMETOOLONG);
} else {
/*
* The name might fit if only the caller could give us a
* big enough buffer.
*/
return (ISC_R_NOSPACE);
}
}

21
fuzz/old.h Normal file
View File

@ -0,0 +1,21 @@
/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
#pragma once
/*%
* For verifying no functional change in the rewrite of dns_name_fromwire()
*/
isc_result_t
old_name_fromwire(dns_name_t *name, isc_buffer_t *source, dns_decompress_t dctx,
unsigned int options, isc_buffer_t *target);

View File

@ -1,3 +1,4 @@
/ascii
/compress
/dns_name_fromwire
/siphash

View File

@ -2,7 +2,8 @@ include $(top_srcdir)/Makefile.top
AM_CPPFLAGS += \
$(LIBISC_CFLAGS) \
$(LIBDNS_CFLAGS)
$(LIBDNS_CFLAGS) \
-I$(top_srcdir)/fuzz
LDADD += \
$(LIBISC_LIBS) \
@ -11,4 +12,10 @@ LDADD += \
noinst_PROGRAMS = \
ascii \
compress \
dns_name_fromwire \
siphash
dns_name_fromwire_SOURCES = \
$(top_builddir)/fuzz/old.c \
$(top_builddir)/fuzz/old.h \
dns_name_fromwire.c

View File

@ -0,0 +1,145 @@
/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#include <isc/ascii.h>
#include <isc/buffer.h>
#include <isc/random.h>
#include <isc/time.h>
#include <isc/util.h>
#include <dns/compress.h>
#include <dns/fixedname.h>
#include <dns/name.h>
#include "old.h"
static uint32_t
old_bench(const uint8_t *data, size_t size) {
isc_result_t result;
dns_fixedname_t fixed;
dns_name_t *name = dns_fixedname_initname(&fixed);
dns_decompress_t dctx = DNS_DECOMPRESS_PERMITTED;
isc_buffer_t buf;
uint32_t count = 0;
isc_buffer_constinit(&buf, data, size);
isc_buffer_add(&buf, size);
isc_buffer_setactive(&buf, size);
while (isc_buffer_consumedlength(&buf) < size) {
result = old_name_fromwire(name, &buf, dctx, 0, NULL);
if (result != ISC_R_SUCCESS) {
isc_buffer_forward(&buf, 1);
}
count++;
}
return (count);
}
static uint32_t
new_bench(const uint8_t *data, size_t size) {
isc_result_t result;
dns_fixedname_t fixed;
dns_name_t *name = dns_fixedname_initname(&fixed);
dns_decompress_t dctx = DNS_DECOMPRESS_PERMITTED;
isc_buffer_t buf;
uint32_t count = 0;
isc_buffer_constinit(&buf, data, size);
isc_buffer_add(&buf, size);
isc_buffer_setactive(&buf, size);
while (isc_buffer_consumedlength(&buf) < size) {
result = dns_name_fromwire(name, &buf, dctx, 0, NULL);
if (result != ISC_R_SUCCESS) {
isc_buffer_forward(&buf, 1);
}
count++;
}
return (count);
}
static void
oldnew_bench(const uint8_t *data, size_t size) {
isc_time_t t0;
isc_time_now_hires(&t0);
uint32_t n1 = old_bench(data, size);
isc_time_t t1;
isc_time_now_hires(&t1);
uint32_t n2 = new_bench(data, size);
isc_time_t t2;
isc_time_now_hires(&t2);
double t01 = (double)isc_time_microdiff(&t1, &t0);
double t12 = (double)isc_time_microdiff(&t2, &t1);
printf(" old %u / %f ms; %f / us\n", n1, t01 / 1000.0, n1 / t01);
printf(" new %u / %f ms; %f / us\n", n2, t12 / 1000.0, n2 / t12);
printf(" old/new %f or %f\n", t01 / t12, t12 / t01);
}
#define NAMES 1000
static uint8_t buf[1024 * NAMES];
int
main(void) {
unsigned int p;
printf("random buffer\n");
isc_random_buf(buf, sizeof(buf));
oldnew_bench(buf, sizeof(buf));
p = 0;
for (unsigned int name = 0; name < NAMES; name++) {
unsigned int start = p;
unsigned int prev = p;
buf[p++] = 0;
for (unsigned int label = 0; label < 127; label++) {
unsigned int ptr = prev - start;
prev = p;
buf[p++] = 1;
buf[p++] = 'a';
buf[p++] = 0xC0 | (ptr >> 8);
buf[p++] = 0xFF & ptr;
}
}
printf("127 compression pointers\n");
oldnew_bench(buf, p);
p = 0;
for (unsigned int name = 0; name < NAMES; name++) {
for (unsigned int label = 0; label < 127; label++) {
buf[p++] = 1;
buf[p++] = 'a';
}
buf[p++] = 0;
}
printf("127 sequential labels\n");
oldnew_bench(buf, p);
p = 0;
for (unsigned int name = 0; name < NAMES; name++) {
for (unsigned int label = 0; label < 4; label++) {
buf[p++] = 62;
for (unsigned int c = 0; c < 62; c++) {
buf[p++] = 'a';
}
}
buf[p++] = 0;
}
printf("4 long sequential labels\n");
oldnew_bench(buf, p);
}