changeset 1745:a6824c54962a

Merge fuzz branch
author Matt Johnston <matt@ucc.asn.au>
date Sun, 18 Oct 2020 22:53:44 +0800
parents 4f13df974cf4 (current diff) 6cf465af5d9f (diff)
children 28ab2cdb84bf
files
diffstat 14 files changed, 244 insertions(+), 130 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile.in	Thu Oct 08 11:00:04 2020 +0800
+++ b/Makefile.in	Sun Oct 18 22:53:44 2020 +0800
@@ -268,7 +268,8 @@
 ## Fuzzing targets
 
 # list of fuzz targets
-FUZZ_TARGETS=fuzzer-preauth fuzzer-pubkey fuzzer-verify fuzzer-preauth_nomaths fuzzer-kexdh fuzzer-kexecdh fuzzer-kexcurve25519
+FUZZ_TARGETS=fuzzer-preauth fuzzer-pubkey fuzzer-verify fuzzer-preauth_nomaths \
+	fuzzer-kexdh fuzzer-kexecdh fuzzer-kexcurve25519 fuzzer-client fuzzer-client_nomaths
 
 FUZZER_OPTIONS = $(addsuffix .options, $(FUZZ_TARGETS))
 
@@ -279,10 +280,7 @@
 fuzzstandalone: FUZZLIB=fuzz-harness.o
 fuzzstandalone: fuzz-harness.o fuzz-targets
 
-# exclude svr-main.o to avoid duplicate main
-svrfuzzobjs=$(subst svr-main.o, ,$(dropbearobjs))
-
-fuzz-harness.o: $(HEADERS) $(LIBTOM_DEPS) Makefile $(svrfuzzobjs) fuzz-common.o
+fuzz-harness.o: $(HEADERS) $(LIBTOM_DEPS) Makefile $(allobjs) fuzz-common.o
 
 # build all the fuzzers. This will require fail to link unless built with
 # make fuzz-targets FUZZLIB=-lFuzzer.a 
@@ -290,25 +288,31 @@
 fuzz-targets: $(FUZZ_TARGETS) $(FUZZER_OPTIONS)
 
 fuzzer-preauth: fuzzer-preauth.o fuzz-harness.o
-	$(CXX) $(CXXFLAGS) [email protected] $(LDFLAGS) $(svrfuzzobjs) -o $@$(EXEEXT) $(LIBTOM_LIBS) $(LIBS) $(FUZZLIB) @CRYPTLIB@
+	$(CXX) $(CXXFLAGS) [email protected] $(LDFLAGS) $(allobjs) -o $@$(EXEEXT) $(LIBTOM_LIBS) $(LIBS) $(FUZZLIB) @CRYPTLIB@
 
 fuzzer-preauth_nomaths: fuzzer-preauth_nomaths.o fuzz-harness.o
-	$(CXX) $(CXXFLAGS) [email protected] $(LDFLAGS) $(svrfuzzobjs) -o $@$(EXEEXT) $(LIBTOM_LIBS) $(LIBS) $(FUZZLIB) @CRYPTLIB@
+	$(CXX) $(CXXFLAGS) [email protected] $(LDFLAGS) $(allobjs) -o $@$(EXEEXT) $(LIBTOM_LIBS) $(LIBS) $(FUZZLIB) @CRYPTLIB@
 
 fuzzer-pubkey: fuzzer-pubkey.o fuzz-harness.o
-	$(CXX) $(CXXFLAGS) [email protected] $(LDFLAGS) $(svrfuzzobjs) -o $@$(EXEEXT) $(LIBTOM_LIBS) $(LIBS) $(FUZZLIB) @CRYPTLIB@
+	$(CXX) $(CXXFLAGS) [email protected] $(LDFLAGS) $(allobjs) -o $@$(EXEEXT) $(LIBTOM_LIBS) $(LIBS) $(FUZZLIB) @CRYPTLIB@
 
 fuzzer-verify: fuzzer-verify.o fuzz-harness.o
