- Fix #1288: [FR] Improve fuzzing of unbound by adapting the netbound

program.
This commit is contained in:
W.C.A. Wijngaards 2025-05-21 12:41:54 +02:00
parent 342a0f48e3
commit ff7dfd52a2
6 changed files with 196 additions and 22 deletions

View file

@ -1,3 +1,7 @@
21 May 2025: Wouter
- Fix #1288: [FR] Improve fuzzing of unbound by adapting the netbound
program.
20 May 2025: Yorgos
- Merge #1285: RST man pages. It introduces restructuredText man pages
to sync the online and source code man page documentation.

View file

@ -900,8 +900,10 @@ run_scenario(struct replay_runtime* runtime)
runtime->now->evt_type == repevt_front_reply) {
answer_check_it(runtime);
advance_moment(runtime);
} else if(pending_matches_range(runtime, &entry, &pending)) {
answer_callback_from_entry(runtime, entry, pending);
} else if(runtime->now && pending_matches_range(runtime,
&entry, &pending)) {
if(entry)
answer_callback_from_entry(runtime, entry, pending);
} else {
do_moment_and_advance(runtime);
}
@ -1274,7 +1276,7 @@ struct serviced_query* outnet_serviced_query(struct outside_network* outnet,
(flags&~(BIT_RD|BIT_CD))?" MORE":"", (dnssec)?" DO":"");
/* create packet with EDNS */
pend->buffer = sldns_buffer_new(512);
pend->buffer = sldns_buffer_new(4096);
log_assert(pend->buffer);
sldns_buffer_write_u16(pend->buffer, 0); /* id */
sldns_buffer_write_u16(pend->buffer, flags);
@ -1334,7 +1336,13 @@ struct serviced_query* outnet_serviced_query(struct outside_network* outnet,
edns.opt_list_in = NULL;
edns.opt_list_out = per_upstream_opt_list;
edns.opt_list_inplace_cb_out = NULL;
attach_edns_record(pend->buffer, &edns);
if(sldns_buffer_capacity(pend->buffer) >=
sldns_buffer_limit(pend->buffer)
+calc_edns_field_size(&edns)) {
attach_edns_record(pend->buffer, &edns);
} else {
verbose(VERB_ALGO, "edns field too large to fit");
}
}
memcpy(&pend->addr, addr, addrlen);
pend->addrlen = addrlen;

View file

