]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- Fix #1288: [FR] Improve fuzzing of unbound by adapting the netbound
authorW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 21 May 2025 10:41:54 +0000 (12:41 +0200)
committerW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 21 May 2025 10:41:54 +0000 (12:41 +0200)
  program.

doc/Changelog
testcode/fake_event.c
testcode/replay.c
testcode/testbound.c
testcode/testpkts.c
util/random.c

index 2e2f96df86c1d33ce6198bfb072f3ad253ab92b5..b86a04524c7062c4c0b73df066410a0d24b24b71 100644 (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.
index f7f3210790ebddd1cf81bea984062b7dcaaf3847..0942fcd83ca935ecba9a96889d0f90955590a1e7 100644 (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;
index 95dde405f6419dcabcf5aca174d8090170cdb920..fd946d53d8ab1326f052a319bd755c07ee408807 100644 (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 == '$') {
index 67af33b6199ef3ea44698d3feab091ffef171aaa..410ec165c68ba094c5fc18bb167432dd601f8caa 100644 (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))
index 8c68c48415a4885f967344705534436da1a0117e..6124610634813d1f9b5f09ba5630358b3b6883ea 100644 (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;
                }
index 6eb102c634b9af80a30f66c403cebdf9f614dd33..92a4f6dd0bd6c9f5d67bfc8046c9070f86db5b15 100644 (file)
  */
 #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)
 {