-	$(CXX) $(CXXFLAGS) [email protected] $(LDFLAGS) $(svrfuzzobjs) -o $@$(EXEEXT) $(LIBTOM_LIBS) $(LIBS) $(FUZZLIB) @CRYPTLIB@
+	$(CXX) $(CXXFLAGS) [email protected] $(LDFLAGS) $(allobjs) -o $@$(EXEEXT) $(LIBTOM_LIBS) $(LIBS) $(FUZZLIB) @CRYPTLIB@
 
 fuzzer-kexdh: fuzzer-kexdh.o fuzz-harness.o
-	$(CXX) $(CXXFLAGS) [email protected] $(LDFLAGS) $(svrfuzzobjs) -o $@$(EXEEXT) $(LIBTOM_LIBS) $(LIBS) $(FUZZLIB) @CRYPTLIB@
+	$(CXX) $(CXXFLAGS) [email protected] $(LDFLAGS) $(allobjs) -o $@$(EXEEXT) $(LIBTOM_LIBS) $(LIBS) $(FUZZLIB) @CRYPTLIB@
 
 fuzzer-kexecdh: fuzzer-kexecdh.o fuzz-harness.o
-	$(CXX) $(CXXFLAGS) [email protected] $(LDFLAGS) $(svrfuzzobjs) -o $@$(EXEEXT) $(LIBTOM_LIBS) $(LIBS) $(FUZZLIB) @CRYPTLIB@
+	$(CXX) $(CXXFLAGS) [email protected] $(LDFLAGS) $(allobjs) -o $@$(EXEEXT) $(LIBTOM_LIBS) $(LIBS) $(FUZZLIB) @CRYPTLIB@
 
 fuzzer-kexcurve25519: fuzzer-kexcurve25519.o fuzz-harness.o
-	$(CXX) $(CXXFLAGS) [email protected] $(LDFLAGS) $(svrfuzzobjs) -o $@$(EXEEXT) $(LIBTOM_LIBS) $(LIBS) $(FUZZLIB) @CRYPTLIB@
+	$(CXX) $(CXXFLAGS) [email protected] $(LDFLAGS) $(allobjs) -o $@$(EXEEXT) $(LIBTOM_LIBS) $(LIBS) $(FUZZLIB) @CRYPTLIB@
+
+fuzzer-client: fuzzer-client.o fuzz-harness.o
+	$(CXX) $(CXXFLAGS) [email protected] $(LDFLAGS) $(allobjs) -o $@$(EXEEXT) $(LIBTOM_LIBS) $(LIBS) $(FUZZLIB) @CRYPTLIB@
+
+fuzzer-client_nomaths: fuzzer-client_nomaths.o fuzz-harness.o
+	$(CXX) $(CXXFLAGS) [email protected] $(LDFLAGS) $(allobjs) -o $@$(EXEEXT) $(LIBTOM_LIBS) $(LIBS) $(FUZZLIB) @CRYPTLIB@
 
 fuzzer-%.options: Makefile
 	echo "[libfuzzer]"               > $@
--- a/cli-kex.c	Thu Oct 08 11:00:04 2020 +0800
+++ b/cli-kex.c	Sun Oct 18 22:53:44 2020 +0800
@@ -46,6 +46,13 @@
 	TRACE(("send_msg_kexdh_init()"))	
 
 	CHECKCLEARTOWRITE();
