diff options
49 files changed, 1757 insertions, 133 deletions
diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..3938238 --- /dev/null +++ b/.hgignore @@ -0,0 +1,2 @@ +.*\.o +.*~ @@ -23,3 +23,4 @@ fd1981f41c626a969f07b4823848deaefef3c8aa 0 iQIcBAABCgAGBQJW4W2TAAoJEESTFJTynGdzu 70705edee9dd29cd3d410f19fbd15cc3489313e2 0 iQIcBAABCgAGBQJW7CQRAAoJEESTFJTynGdzTj0QAJL38CKSZthBAeI9c6B+IlwIeT6kPZaPqk1pkycCTWOe87NiNU9abrsF+JrjTuRQiO1EpM2IvfQEIXTijUcMxvld3PnzrZDDv6UvBLtOkn3i++HSVRO0MOuTKI8gFDEPUxRtcaCKXEbqYnf1OTK25FT09Vb//qP9mK1thvlLJmbV+D2a9MkMK66rom1d1h+347IsuwsM+ycHjB80VVAQLA7VYLC5YIwmL17dSmcQLvetfikAMwwmUE+KES4qiLSaqOcAWcKcU67RZzgMMv5o0rESlQmv1nj0mHZtHoUR71sd21emPaRXLOr0oT5YogWUphKq2qVthRn2B06+vd3hPdtn92CmJw9j7zT2jl4OeSjNm9qfAajsRzHIANssFxkGAb7w/LxcMoO29JC+01iUUJMdOVm+4Ns6wGI7qxssWPKdB+VbQUDlHrXLR+sopO524uhkYoWB6DVfTj4R6tImaHtj5/VXON0lsYaLGj8cSH60emL6nNQ0lYV/bSlk6l0s+0x3uXGZnp9oKA+vqMzHfG3vJeMm6KUqtFVjUsYx+q8nHm5/SlWxj1EwnkH8s8ELKZAUXjd76nWEwJ7JFRNRSQWvjOUh3/rsOo4JopzZXPsjCjm+Vql9TG0X6hB21noai32oD5RvfhtR/NX6sXNS5TKZz/j/cMsMnAAsSKb6W7Jm 9030ffdbe5625e35ed7189ab84a41dfc8d413e9c 0 iQIcBAABCgAGBQJXkOg0AAoJEESTFJTynGdzc1kP/3vSKCnhOOvjCjnpTQadYcCUq8vTNnfLHYVu0R4ItPa/jT6RmxoaYP+lZnLnnBx9+aX7kzwHsa9BUX3MbMEyLrOzX2I+bDJbNPhQyupyCuPYlf5Q9KVcO9YlpbsC4q5XBzCn3j2+pT8kSfi9uD8fgY3TgE4w9meINrfQAealfjwMLT8S/I49/ni0r+usSfk/dnSShJYDUO7Ja0VWbJea/GkkZTu30bCnMUZPjRApipU3hPP63WFjkSMT1rp2mAXbWqyr9lf8z32yxzM9nMSjq4ViRFzFlkGtE3EVRJ4PwkO7JuiWAMPJpiQcEr+r52cCsmWhiGyHuINo01MwoMO9/n6uL1WVa3mJcE9se3xBOvfgDu2FRFGCAdm1tef+AGVo9EG1uJXi0sX2yUc6DMeuYaRWrXMMlZh7zp9cuNU9Y/lLui9RFmq66yeXG3Z2B72doju3Ig5QGrNNw2AOsSzeHdAtOp6ychqPcl9QfIeJQG18KyPSefZKM3G8YRKBRIwXFEH6iZJe5ZIP4iXrHDMn2JqtTRtDqKR8VNDAgb9z4Ffx8QRxFyj5JzTTMM1GddHb9udLvTQlO0ULYG7hCSMRNzvUBE2aTw8frjLRyfyyg3QpDu/hz8op8s1ecE8rTCD8RuX9DiiylNozypPtGNS+UDbAmkc1PCWaRpPVl+9K6787 5c9207ceedaea794f958224c19214d66af6e2d56 0 iQIzBAABCgAdFiEE9zR+8u4uB6JnYoypRJMUlPKcZ3MFAlkdtooACgkQRJMUlPKcZ3P6ZxAAmLy/buZB/d96DJF/pViRWt/fWdjQFC4MqWfeSLW02OZ8Qkm1vPL3ln6WPHC2thy3xZWVg2uan3pLk/XXnsIFu8Q7r1EAfFFpvlMUmdl7asE8V6ilaeqmiI7bIvGMFbf4cZkQliLjiFkJX56tFHRCNi+rb7WgRuru3/GzPXUq2AvXZvFpFJgik0B72TxVlmCKeBRZq1FvP0UhAH48RJWYJksdEyzh2paMfjX9ZO5Q2SFFrmPw6k2ArdJFC1AYcgceZC84y06RKJ0WiSntUPlEUXgQbQVVWbtQDhjfJXMr/beuroNdT/vsRraLVkAzvhaDXNnHlAJNLQxci+AcLpnzZhxMW+ax7RRtrpXGxRN4cs0lBGUcSkaDybFqMYXwEjXAE8w6fdJRWCIlxctkAW/iNEO4kAG97hI2Qwcw5oU2Ymnv09zyGR+XJE35pJqPulJHExdwanJHvmjH0QF7TNFS82yxS5dKnP954cj3Lu9SWGYWjxQJRmLtOwb+lqqol4VTxG7Ois4uef9/Tpp9skeMZXVeNlpn2wrp6iFcX3uiiVDg9VKkl3ig6UqCiqQSuiIN87RXwUOeHXlCnW3adz3Xei0ziBrwLSql7lBIHGEAlUUNmJ3CrR8IwQtcynGEMKfNIeZ/XK+uNlm9cJIqZf1fzqc8KexlyS9AS0i/kiYZTr4= +2f0c3f3361d3ea4eb9129ed8810699fda7e7a8ee 0 iQIzBAABCgAdFiEE9zR+8u4uB6JnYoypRJMUlPKcZ3MFAlqVb+IACgkQRJMUlPKcZ3OENA//R9HsOUJQB2QZjRgAvqgLn2AMLUvmWb2etTZEc3Nps957Fw1F4kjh6VGfIpWuytfsDx1W8qRx09ikTdb3YteMWCuX8/aFreSPrioYmzrAEcxkZdA7B/jciqU0iXuHiJ9saKk5TR70aNp+iRy0hjAgiYEsVMF9YKHzULOJcHr70x9XVKquubQkwNqJA+/b2JbK2j46wM5nVK/alGSI2kMmEzXmAHQxsvf1OLMvgH8ou/l0xsg/CuFEK299XKfZAbsFEXrjuoWZ1aSa6rTeOWsWli5T+czyyJHI4Eu0Sz/gaR8+MPhJSYes8YjvzEdv32rRMDVOdBq4e+HoTgFt/THYABP6/R1H5fX3Lm4K8u9F9SwJbb/YKRAIrfWDob8ApnGFHk2dyYO20Fskbbg6b1pC7ulDWsufu8lYkQyMlTc3dR6P4eTB6mKO4x+gMG6tIYZ60fiULoEnMJCgegPtevmz+TG1rzdjh3ljiw9Dxz5lNtL+W7sBKKHwhyG0u+bavgmvBMKNL/rdHEM+0yCIz1U6Lb8sVaST1E4zbdm7cWHbSozBij3G0GBSkLFEq7ZLlh8wco9rELRh0Y9fFsWY9j6H/PTOu0GfHrYluFb9WGywHAquQY8j2croRx+MrvTbR1wZrbevPNm9gqk3vgOiDWu7KwxLLqcj+dEQ7tccptVYtbM= @@ -55,3 +55,4 @@ cbd674d63cd4f3781464a8d4056a5506c8ae926f DROPBEAR_2015.67 309e1c4a87682b6ca7d80b8555a1db416c3cb7ac DROPBEAR_2016.73 0ed3d2bbf956cb8a9bf0f4b5a86b7dd9688205cb DROPBEAR_2016.74 c31276613181c5cff7854e7ef586ace03424e55e DROPBEAR_2017.75 +1c66ca4f3791c82501c88e7637312182c7294978 DROPBEAR_2018.76 diff --git a/.travis.yml b/.travis.yml index f938dcb..9bcbce4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,20 +9,26 @@ matrix: - os: linux compiler: gcc env: WEXTRAFLAGS=-Werror + sudo: false - env: MULTI=1 WEXTRAFLAGS=-Werror # libtom has some warnings, so no WEXTRAFLAGS - - env: BUNDLEDLIBTOM=--enable-bundled-libtom WEXTRAFLAGS="" + - env: CONFIGURE_FLAGS=--enable-bundled-libtom WEXTRAFLAGS="" - env: NOWRITEV=1 WEXTRAFLAGS=-Werror # libtomcrypt 1.18.1 fixes clang problems, distro doesn't have that yet - os: linux compiler: clang - env: BUNDLEDLIBTOM=--enable-bundled-libtom WEXTRAFLAGS="" + env: CONFIGURE_FLAGS=--enable-bundled-libtom WEXTRAFLAGS="" - os: osx compiler: clang env: WEXTRAFLAGS="" + # Note: the fuzzing malloc wrapper doesn't replace free() in system libtomcrypt, so need bundled. + - env: DO_FUZZ=1 CONFIGURE_FLAGS="--enable-fuzz --disable-harden --enable-bundled-libtom" WEXTRAFLAGS="" LDFLAGS=-fsanitize=address EXTRACFLAGS=-fsanitize=address CXX=clang++ + compiler: clang + # sanitizers need ptrace which is privileged https://github.com/travis-ci/travis-ci/issues/9033 + sudo: required + # container-based builds -sudo: false addons: apt: packages: @@ -30,24 +36,28 @@ addons: - zlib1g-dev - libtomcrypt-dev - libtommath-dev - + - mercurial before_install: - if [ "$CC" = "clang" ]; then WEXTRAFLAGS="$WEXTRAFLAGS -Wno-error=incompatible-library-redeclaration" ; fi # workaround -script: - - autoconf && autoheader && ./configure "$BUNDLEDLIBTOM" CFLAGS="-O2 -Wall -Wno-pointer-sign $WEXTRAFLAGS" --prefix="$HOME/inst" +install: + - autoconf + - autoheader + - ./configure $CONFIGURE_FLAGS CFLAGS="-O2 -Wall -Wno-pointer-sign $WEXTRAFLAGS $EXTRACFLAGS" --prefix="$HOME/inst" || (cat config.log; exit 1) - if [ "$NOWRITEV" = "1" ]; then sed -i -e s/HAVE_WRITEV/DONT_HAVE_WRITEV/ config.h ; fi - make -j3 + - test -z $DO_FUZZ || make fuzzstandalone # avoid concurrent install, osx/freebsd is racey (https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=208093) - make install -after_success: +script: - ~/inst/bin/dropbearkey -t rsa -f testrsa - ~/inst/bin/dropbearkey -t dss -f testdss - ~/inst/bin/dropbearkey -t ecdsa -f testec256 -s 256 - ~/inst/bin/dropbearkey -t ecdsa -f testec384 -s 384 - ~/inst/bin/dropbearkey -t ecdsa -f testec521 -s 521 + - test -z $DO_FUZZ || ./fuzzers_test.sh branches: only: diff --git a/FUZZER-NOTES.md b/FUZZER-NOTES.md new file mode 100644 index 0000000..7b88238 --- /dev/null +++ b/FUZZER-NOTES.md @@ -0,0 +1,74 @@ +# Fuzzing Dropbear + +Dropbear is process-per-session so it assumes calling `dropbear_exit()` +is fine at any point to clean up. This makes fuzzing a bit trickier. +A few pieces of wrapping infrastructure are used to work around this. + +The [libfuzzer](http://llvm.org/docs/LibFuzzer.html#fuzz-target) harness +expects a long running process to continually run a test function with +a string of crafted input. That process should not leak resources or exit. + +## longjmp + +When dropbear runs in fuzz mode it sets up a +[`setjmp()`](http://man7.org/linux/man-pages/man3/setjmp.3.html) target prior +to launching the code to be fuzzed, and then [`dropbear_exit()`](dbutil.c#L125) +calls `longjmp()` back there. This avoids exiting though it doesn't free +memory or other resources. + +## malloc Wrapper + +Dropbear normally uses a [`m_malloc()`](dbmalloc.c) function that is the same as `malloc()` but +exits if allocation fails. In fuzzing mode this is replaced with a tracking allocator +that stores all allocations in a linked list. After the `longjmp()` occurs the fuzzer target +calls [`m_malloc_free_epoch(1, 1)`](dbmalloc.c) to clean up any unreleased memory. + +If the fuzz target runs to completion it calls `m_malloc_free_epoch(1, 0)` which will reset +the tracked allocations but will not free memory - that allows libfuzzer's leak checking +to detect leaks in normal operation. + +## File Descriptor Input + +As a network process Dropbear reads and writes from a socket. The wrappers for +`read()`/`write()`/`select()` in [fuzz-wrapfd.c](fuzz-wrapfd.c) will read from the +fuzzer input that has been set up with `wrapfd_add()`. `write()` output is +currently discarded. +These also test error paths such as EINTR and short reads with certain probabilities. + +This allows running the entire dropbear server process with network input provided by the +fuzzer, without many modifications to the main code. At the time of writing this +only runs the pre-authentication stages, though post-authentication could be run similarly. + +## Encryption and Randomness + +When running in fuzzing mode Dropbear uses a [fixed seed](dbrandom.c#L185) +every time so that failures can be reproduced. + +Since the fuzzer cannot generate valid encrypted input the packet decryption and +message authentication calls are disabled, see [packet.c](packet.c). +MAC failures are set to occur with a low probability to test that error path. + +## Fuzzers + +Current fuzzers are + +- [fuzzer-preauth](fuzzer-preauth.c) - the fuzzer input is treated as a stream of session input. This will + test key exchange, packet ordering, authentication attempts etc. + +- [fuzzer-preauth_nomaths](fuzzer-preauth_nomaths.c) - the same as fuzzer-preauth but with asymmetric crypto + routines replaced with dummies for faster runtime. corpora are shared + between fuzzers by [oss-fuzz](https://github.com/google/oss-fuzz) so this + will help fuzzer-preauth too. + +- [fuzzer-verify](fuzzer-verify.c) - read a key and signature from fuzzer input and verify that signature. + It would not be expected to pass, though some keys with bad parameters are + able to validate with a trivial signature - extra checks are added for that. + +- [fuzzer-pubkey](fuzzer-pubkey.c) - test parsing of an `authorized_keys` line. + +- [fuzzer-kexdh](fuzzer-kexdh.c) - test Diffie-Hellman key exchange where the fuzz input is the + ephemeral public key that would be received over the network. This is testing `mp_expt_mod()` + and and other libtommath routines. + +- [fuzzer-kexecdh](fuzzer-kexecdh.c) - test Elliptic Curve Diffie-Hellman key exchange like fuzzer-kexdh. + This is testing libtommath ECC routines. diff --git a/Makefile.in b/Makefile.in index e7d52a2..be2d39e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -36,6 +36,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 \ + dbmalloc.o \ gensignkey.o gendss.o genrsa.o SVROBJS=svr-kex.o svr-auth.o sshpty.o \ @@ -59,11 +60,25 @@ CONVERTOBJS=dropbearconvert.o keyimport.o SCPOBJS=scp.o progressmeter.o atomicio.o scpmisc.o compat.o -dropbearobjs=$(COMMONOBJS) $(CLISVROBJS) $(SVROBJS) -dbclientobjs=$(COMMONOBJS) $(CLISVROBJS) $(CLIOBJS) -dropbearkeyobjs=$(COMMONOBJS) $(KEYOBJS) -dropbearconvertobjs=$(COMMONOBJS) $(CONVERTOBJS) -scpobjs=$(SCPOBJS) +ifeq (@DROPBEAR_FUZZ@, 1) + allobjs = $(COMMONOBJS) fuzz-common.o fuzz-wrapfd.o $(CLISVROBJS) $(CLIOBJS) $(SVROBJS) @CRYPTLIB@ + allobjs:=$(subst svr-main.o, ,$(allobjs)) + allobjs:=$(subst cli-main.o, ,$(allobjs)) + allobjs:=$(sort $(allobjs)) + + dropbearobjs=$(allobjs) svr-main.o + dbclientobjs=$(allobjs) cli-main.o + dropbearkeyobjs=$(allobjs) $(KEYOBJS) + dropbearconvertobjs=$(allobjs) $(CONVERTOBJS) + # CXX only set when fuzzing + CXX=@CXX@ +else + dropbearobjs=$(COMMONOBJS) $(CLISVROBJS) $(SVROBJS) + dbclientobjs=$(COMMONOBJS) $(CLISVROBJS) $(CLIOBJS) + dropbearkeyobjs=$(COMMONOBJS) $(KEYOBJS) + dropbearconvertobjs=$(COMMONOBJS) $(CONVERTOBJS) + scpobjs=$(SCPOBJS) +endif VPATH=@srcdir@ srcdir=@srcdir@ @@ -180,7 +195,7 @@ dbclient: $(HEADERS) $(LIBTOM_DEPS) Makefile $(CC) $(LDFLAGS) -o $@$(EXEEXT) $($@objs) $(LIBTOM_LIBS) $(LIBS) dropbearkey dropbearconvert: $(HEADERS) $(LIBTOM_DEPS) Makefile - $(CC) $(LDFLAGS) -o $@$(EXEEXT) $($@objs) $(LIBTOM_LIBS) + $(CC) $(LDFLAGS) -o $@$(EXEEXT) $($@objs) $(LIBTOM_LIBS) $(LIBS) # scp doesn't use the libs so is special. scp: $(SCPOBJS) $(HEADERS) Makefile @@ -236,3 +251,58 @@ distclean: clean tidy tidy: -rm -f *~ *.gcov */*~ + +## Fuzzing targets + +# list of fuzz targets +FUZZ_TARGETS=fuzzer-preauth fuzzer-pubkey fuzzer-verify fuzzer-preauth_nomaths fuzzer-kexdh fuzzer-kexecdh + +FUZZER_OPTIONS = $(addsuffix .options, $(FUZZ_TARGETS)) + +list-fuzz-targets: + @echo $(FUZZ_TARGETS) + +# fuzzers that don't use libfuzzer, just a standalone harness that feeds inputs +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)) + +# build all the fuzzers. This will require fail to link unless built with +# make fuzz-targets FUZZLIB=-lFuzzer.a +# or similar - the library provides main(). +fuzz-targets: $(FUZZ_TARGETS) $(FUZZER_OPTIONS) + +fuzzer-preauth: fuzzer-preauth.o $(HEADERS) $(LIBTOM_DEPS) Makefile $(svrfuzzobjs) + $(CXX) $(CXXFLAGS) $@.o $(LDFLAGS) $(svrfuzzobjs) -o $@$(EXEEXT) $(LIBTOM_LIBS) $(LIBS) $(FUZZLIB) @CRYPTLIB@ + +fuzzer-preauth_nomaths: fuzzer-preauth_nomaths.o $(HEADERS) $(LIBTOM_DEPS) Makefile $(svrfuzzobjs) + $(CXX) $(CXXFLAGS) $@.o $(LDFLAGS) $(svrfuzzobjs) -o $@$(EXEEXT) $(LIBTOM_LIBS) $(LIBS) $(FUZZLIB) @CRYPTLIB@ + +fuzzer-pubkey: fuzzer-pubkey.o $(HEADERS) $(LIBTOM_DEPS) Makefile $(svrfuzzobjs) + $(CXX) $(CXXFLAGS) $@.o $(LDFLAGS) $(svrfuzzobjs) -o $@$(EXEEXT) $(LIBTOM_LIBS) $(LIBS) $(FUZZLIB) @CRYPTLIB@ + +fuzzer-verify: fuzzer-verify.o $(HEADERS) $(LIBTOM_DEPS) Makefile $(svrfuzzobjs) + $(CXX) $(CXXFLAGS) $@.o $(LDFLAGS) $(svrfuzzobjs) -o $@$(EXEEXT) $(LIBTOM_LIBS) $(LIBS) $(FUZZLIB) @CRYPTLIB@ + +fuzzer-kexdh: fuzzer-kexdh.o $(HEADERS) $(LIBTOM_DEPS) Makefile $(svrfuzzobjs) + $(CXX) $(CXXFLAGS) $@.o $(LDFLAGS) $(svrfuzzobjs) -o $@$(EXEEXT) $(LIBTOM_LIBS) $(LIBS) $(FUZZLIB) @CRYPTLIB@ + +fuzzer-kexecdh: fuzzer-kexecdh.o $(HEADERS) $(LIBTOM_DEPS) Makefile $(svrfuzzobjs) + $(CXX) $(CXXFLAGS) $@.o $(LDFLAGS) $(svrfuzzobjs) -o $@$(EXEEXT) $(LIBTOM_LIBS) $(LIBS) $(FUZZLIB) @CRYPTLIB@ + +fuzzer-%.options: Makefile + echo "[libfuzzer]" > $@ + echo "max_len = 50000" >> $@ + +# run this to update hardcoded hostkeys for for fuzzing. +# hostkeys.c is checked in to hg. +fuzz-hostkeys: + dropbearkey -t rsa -f keyr + dropbearkey -t dss -f keyd + dropbearkey -t ecdsa -size 256 -f keye + echo > hostkeys.c + /usr/bin/xxd -i -a keyr >> hostkeys.c + /usr/bin/xxd -i -a keye >> hostkeys.c + /usr/bin/xxd -i -a keyd >> hostkeys.c @@ -209,6 +209,7 @@ char* buf_getstring(buffer* buf, unsigned int *retlen) { unsigned int len; char* ret; + void* src = NULL; len = buf_getint(buf); if (len > MAX_STRING_LEN) { dropbear_exit("String too long"); @@ -217,8 +218,9 @@ char* buf_getstring(buffer* buf, unsigned int *retlen) { if (retlen != NULL) { *retlen = len; } + src = buf_getptr(buf, len); ret = m_malloc(len+1); - memcpy(ret, buf_getptr(buf, len), len); + memcpy(ret, src, len); buf_incrpos(buf, len); ret[len] = '\0'; diff --git a/common-kex.c b/common-kex.c index c4790b8..d4933dd 100644 --- a/common-kex.c +++ b/common-kex.c @@ -48,7 +48,6 @@ static void read_kex_algos(void); /* helper function for gen_new_keys */ static void hashkeys(unsigned char *out, unsigned int outlen, const hash_state * hs, const unsigned char X); -static void finish_kexhashbuf(void); /* Send our list of algorithms we can use */ @@ -391,6 +390,14 @@ int is_compress_recv() { && ses.keys->recv.algo_comp == DROPBEAR_COMP_ZLIB_DELAY); } +static void* dropbear_zalloc(void* UNUSED(opaque), uInt items, uInt size) { + return m_calloc(items, size); +} + +static void dropbear_zfree(void* UNUSED(opaque), void* ptr) { + m_free(ptr); +} + /* Set up new zlib compression streams, close the old ones. Only * called from gen_new_keys() */ static void gen_new_zstream_recv() { @@ -399,8 +406,8 @@ static void gen_new_zstream_recv() { if (ses.newkeys->recv.algo_comp == DROPBEAR_COMP_ZLIB || ses.newkeys->recv.algo_comp == DROPBEAR_COMP_ZLIB_DELAY) { ses.newkeys->recv.zstream = (z_streamp)m_malloc(sizeof(z_stream)); - ses.newkeys->recv.zstream->zalloc = Z_NULL; - ses.newkeys->recv.zstream->zfree = Z_NULL; + ses.newkeys->recv.zstream->zalloc = dropbear_zalloc; + ses.newkeys->recv.zstream->zfree = dropbear_zfree; if (inflateInit(ses.newkeys->recv.zstream) != Z_OK) { dropbear_exit("zlib error"); @@ -423,8 +430,8 @@ static void gen_new_zstream_trans() { if (ses.newkeys->trans.algo_comp == DROPBEAR_COMP_ZLIB || ses.newkeys->trans.algo_comp == DROPBEAR_COMP_ZLIB_DELAY) { ses.newkeys->trans.zstream = (z_streamp)m_malloc(sizeof(z_stream)); - ses.newkeys->trans.zstream->zalloc = Z_NULL; - ses.newkeys->trans.zstream->zfree = Z_NULL; + ses.newkeys->trans.zstream->zalloc = dropbear_zalloc; + ses.newkeys->trans.zstream->zfree = dropbear_zfree; if (deflateInit2(ses.newkeys->trans.zstream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, DROPBEAR_ZLIB_WINDOW_BITS, @@ -687,6 +694,9 @@ void kexecdh_comb_key(struct kex_ecdh_param *param, buffer *pub_them, /* K, the shared secret */ buf_putmpint(ses.kexhashbuf, ses.dh_K); + ecc_free(Q_them); + m_free(Q_them); + /* calculate the hash H to sign */ finish_kexhashbuf(); } @@ -761,8 +771,7 @@ void kexcurve25519_comb_key(const struct kex_curve25519_param *param, const buff #endif /* DROPBEAR_CURVE25519 */ - -static void finish_kexhashbuf(void) { +void finish_kexhashbuf(void) { hash_state hs; const struct ltc_hash_descriptor *hash_desc = ses.newkeys->algo_kex->hash_desc; @@ -943,6 +952,12 @@ static void read_kex_algos() { ses.newkeys->trans.algo_comp = s2c_comp_algo->val; } +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + fuzz_kex_fakealgos(); + } +#endif + /* reserved for future extensions */ buf_getint(ses.payload); diff --git a/common-session.c b/common-session.c index a3a2f17..96dd4dc 100644 --- a/common-session.c +++ b/common-session.c @@ -75,14 +75,18 @@ void common_session_init(int sock_in, int sock_out) { ses.last_packet_time_any_sent = 0; ses.last_packet_time_keepalive_sent = 0; +#if DROPBEAR_FUZZ + if (!fuzz.fuzzing) +#endif + { if (pipe(ses.signal_pipe) < 0) { dropbear_exit("Signal pipe failed"); } setnonblocking(ses.signal_pipe[0]); setnonblocking(ses.signal_pipe[1]); - ses.maxfd = MAX(ses.maxfd, ses.signal_pipe[0]); ses.maxfd = MAX(ses.maxfd, ses.signal_pipe[1]); + } ses.writepayload = buf_new(TRANS_MAX_PAYLOAD_LEN); ses.transseq = 0; @@ -148,13 +152,19 @@ void session_loop(void(*loophandler)(void)) { timeout.tv_sec = select_timeout(); timeout.tv_usec = 0; - FD_ZERO(&writefd); - FD_ZERO(&readfd); + DROPBEAR_FD_ZERO(&writefd); + DROPBEAR_FD_ZERO(&readfd); + dropbear_assert(ses.payload == NULL); /* We get woken up when signal handlers write to this pipe. SIGCHLD in svr-chansession is the only one currently. */ +#if DROPBEAR_FUZZ + if (!fuzz.fuzzing) +#endif + { FD_SET(ses.signal_pipe[0], &readfd); + } /* set up for channels which can be read/written */ setchannelfds(&readfd, &writefd, writequeue_has_space); @@ -195,8 +205,8 @@ void session_loop(void(*loophandler)(void)) { * want to iterate over channels etc for reading, to handle * server processes exiting etc. * We don't want to read/write FDs. */ - FD_ZERO(&writefd); - FD_ZERO(&readfd); + DROPBEAR_FD_ZERO(&writefd); + DROPBEAR_FD_ZERO(&readfd); } /* We'll just empty out the pipe if required. We don't do @@ -298,6 +308,16 @@ void session_cleanup() { buf_free(dequeue(&ses.writequeue)); } + m_free(ses.newkeys); +#ifndef DISABLE_ZLIB + if (ses.keys->recv.zstream != NULL) { + if (inflateEnd(ses.keys->recv.zstream) == Z_STREAM_ERROR) { + dropbear_exit("Crypto error"); + } + m_free(ses.keys->recv.zstream); + } +#endif + m_free(ses.remoteident); m_free(ses.authstate.pw_dir); m_free(ses.authstate.pw_name); @@ -327,7 +347,7 @@ void session_cleanup() { void send_session_identification() { buffer *writebuf = buf_new(strlen(LOCAL_IDENT "\r\n") + 1); buf_putbytes(writebuf, (const unsigned char *) LOCAL_IDENT "\r\n", strlen(LOCAL_IDENT "\r\n")); - writebuf_enqueue(writebuf, 0); + writebuf_enqueue(writebuf); } static void read_session_identification() { @@ -387,7 +407,7 @@ static int ident_readln(int fd, char* buf, int count) { return -1; } - FD_ZERO(&fds); + DROPBEAR_FD_ZERO(&fds); /* select since it's a non-blocking fd */ diff --git a/configure.ac b/configure.ac index 85a23f4..c0bb8a3 100644 --- a/configure.ac +++ b/configure.ac @@ -9,6 +9,13 @@ AC_PREREQ(2.59) AC_INIT AC_CONFIG_SRCDIR(buffer.c) +# Record which revision is being built +if test -s "`which hg`" && test -d "$srcdir/.hg"; then + hgrev=`hg id -i -R "$srcdir"` + AC_MSG_NOTICE([Source directory Mercurial base revision $hgrev]) +fi + +ORIGCFLAGS="$CFLAGS" # Checks for programs. AC_PROG_CC @@ -29,7 +36,7 @@ AC_DEFUN(DB_TRYADDCFLAGS, }]) # set compile flags prior to other tests -if test -z "$OLDCFLAGS" && test "$GCC" = "yes"; then +if test -z "$ORIGCFLAGS" && test "$GCC" = "yes"; then AC_MSG_NOTICE(No \$CFLAGS set... using "-Os -W -Wall" for GCC) CFLAGS="-Os -W -Wall" fi @@ -315,7 +322,24 @@ AC_ARG_ENABLE(shadow, AC_MSG_NOTICE(Using shadow passwords if available) ] ) - + +AC_ARG_ENABLE(fuzz, + [ --enable-fuzz Build fuzzing. Not recommended for deployment.], + [ + AC_DEFINE(DROPBEAR_FUZZ, 1, Fuzzing) + AC_MSG_NOTICE(Enabling fuzzing) + DROPBEAR_FUZZ=1 + # libfuzzer needs linking with c++ libraries + AC_PROG_CXX + ], + [ + AC_DEFINE(DROPBEAR_FUZZ, 0, Fuzzing) + DROPBEAR_FUZZ=0 + ] + +) +AC_SUBST(DROPBEAR_FUZZ) +AC_SUBST(CXX) # Checks for header files. AC_HEADER_STDC diff --git a/dbhelpers.c b/dbhelpers.c index f7461d9..ce5c379 100644 --- a/dbhelpers.c +++ b/dbhelpers.c @@ -9,16 +9,9 @@ void m_burn(void *data, unsigned int len) { #elif defined(HAVE_EXPLICIT_BZERO) explicit_bzero(data, len); #else -/* Based on the method in David Wheeler's - * "Secure Programming for Linux and Unix HOWTO". May not be safe - * against link-time optimisation. */ - volatile char *p = data; - - if (data == NULL) - return; - while (len--) { - *p++ = 0x0; - } + /* This must be volatile to avoid compiler optimisation */ + volatile void *p = data; + memset((void*)p, 0x0, len); #endif } diff --git a/dbmalloc.c b/dbmalloc.c new file mode 100644 index 0000000..8c6701b --- /dev/null +++ b/dbmalloc.c @@ -0,0 +1,182 @@ +#include "dbmalloc.h" +#include "dbutil.h" + + +void * m_calloc(size_t nmemb, size_t size) { + if (SIZE_T_MAX / nmemb < size) { + dropbear_exit("m_calloc failed"); + } + return m_malloc(nmemb*size); +} + +void * m_strdup(const char * str) { + char* ret; + unsigned int len; + len = strlen(str); + + ret = m_malloc(len+1); + if (ret == NULL) { + dropbear_exit("m_strdup failed"); + } + memcpy(ret, str, len+1); + return ret; +} + +#if !DROPBEAR_TRACKING_MALLOC + +/* Simple wrappers around malloc etc */ +void * m_malloc(size_t size) { + + void* ret; + + if (size == 0) { + dropbear_exit("m_malloc failed"); + } + ret = calloc(1, size); + if (ret == NULL) { + dropbear_exit("m_malloc failed"); + } + return ret; + +} + +void * m_realloc(void* ptr, size_t size) { + + void *ret; + + if (size == 0) { + dropbear_exit("m_realloc failed"); + } + ret = realloc(ptr, size); + if (ret == NULL) { + dropbear_exit("m_realloc failed"); + } + return ret; +} + + +#else + +/* For fuzzing */ + +struct dbmalloc_header { + unsigned int epoch; + struct dbmalloc_header *prev; + struct dbmalloc_header *next; +}; + +static void put_alloc(struct dbmalloc_header *header); +static void remove_alloc(struct dbmalloc_header *header); + +/* end of the linked list */ +static struct dbmalloc_header* staple; + +unsigned int current_epoch = 0; + +void m_malloc_set_epoch(unsigned int epoch) { + current_epoch = epoch; +} + +void m_malloc_free_epoch(unsigned int epoch, int dofree) { + struct dbmalloc_header* header; + struct dbmalloc_header* nextheader = NULL; + struct dbmalloc_header* oldstaple = staple; + staple = NULL; + /* free allocations from this epoch, create a new staple-anchored list from + the remainder */ + for (header = oldstaple; header; header = nextheader) + { + nextheader = header->next; + if (header->epoch == epoch) { + if (dofree) { + free(header); + } + } else { + header->prev = NULL; + header->next = NULL; + put_alloc(header); + } + } +} + +static void put_alloc(struct dbmalloc_header *header) { + assert(header->next == NULL); + assert(header->prev == NULL); + if (staple) { + staple->prev = header; + } + header->next = staple; + staple = header; +} + +static void remove_alloc(struct dbmalloc_header *header) { + if (header->prev) { + header->prev->next = header->next; + } + if (header->next) { + header->next->prev = header->prev; + } + if (staple == header) { + staple = header->next; + } + header->prev = NULL; + header->next = NULL; +} + +static struct dbmalloc_header* get_header(void* ptr) { + char* bptr = ptr; + return (struct dbmalloc_header*)&bptr[-sizeof(struct dbmalloc_header)]; +} + +void * m_malloc(size_t size) { + char* mem = NULL; + struct dbmalloc_header* header = NULL; + + if (size == 0 || size > 1e9) { + dropbear_exit("m_malloc failed"); + } + + size = size + sizeof(struct dbmalloc_header); + + mem = calloc(1, size); + if (mem == NULL) { + dropbear_exit("m_malloc failed"); + } + header = (struct dbmalloc_header*)mem; + put_alloc(header); + header->epoch = current_epoch; + return &mem[sizeof(struct dbmalloc_header)]; +} + +void * m_realloc(void* ptr, size_t size) { + char* mem = NULL; + struct dbmalloc_header* header = NULL; + if (size == 0 || size > 1e9) { + dropbear_exit("m_realloc failed"); + } + + header = get_header(ptr); + remove_alloc(header); + + size = size + sizeof(struct dbmalloc_header); + mem = realloc(header, size); + if (mem == NULL) { + dropbear_exit("m_realloc failed"); + } + + header = (struct dbmalloc_header*)mem; + put_alloc(header); + return &mem[sizeof(struct dbmalloc_header)]; +} + +void m_free_direct(void* ptr) { + struct dbmalloc_header* header = NULL; + if (!ptr) { + return; + } + header = get_header(ptr); + remove_alloc(header); + free(header); +} + +#endif /* DROPBEAR_TRACKING_MALLOC */ diff --git a/dbmalloc.h b/dbmalloc.h new file mode 100644 index 0000000..d5e814e --- /dev/null +++ b/dbmalloc.h @@ -0,0 +1,27 @@ +#ifndef DBMALLOC_H_ +#define DBMALLOC_H_ + +#include "stdint.h" +#include "stdlib.h" +#include "options.h" + +void * m_malloc(size_t size); +void * m_calloc(size_t nmemb, size_t size); +void * m_strdup(const char * str); +void * m_realloc(void* ptr, size_t size); + +#if DROPBEAR_TRACKING_MALLOC +void m_free_direct(void* ptr); +void m_malloc_set_epoch(unsigned int epoch); +void m_malloc_free_epoch(unsigned int epoch, int dofree); + +#else +/* plain wrapper */ +#define m_free_direct free + +#endif + +#define m_free(X) do {m_free_direct(X); (X) = NULL;} while (0) + + +#endif /* DBMALLOC_H_ */ @@ -27,7 +27,7 @@ #include "dbutil.h" #include "bignum.h" #include "dbrandom.h" - +#include "runopts.h" /* this is used to generate unique output from the same hashpool */ static uint32_t counter = 0; @@ -88,7 +88,7 @@ process_file(hash_state *hs, const char *filename, timeout.tv_sec = 2; timeout.tv_usec = 0; - FD_ZERO(&read_fds); + DROPBEAR_FD_ZERO(&read_fds); FD_SET(readfd, &read_fds); res = select(readfd + 1, &read_fds, NULL, NULL, &timeout); if (res == 0) @@ -145,6 +145,12 @@ void addrandom(const unsigned char * buf, unsigned int len) { hash_state hs; +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + return; + } +#endif + /* hash in the new seed data */ sha1_init(&hs); /* existing state (zeroes on startup) */ @@ -157,6 +163,11 @@ void addrandom(const unsigned char * buf, unsigned int len) static void write_urandom() { +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + return; + } +#endif #if !DROPBEAR_USE_PRNGD /* This is opportunistic, don't worry about failure */ unsigned char buf[INIT_SEED_SIZE]; @@ -170,6 +181,18 @@ static void write_urandom() #endif } +#if DROPBEAR_FUZZ +void fuzz_seed(void) { + hash_state hs; + sha1_init(&hs); + sha1_process(&hs, "fuzzfuzzfuzz", strlen("fuzzfuzzfuzz")); + sha1_done(&hs, hashpool); + + counter = 0; + donerandinit = 1; +} +#endif + /* Initialise the prng from /dev/urandom or prngd. This function can * be called multiple times */ void seedrandom() { @@ -180,8 +203,15 @@ void seedrandom() { struct timeval tv; clock_t clockval; +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + return; + } +#endif + /* hash in the new seed data */ sha1_init(&hs); + /* existing state */ sha1_process(&hs, (void*)hashpool, sizeof(hashpool)); @@ -120,6 +120,13 @@ static void generic_dropbear_exit(int exitcode, const char* format, _dropbear_log(LOG_INFO, fmtbuf, param); +#if DROPBEAR_FUZZ + /* longjmp before cleaning up svr_opts */ + if (fuzz.do_jmp) { + longjmp(fuzz.jmp, 1); + } +#endif + exit(exitcode); } @@ -392,6 +399,7 @@ void printhex(const char * label, const unsigned char * buf, int len) { void printmpint(const char *label, mp_int *mp) { buffer *buf = buf_new(1000); buf_putmpint(buf, mp); + fprintf(stderr, "%d bits ", mp_count_bits(mp)); printhex(label, buf->data, buf->len); buf_free(buf); @@ -520,57 +528,26 @@ void m_close(int fd) { } } -void * m_malloc(size_t size) { - - void* ret; - - if (size == 0) { - dropbear_exit("m_malloc failed"); - } - ret = calloc(1, size); - if (ret == NULL) { - dropbear_exit("m_malloc failed"); - } - return ret; - -} - -void * m_strdup(const char * str) { - char* ret; - - ret = strdup(str); - if (ret == NULL) { - dropbear_exit("m_strdup failed"); - } - return ret; -} - -void * m_realloc(void* ptr, size_t size) { - - void *ret; - - if (size == 0) { - dropbear_exit("m_realloc failed"); - } - ret = realloc(ptr, size); - if (ret == NULL) { - dropbear_exit("m_realloc failed"); - } - return ret; -} - void setnonblocking(int fd) { TRACE(("setnonblocking: %d", fd)) +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + return; + } +#endif + if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) { if (errno == ENODEV) { /* Some devices (like /dev/null redirected in) * can't be set to non-blocking */ TRACE(("ignoring ENODEV for setnonblocking")) } else { + { dropbear_exit("Couldn't set nonblocking"); } + } } TRACE(("leave setnonblocking")) } @@ -652,7 +629,14 @@ static clockid_t get_linux_clock_source() { #endif time_t monotonic_now() { +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + /* time stands still when fuzzing */ + return 5; + } +#endif #if defined(__linux__) && defined(SYS_clock_gettime) + { static clockid_t clock_source = -2; if (clock_source == -2) { @@ -669,9 +653,11 @@ time_t monotonic_now() { } return ts.tv_sec; } + } #endif /* linux clock_gettime */ #if defined(HAVE_MACH_ABSOLUTE_TIME) + { /* OS X, see https://developer.apple.com/library/mac/qa/qa1398/_index.html */ static mach_timebase_info_data_t timebase_info; if (timebase_info.denom == 0) { @@ -679,6 +665,7 @@ time_t monotonic_now() { } return mach_absolute_time() * timebase_info.numer / timebase_info.denom / 1e9; + } #endif /* osx mach_absolute_time */ /* Fallback for everything else - this will sometimes go backwards */ @@ -700,6 +687,6 @@ void fsync_parent_dir(const char* fn) { TRACE(("error opening directory %s for fsync: %s", dir, strerror(errno))) } - free(fn_dir); + m_free(fn_dir); #endif } @@ -30,6 +30,7 @@ #include "buffer.h" #include "queue.h" #include "dbhelpers.h" +#include "dbmalloc.h" #ifndef DISABLE_SYSLOG void startsyslog(const char *ident); @@ -66,10 +67,6 @@ int buf_readfile(buffer* buf, const char* filename); int buf_getline(buffer * line, FILE * authfile); void m_close(int fd); -void * m_malloc(size_t size); -void * m_strdup(const char * str); -void * m_realloc(void* ptr, size_t size); -#define m_free(X) do {free(X); (X) = NULL;} while (0) void setnonblocking(int fd); void disallow_core(void); int m_str_to_uint(const char* str, unsigned int *val); @@ -91,4 +88,11 @@ char * expand_homedir_path(const char *inpath); void fsync_parent_dir(const char* fn); +#if DROPBEAR_MSAN +/* FD_ZERO seems to leave some memory uninitialized. clear it to avoid false positives */ +#define DROPBEAR_FD_ZERO(fds) do { memset((fds), 0x0, sizeof(fd_set)); FD_ZERO(fds); } while(0) +#else +#define DROPBEAR_FD_ZERO(fds) FD_ZERO(fds) +#endif + #endif /* DROPBEAR_DBUTIL_H_ */ @@ -39,7 +39,9 @@ /*#define CHECKCLEARTOWRITE() assert(ses.writepayload->len == 0 && \ ses.writepayload->pos == 0)*/ +#ifndef CHECKCLEARTOWRITE #define CHECKCLEARTOWRITE() +#endif /* Define this, compile with -pg and set GMON_OUT_PREFIX=gmon to get gmon * output when Dropbear forks. This will allow it gprof to be used. @@ -54,6 +56,7 @@ /* you don't need to touch this block */ #if DEBUG_TRACE +extern int debug_trace; #define TRACE(X) dropbear_trace X; #define TRACE2(X) dropbear_trace2 X; #else /*DEBUG_TRACE*/ @@ -73,6 +73,18 @@ int buf_get_dss_pub_key(buffer* buf, dropbear_dss_key *key) { goto out; } + /* test 1 < g < p */ + if (mp_cmp_d(key->g, 1) != MP_GT) { + dropbear_log(LOG_WARNING, "Bad DSS g"); + ret = DROPBEAR_FAILURE; + goto out; + } + if (mp_cmp(key->g, key->p) != MP_LT) { + dropbear_log(LOG_WARNING, "Bad DSS g"); + ret = DROPBEAR_FAILURE; + goto out; + } + ret = DROPBEAR_SUCCESS; TRACE(("leave buf_get_dss_pub_key: success")) out: @@ -172,6 +184,13 @@ int buf_dss_verify(buffer* buf, const dropbear_dss_key *key, const buffer *data_ goto out; } +#if DEBUG_DSS_VERIFY + printmpint("dss verify p", key->p); + printmpint("dss verify q", key->q); + printmpint("dss verify g", key->g); + printmpint("dss verify y", key->y); +#endif + /* hash the data */ sha1_init(&hs); sha1_process(&hs, data_buf->data, data_buf->len); @@ -181,6 +200,9 @@ int buf_dss_verify(buffer* buf, const dropbear_dss_key *key, const buffer *data_ /* w = (s')-1 mod q */ /* let val1 = s' */ bytes_to_mp(&val1, (const unsigned char*) &string[SHA1_HASH_SIZE], SHA1_HASH_SIZE); +#if DEBUG_DSS_VERIFY + printmpint("dss verify s'", &val1); +#endif if (mp_cmp(&val1, key->q) != MP_LT) { TRACE(("verify failed, s' >= q")) @@ -198,6 +220,9 @@ int buf_dss_verify(buffer* buf, const dropbear_dss_key *key, const buffer *data_ /* u1 = ((SHA(M')w) mod q */ /* let val1 = SHA(M') = msghash */ bytes_to_mp(&val1, msghash, SHA1_HASH_SIZE); +#if DEBUG_DSS_VERIFY + printmpint("dss verify r'", &val1); +#endif /* let val3 = u1 = ((SHA(M')w) mod q */ if (mp_mulmod(&val1, &val2, key->q, &val3) != MP_OKAY) { @@ -16,7 +16,7 @@ #elif DROPBEAR_ECC_521 #define ECDSA_DEFAULT_SIZE 521 #else -#define ECDSA_DEFAULT_SIZE 0 +#error ECDSA cannot be enabled without enabling at least one size (256, 384, 521) #endif ecc_key *gen_ecdsa_priv_key(unsigned int bit_size); diff --git a/fuzz-common.c b/fuzz-common.c new file mode 100644 index 0000000..5c90c45 --- /dev/null +++ b/fuzz-common.c @@ -0,0 +1,201 @@ +#include "includes.h" + +#include "includes.h" +#include "fuzz.h" +#include "dbutil.h" +#include "runopts.h" +#include "crypto_desc.h" +#include "session.h" +#include "dbrandom.h" +#include "bignum.h" +#include "fuzz-wrapfd.h" + +struct dropbear_fuzz_options fuzz; + +static void fuzz_dropbear_log(int UNUSED(priority), const char* format, va_list param); +static void load_fixed_hostkeys(void); + +void fuzz_common_setup(void) { + fuzz.fuzzing = 1; + fuzz.wrapfds = 1; + fuzz.do_jmp = 1; + fuzz.input = m_malloc(sizeof(buffer)); + _dropbear_log = fuzz_dropbear_log; + crypto_init(); + fuzz_seed(); + /* let any messages get flushed */ + setlinebuf(stdout); +} + +int fuzz_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; + + memset(&ses, 0x0, sizeof(ses)); + memset(&svr_ses, 0x0, sizeof(svr_ses)); + wrapfd_setup(); + + fuzz_seed(); + + return DROPBEAR_SUCCESS; +} + +#if DEBUG_TRACE +static void fuzz_dropbear_log(int UNUSED(priority), const char* format, va_list param) { + if (debug_trace) { + char printbuf[1024]; + vsnprintf(printbuf, sizeof(printbuf), format, param); + fprintf(stderr, "%s\n", printbuf); + } +} +#else +static void fuzz_dropbear_log(int UNUSED(priority), const char* UNUSED(format), va_list UNUSED(param)) { + /* No print */ +} +#endif /* DEBUG_TRACE */ + +void fuzz_svr_setup(void) { + fuzz_common_setup(); + + _dropbear_exit = svr_dropbear_exit; + + char *argv[] = { + "-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(); +} + +static void load_fixed_hostkeys(void) { +#include "fuzz-hostkeys.c" + + buffer *b = buf_new(3000); + enum signkey_type type; + + TRACE(("load fixed hostkeys")) + + svr_opts.hostkey = new_sign_key(); + + buf_setlen(b, 0); + buf_putbytes(b, keyr, keyr_len); + buf_setpos(b, 0); + type = DROPBEAR_SIGNKEY_RSA; + if (buf_get_priv_key(b, svr_opts.hostkey, &type) == DROPBEAR_FAILURE) { + dropbear_exit("failed fixed rsa hostkey"); + } + + buf_setlen(b, 0); + buf_putbytes(b, keyd, keyd_len); + buf_setpos(b, 0); + type = DROPBEAR_SIGNKEY_DSS; + if (buf_get_priv_key(b, svr_opts.hostkey, &type) == DROPBEAR_FAILURE) { + dropbear_exit("failed fixed dss hostkey"); + } + + buf_setlen(b, 0); + buf_putbytes(b, keye, keye_len); + buf_setpos(b, 0); + type = DROPBEAR_SIGNKEY_ECDSA_NISTP256; + if (buf_get_priv_key(b, svr_opts.hostkey, &type) == DROPBEAR_FAILURE) { + dropbear_exit("failed fixed ecdsa hostkey"); + } + + buf_free(b); +} + +void fuzz_kex_fakealgos(void) { + ses.newkeys->recv.crypt_mode = &dropbear_mode_none; +} + +void fuzz_get_socket_address(int UNUSED(fd), char **local_host, char **local_port, + char **remote_host, char **remote_port, int UNUSED(host_lookup)) { + if (local_host) { + *local_host = m_strdup("fuzzlocalhost"); + } + if (local_port) { + *local_port = m_strdup("1234"); + } + if (remote_host) { + *remote_host = m_strdup("fuzzremotehost"); + } + if (remote_port) { + *remote_port = m_strdup("9876"); + } +} + +/* cut down version of svr_send_msg_kexdh_reply() that skips slow maths. Still populates structures */ +void fuzz_fake_send_kexdh_reply(void) { + assert(!ses.dh_K); + m_mp_alloc_init_multi(&ses.dh_K, NULL); + mp_set_int(ses.dh_K, 12345678); + finish_kexhashbuf(); +} + +int fuzz_run_preauth(const uint8_t *Data, size_t Size, int skip_kexmaths) { + static int once = 0; + if (!once) { + fuzz_svr_setup(); + fuzz.skip_kexmaths = skip_kexmaths; + once = 1; + } + + if (fuzz_set_input(Data, Size) == DROPBEAR_FAILURE) { + return 0; + } + + /* + get prefix. 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 = 20; + wrapfd_add(fakesock, fuzz.input, PLAIN); + + m_malloc_set_epoch(1); + if (setjmp(fuzz.jmp) == 0) { + svr_session(fakesock, fakesock); + 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++) { + if (strcmp(t->name, name) == 0) { + return t->data; + } + } + assert(0); +} diff --git a/fuzz-harness.c b/fuzz-harness.c new file mode 100644 index 0000000..be23d4e --- /dev/null +++ b/fuzz-harness.c @@ -0,0 +1,48 @@ +#include "includes.h" +#include "buffer.h" +#include "dbutil.h" + +extern int LLVMFuzzerTestOneInput(const unsigned char *data, size_t size); + +int main(int argc, char ** argv) { + int i; + 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; + TRACE(("debug printing on")) + } +#endif + } + + int old_fuzz_wrapfds = 0; + for (i = 1; i < argc; i++) { + if (argv[i][0] == '-') { + /* ignore arguments */ + continue; + } + + char* fn = argv[i]; + buf_setlen(input, 0); + buf_readfile(input, fn); + buf_setpos(input, 0); + + fuzz.wrapfds = old_fuzz_wrapfds; + printf("Running %s once \n", fn); + LLVMFuzzerTestOneInput(input->data, input->len); + printf("Running %s twice \n", fn); + LLVMFuzzerTestOneInput(input->data, input->len); + printf("Done %s\n", fn); + + /* Disable wrapfd so it won't interfere with buf_readfile() above */ + old_fuzz_wrapfds = fuzz.wrapfds; + fuzz.wrapfds = 0; + } + + printf("Finished\n"); + + return 0; +} diff --git a/fuzz-hostkeys.c b/fuzz-hostkeys.c new file mode 100644 index 0000000..dc1615d --- /dev/null +++ b/fuzz-hostkeys.c @@ -0,0 +1,129 @@ + +unsigned char keyr[] = { + 0x00, 0x00, 0x00, 0x07, 0x73, 0x73, 0x68, 0x2d, 0x72, 0x73, 0x61, 0x00, + 0x00, 0x00, 0x03, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0xb1, + 0x06, 0x95, 0xc9, 0xa8, 0x38, 0xb9, 0x99, 0x91, 0xb5, 0x17, 0x39, 0xb9, + 0xfa, 0xa4, 0x49, 0xf8, 0x2a, 0x4c, 0x14, 0xbd, 0xb6, 0x85, 0xdb, 0x38, + 0x99, 0x44, 0xfa, 0xd6, 0xaa, 0x67, 0xef, 0x00, 0x75, 0x2b, 0x6a, 0x5c, + 0x1b, 0x50, 0xa8, 0x52, 0xf9, 0xa7, 0xee, 0xe2, 0xb3, 0x80, 0x38, 0x92, + 0x20, 0x86, 0x7c, 0xe5, 0x89, 0xb3, 0x06, 0xe4, 0x3b, 0xd1, 0xe2, 0x45, + 0xea, 0xc1, 0xd5, 0x8e, 0x05, 0xfb, 0x90, 0x29, 0xd9, 0x41, 0xb3, 0x05, + 0x31, 0x1e, 0xcc, 0xeb, 0x89, 0xdc, 0xd2, 0x6a, 0x99, 0x23, 0xbd, 0x7a, + 0xbe, 0x8c, 0xe3, 0x3f, 0xa1, 0xe8, 0xf5, 0xb4, 0x51, 0x40, 0xb4, 0xb1, + 0xc1, 0x16, 0x9f, 0x07, 0xbb, 0x99, 0xaa, 0x4b, 0x8f, 0x11, 0x19, 0x3c, + 0x18, 0xbd, 0x6e, 0xce, 0x14, 0x54, 0x2c, 0x16, 0x4a, 0x5f, 0x89, 0xe4, + 0x6b, 0x9f, 0x55, 0x68, 0xcc, 0x09, 0x8e, 0x4b, 0x92, 0xc8, 0x87, 0xfe, + 0x09, 0xed, 0x53, 0x6e, 0xff, 0x5f, 0x15, 0x0d, 0x19, 0x9d, 0xa6, 0x54, + 0xd2, 0xea, 0x59, 0x4f, 0xa1, 0x7c, 0xf6, 0xf5, 0x7f, 0x32, 0x23, 0xed, + 0x72, 0xa8, 0x96, 0x17, 0x87, 0x06, 0xf2, 0xc7, 0xcd, 0xda, 0x4a, 0x10, + 0xd1, 0xfd, 0xb8, 0xf1, 0xaf, 0x25, 0x55, 0x32, 0x45, 0x39, 0x95, 0xec, + 0x0c, 0xa9, 0xf0, 0x47, 0x8b, 0x66, 0xe0, 0xb7, 0xa2, 0xf6, 0x35, 0x50, + 0x27, 0xe7, 0x2f, 0x90, 0x35, 0x5b, 0xd5, 0x62, 0x19, 0xb4, 0x41, 0xd4, + 0x52, 0xe7, 0x7f, 0x97, 0xfc, 0x5b, 0x4a, 0x5b, 0x19, 0x06, 0x65, 0x2d, + 0x23, 0x29, 0x15, 0x8b, 0x05, 0xaf, 0xbe, 0xd3, 0x4a, 0x27, 0x5b, 0xc9, + 0xc0, 0xd0, 0xd2, 0xba, 0x8b, 0x00, 0x7a, 0x2f, 0x39, 0xa0, 0x13, 0xb9, + 0xe6, 0xf5, 0x4b, 0x21, 0x54, 0x57, 0xb3, 0xf9, 0x6c, 0x6f, 0xd0, 0x17, + 0xf4, 0x50, 0x9d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xf2, 0xda, 0x5f, 0xfb, + 0xe2, 0xda, 0xfc, 0xe0, 0xdf, 0x3a, 0x0e, 0x14, 0x18, 0xc1, 0xd9, 0x1f, + 0x43, 0xe3, 0x65, 0x3e, 0x07, 0xe7, 0x8d, 0xdc, 0x1d, 0x11, 0xc1, 0xd6, + 0xc0, 0xd8, 0xda, 0x53, 0xf5, 0x04, 0x73, 0x51, 0x1b, 0x26, 0xef, 0x4e, + 0xf5, 0xce, 0x3d, 0x77, 0x21, 0x94, 0xd0, 0xc7, 0xc1, 0xda, 0x19, 0x7d, + 0xf8, 0xc5, 0x4c, 0xc8, 0xee, 0x7d, 0xd1, 0xbb, 0x02, 0x90, 0x2b, 0xff, + 0x4e, 0x4d, 0xd7, 0x9d, 0x72, 0x0c, 0x60, 0x0f, 0x4b, 0x83, 0xf5, 0xc2, + 0x26, 0xd6, 0x22, 0xb8, 0x60, 0x3a, 0xf9, 0x2f, 0x92, 0x2a, 0x2e, 0x14, + 0xa7, 0x56, 0x1c, 0x56, 0x05, 0x41, 0x92, 0xac, 0xb1, 0x4e, 0x44, 0x1e, + 0x70, 0x42, 0xda, 0xc7, 0xc8, 0x9c, 0xae, 0x29, 0x2d, 0x0c, 0x3a, 0xff, + 0x9b, 0xb6, 0xad, 0xb4, 0xfb, 0x49, 0x28, 0x96, 0x74, 0xf5, 0x94, 0x74, + 0xb7, 0x40, 0x93, 0x2b, 0x34, 0x29, 0xd2, 0x8a, 0xf3, 0x99, 0xf9, 0xe9, + 0xd8, 0xcc, 0x48, 0x1d, 0x3e, 0xc1, 0x82, 0x35, 0x4f, 0xef, 0xb1, 0x81, + 0x3c, 0xe1, 0xa1, 0x03, 0x65, 0xac, 0x21, 0x21, 0x40, 0x61, 0xfb, 0xd3, + 0x54, 0xac, 0xa1, 0xf2, 0xf0, 0x61, 0xd9, 0x01, 0x4e, 0xc2, 0x28, 0xb1, + 0x7c, 0x27, 0x6e, 0x56, 0x68, 0x69, 0x8f, 0xc5, 0xfd, 0xca, 0x39, 0x6e, + 0x22, 0x09, 0xf1, 0xb4, 0xd5, 0xac, 0xb8, 0xe0, 0x1b, 0x21, 0x86, 0xf4, + 0xc8, 0x15, 0xc6, 0x1f, 0x21, 0xae, 0xcb, 0xab, 0x5a, 0x09, 0x30, 0x9e, + 0xdd, 0x6c, 0x38, 0x59, 0xec, 0x59, 0x3a, 0x08, 0xee, 0x46, 0x7b, 0x78, + 0x23, 0xbc, 0xfc, 0xe2, 0xda, 0xe8, 0x1a, 0x65, 0xe6, 0xe0, 0x78, 0xd3, + 0xb0, 0x03, 0x2e, 0xf1, 0xb8, 0xca, 0x8e, 0x90, 0x75, 0xaf, 0xf7, 0xa8, + 0x48, 0xed, 0x82, 0xc9, 0xcf, 0x44, 0x56, 0xfc, 0x05, 0xfd, 0x6b, 0x00, + 0x00, 0x00, 0x81, 0x00, 0xfc, 0x94, 0xdf, 0x42, 0xc7, 0x9a, 0xa2, 0xff, + 0x32, 0xdf, 0x06, 0xb6, 0x4d, 0x90, 0x31, 0x28, 0x28, 0xdb, 0x03, 0xf9, + 0xa6, 0xb3, 0xa2, 0x91, 0x4c, 0xdf, 0x6e, 0xf6, 0xb9, 0x44, 0x3b, 0xdd, + 0x17, 0xc1, 0xc8, 0x1d, 0xd1, 0xc0, 0xc0, 0x30, 0x22, 0xbe, 0x24, 0x2e, + 0x0e, 0xdf, 0xe0, 0x18, 0x37, 0x3e, 0xb8, 0x7f, 0xb2, 0x50, 0x34, 0xc4, + 0x08, 0x5e, 0x69, 0x1f, 0xd5, 0xc9, 0xce, 0x47, 0x7d, 0x75, 0x5e, 0x3b, + 0x87, 0xdd, 0x46, 0x35, 0x01, 0x0f, 0x17, 0x8a, 0xf1, 0xf1, 0xc4, 0xa9, + 0x94, 0xa7, 0x6e, 0xce, 0x80, 0xe3, 0x17, 0x2e, 0xb0, 0xef, 0x63, 0xa7, + 0x11, 0x86, 0x96, 0x4a, 0x63, 0x2d, 0x9e, 0x92, 0x62, 0x43, 0x43, 0x72, + 0xa5, 0xdc, 0xa0, 0xcd, 0x19, 0x93, 0xd7, 0xe0, 0x80, 0x41, 0x27, 0xea, + 0xe4, 0xe8, 0xc1, 0x91, 0x9e, 0x13, 0xb3, 0x9c, 0xd1, 0xed, 0xcb, 0xbf, + 0x00, 0x00, 0x00, 0x81, 0x00, 0xb3, 0x6b, 0xee, 0xa4, 0x70, 0x4e, 0xfb, + 0xf9, 0x7e, 0x2e, 0x74, 0x5d, 0x3e, 0x8b, 0x3f, 0xff, 0x8c, 0xde, 0x68, + 0x38, 0xda, 0xce, 0xc0, 0x66, 0x4b, 0xca, 0x35, 0xc3, 0x97, 0xa8, 0xf0, + 0x00, 0x8e, 0xb3, 0x46, 0x60, 0xd0, 0x4d, 0x7e, 0x7b, 0xdf, 0x17, 0x7b, + 0x2f, 0xc4, 0x16, 0xee, 0x45, 0xdb, 0xa5, 0x5d, 0xc0, 0x72, 0xe9, 0xc6, + 0x91, 0x0f, 0xd9, 0x30, 0x74, 0x6c, 0xde, 0x93, 0xb5, 0xb6, 0xaf, 0x52, + 0x53, 0x3c, 0x08, 0x55, 0xea, 0xb8, 0x66, 0x07, 0xbe, 0xce, 0xf9, 0x80, + 0x8d, 0xe0, 0xca, 0xdc, 0x63, 0xe8, 0x58, 0x94, 0x22, 0x4f, 0x08, 0x66, + 0x13, 0x9e, 0x63, 0x2e, 0x92, 0x7a, 0xb6, 0x66, 0x94, 0x9b, 0x71, 0x66, + 0xd3, 0x08, 0xc9, 0x89, 0xea, 0x78, 0x35, 0x0d, 0xf2, 0x25, 0x55, 0xd4, + 0xb0, 0x9b, 0xea, 0x18, 0x77, 0xf6, 0x25, 0x02, 0xb4, 0x5e, 0x71, 0xea, + 0xa3 +}; +unsigned int keyr_len = 805; +unsigned char keye[] = { + 0x00, 0x00, 0x00, 0x13, 0x65, 0x63, 0x64, 0x73, 0x61, 0x2d, 0x73, 0x68, + 0x61, 0x32, 0x2d, 0x6e, 0x69, 0x73, 0x74, 0x70, 0x32, 0x35, 0x36, 0x00, + 0x00, 0x00, 0x08, 0x6e, 0x69, 0x73, 0x74, 0x70, 0x32, 0x35, 0x36, 0x00, + 0x00, 0x00, 0x41, 0x04, 0x0a, 0x00, 0x6c, 0x7c, 0x1c, 0xc4, 0x03, 0x44, + 0x46, 0x70, 0xba, 0x00, 0x7c, 0x79, 0x89, 0x7b, 0xc3, 0xd6, 0x32, 0x98, + 0x34, 0xe7, 0x1c, 0x60, 0x04, 0x73, 0xd9, 0xb5, 0x7e, 0x94, 0x04, 0x04, + 0xea, 0xc8, 0xb8, 0xfb, 0xd4, 0x70, 0x9f, 0x29, 0xa7, 0x8d, 0x9a, 0x64, + 0x3a, 0x8c, 0x45, 0x23, 0x37, 0x5a, 0x2b, 0x4f, 0x54, 0x91, 0x80, 0xf1, + 0xac, 0x3a, 0xf5, 0x6d, 0xfa, 0xe8, 0x76, 0x20, 0x00, 0x00, 0x00, 0x21, + 0x00, 0xc2, 0xaf, 0xbe, 0xdc, 0x06, 0xff, 0x3d, 0x08, 0x9b, 0x73, 0xe0, + 0x3c, 0x58, 0x28, 0x70, 0x9b, 0x23, 0x39, 0x51, 0xd7, 0xbc, 0xa7, 0x1a, + 0xf5, 0xb4, 0x23, 0xd3, 0xf6, 0x17, 0xa6, 0x9c, 0x02 +}; +unsigned int keye_len = 141; +unsigned char keyd[] = { + 0x00, 0x00, 0x00, 0x07, 0x73, 0x73, 0x68, 0x2d, 0x64, 0x73, 0x73, 0x00, + 0x00, 0x00, 0x81, 0x00, 0xb0, 0x02, 0x19, 0x8b, 0xf3, 0x46, 0xf9, 0xc5, + 0x47, 0x78, 0x3d, 0x7f, 0x04, 0x10, 0x0a, 0x43, 0x8e, 0x00, 0x9e, 0xa4, + 0x30, 0xfd, 0x47, 0xb9, 0x05, 0x9e, 0x95, 0xaa, 0x37, 0x9a, 0x91, 0xbf, + 0xf8, 0xb9, 0xe0, 0x8d, 0x97, 0x49, 0x87, 0xe2, 0xe6, 0x90, 0xc1, 0xe4, + 0x61, 0x57, 0x77, 0xfd, 0x91, 0x1d, 0xe1, 0x4b, 0xa0, 0xb2, 0xbc, 0xa1, + 0x6a, 0x6a, 0xdd, 0x31, 0xda, 0xe7, 0x54, 0x03, 0xfd, 0x48, 0x62, 0x8a, + 0x1d, 0x1d, 0xe2, 0x26, 0x76, 0x29, 0x08, 0xab, 0x65, 0x88, 0x74, 0x02, + 0x1e, 0xa9, 0x29, 0x1b, 0x69, 0x3b, 0xb4, 0x5f, 0x62, 0x80, 0xa3, 0xa6, + 0x4b, 0xc3, 0x0e, 0x89, 0x24, 0xe4, 0x8a, 0x31, 0xae, 0x89, 0x7a, 0x7a, + 0x58, 0x44, 0x46, 0x77, 0x62, 0x33, 0xa2, 0x5d, 0x17, 0x0e, 0x0b, 0x64, + 0xee, 0x1a, 0x02, 0xbd, 0xf8, 0x27, 0x86, 0xe1, 0x87, 0x92, 0x84, 0xc7, + 0x00, 0x00, 0x00, 0x15, 0x00, 0xb3, 0x8b, 0x81, 0x39, 0x9c, 0xba, 0xe1, + 0x1d, 0x9a, 0x8b, 0x89, 0xb3, 0x08, 0x9b, 0x12, 0xa8, 0x7b, 0xea, 0x25, + 0x8d, 0x00, 0x00, 0x00, 0x80, 0x76, 0x3f, 0x72, 0xb2, 0xef, 0xc3, 0x16, + 0xd8, 0x09, 0x36, 0x23, 0x03, 0xf9, 0x5c, 0xac, 0x8b, 0x51, 0x35, 0x2e, + 0x36, 0xba, 0x39, 0xd0, 0x57, 0x19, 0x4f, 0x14, 0x8b, 0xea, 0x32, 0xfc, + 0x86, 0x41, 0xea, 0x85, 0x71, 0x4d, 0x52, 0x0c, 0xff, 0xc1, 0xd3, 0xd5, + 0xcd, 0x2e, 0x37, 0xcc, 0xe1, 0xcc, 0x22, 0x38, 0xa8, 0x47, 0x16, 0x34, + 0x3b, 0x32, 0x9c, 0x2f, 0x0f, 0xcd, 0x5f, 0x7f, 0x06, 0x64, 0x89, 0xc5, + 0x02, 0x4f, 0x9a, 0x70, 0x11, 0xf0, 0xaa, 0xe1, 0x7a, 0x75, 0x49, 0x8d, + 0x0f, 0x8d, 0x5b, 0x54, 0xe2, 0xe7, 0x10, 0x6e, 0xe5, 0xbd, 0xb7, 0x62, + 0xf7, 0x40, 0x59, 0x39, 0x31, 0xd9, 0x13, 0x7b, 0xa3, 0xdf, 0x0d, 0x31, + 0x52, 0x43, 0xe0, 0xaf, 0x19, 0x12, 0x15, 0x12, 0x34, 0x01, 0x6f, 0xcf, + 0x62, 0x21, 0xe4, 0xc8, 0x34, 0x69, 0xc9, 0x85, 0xe3, 0xde, 0xd7, 0x0c, + 0xac, 0x00, 0x00, 0x00, 0x80, 0x41, 0xa3, 0xc5, 0xa4, 0x89, 0x86, 0xc8, + 0x17, 0xf3, 0x8e, 0x68, 0x72, 0xbe, 0x13, 0x8b, 0x63, 0xe3, 0x07, 0xe3, + 0xd5, 0xa4, 0xa2, 0xd3, 0x2c, 0x2f, 0xbe, 0x16, 0x71, 0xc9, 0x79, 0x64, + 0x5a, 0x1e, 0x19, 0x82, 0x07, 0xe2, 0x93, 0xda, 0x22, 0xcf, 0x6d, 0xdd, + 0x38, 0xcb, 0x6e, 0x6b, 0x0f, 0x95, 0x8d, 0xfa, 0x3f, 0xbb, 0xb8, 0x6a, + 0x7d, 0xc3, 0x22, 0x1e, 0x49, 0xcf, 0x98, 0x73, 0x05, 0x5d, 0x97, 0xfa, + 0x4c, 0xf2, 0x82, 0x3d, 0x98, 0x61, 0x4e, 0x96, 0x80, 0x26, 0x79, 0xda, + 0x24, 0xf8, 0xa1, 0x9c, 0x71, 0x82, 0xe6, 0xc7, 0xdc, 0xc2, 0xa5, 0xd0, + 0xf4, 0x36, 0xba, 0xaa, 0xee, 0xd3, 0x43, 0x46, 0x1d, 0xaa, 0x53, 0xea, + 0x85, 0x2c, 0x1b, 0xc8, 0x7c, 0x3c, 0xe7, 0x06, 0x44, 0xab, 0x16, 0xad, + 0xc6, 0x54, 0x91, 0x9a, 0xb9, 0xc0, 0xeb, 0x93, 0x8c, 0xca, 0x39, 0xcf, + 0x6f, 0x00, 0x00, 0x00, 0x15, 0x00, 0x90, 0x26, 0x0a, 0xfc, 0x15, 0x99, + 0x7b, 0xac, 0xaa, 0x0c, 0xa2, 0xca, 0x7b, 0xa8, 0xd4, 0xdf, 0x68, 0x56, + 0xf9, 0x39 +}; +unsigned int keyd_len = 458; diff --git a/fuzz-wrapfd.c b/fuzz-wrapfd.c new file mode 100644 index 0000000..ed8968a --- /dev/null +++ b/fuzz-wrapfd.c @@ -0,0 +1,246 @@ +#define FUZZ_SKIP_WRAP 1 +#include "includes.h" +#include "fuzz-wrapfd.h" + +#include "dbutil.h" + +#include "fuzz.h" + +#define IOWRAP_MAXFD (FD_SETSIZE-1) +static const int MAX_RANDOM_IN = 50000; +static const double CHANCE_CLOSE = 1.0 / 600; +static const double CHANCE_INTR = 1.0 / 900; +static const double CHANCE_READ1 = 0.96; +static const double CHANCE_READ2 = 0.5; +static const double CHANCE_WRITE1 = 0.96; +static const double CHANCE_WRITE2 = 0.5; + +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 unsigned short rand_state[3]; + +void wrapfd_setup(void) { + TRACE(("wrapfd_setup")) + nused = 0; + memset(wrap_fds, 0x0, sizeof(wrap_fds)); + memset(wrap_used, 0x0, sizeof(wrap_used)); + + memset(rand_state, 0x0, sizeof(rand_state)); + wrapfd_setseed(50); +} + +void wrapfd_setseed(uint32_t seed) { + memcpy(rand_state, &seed, sizeof(seed)); + 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); + assert(wrap_fds[fd].mode == UNUSED); + assert(buf || mode == RANDOMIN); + + wrap_fds[fd].mode = mode; + wrap_fds[fd].buf = buf; + wrap_fds[fd].closein = 0; + wrap_fds[fd].closeout = 0; + wrap_used[nused] = fd; + + nused++; +} + +void wrapfd_remove(int fd) { + unsigned int i, j; + 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--; +} + +int wrapfd_close(int fd) { + if (fd >= 0 && fd <= IOWRAP_MAXFD && wrap_fds[fd].mode != UNUSED) { + wrapfd_remove(fd); + return 0; + } else { + return close(fd); + } +} + +int wrapfd_read(int fd, void *out, size_t count) { + size_t maxread; + buffer *buf; + + if (!fuzz.wrapfds) { + return read(fd, out, count); + } + + if (fd < 0 || fd > IOWRAP_MAXFD || wrap_fds[fd].mode == UNUSED) { + /* XXX - assertion failure? */ + TRACE(("Bad read descriptor %d\n", fd)) + errno = EBADF; + return -1; + } + + assert(count != 0); + + if (wrap_fds[fd].closein || erand48(rand_state) < CHANCE_CLOSE) { + wrap_fds[fd].closein = 1; + errno = ECONNRESET; + return -1; + } + + 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 */ + if (maxread > 0) { + 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 (!fuzz.wrapfds) { + return write(fd, in, count); + } + + if (fd < 0 || fd > IOWRAP_MAXFD || wrap_fds[fd].mode == UNUSED) { + /* XXX - assertion failure? */ + 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 (wrap_fds[fd].closeout || erand48(rand_state) < CHANCE_CLOSE) { + wrap_fds[fd].closeout = 1; + errno = ECONNRESET; + return -1; + } + + 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 *exceptfds, struct timeval *timeout) { + int i, nset, sel; + int ret = 0; + int fdlist[IOWRAP_MAXFD+1]; + + memset(fdlist, 0x0, sizeof(fdlist)); + + if (!fuzz.wrapfds) { + return select(nfds, readfds, writefds, exceptfds, timeout); + } + + assert(nfds <= IOWRAP_MAXFD+1); + + if (erand48(rand_state) < CHANCE_INTR) { + errno = EINTR; + return -1; + } + + /* read */ + if (readfds != NULL && 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; + nset++; + } + } + DROPBEAR_FD_ZERO(readfds); + + if (nset > 0) { + /* set one */ + sel = fdlist[nrand48(rand_state) % nset]; + FD_SET(sel, readfds); + ret++; + + if (erand48(rand_state) < CHANCE_READ2) { + sel = fdlist[nrand48(rand_state) % nset]; + if (!FD_ISSET(sel, readfds)) { + FD_SET(sel, readfds); + ret++; + } + } + } + } + + /* write */ + if (writefds != NULL && 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; + nset++; + } + } + DROPBEAR_FD_ZERO(writefds); + + /* set one */ + if (nset > 0) { + sel = fdlist[nrand48(rand_state) % nset]; + FD_SET(sel, writefds); + ret++; + + if (erand48(rand_state) < CHANCE_WRITE2) { + sel = fdlist[nrand48(rand_state) % nset]; + if (!FD_ISSET(sel, writefds)) { + FD_SET(sel, writefds); + ret++; + } + } + } + } + return ret; +} + diff --git a/fuzz-wrapfd.h b/fuzz-wrapfd.h new file mode 100644 index 0000000..7aed43a --- /dev/null +++ b/fuzz-wrapfd.h @@ -0,0 +1,25 @@ +#ifndef FUZZ_WRAPFD_H +#define FUZZ_WRAPFD_H + +#include "buffer.h" + +enum wrapfd_mode { + UNUSED = 0, + PLAIN, + INPROGRESS, + RANDOMIN +}; + +void wrapfd_setup(void); +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); + +// called via #defines for read/write/select +int wrapfd_read(int fd, void *out, size_t count); +int wrapfd_write(int fd, const void* in, size_t count); +int wrapfd_select(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, struct timeval *timeout); +int wrapfd_close(int fd); + +#endif // FUZZ_WRAPFD_H @@ -0,0 +1,72 @@ +#ifndef DROPBEAR_FUZZ_H +#define DROPBEAR_FUZZ_H + +#include "config.h" + +#if DROPBEAR_FUZZ + +#include "includes.h" +#include "buffer.h" +#include "algo.h" +#include "fuzz-wrapfd.h" + +// once per process +void fuzz_common_setup(void); +void fuzz_svr_setup(void); + +// must be called once per fuzz iteration. +// returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE +int fuzz_set_input(const uint8_t *Data, size_t Size); + +int fuzz_run_preauth(const uint8_t *Data, size_t Size, int skip_kexmaths); +const void* fuzz_get_algo(const algo_type *algos, const char* name); + +// fuzzer functions that intrude into general code +void fuzz_kex_fakealgos(void); +int fuzz_checkpubkey_line(buffer* line, int line_num, char* filename, + const char* algo, unsigned int algolen, + const unsigned char* keyblob, unsigned int keybloblen); +extern const char * const * fuzz_signkey_names; +void fuzz_seed(void); +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); + +// fake IO wrappers +#ifndef FUZZ_SKIP_WRAP +#define select(nfds, readfds, writefds, exceptfds, timeout) \ + wrapfd_select(nfds, readfds, writefds, exceptfds, timeout) +#define write(fd, buf, count) wrapfd_write(fd, buf, count) +#define read(fd, buf, count) wrapfd_read(fd, buf, count) +#define close(fd) wrapfd_close(fd) +#endif // FUZZ_SKIP_WRAP + +struct dropbear_fuzz_options { + int fuzzing; + + // fuzzing input + buffer *input; + struct dropbear_cipher recv_cipher; + struct dropbear_hash recv_mac; + int wrapfds; + + // whether to skip slow bignum maths + int skip_kexmaths; + + // 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; + +#endif // DROPBEAR_FUZZ + +#endif /* DROPBEAR_FUZZ_H */ diff --git a/fuzzer-kexdh.c b/fuzzer-kexdh.c new file mode 100644 index 0000000..224ff58 --- /dev/null +++ b/fuzzer-kexdh.c @@ -0,0 +1,76 @@ +#include "fuzz.h" +#include "session.h" +#include "fuzz-wrapfd.h" +#include "debug.h" +#include "runopts.h" +#include "algo.h" +#include "bignum.h" + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + static int once = 0; + static struct key_context* keep_newkeys = NULL; + /* number of generated parameters is limited by the timeout for the first run. + TODO move this to the libfuzzer initialiser function instead if the timeout + doesn't apply there */ + #define NUM_PARAMS 20 + static struct kex_dh_param *dh_params[NUM_PARAMS]; + + if (!once) { + fuzz_common_setup(); + fuzz_svr_setup(); + + keep_newkeys = (struct key_context*)m_malloc(sizeof(struct key_context)); + keep_newkeys->algo_kex = fuzz_get_algo(sshkex, "diffie-hellman-group14-sha256"); + keep_newkeys->algo_hostkey = DROPBEAR_SIGNKEY_ECDSA_NISTP256; + ses.newkeys = keep_newkeys; + + /* Pre-generate parameters */ + int i; + for (i = 0; i < NUM_PARAMS; i++) { + dh_params[i] = gen_kexdh_param(); + } + + once = 1; + } + + if (fuzz_set_input(Data, Size) == DROPBEAR_FAILURE) { + return 0; + } + + m_malloc_set_epoch(1); + + if (setjmp(fuzz.jmp) == 0) { + /* Based on recv_msg_kexdh_init()/send_msg_kexdh_reply() + with DROPBEAR_KEX_NORMAL_DH */ + ses.newkeys = keep_newkeys; + + /* Choose from the collection of ecdh params */ + unsigned int e = buf_getint(fuzz.input); + struct kex_dh_param * dh_param = dh_params[e % NUM_PARAMS]; + + DEF_MP_INT(dh_e); + m_mp_init(&dh_e); + if (buf_getmpint(fuzz.input, &dh_e) != DROPBEAR_SUCCESS) { + dropbear_exit("Bad kex value"); + } + + ses.kexhashbuf = buf_new(KEXHASHBUF_MAX_INTS); + kexdh_comb_key(dh_param, &dh_e, svr_opts.hostkey); + + mp_clear(ses.dh_K); + m_free(ses.dh_K); + mp_clear(&dh_e); + + buf_free(ses.hash); + buf_free(ses.session_id); + /* kexhashbuf is freed in kexdh_comb_key */ + + m_malloc_free_epoch(1, 0); + } else { + m_malloc_free_epoch(1, 1); + TRACE(("dropbear_exit longjmped")) + /* dropbear_exit jumped here */ + } + + return 0; +} diff --git a/fuzzer-kexecdh.c b/fuzzer-kexecdh.c new file mode 100644 index 0000000..c3a450a --- /dev/null +++ b/fuzzer-kexecdh.c @@ -0,0 +1,82 @@ +#include "fuzz.h" +#include "session.h" +#include "fuzz-wrapfd.h" +#include "debug.h" +#include "runopts.h" +#include "algo.h" +#include "bignum.h" + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + static int once = 0; + static const struct dropbear_kex *ecdh[3]; /* 256, 384, 521 */ + static struct key_context* keep_newkeys = NULL; + /* number of generated parameters is limited by the timeout for the first run */ + #define NUM_PARAMS 80 + static struct kex_ecdh_param *ecdh_params[NUM_PARAMS]; + + if (!once) { + fuzz_common_setup(); + fuzz_svr_setup(); + + /* ses gets zeroed by fuzz_set_input */ + keep_newkeys = (struct key_context*)m_malloc(sizeof(struct key_context)); + ecdh[0] = fuzz_get_algo(sshkex, "ecdh-sha2-nistp256"); + ecdh[1] = fuzz_get_algo(sshkex, "ecdh-sha2-nistp384"); + ecdh[2] = fuzz_get_algo(sshkex, "ecdh-sha2-nistp521"); + assert(ecdh[0]); + assert(ecdh[1]); + assert(ecdh[2]); + keep_newkeys->algo_hostkey = DROPBEAR_SIGNKEY_ECDSA_NISTP256; + ses.newkeys = keep_newkeys; + + /* Pre-generate parameters */ + int i; + for (i = 0; i < NUM_PARAMS; i++) { + ses.newkeys->algo_kex = ecdh[i % 3]; + ecdh_params[i] = gen_kexecdh_param(); + } + + once = 1; + } + + if (fuzz_set_input(Data, Size) == DROPBEAR_FAILURE) { + return 0; + } + + m_malloc_set_epoch(1); + + if (setjmp(fuzz.jmp) == 0) { + /* Based on recv_msg_kexdh_init()/send_msg_kexdh_reply() + with DROPBEAR_KEX_ECDH */ + ses.newkeys = keep_newkeys; + + /* random choice of ecdh 256, 384, 521 */ + unsigned char b = buf_getbyte(fuzz.input); + ses.newkeys->algo_kex = ecdh[b % 3]; + + /* Choose from the collection of ecdh params */ + unsigned int e = buf_getint(fuzz.input); + struct kex_ecdh_param *ecdh_param = ecdh_params[e % NUM_PARAMS]; + + buffer * ecdh_qs = buf_getstringbuf(fuzz.input); + + ses.kexhashbuf = buf_new(KEXHASHBUF_MAX_INTS); + kexecdh_comb_key(ecdh_param, ecdh_qs, svr_opts.hostkey); + + mp_clear(ses.dh_K); + m_free(ses.dh_K); + buf_free(ecdh_qs); + + buf_free(ses.hash); + buf_free(ses.session_id); + /* kexhashbuf is freed in kexdh_comb_key */ + + m_malloc_free_epoch(1, 0); + } else { + m_malloc_free_epoch(1, 1); + TRACE(("dropbear_exit longjmped")) + /* dropbear_exit jumped here */ + } + + return 0; +} diff --git a/fuzzer-preauth.c b/fuzzer-preauth.c new file mode 100644 index 0000000..3ac49f4 --- /dev/null +++ b/fuzzer-preauth.c @@ -0,0 +1,6 @@ +#include "fuzz.h" + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + return fuzz_run_preauth(Data, Size, 0); +} + diff --git a/fuzzer-preauth_nomaths.c b/fuzzer-preauth_nomaths.c new file mode 100644 index 0000000..efdc2c3 --- /dev/null +++ b/fuzzer-preauth_nomaths.c @@ -0,0 +1,6 @@ +#include "fuzz.h" + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + return fuzz_run_preauth(Data, Size, 1); +} + diff --git a/fuzzer-pubkey.c b/fuzzer-pubkey.c new file mode 100644 index 0000000..033f496 --- /dev/null +++ b/fuzzer-pubkey.c @@ -0,0 +1,54 @@ +#include "fuzz.h" +#include "session.h" +#include "fuzz-wrapfd.h" +#include "debug.h" + +static void setup_fuzzer(void) { + fuzz_common_setup(); +} + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + static int once = 0; + if (!once) { + setup_fuzzer(); + once = 1; + } + + if (fuzz_set_input(Data, Size) == DROPBEAR_FAILURE) { + return 0; + } + + m_malloc_set_epoch(1); + + if (setjmp(fuzz.jmp) == 0) { + buffer *line = buf_getstringbuf(fuzz.input); + buffer *keyblob = buf_getstringbuf(fuzz.input); + + unsigned int algolen; + char* algoname = buf_getstring(keyblob, &algolen); + + if (have_algo(algoname, algolen, sshhostkey) == DROPBEAR_FAILURE) { + dropbear_exit("fuzzer imagined a bogus algorithm"); + } + + int ret = fuzz_checkpubkey_line(line, 5, "/home/me/authorized_keys", + algoname, algolen, + keyblob->data, keyblob->len); + + if (ret == DROPBEAR_SUCCESS) { + /* fuzz_checkpubkey_line() should have cleaned up for failure */ + svr_pubkey_options_cleanup(); + } + + buf_free(line); + buf_free(keyblob); + m_free(algoname); + m_malloc_free_epoch(1, 0); + } else { + m_malloc_free_epoch(1, 1); + TRACE(("dropbear_exit longjmped")) + /* dropbear_exit jumped here */ + } + + return 0; +} diff --git a/fuzzer-verify.c b/fuzzer-verify.c new file mode 100644 index 0000000..bbef524 --- /dev/null +++ b/fuzzer-verify.c @@ -0,0 +1,64 @@ +#include "fuzz.h" +#include "session.h" +#include "fuzz-wrapfd.h" +#include "debug.h" + +static void setup_fuzzer(void) { + fuzz_common_setup(); +} + +static buffer *verifydata; + +/* Tests reading a public key and verifying a signature */ +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + static int once = 0; + if (!once) { + setup_fuzzer(); + verifydata = buf_new(30); + buf_putstring(verifydata, "x", 1); + once = 1; + } + + if (fuzz_set_input(Data, Size) == DROPBEAR_FAILURE) { + return 0; + } + + m_malloc_set_epoch(1); + + if (setjmp(fuzz.jmp) == 0) { + sign_key *key = new_sign_key(); + enum signkey_type type = DROPBEAR_SIGNKEY_ANY; + if (buf_get_pub_key(fuzz.input, key, &type) == DROPBEAR_SUCCESS) { + if (buf_verify(fuzz.input, key, verifydata) == DROPBEAR_SUCCESS) { + /* The fuzzer is capable of generating keys with a signature to match. + We don't want false positives if the key is bogus, since a client/server + wouldn't be trusting a bogus key anyway */ + int boguskey = 0; + + if (type == DROPBEAR_SIGNKEY_DSS) { + /* So far have seen dss keys with bad p/q/g domain parameters */ + int pprime, qprime; + assert(mp_prime_is_prime(key->dsskey->p, 5, &pprime) == MP_OKAY); + assert(mp_prime_is_prime(key->dsskey->q, 18, &qprime) == MP_OKAY); + boguskey = !(pprime && qprime); + /* Could also check g**q mod p == 1 */ + } + + if (!boguskey) { + printf("Random key/signature managed to verify!\n"); + abort(); + } + + + } + } + sign_key_free(key); + m_malloc_free_epoch(1, 0); + } else { + m_malloc_free_epoch(1, 1); + TRACE(("dropbear_exit longjmped")) + /* dropbear_exit jumped here */ + } + + return 0; +} diff --git a/fuzzers_test.sh b/fuzzers_test.sh new file mode 100755 index 0000000..1618ee1 --- /dev/null +++ b/fuzzers_test.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +# runs fuzz corpus with standalone fuzzers + +result=0 + +hg clone https://secure.ucc.asn.au/hg/dropbear-fuzzcorpus fuzzcorpus || exit 1 +for f in `make list-fuzz-targets`; do + ./$f fuzzcorpus/$f/* || result=1 +done + +exit $result @@ -56,6 +56,7 @@ #include <stdarg.h> #include <dirent.h> #include <time.h> +#include <setjmp.h> #ifdef HAVE_UTMP_H #include <utmp.h> @@ -131,7 +132,6 @@ #include <tommath.h> #endif - #include "compat.h" #ifndef HAVE_U_INT8_T @@ -155,6 +155,10 @@ typedef unsigned int u_int32_t; typedef u_int32_t uint32_t; #endif /* HAVE_UINT32_T */ +#ifndef SIZE_T_MAX +#define SIZE_T_MAX ULONG_MAX +#endif /* SIZE_T_MAX */ + #ifdef HAVE_LINUX_PKT_SCHED_H #include <linux/types.h> #include <linux/pkt_sched.h> @@ -162,6 +166,8 @@ typedef u_int32_t uint32_t; #include "fake-rfc2553.h" +#include "fuzz.h" + #ifndef LOG_AUTHPRIV #define LOG_AUTHPRIV LOG_AUTH #endif @@ -34,6 +34,7 @@ void recv_msg_kexinit(void); void send_msg_newkeys(void); void recv_msg_newkeys(void); void kexfirstinitialise(void); +void finish_kexhashbuf(void); struct kex_dh_param *gen_kexdh_param(void); void free_kexdh_param(struct kex_dh_param *param); diff --git a/libtomcrypt/src/headers/tomcrypt_custom.h b/libtomcrypt/src/headers/tomcrypt_custom.h index b7bd20c..f2351da 100644 --- a/libtomcrypt/src/headers/tomcrypt_custom.h +++ b/libtomcrypt/src/headers/tomcrypt_custom.h @@ -12,6 +12,12 @@ #include "tomcrypt_dropbear.h" +#include "dbmalloc.h" +#define XMALLOC m_malloc +#define XFREE m_free_direct +#define XREALLOC m_realloc +#define XCALLOC m_calloc + /* macros for various libc functions you can change for embedded targets */ #ifndef XMALLOC #define XMALLOC malloc diff --git a/libtommath/bn_fast_s_mp_mul_digs.c b/libtommath/bn_fast_s_mp_mul_digs.c index a1015af..783e7ca 100644 --- a/libtommath/bn_fast_s_mp_mul_digs.c +++ b/libtommath/bn_fast_s_mp_mul_digs.c @@ -87,7 +87,7 @@ int fast_s_mp_mul_digs (mp_int * a, mp_int * b, mp_int * c, int digs) { mp_digit *tmpc; tmpc = c->dp; - for (ix = 0; ix < (pa + 1); ix++) { + for (ix = 0; ix < pa; ix++) { /* now extract the previous digit [below the carry] */ *tmpc++ = W[ix]; } diff --git a/libtommath/tommath_class.h b/libtommath/tommath_class.h index bb4a570..b9d9c0c 100644 --- a/libtommath/tommath_class.h +++ b/libtommath/tommath_class.h @@ -1062,6 +1062,12 @@ #undef BN_MP_TOOM_MUL_C #undef BN_MP_TOOM_SQR_C +#include "dbmalloc.h" +#define XMALLOC m_malloc +#define XFREE m_free_direct +#define XREALLOC m_realloc +#define XCALLOC m_calloc + /* $Source$ */ /* $Revision$ */ /* $Date$ */ @@ -245,6 +245,7 @@ void set_connect_fds(fd_set *writefd) { } iter = next_iter; } + TRACE(("leave set_connect_fds")) } void handle_connect_fds(const fd_set *writefd) { @@ -305,10 +306,10 @@ void packet_queue_to_iovec(const struct Queue *queue, struct iovec *iov, unsigne for (l = queue->head, i = 0; i < *iov_count; l = l->link, i++) { writebuf = (buffer*)l->item; - len = writebuf->len - 1 - writebuf->pos; + len = writebuf->len - writebuf->pos; dropbear_assert(len > 0); - TRACE2(("write_packet writev #%d type %d len %d/%d", i, writebuf->data[writebuf->len-1], - len, writebuf->len-1)) + TRACE2(("write_packet writev #%d len %d/%d", i, + len, writebuf->len)) iov[i].iov_base = buf_getptr(writebuf, len); iov[i].iov_len = len; } @@ -319,7 +320,7 @@ void packet_queue_consume(struct Queue *queue, ssize_t written) { int len; while (written > 0) { writebuf = (buffer*)examine(queue); - len = writebuf->len - 1 - writebuf->pos; + len = writebuf->len - writebuf->pos; if (len > written) { /* partial buffer write */ buf_incrpos(writebuf, written); @@ -360,6 +361,12 @@ void set_sock_priority(int sock, enum dropbear_prio prio) { int so_prio_val = 0; #endif +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + TRACE(("fuzzing skips set_sock_prio")) + return; + } +#endif /* Don't log ENOTSOCK errors so that this can harmlessly be called * on a client '-J' proxy pipe */ @@ -584,6 +591,13 @@ void get_socket_address(int fd, char **local_host, char **local_port, { struct sockaddr_storage addr; socklen_t addrlen; + +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + fuzz_get_socket_address(fd, local_host, local_port, remote_host, remote_port, host_lookup); + return; + } +#endif if (local_host || local_port) { addrlen = sizeof(addr); @@ -35,6 +35,7 @@ #include "auth.h" #include "channel.h" #include "netio.h" +#include "runopts.h" static int read_packet_init(void); static void make_mac(unsigned int seqno, const struct key_context_directional * key_state, @@ -64,7 +65,6 @@ void write_packet() { #else int len; buffer* writebuf; - int packet_type; #endif TRACE2(("enter write_packet")) @@ -76,6 +76,15 @@ void write_packet() { /* This may return EAGAIN. The main loop sometimes calls write_packet() without bothering to test with select() since it's likely to be necessary */ +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + /* pretend to write one packet at a time */ + /* TODO(fuzz): randomise amount written based on the fuzz input */ + written = iov[0].iov_len; + } + else +#endif + { written = writev(ses.sock_out, iov, iov_count); if (written < 0) { if (errno == EINTR || errno == EAGAIN) { @@ -85,6 +94,7 @@ void write_packet() { dropbear_exit("Error writing: %s", strerror(errno)); } } + } packet_queue_consume(&ses.writequeue, written); ses.writequeue_len -= written; @@ -94,15 +104,15 @@ void write_packet() { } #else /* No writev () */ +#if DROPBEAR_FUZZ + _Static_assert(0, "No fuzzing code for no-writev writes"); +#endif /* Get the next buffer in the queue of encrypted packets to write*/ writebuf = (buffer*)examine(&ses.writequeue); /* The last byte of the buffer is not to be transmitted, but is * a cleartext packet_type indicator */ - packet_type = writebuf->data[writebuf->len-1]; - len = writebuf->len - 1 - writebuf->pos; - TRACE2(("write_packet type %d len %d/%d", packet_type, - len, writebuf->len-1)) + len = writebuf->len - writebuf->pos; dropbear_assert(len > 0); /* Try to write as much as possible */ written = write(ses.sock_out, buf_getptr(writebuf, len), len); @@ -352,6 +362,20 @@ static int checkmac() { buf_setpos(ses.readbuf, 0); make_mac(ses.recvseq, &ses.keys->recv, ses.readbuf, contents_len, mac_bytes); +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + /* fail 1 in 2000 times to test error path. */ + unsigned int value = 0; + if (mac_size > sizeof(value)) { + memcpy(&value, mac_bytes, sizeof(value)); + } + if (value % 2000 == 99) { + return DROPBEAR_FAILURE; + } + return DROPBEAR_SUCCESS; + } +#endif + /* compare the hash */ buf_setpos(ses.readbuf, contents_len); if (constant_time_memcmp(mac_bytes, buf_getptr(ses.readbuf, mac_size), mac_size) != 0) { @@ -578,7 +602,7 @@ void encrypt_packet() { /* Update counts */ ses.kexstate.datatrans += writebuf->len; - writebuf_enqueue(writebuf, packet_type); + writebuf_enqueue(writebuf); /* Update counts */ ses.transseq++; @@ -598,14 +622,11 @@ void encrypt_packet() { TRACE2(("leave encrypt_packet()")) } -void writebuf_enqueue(buffer * writebuf, unsigned char packet_type) { - /* The last byte of the buffer stores the cleartext packet_type. It is not - * transmitted but is used for transmit timeout purposes */ - buf_putbyte(writebuf, packet_type); +void writebuf_enqueue(buffer * writebuf) { /* enqueue the packet for sending. It will get freed after transmission. */ buf_setpos(writebuf, 0); enqueue(&ses.writequeue, (void*)writebuf); - ses.writequeue_len += writebuf->len-1; + ses.writequeue_len += writebuf->len; } @@ -35,7 +35,7 @@ void read_packet(void); void decrypt_packet(void); void encrypt_packet(void); -void writebuf_enqueue(buffer * writebuf, unsigned char packet_type); +void writebuf_enqueue(buffer * writebuf); void process_packet(void); @@ -580,6 +580,10 @@ int cmp_base64_key(const unsigned char* keyblob, unsigned int keybloblen, /* now we have the actual data */ len = line->len - line->pos; + if (len == 0) { + /* base64_decode doesn't like NULL argument */ + return DROPBEAR_FAILURE; + } decodekeylen = len * 2; /* big to be safe */ decodekey = buf_new(decodekeylen); @@ -623,3 +627,8 @@ out: return ret; } #endif + +#if DROPBEAR_FUZZ +const char * const * fuzz_signkey_names = signkey_names; + +#endif @@ -386,7 +386,12 @@ void send_msg_userauth_failure(int partial, int incrfail) { genrandom((unsigned char*)&delay, sizeof(delay)); /* We delay for 300ms +- 50ms */ delay = 250000 + (delay % 100000); +#if DROPBEAR_FUZZ + if (!fuzz.fuzzing) +#endif + { usleep(delay); + } ses.authstate.failcount++; } diff --git a/svr-authpubkey.c b/svr-authpubkey.c index ff481c8..ae1402d 100644 --- a/svr-authpubkey.c +++ b/svr-authpubkey.c @@ -176,6 +176,10 @@ out: sign_key_free(key); key = NULL; } + /* Retain pubkey options only if auth succeeded */ + if (!ses.authstate.authdone) { + svr_pubkey_options_cleanup(); + } TRACE(("leave pubkeyauth")) } @@ -206,7 +210,12 @@ static int checkpubkey_line(buffer* line, int line_num, const char* filename, if (line->len < MIN_AUTHKEYS_LINE || line->len > MAX_AUTHKEYS_LINE) { TRACE(("checkpubkey_line: bad line length %d", line->len)) - return DROPBEAR_FAILURE; + goto out; + } + + if (memchr(line->data, 0x0, line->len) != NULL) { + TRACE(("checkpubkey_line: bad line has null char")) + goto out; } /* compare the algorithm. +3 so we have enough bytes to read a space and some base64 characters too. */ @@ -482,4 +491,12 @@ static int checkfileperm(char * filename) { return DROPBEAR_SUCCESS; } +#if DROPBEAR_FUZZ +int fuzz_checkpubkey_line(buffer* line, int line_num, char* filename, + const char* algo, unsigned int algolen, + const unsigned char* keyblob, unsigned int keybloblen) { + return checkpubkey_line(line, line_num, filename, algo, algolen, keyblob, keybloblen); +} +#endif + #endif diff --git a/svr-authpubkeyoptions.c b/svr-authpubkeyoptions.c index 19f07b9..ba6f698 100644 --- a/svr-authpubkeyoptions.c +++ b/svr-authpubkeyoptions.c @@ -113,7 +113,6 @@ void svr_pubkey_options_cleanup() { m_free(ses.authstate.pubkey_options->forced_command); } m_free(ses.authstate.pubkey_options); - ses.authstate.pubkey_options = NULL; } } @@ -169,6 +168,12 @@ int svr_add_pubkey_options(buffer *options_buf, int line_num, const char* filena if (match_option(options_buf, "command=\"") == DROPBEAR_SUCCESS) { int escaped = 0; const unsigned char* command_start = buf_getptr(options_buf, 0); + + if (ses.authstate.pubkey_options->forced_command) { + /* multiple command= options */ + goto bad_option; + } + while (options_buf->pos < options_buf->len) { const char c = buf_getbyte(options_buf); if (!escaped && c == '"') { @@ -179,6 +179,13 @@ static void send_msg_kexdh_reply(mp_int *dh_e, buffer *ecdh_qs) { } #endif +#if DROPBEAR_FUZZ + if (fuzz.fuzzing && fuzz.skip_kexmaths) { + fuzz_fake_send_kexdh_reply(); + return; + } +#endif + buf_putbyte(ses.writepayload, SSH_MSG_KEXDH_REPLY); buf_put_pub_key(ses.writepayload, svr_opts.hostkey, ses.newkeys->algo_hostkey); @@ -178,7 +178,7 @@ static void main_noinetd() { /* incoming connection select loop */ for(;;) { - FD_ZERO(&fds); + DROPBEAR_FD_ZERO(&fds); /* listening sockets */ for (i = 0; i < listensockcount; i++) { diff --git a/svr-runopts.c b/svr-runopts.c index fe83e02..d6c78df 100644 --- a/svr-runopts.c +++ b/svr-runopts.c @@ -523,10 +523,13 @@ static void addhostkey(const char *keyfile) { svr_opts.num_hostkey_files++; } + void load_all_hostkeys() { int i; - int disable_unset_keys = 1; int any_keys = 0; +#ifdef DROPBEAR_ECDSA + int loaded_any_ecdsa = 0; +#endif svr_opts.hostkey = new_sign_key(); @@ -551,14 +554,8 @@ void load_all_hostkeys() { #endif } -#if DROPBEAR_DELAY_HOSTKEY - if (svr_opts.delay_hostkey) { - disable_unset_keys = 0; - } -#endif - #if DROPBEAR_RSA - if (disable_unset_keys && !svr_opts.hostkey->rsakey) { + if (!svr_opts.delay_hostkey && !svr_opts.hostkey->rsakey) { disablekey(DROPBEAR_SIGNKEY_RSA); } else { any_keys = 1; @@ -566,39 +563,54 @@ void load_all_hostkeys() { #endif #if DROPBEAR_DSS - if (disable_unset_keys && !svr_opts.hostkey->dsskey) { + if (!svr_opts.delay_hostkey && !svr_opts.hostkey->dsskey) { disablekey(DROPBEAR_SIGNKEY_DSS); } else { any_keys = 1; } #endif - #if DROPBEAR_ECDSA + /* We want to advertise a single ecdsa algorithm size. + - If there is a ecdsa hostkey at startup we choose that that size. + - If we generate at runtime we choose the default ecdsa size. + - Otherwise no ecdsa keys will be advertised */ + + /* check if any keys were loaded at startup */ + loaded_any_ecdsa = + 0 #if DROPBEAR_ECC_256 - if ((disable_unset_keys || ECDSA_DEFAULT_SIZE != 256) - && !svr_opts.hostkey->ecckey256) { + || svr_opts.hostkey->ecckey256 +#endif +#if DROPBEAR_ECC_384 + || svr_opts.hostkey->ecckey384 +#endif +#if DROPBEAR_ECC_521 + || svr_opts.hostkey->ecckey521 +#endif + ; + any_keys |= loaded_any_ecdsa; + + /* Or an ecdsa key could be generated at runtime */ + any_keys |= svr_opts.delay_hostkey; + + /* At most one ecdsa key size will be left enabled */ +#if DROPBEAR_ECC_256 + if (!svr_opts.hostkey->ecckey256 + && (!svr_opts.delay_hostkey || loaded_any_ecdsa || ECDSA_DEFAULT_SIZE != 256 )) { disablekey(DROPBEAR_SIGNKEY_ECDSA_NISTP256); - } else { - any_keys = 1; } #endif - #if DROPBEAR_ECC_384 - if ((disable_unset_keys || ECDSA_DEFAULT_SIZE != 384) - && !svr_opts.hostkey->ecckey384) { + if (!svr_opts.hostkey->ecckey384 + && (!svr_opts.delay_hostkey || loaded_any_ecdsa || ECDSA_DEFAULT_SIZE != 384 )) { disablekey(DROPBEAR_SIGNKEY_ECDSA_NISTP384); - } else { - any_keys = 1; } #endif - #if DROPBEAR_ECC_521 - if ((disable_unset_keys || ECDSA_DEFAULT_SIZE != 521) - && !svr_opts.hostkey->ecckey521) { + if (!svr_opts.hostkey->ecckey521 + && (!svr_opts.delay_hostkey || loaded_any_ecdsa || ECDSA_DEFAULT_SIZE != 521 )) { disablekey(DROPBEAR_SIGNKEY_ECDSA_NISTP521); - } else { - any_keys = 1; } #endif #endif /* DROPBEAR_ECDSA */ diff --git a/svr-session.c b/svr-session.c index ae9cb24..a816398 100644 --- a/svr-session.c +++ b/svr-session.c @@ -40,6 +40,7 @@ #include "auth.h" #include "runopts.h" #include "crypto_desc.h" +#include "fuzz.h" static void svr_remoteclosed(void); static void svr_algos_initialise(void); @@ -184,6 +185,13 @@ void svr_dropbear_exit(int exitcode, const char* format, va_list param) { session_cleanup(); } +#if DROPBEAR_FUZZ + /* longjmp before cleaning up svr_opts */ + if (fuzz.do_jmp) { + longjmp(fuzz.jmp, 1); + } +#endif + if (svr_opts.hostkey) { sign_key_free(svr_opts.hostkey); svr_opts.hostkey = NULL; @@ -193,6 +201,7 @@ void svr_dropbear_exit(int exitcode, const char* format, va_list param) { m_free(svr_opts.ports[i]); } + exit(exitcode); } @@ -238,7 +247,9 @@ void svr_dropbear_log(int priority, const char* format, va_list param) { static void svr_remoteclosed() { m_close(ses.sock_in); - m_close(ses.sock_out); + if (ses.sock_in != ses.sock_out) { + m_close(ses.sock_out); + } ses.sock_in = -1; ses.sock_out = -1; dropbear_close("Exited normally"); diff --git a/sysoptions.h b/sysoptions.h index 3f5c5e6..5bdb3e3 100644 --- a/sysoptions.h +++ b/sysoptions.h @@ -316,4 +316,17 @@ If you test it please contact the Dropbear author */ #define DROPBEAR_CLIENT_TCP_FAST_OPEN 0 #endif +#define DROPBEAR_TRACKING_MALLOC (DROPBEAR_FUZZ) + +/* Used to work around Memory Sanitizer false positives */ +#if defined(__has_feature) +# if __has_feature(memory_sanitizer) +# define DROPBEAR_MSAN 1 +# endif +#endif +#ifndef DROPBEAR_MSAN +#define DROPBEAR_MSAN 0 +#endif + + /* no include guard for this file */ |