@ -795,7 +795,7 @@ macro_expand(rbtree_type* store, struct replay_runtime* runtime, char** text)
char buf[10240];
char* at = *text;
size_t len = macro_length(at);
int dofunc = 0;
int tries = 0, dofunc = 0;
char* arithstart = NULL;
if(len >= sizeof(buf))
return NULL; /* too long */
@ -834,6 +834,8 @@ macro_expand(rbtree_type* store, struct replay_runtime* runtime, char** text)
/* actual macro text expansion */
while(*at) {
size_t remain = sizeof(buf)-strlen(buf);
if(tries++ > 10000)
return NULL; /* looks like got into an infinite loop, bail out */
if(strncmp(at, "${", 2) == 0) {
at = do_macro_recursion(store, runtime, at, remain);
} else if(*at == '$') {

View file

@ -339,6 +339,35 @@ static void remove_configfile(void)
cfgfiles = NULL;
}
/** perform the playback on the playback_file with the args. */
static int
perform_playback(char* playback_file, int pass_argc, char** pass_argv)
{
struct replay_scenario* scen = NULL;
int c, res;
/* setup test environment */
scen = setup_playback(playback_file, &pass_argc, pass_argv);
/* init fake event backend */
fake_event_init(scen);
pass_argv[pass_argc] = NULL;
echo_cmdline(pass_argc, pass_argv);
/* run the normal daemon */
res = daemon_main(pass_argc, pass_argv);
fake_event_cleanup();
for(c=1; c<pass_argc; c++)
free(pass_argv[c]);
return res;
}
/* For fuzzing the main routine is replaced with
* LLVMFuzzerTestOneInput. */
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
#define main dummy_main
#endif
/**
* Main fake event test program. Setup, teardown and report errors.
* @param argc: arg count.
@ -354,7 +383,6 @@ main(int argc, char* argv[])
char* playback_file = NULL;
int init_optind = optind;
char* init_optarg = optarg;
struct replay_scenario* scen = NULL;
/* we do not want the test to depend on the timezone */
(void)putenv("TZ=UTC");
@ -462,24 +490,11 @@ main(int argc, char* argv[])
if(atexit(&remove_configfile) != 0)
fatal_exit("atexit() failed: %s", strerror(errno));
/* setup test environment */
scen = setup_playback(playback_file, &pass_argc, pass_argv);
/* init fake event backend */
fake_event_init(scen);
pass_argv[pass_argc] = NULL;
echo_cmdline(pass_argc, pass_argv);
/* reset getopt processing */
optind = init_optind;
optarg = init_optarg;
/* run the normal daemon */
res = daemon_main(pass_argc, pass_argv);
fake_event_cleanup();
for(c=1; c<pass_argc; c++)
free(pass_argv[c]);
res = perform_playback(playback_file, pass_argc, pass_argv);
if(res == 0) {
log_info("Testbound Exit Success\n");
/* remove configfile from here, the atexit() is for when
@ -499,6 +514,101 @@ main(int argc, char* argv[])
return res;
}
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
static int delete_file(const char *pathname) {
int ret = unlink(pathname);
free((void *)pathname);
return ret;
}
static char *buf_to_file(const uint8_t *buf, size_t size) {
int fd;
size_t pos;
char *pathname = strdup("/tmp/fuzz-XXXXXX");
if (pathname == NULL)
return NULL;
fd = mkstemp(pathname);
if (fd == -1) {
log_err("mkstemp of file %s failed: %s", pathname, strerror(errno));
free(pathname);
return NULL;
}
pos = 0;
while (pos < size) {
int nbytes = write(fd, &buf[pos], size - pos);
if (nbytes <= 0) {
if (nbytes == -1 && errno == EINTR)
continue;
log_err("write to file %s failed: %s", pathname, strerror(errno));
goto err;
}
pos += nbytes;
}
if (close(fd) == -1) {
log_err("close of file %s failed: %s", pathname, strerror(errno));
goto err;
}
return pathname;
err:
delete_file(pathname);
return NULL;
}
/* based on main() above, but with: hard-coded passed args, file created from fuzz input */
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
{
int c, res;
int pass_argc = 0;
char* pass_argv[MAXARG];
char* playback_file = NULL;
/* we do not want the test to depend on the timezone */
(void)putenv("TZ=UTC");
memset(pass_argv, 0, sizeof(pass_argv));
#ifdef HAVE_SYSTEMD
/* we do not want the test to use systemd daemon startup notification*/
(void)unsetenv("NOTIFY_SOCKET");
#endif /* HAVE_SYSTEMD */
checklock_start();
log_init(NULL, 0, NULL);
/* determine commandline options for the daemon */
pass_argc = 1;
pass_argv[0] = "unbound";
add_opts("-d", &pass_argc, pass_argv);
playback_file = buf_to_file(Data, Size);
if (playback_file) {
log_info("Start of %s testbound program.", PACKAGE_STRING);
res = perform_playback(playback_file, pass_argc, pass_argv);
if(res == 0) {
log_info("Testbound Exit Success\n");
/* remove configfile from here, the atexit() is for when
* there is a crash to remove the tmpdir file.
* This one removes the file while alloc and log locks are
* still valid, and can be logged (for memory calculation),
* it leaves the ptr NULL so the atexit does nothing. */
remove_configfile();
#ifdef HAVE_PTHREAD
/* dlopen frees its thread state (dlopen of gost engine) */
pthread_exit(NULL);
#endif
}
delete_file(playback_file);
}
if(log_get_lock()) {
lock_basic_destroy((lock_basic_type*)log_get_lock());
}
return res;
}
#endif /* FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */
/* fake remote control */
struct listen_port* daemon_remote_open_ports(struct config_file*
ATTR_UNUSED(cfg))

View file

@ -923,10 +923,14 @@ pkt_snip_edns_option(uint8_t* pkt, size_t len, sldns_edns_option code,
if(!pkt_find_edns_opt(&opt_position, &remaining)) return 0;
if(remaining < 8) return -1; /* malformed */
rdlen = sldns_read_uint16(opt_position+6);
if(remaining < ((size_t)rdlen)+8)
return -1; /* malformed */
rdata = opt_position + 8;
while(rdlen > 0) {
if(rdlen < 4) return -1; /* malformed */
optlen = sldns_read_uint16(rdata+2);
if((size_t)rdlen < 4+((size_t)optlen))
return -1; /* malformed */
if(sldns_read_uint16(rdata) == code) {
/* save data to buf for caller inspection */
memmove(buf, rdata+4, optlen);
@ -1134,8 +1138,9 @@ static void lowercase_dname(uint8_t** p, size_t* remain)
while(**p != 0) {
/* compressed? */
if((**p & 0xc0) == 0xc0) {
*p += 2;
*remain -= 2;
llen = *remain < 2 ? (unsigned int)*remain : 2;
*p += llen;
*remain -= llen;
return;
}
llen = (unsigned int)**p;
@ -1178,6 +1183,12 @@ static void lowercase_rdata(uint8_t** p, size_t* remain,
uint8_t len;
if(rdataremain == 0) return;
len = **p;
if(rdataremain < ((size_t)len)+1) {
/* malformed LDNS_RDF_TYPE_STR, skip remainder */
*p += rdataremain;
*remain -= rdatalen;
return;
}
*p += len+1;
rdataremain -= len+1;
} else {
@ -1207,6 +1218,12 @@ static void lowercase_rdata(uint8_t** p, size_t* remain,
break;
default: error("bad rdf type in lowercase %d", (int)f);
}
if (rdataremain < (size_t)len) {
/* malformed RDF, skip remainder */
*p += rdataremain;
*remain -= rdatalen;
return;
}
*p += len;
rdataremain -= len;
}

View file

@ -78,6 +78,37 @@
*/
#define MAX_VALUE 0x7fffffff
/* If the build mode is for fuzzing this removes randomness from the output.
* This helps fuzz engines from having state increase due to the randomness. */
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
struct ub_randstate {
unsigned int dummy;
};
struct ub_randstate* ub_initstate(struct ub_randstate* ATTR_UNUSED(from))
{
struct ub_randstate* s = (struct ub_randstate*)calloc(1, sizeof(*s));
if(!s) {
log_err("malloc failure in random init");
return NULL;
}
return s;
}
long int ub_random(struct ub_randstate* state)
{
state->dummy++;
return (long int)(state->dummy & MAX_VALUE);
}
long int
ub_random_max(struct ub_randstate* state, long int x)
{
state->dummy++;
return ((long int)state->dummy % x);
}
#else /* !FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */
#if defined(HAVE_SSL) || defined(HAVE_LIBBSD)
struct ub_randstate*
ub_initstate(struct ub_randstate* ATTR_UNUSED(from))
@ -200,6 +231,8 @@ ub_random_max(struct ub_randstate* state, long int x)
}
#endif /* HAVE_NSS or HAVE_NETTLE and !HAVE_LIBBSD */
#endif /* FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */
void
ub_randfree(struct ub_randstate* s)
{