+
+#if DROPBEAR_FUZZ
+	if (fuzz.fuzzing && fuzz.skip_kexmaths) {
+		return;
+	}
+#endif
+
 	buf_putbyte(ses.writepayload, SSH_MSG_KEXDH_INIT);
 	switch (ses.newkeys->algo_kex->mode) {
 #if DROPBEAR_NORMAL_DH
@@ -98,6 +105,12 @@
 	unsigned char* keyblob = NULL;
 
 	TRACE(("enter recv_msg_kexdh_reply"))
+	
+#if DROPBEAR_FUZZ
+	if (fuzz.fuzzing && fuzz.skip_kexmaths) {
+		return;
+	}
+#endif
 
 	if (cli_ses.kex_state != KEXDH_INIT_SENT) {
 		dropbear_exit("Received out-of-order kexdhreply");
--- a/cli-main.c	Thu Oct 08 11:00:04 2020 +0800
+++ b/cli-main.c	Sun Oct 18 22:53:44 2020 +0800
@@ -31,9 +31,7 @@
 #include "dbrandom.h"
 #include "crypto_desc.h"
 #include "netio.h"
-
-static void cli_dropbear_exit(int exitcode, const char* format, va_list param) ATTRIB_NORETURN;
-static void cli_dropbear_log(int priority, const char* format, va_list param);
+#include "fuzz.h"
 
 #if DROPBEAR_CLI_PROXYCMD
 static void cli_proxy_cmd(int *sock_in, int *sock_out, pid_t *pid_out);
@@ -98,58 +96,6 @@
 }
 #endif /* DBMULTI stuff */
 
-static void cli_dropbear_exit(int exitcode, const char* format, va_list param) {
-	char exitmsg[150];
-	char fullmsg[300];
-
-	/* Note that exit message must be rendered before session cleanup */
-
-	/* Render the formatted exit message */
-	vsnprintf(exitmsg, sizeof(exitmsg), format, param);
-	TRACE(("Exited, cleaning up: %s", exitmsg))
-
-	/* Add the prefix depending on session/auth state */
-	if (!ses.init_done) {
-		snprintf(fullmsg, sizeof(fullmsg), "Exited: %s", exitmsg);
-	} else {
-		snprintf(fullmsg, sizeof(fullmsg), 
-				"Connection to %s@%s:%s exited: %s", 
-				cli_opts.username, cli_opts.remotehost, 
-				cli_opts.remoteport, exitmsg);
-	}
-
-	/* Do the cleanup first, since then the terminal will be reset */
-	session_cleanup();
-	/* Avoid printing onwards from terminal cruft */
-	fprintf(stderr, "\n");
-
-	dropbear_log(LOG_INFO, "%s", fullmsg);
-	exit(exitcode);
-}
-
-static void cli_dropbear_log(int priority,
-		const char* format, va_list param) {
-
-	char printbuf[1024];
-	const char *name;
-
-	name = cli_opts.progname;
-	if (!name) {
-		name = "dbclient";
-	}
-
-	vsnprintf(printbuf, sizeof(printbuf), format, param);
-
-#ifndef DISABLE_SYSLOG
-	if (opts.usingsyslog) {
-		syslog(priority, "%s", printbuf);
-	}
-#endif
-
-	fprintf(stderr, "%s: %s\n", name, printbuf);
-	fflush(stderr);
-}
-
 static void exec_proxy_cmd(const void *user_data_cmd) {
 	const char *cmd = user_data_cmd;
 	char *usershell;
@@ -199,4 +145,5 @@
 	kill_proxy_command();
 	_exit(1);
 }
+
 #endif /* DROPBEAR_CLI_PROXYCMD */
--- a/cli-session.c	Thu Oct 08 11:00:04 2020 +0800
+++ b/cli-session.c	Sun Oct 18 22:53:44 2020 +0800
@@ -352,6 +352,11 @@
 	(void)fcntl(cli_ses.stdoutcopy, F_SETFL, cli_ses.stdoutflags);
 	(void)fcntl(cli_ses.stderrcopy, F_SETFL, cli_ses.stderrflags);
 
+	/* Don't leak */
+	m_close(cli_ses.stdincopy);
+	m_close(cli_ses.stdoutcopy);
+	m_close(cli_ses.stderrcopy);
+
 	cli_tty_cleanup();
 	if (cli_ses.server_sig_algs) {
 		buf_free(cli_ses.server_sig_algs);
@@ -407,3 +412,63 @@
 	/* Send a proper rejection */
 	send_msg_request_failure();
 }
+
+void cli_dropbear_exit(int exitcode, const char* format, va_list param) {
+	char exitmsg[150];
+	char fullmsg[300];
+
+	/* Note that exit message must be rendered before session cleanup */
+
+	/* Render the formatted exit message */
+	vsnprintf(exitmsg, sizeof(exitmsg), format, param);
+	TRACE(("Exited, cleaning up: %s", exitmsg))
+
+	/* Add the prefix depending on session/auth state */
+	if (!ses.init_done) {
+		snprintf(fullmsg, sizeof(fullmsg), "Exited: %s", exitmsg);
+	} else {
+		snprintf(fullmsg, sizeof(fullmsg), 
+				"Connection to %s@%s:%s exited: %s", 
+				cli_opts.username, cli_opts.remotehost, 
+				cli_opts.remoteport, exitmsg);
+	}
+
+	/* Do the cleanup first, since then the terminal will be reset */
+	session_cleanup();
+	
+#if DROPBEAR_FUZZ
+    if (fuzz.do_jmp) {
+        longjmp(fuzz.jmp, 1);
+    }
+#endif
+
+	/* Avoid printing onwards from terminal cruft */
+	fprintf(stderr, "\n");
+
+	dropbear_log(LOG_INFO, "%s", fullmsg);
+
+	exit(exitcode);
+}
+
+void cli_dropbear_log(int priority, const char* format, va_list param) {
+
+	char printbuf[1024];
+	const char *name;
+
+	name = cli_opts.progname;
+	if (!name) {
+		name = "dbclient";
+	}
+
+	vsnprintf(printbuf, sizeof(printbuf), format, param);
+
+#ifndef DISABLE_SYSLOG
+	if (opts.usingsyslog) {
+		syslog(priority, "%s", printbuf);
+	}
+#endif
+
+	fprintf(stderr, "%s: %s\n", name, printbuf);
+	fflush(stderr);
+}
+
--- a/common-kex.c	Thu Oct 08 11:00:04 2020 +0800
+++ b/common-kex.c	Sun Oct 18 22:53:44 2020 +0800
@@ -487,6 +487,12 @@
 		TRACE(("continue recv_msg_kexinit: sent kexinit"))
 	}
 
+	/* "Once a party has sent a SSH_MSG_KEXINIT message ...
+	further SSH_MSG_KEXINIT messages MUST NOT be sent" */
+	if (ses.kexstate.recvkexinit) {
+		dropbear_exit("Unexpected KEXINIT");
+	}
+
 	/* start the kex hash */
 	local_ident_len = strlen(LOCAL_IDENT);
 	remote_ident_len = strlen(ses.remoteident);
--- a/dbutil.c	Thu Oct 08 11:00:04 2020 +0800
+++ b/dbutil.c	Sun Oct 18 22:53:44 2020 +0800
@@ -121,7 +121,6 @@
 	_dropbear_log(LOG_INFO, fmtbuf, param);
 
 #if DROPBEAR_FUZZ
-	/* longjmp before cleaning up svr_opts */
     if (fuzz.do_jmp) {
         longjmp(fuzz.jmp, 1);
     }
@@ -258,6 +257,12 @@
 	const int FDIN = 0;
 	const int FDOUT = 1;
 
+#if DROPBEAR_FUZZ
+	if (fuzz.fuzzing) {
+		return fuzz_spawn_command(ret_writefd, ret_readfd, ret_errfd, ret_pid);
+	}
+#endif
+
 	/* redirect stdin/stdout/stderr */
 	if (pipe(infds) != 0) {
 		return DROPBEAR_FAILURE;
--- a/fuzz-common.c	Thu Oct 08 11:00:04 2020 +0800
+++ b/fuzz-common.c	Sun Oct 18 22:53:44 2020 +0800
@@ -16,6 +16,7 @@
 static void load_fixed_hostkeys(void);
 
 void fuzz_common_setup(void) {
+	disallow_core();
     fuzz.fuzzing = 1;
     fuzz.wrapfds = 1;
     fuzz.do_jmp = 1;
@@ -36,7 +37,8 @@
 
     memset(&ses, 0x0, sizeof(ses));
     memset(&svr_ses, 0x0, sizeof(svr_ses));
-    wrapfd_setup();
+    memset(&cli_ses, 0x0, sizeof(cli_ses));
+    wrapfd_setup(fuzz.input);
 
     fuzz_seed();
 
@@ -63,19 +65,30 @@
     _dropbear_exit = svr_dropbear_exit;
 
     char *argv[] = { 
+		"dropbear",
         "-E", 
     };
 
     int argc = sizeof(argv) / sizeof(*argv);
     svr_getopts(argc, argv);
 
-    /* user lookups might be slow, cache it */
-    fuzz.pw_name = m_strdup("person");
-    fuzz.pw_dir = m_strdup("/tmp");
-    fuzz.pw_shell = m_strdup("/bin/zsh");
-    fuzz.pw_passwd = m_strdup("!!zzznope");
+    load_fixed_hostkeys();
+}
+
+void fuzz_cli_setup(void) {
+    fuzz_common_setup();
+    
+	_dropbear_exit = cli_dropbear_exit;
+	_dropbear_log = cli_dropbear_log;
 
-    load_fixed_hostkeys();
+    char *argv[] = { 
+		"dbclient",
+		"-y",
+        "localhost",
+    };
+
+    int argc = sizeof(argv) / sizeof(*argv);
+    cli_getopts(argc, argv);
 }
 
 static void load_fixed_hostkeys(void) {
@@ -151,6 +164,17 @@
     finish_kexhashbuf();
 }
 
+/* fake version of spawn_command() */
+int fuzz_spawn_command(int *ret_writefd, int *ret_readfd, int *ret_errfd, pid_t *ret_pid) {
+    *ret_writefd = wrapfd_new();
+    *ret_readfd = wrapfd_new();
+    if (ret_errfd) {
+        *ret_errfd = wrapfd_new();
+    }
+    *ret_pid = 999;
+    return DROPBEAR_SUCCESS;
+}
+
 int fuzz_run_preauth(const uint8_t *Data, size_t Size, int skip_kexmaths) {
     static int once = 0;
     if (!once) {
@@ -164,7 +188,7 @@
     }
 
     /*
-      get prefix. input format is
+      get prefix, allowing for future extensibility. input format is
       string prefix
           uint32 wrapfd seed
           ... to be extended later
@@ -182,8 +206,7 @@
     uint32_t wrapseed = buf_getint(fuzz.input);
     wrapfd_setseed(wrapseed);
 
-    int fakesock = 20;
-    wrapfd_add(fakesock, fuzz.input, PLAIN);
+    int fakesock = wrapfd_new();
 
     m_malloc_set_epoch(1);
     if (setjmp(fuzz.jmp) == 0) {
@@ -198,6 +221,52 @@
     return 0;
 }
 
+int fuzz_run_client(const uint8_t *Data, size_t Size, int skip_kexmaths) {
+    static int once = 0;
+    if (!once) {
+        fuzz_cli_setup();
+        fuzz.skip_kexmaths = skip_kexmaths;
+        once = 1;
+    }
+
+    if (fuzz_set_input(Data, Size) == DROPBEAR_FAILURE) {
+        return 0;
+    }
+
+    /*
+      get prefix, allowing for future extensibility. input format is
+      string prefix
+          uint32 wrapfd seed
+          ... to be extended later
+      [bytes] ssh input stream
+    */
+
+    /* be careful to avoid triggering buffer.c assertions */
+    if (fuzz.input->len < 8) {
+        return 0;
+    }
+    size_t prefix_size = buf_getint(fuzz.input);
+    if (prefix_size != 4) {
+        return 0;
+    }
+    uint32_t wrapseed = buf_getint(fuzz.input);
+    wrapfd_setseed(wrapseed);
+
+    int fakesock = wrapfd_new();
+
+    m_malloc_set_epoch(1);
+    if (setjmp(fuzz.jmp) == 0) {
+        cli_session(fakesock, fakesock, NULL, 0);
+        m_malloc_free_epoch(1, 0);
+    } else {
+        m_malloc_free_epoch(1, 1);
+        TRACE(("dropbear_exit longjmped"))
+        /* dropbear_exit jumped here */
+    }
+
+    return 0;
+}
+
 const void* fuzz_get_algo(const algo_type *algos, const char* name) {
     const algo_type *t;
     for (t = algos; t->name; t++) {
--- a/fuzz-harness.c	Thu Oct 08 11:00:04 2020 +0800
+++ b/fuzz-harness.c	Sun Oct 18 22:53:44 2020 +0800
@@ -9,7 +9,6 @@
     buffer *input = buf_new(100000);
 
     for (i = 1; i < argc; i++) {
-        printf("arg %s\n", argv[i]);
 #if DEBUG_TRACE
         if (strcmp(argv[i], "-v") == 0) {
             debug_trace = 1;
@@ -30,6 +29,7 @@
         buf_readfile(input, fn);
         buf_setpos(input, 0);
 
+		/* Run twice to catch problems with statefulness */
         fuzz.wrapfds = old_fuzz_wrapfds;
         printf("Running %s once \n", fn);
         LLVMFuzzerTestOneInput(input->data, input->len);
--- a/fuzz-wrapfd.c	Thu Oct 08 11:00:04 2020 +0800
+++ b/fuzz-wrapfd.c	Sun Oct 18 22:53:44 2020 +0800
@@ -17,25 +17,33 @@
 
 struct fdwrap {
 	enum wrapfd_mode mode;
-	buffer *buf;
 	int closein;
 	int closeout;
 };
 
-static struct fdwrap wrap_fds[IOWRAP_MAXFD+1];
-/* for quick selection of in-use descriptors */
-static int wrap_used[IOWRAP_MAXFD+1];
-static unsigned int nused;
+static struct fdwrap wrap_fds[IOWRAP_MAXFD+1] = {0};
+static int wrapfd_maxfd = -1;
 static unsigned short rand_state[3];
+static buffer *input_buf;
+static int devnull_fd = -1;
+
+static void wrapfd_remove(int fd);
 
-void wrapfd_setup(void) {
+void wrapfd_setup(buffer *buf) {
 	TRACE(("wrapfd_setup"))
-	nused = 0;
-	memset(wrap_fds, 0x0, sizeof(wrap_fds));
-	memset(wrap_used, 0x0, sizeof(wrap_used));
+
+	// clean old ones
+	int i;
+	for (i = 0; i <= wrapfd_maxfd; i++) {
+		if (wrap_fds[i].mode == COMMONBUF) {
+			wrapfd_remove(i);
+		}
+	}
+	wrapfd_maxfd = -1;
 
 	memset(rand_state, 0x0, sizeof(rand_state));
 	wrapfd_setseed(50);
+	input_buf = buf;
 }
 
 void wrapfd_setseed(uint32_t seed) {
@@ -43,39 +51,30 @@
 	nrand48(rand_state);
 }
 
-void wrapfd_add(int fd, buffer *buf, enum wrapfd_mode mode) {
-	TRACE(("wrapfd_add %d buf %p mode %d", fd, buf, mode))
-	assert(fd >= 0);
-	assert(fd <= IOWRAP_MAXFD);
+int wrapfd_new() {
+	if (devnull_fd == -1) {
+		devnull_fd = open("/dev/null", O_RDONLY);
+		assert(devnull_fd != -1);
+	}
+
+	int fd = dup(devnull_fd);
+	assert(fd != -1);
 	assert(wrap_fds[fd].mode == UNUSED);
-	assert(buf || mode == RANDOMIN);
-
-	wrap_fds[fd].mode = mode;
-	wrap_fds[fd].buf = buf;
+	wrap_fds[fd].mode = COMMONBUF;
 	wrap_fds[fd].closein = 0;
 	wrap_fds[fd].closeout = 0;
-	wrap_used[nused] = fd;
+	wrapfd_maxfd = MAX(fd, wrapfd_maxfd);
 
-	nused++;
+	return fd;
 }
 
-void wrapfd_remove(int fd) {
-	unsigned int i, j;
+static void wrapfd_remove(int fd) {
 	TRACE(("wrapfd_remove %d", fd))
 	assert(fd >= 0);
 	assert(fd <= IOWRAP_MAXFD);
 	assert(wrap_fds[fd].mode != UNUSED);
 	wrap_fds[fd].mode = UNUSED;
-
-
-	/* remove from used list */
-	for (i = 0, j = 0; i < nused; i++) {
-		if (wrap_used[i] != fd) {
-			wrap_used[j] = wrap_used[i];
-			j++;
-		}
-	}
-	nused--;
+	m_close(fd);
 }
 
 int wrapfd_close(int fd) {
@@ -115,15 +114,14 @@
 		return -1;
 	}
 
-	buf = wrap_fds[fd].buf;
-	if (buf) {
-		maxread = MIN(buf->len - buf->pos, count);
+	if (input_buf) {
+		maxread = MIN(input_buf->len - input_buf->pos, count);
 		/* returns 0 if buf is EOF, as intended */
 		if (maxread > 0) {
 			maxread = nrand48(rand_state) % maxread + 1;
 		}
-		memcpy(out, buf_getptr(buf, maxread), maxread);
-		buf_incrpos(buf, maxread);
+		memcpy(out, buf_getptr(input_buf, maxread), maxread);
+		buf_incrpos(input_buf, maxread);
 		return maxread;
 	}
 
@@ -175,8 +173,6 @@
 	int ret = 0;
 	int fdlist[IOWRAP_MAXFD+1];
 
-	memset(fdlist, 0x0, sizeof(fdlist));
-
 	if (!fuzz.wrapfds) {
 		return select(nfds, readfds, writefds, exceptfds, timeout);
 	}
--- a/fuzz-wrapfd.h	Thu Oct 08 11:00:04 2020 +0800
+++ b/fuzz-wrapfd.h	Sun Oct 18 22:53:44 2020 +0800
@@ -5,15 +5,13 @@
 
 enum wrapfd_mode {
     UNUSED = 0,
-    PLAIN,
-    INPROGRESS,
-    RANDOMIN
+    COMMONBUF, // using the common buffer
 };
 
-void wrapfd_setup(void);
+// buf is a common buffer read by all wrapped FDs. doesn't take ownership of buf
+void wrapfd_setup(buffer *buf);
 void wrapfd_setseed(uint32_t seed);
-// doesn't take ownership of buf. buf is optional.
-void wrapfd_add(int fd, buffer *buf, enum wrapfd_mode mode);
+int wrapfd_new();
 
 // called via #defines for read/write/select
 int wrapfd_read(int fd, void *out, size_t count);
--- a/fuzz.h	Thu Oct 08 11:00:04 2020 +0800
+++ b/fuzz.h	Sun Oct 18 22:53:44 2020 +0800
@@ -13,6 +13,7 @@
 // once per process
 void fuzz_common_setup(void);
 void fuzz_svr_setup(void);
+void fuzz_cli_setup(void);
 
 // must be called once per fuzz iteration. 
 // returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE
@@ -28,9 +29,12 @@
         const unsigned char* keyblob, unsigned int keybloblen);
 extern const char * const * fuzz_signkey_names;
 void fuzz_seed(void);
+
+// helpers
 void fuzz_get_socket_address(int fd, char **local_host, char **local_port,
                         char **remote_host, char **remote_port, int host_lookup);
 void fuzz_fake_send_kexdh_reply(void);
+int fuzz_spawn_command(int *ret_writefd, int *ret_readfd, int *ret_errfd, pid_t *ret_pid);
 
 // fake IO wrappers
 #ifndef FUZZ_SKIP_WRAP
@@ -56,13 +60,6 @@
     // dropbear_exit() jumps back
     int do_jmp;
     sigjmp_buf jmp;
-
-    uid_t pw_uid;
-    gid_t pw_gid;
-    char* pw_name;
-    char* pw_dir;
-    char* pw_shell;
-    char* pw_passwd;
 };
 
 extern struct dropbear_fuzz_options fuzz;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fuzzer-client.c	Sun Oct 18 22:53:44 2020 +0800
@@ -0,0 +1,6 @@
+#include "fuzz.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
+	return fuzz_run_client(Data, Size, 0);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fuzzer-client_nomaths.c	Sun Oct 18 22:53:44 2020 +0800
@@ -0,0 +1,6 @@
+#include "fuzz.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
+	return fuzz_run_client(Data, Size, 1);
+}
+
--- a/session.h	Thu Oct 08 11:00:04 2020 +0800
+++ b/session.h	Sun Oct 18 22:53:44 2020 +0800
@@ -64,6 +64,8 @@
 /* Client */
 void cli_session(int sock_in, int sock_out, struct dropbear_progress_connection *progress, pid_t proxy_cmd_pid) ATTRIB_NORETURN;
 void cli_connected(int result, int sock, void* userdata, const char *errstring);
+void cli_dropbear_exit(int exitcode, const char* format, va_list param) ATTRIB_NORETURN;
+void cli_dropbear_log(int priority, const char* format, va_list param);
 void cleantext(char* dirtytext);
 void kill_proxy_command(void);