How long it would have taken to fuzz recent buffer overflow in sudo?

29.01.2021

After recent sudo vulnerability I was left wondering with obvious question: how such things could be unnoticed for almost 10 years? Are they are really so hard to find, unlikely to be found by fuzzing, or just nobody attempted it?

I was curious whether this bug could have been discovered in reasonable time using well-known afl fuzzer.

First, obviously sudo is setuid binary. Running potentially bugged binaries with random input as root isn’t safest idea. In order to avoid any potential accidental system damage, I set up fresh virtual machine for this purpose. I picked sudo 1.9.5.p1 for this test. afl is designed to generate output into file or stdout, but we want to fuzz command line arguments. So we need to patch sudo ourselves to ignore real argv and load it from stdin instead.

afl source helpfully provides appropiate function in argv-fuzz-inl.h, though author himself isn’t so sure about usefulness of this:

AFL doesn’t support argv fuzzing, because TBH, it’s just not horribly useful in practice. There is an example in experimental/argv_fuzzing/ showing how to do it in a general case if you really want to.

Well.. does it? Let’s see.

Example supplied by argv-fuzz-inl.h reads NUL-separated arguments from stdin. Note that sudo provides both sudo and sudoedit utilities in single binary, so to test both we need to expose to fuzzer argv[0] as well. Code in argv-fuzz-inl.h doesn’t do this, so it needs to be fixed:

  int   rc  = 0; /* start at argv[0] */

In sudo itself patch is straightforward, just wire AFL_INIT_ARGV at the beginning of main:

--- a/src/sudo.c
+++ b/src/sudo.c
@@ -66,6 +66,8 @@
 #include "sudo_plugin.h"
 #include "sudo_plugin_int.h"
 
+#include "argv-fuzz-inl.h"
+
 /*
  * Local variables
  */
@@ -149,6 +151,7 @@ sudo_dso_public int main(int argc, char *argv[], char *envp[]);
 int
 main(int argc, char *argv[], char *envp[])
 {
+AFL_INIT_ARGV();
     int nargc, status = 0;
     char **nargv, **env_add, **user_info;

Quick test shows that sudo/sudoedit selection doesn’t work correctly from testcases passed in stdin, because for some reason it uses __progname. Quick workaround:

--- a/lib/util/progname.c
+++ b/lib/util/progname.c
@@ -83,7 +83,7 @@ void
 initprogname2(const char *name, const char * const * allowed)
 {
     int i;
-# ifdef HAVE___PROGNAME
+# if 0
     extern const char *__progname;
 
     if (__progname != NULL && *__progname != '\0')

Finally we don’t want to wait for password input, so just make it unconditionally fail:

--- a/plugins/sudoers/auth/sudo_auth.c
+++ b/plugins/sudoers/auth/sudo_auth.c
@@ -259,7 +259,7 @@ verify_user(struct passwd *pw, char *prompt, int validated,
            "--disable-authentication configure option."));
        debug_return_int(-1);
     }
-
+return 0;
     /* Enable suspend during password entry. */
     sigemptyset(&sa.sa_mask);
     sa.sa_flags = SA_RESTART;

For some reason afl-gcc instrumentation didn’t work, so I used LLVM-based one. We just need to override CC for ./configure:

CC=afl-clang-fast ./configure

I used two trivial testcases, invoking both available utilities:

echo -ne 'sudo\0ls\0\0' > case1
echo -ne 'sudoedit\0test\0\0' > case2

With everything ready, I launched four afl instances in parallel mode. Half hour later, on one of instances appears…

Crash after 30 minutes

bingo! Let’s see what it is:

debian@debian:~/fuzz$ xxd sync_dir/fuzz3/crashes/id:000000,sig:06,src:000561,op:havoc,rep:2
00000000: 7375 646f 6564 6974 002d 7320 2d20 8052  sudoedit.-s - .R
00000010: 202d 7320 ff20 8001 0073 2073 20ff 2080   -s . ...s s . .
00000020: 0100 7320 2d35 8080 ff20 8001 0073 202d  ..s -5... ...s -
00000030: 0880 80f8 3f2d 7320 2d20 802d 7320 2d20  ....?-s - .-s - 
00000040: 803b 202d 7320 ff20 8001 0073 202d 2080  .; -s . ...s - .
00000050: 8080 3b39 9573 20ef 2080 806a 8000 7f80  ..;9.s . ..j....
00000060: 3b20 2d73 202d 2080 8020 3995 7320 ef09  ; -s - .. 9.s ..
00000070: 8080 6a80 007f 803b 202d 7320 2d20 8080  ..j....; -s - ..
00000080: 3bf6 202d 732d 7320 2d20 8052 202d 7320  ;. -s-s - .R -s 
00000090: ff20 2d35 8080 ff20 8001 0073 202d 0880  . -5... ...s -..
000000a0: 80f8 3f2d 7373 202d 2080 8080 3b39 9573  ..?-ss - ...;9.s
000000b0: 20ef 2080 806a 8000 7f80 3b20 2d73 202d   . ..j....; -s -
000000c0: 2080 8020 3995 7320 ef09 8080 6a80 007f   .. 9.s ....j...
000000d0: 803b 202d 7320 2d20 8080 3bf6 202d 732d  .; -s - ..;. -s-
000000e0: 7320 2d20 8052 202d 7320 ff20 8001 0073  s - .R -s . ...s
000000f0: 202d 3580 80ff 2080 0100 7320 2d08 8080   -5... ...s -...
00000100: f83f 2d73 202d 2080 2d73 202d 2080 3b20  .?-s - .-s - .; 
00000110: 8000 7f80 3b20 2d73 202d 2080 803b f620  ....; -s - ..;. 
00000120: 2d73 202d 2080 3b20 2d73 20ff 2080 0100  -s - .; -s . ...
00000130: 7320 2d20 8064 f83f 2d73 202d 2080 803b  s - .d.?-s - ..;
00000140: 392d 8054 8080 8080 5c20 2d74 202d 2680  9-.T....\ -t -&.
00000150: 0100 80                                  ...

Sure enough, this is crash exploiting sudoedit -s. Given I ran 4 instances in parallel, answer to the question from title is: around 2 hours of CPU time.

Yep. 2 hours of CPU time is enough to find critical security bug in widely used setuid utility.

Does nobody fuzzed sudo in the last decade, or, maybe sudo was tested but sudoedit was forgotten? (honestly, before this I didn’t know that sudoedit existed). I guess the conclusion is that there still are serious bugs in widely used utilities that can be found by quick fuzzer run.