mirror of
https://github.com/NLnetLabs/unbound.git
synced 2026-02-02 19:59:28 -05:00
- Fix #1288: [FR] Improve fuzzing of unbound by adapting the netbound
program.
This commit is contained in:
parent
342a0f48e3
commit
ff7dfd52a2
6 changed files with 196 additions and 22 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 == '$') {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue