diff --git a/Makefile.in b/Makefile.in index 192d8f9e3..19c747f47 100644 --- a/Makefile.in +++ b/Makefile.in @@ -34,7 +34,7 @@ COMMONOBJS=dbutil.o buffer.o dbhelpers.o \ queue.o \ atomicio.o compat.o fake-rfc2553.o \ ltc_prng.o ecc.o ecdsa.o crypto_desc.o \ - gensignkey.o gendss.o genrsa.o fuzz-common.o + gensignkey.o gendss.o genrsa.o SVROBJS=svr-kex.o svr-auth.o sshpty.o \ svr-authpasswd.o svr-authpubkey.o svr-authpubkeyoptions.o svr-session.o svr-service.o \ @@ -57,6 +57,10 @@ CONVERTOBJS=dropbearconvert.o keyimport.o SCPOBJS=scp.o progressmeter.o atomicio.o scpmisc.o compat.o +ifeq (@DROPBEAR_FUZZ@, 1) + COMMONOBJS += fuzz-common.o fuzz-wrapfd.o +endif + HEADERS=options.h dbutil.h session.h packet.h algo.h ssh.h buffer.h kex.h \ dss.h bignum.h signkey.h rsa.h dbrandom.h service.h auth.h \ debug.h channel.h chansession.h config.h queue.h sshpty.h \ @@ -270,3 +274,4 @@ fuzz-hostkeys: /usr/bin/xxd -i -a keyr >> hostkeys.c /usr/bin/xxd -i -a keye >> hostkeys.c /usr/bin/xxd -i -a keyd >> hostkeys.c + diff --git a/configure.ac b/configure.ac index 03a1e4f7b..9a7fbc952 100644 --- a/configure.ac +++ b/configure.ac @@ -223,10 +223,14 @@ AC_ARG_ENABLE(fuzz, [ AC_DEFINE(DROPBEAR_FUZZ, 1, Fuzzing) AC_MSG_NOTICE(Enabling fuzzing) + DROPBEAR_FUZZ=1 + ], + [ + DROPBEAR_FUZZ=0 ] -) - +) +AC_SUBST(DROPBEAR_FUZZ) # Checks for header files. AC_HEADER_STDC diff --git a/fuzz-common.c b/fuzz-common.c index d9587c929..bfd263460 100644 --- a/fuzz-common.c +++ b/fuzz-common.c @@ -8,6 +8,8 @@ #include "runopts.h" #include "crypto_desc.h" #include "session.h" +#include "dbrandom.h" +#include "fuzz-wrapfd.h" struct dropbear_fuzz_options fuzz; @@ -15,9 +17,40 @@ static void load_fixed_hostkeys(void); static void common_setup_fuzzer(void) { fuzz.fuzzing = 1; + fuzz.input = m_malloc(sizeof(buffer)); crypto_init(); } +int fuzzer_set_input(const uint8_t *Data, size_t Size) { + + fuzz.input->data = (unsigned char*)Data; + fuzz.input->size = Size; + fuzz.input->len = Size; + fuzz.input->pos = 0; + + // get prefix. input format is + // string prefix + // uint32_t seed + // ... to be extended later + // [bytes] ssh input stream + + // be careful to avoid triggering buffer.c assertions + if (fuzz.input->len < 8) { + return DROPBEAR_FAILURE; + } + size_t prefix_size = buf_getint(fuzz.input); + if (prefix_size != 4) { + return DROPBEAR_FAILURE; + } + uint32_t wrapseed = buf_getint(fuzz.input); + wrapfd_setup(wrapseed); + + seedrandom(); + + return DROPBEAR_SUCCESS; +} + + void svr_setup_fuzzer(void) { struct passwd *pw; diff --git a/fuzz-wrapfd.c b/fuzz-wrapfd.c new file mode 100644 index 000000000..7509afee4 --- /dev/null +++ b/fuzz-wrapfd.c @@ -0,0 +1,193 @@ +#include "includes.h" +#include "fuzz-wrapfd.h" + +static const int IOWRAP_MAXFD = FD_SETSIZE-1; +static const int MAX_RANDOM_IN = 50000; +static const double CHANCE_CLOSE = 1.0 / 300; +static const double CHANCE_INTR = 1.0 / 200; +static const double CHANCE_READ1 = 0.6; +static const double CHANCE_READ2 = 0.3; +static const double CHANCE_WRITE1 = 0.8; +static const double CHANCE_WRITE2 = 0.3; + +struct fdwrap { + enum wrapfd_mode mode; + buffer *buf; +}; + +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 unsigned short rand_state[3]; + +void wrapfd_setup(uint32_t seed) { + nused = 0; + memset(wrap_fds, 0x0, sizeof(wrap_fds)); + + *((uint32_t*)rand_state) = seed; + nrand48(rand_state); +} + +void wrapfd_add(int fd, buffer *buf, enum wrapfd_mode mode) { + assert(fd >= 0); + assert(fd <= IOWRAP_MAXFD); + assert(wrap_fds[fd].mode == UNUSED); + assert(buf || mode == RANDOMIN); + + wrap_fds[fd].mode = mode; + wrap_fds[fd].buf = buf; + wrap_used[nused] = fd; + + nused++; +} + +void wrapfd_remove(int fd) { + unsigned int i, j; + 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--; +} + + +int wrapfd_read(int fd, void *out, size_t count) { + size_t maxread; + buffer *buf; + + if (fd < 0 || fd > IOWRAP_MAXFD || wrap_fds[fd].mode != UNUSED) { + TRACE(("Bad read descriptor %d\n", fd)) + errno = EBADF; + return -1; + } + + assert(count != 0); + + if (erand48(rand_state) < CHANCE_CLOSE) { + wrapfd_remove(fd); + return 0; + } + + if (erand48(rand_state) < CHANCE_INTR) { + errno = EINTR; + return -1; + } + + buf = wrap_fds[fd].buf; + if (buf) { + maxread = MIN(buf->len - buf->pos, count); + // returns 0 if buf is EOF, as intended + maxread = nrand48(rand_state) % maxread + 1; + memcpy(out, buf_getptr(buf, maxread), maxread); + buf_incrpos(buf, maxread); + return maxread; + } + + maxread = MIN(MAX_RANDOM_IN, count); + maxread = nrand48(rand_state) % maxread + 1; + memset(out, 0xef, maxread); + return maxread; +} + +int wrapfd_write(int fd, const void* in, size_t count) { + unsigned const volatile char* volin = in; + unsigned int i; + if (fd < 0 || fd > IOWRAP_MAXFD || wrap_fds[fd].mode != UNUSED) { + TRACE(("Bad read descriptor %d\n", fd)) + errno = EBADF; + return -1; + } + + assert(count != 0); + + // force read to exercise sanitisers + for (i = 0; i < count; i++) { + (void)volin[i]; + } + + if (erand48(rand_state) < CHANCE_CLOSE) { + wrapfd_remove(fd); + return 0; + } + + if (erand48(rand_state) < CHANCE_INTR) { + errno = EINTR; + return -1; + } + + return nrand48(rand_state) % (count+1); +} + +int wrapfd_select(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *UNUSED(exceptfds), struct timeval *UNUSED(timeout)) { + int i, nset; + int ret = 0; + int fdlist[IOWRAP_MAXFD+1] = {0}; + + assert(nfds <= IOWRAP_MAXFD+1); + + if (erand48(rand_state) < CHANCE_INTR) { + errno = EINTR; + return -1; + } + + // read + if (erand48(rand_state) < CHANCE_READ1) { + for (i = 0, nset = 0; i < nfds; i++) { + if (FD_ISSET(i, readfds)) { + assert(wrap_fds[i].mode != UNUSED); + fdlist[nset] = i; + } + } + FD_ZERO(readfds); + + if (nset > 0) { + // set one + FD_SET(fdlist[random() % nset], readfds); + ret++; + + if (erand48(rand_state) < CHANCE_READ2) { + i = fdlist[random() % nset]; + if (!FD_ISSET(i, readfds)) { + FD_SET(i, readfds); + ret++; + } + } + } + } + + // write + if (erand48(rand_state) < CHANCE_WRITE1) { + for (i = 0, nset = 0; i < nfds; i++) { + if (FD_ISSET(i, writefds)) { + assert(wrap_fds[i].mode != UNUSED); + fdlist[nset] = i; + } + } + FD_ZERO(writefds); + + // set one + if (nset > 0) { + FD_SET(fdlist[nrand48(rand_state) % nset], writefds); + ret++; + + if (erand48(rand_state) < CHANCE_WRITE2) { + i = fdlist[nrand48(rand_state) % nset]; + if (!FD_ISSET(i, writefds)) { + FD_SET(i, writefds); + ret++; + } + } + } + } + return ret; +} diff --git a/fuzz-wrapfd.h b/fuzz-wrapfd.h new file mode 100644 index 000000000..d4578b742 --- /dev/null +++ b/fuzz-wrapfd.h @@ -0,0 +1,17 @@ +#ifndef FUZZ_WRAPFD_H +#define FUZZ_WRAPFD_H + +#include "buffer.h" + +enum wrapfd_mode { + UNUSED = 0, + PLAIN, + INPROGRESS, + RANDOMIN, +}; + +void wrapfd_setup(uint32_t wrapseed); +// doesn't take ownership of buf. buf is optional. +void wrapfd_add(int fd, buffer *buf, enum wrapfd_mode mode); + +#endif // FUZZ_WRAPFD_H diff --git a/fuzz.h b/fuzz.h index e7360e33b..c46ded9b2 100644 --- a/fuzz.h +++ b/fuzz.h @@ -6,8 +6,12 @@ #ifdef DROPBEAR_FUZZ +// once per process void svr_setup_fuzzer(void); +// once per input. returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE +int fuzzer_set_input(const uint8_t *Data, size_t Size); + struct dropbear_fuzz_options { int fuzzing; @@ -15,7 +19,7 @@ struct dropbear_fuzz_options { FILE* recordf; // fuzzing input - buffer input; + buffer *input; // dropbear_exit() jumps back sigjmp_buf jmp; diff --git a/fuzzer-preauth.c b/fuzzer-preauth.c index 6a40108dc..7564973c8 100644 --- a/fuzzer-preauth.c +++ b/fuzzer-preauth.c @@ -1,10 +1,10 @@ #include "fuzz.h" #include "dbrandom.h" #include "session.h" +#include "fuzz-wrapfd.h" -static int setup_fuzzer(void) { +static void setup_fuzzer(void) { svr_setup_fuzzer(); - return 0; } int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { @@ -14,15 +14,15 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { once = 1; } - fuzz.input.data = (unsigned char*)Data; - fuzz.input.size = Size; - fuzz.input.len = Size; - fuzz.input.pos = 0; + if (fuzzer_set_input(Data, Size) == DROPBEAR_FAILURE) { + return 0; + } - seedrandom(); + int fakesock = 1; + wrapfd_add(fakesock, fuzz.input, PLAIN); if (setjmp(fuzz.jmp) == 0) { - svr_session(-1, -1); + svr_session(fakesock, fakesock); } else { // dropbear_exit jumped here }