Skip to content

Instantly share code, notes, and snippets.

@edwintorok
Last active July 14, 2024 21:33
Show Gist options
  • Save edwintorok/f7ccb146471cfb17b2e92510b8c12bd0 to your computer and use it in GitHub Desktop.
Save edwintorok/f7ccb146471cfb17b2e92510b8c12bd0 to your computer and use it in GitHub Desktop.
Fuzzing the OCaml runtime

Notes on how to run the fuzzer (not included in the PR, but for future reference): On Fedora39 (Fedora40 afl packages are a bit broken, I'll open a bug):

export AFL_USE_ASAN=1
export ASAN_OPTIONS="detect_leaks=0,use_sigaltstack=0"
./configure CC=afl-clang-fast 'CFLAGS=-Og -g -fno-omit-frame-pointer'
make -j32
cat >parse0.ml <<EOF
open Runtime_events

let runtime_begin _ _ _ = ()
let runtime_end _ _ _ = ()
let runtime_counter _ _ _ _ = ()
let alloc _ _ _ = ()
let lifecycle _ _ _ _ = ()
let lost_events _ _ = ()
let callbacks = Runtime_events.Callbacks.create
  ~runtime_begin ~runtime_end ~runtime_counter ~alloc ~lifecycle ~lost_events ()

let parse path_pid =
  let cursor =
      Runtime_events.create_cursor path_pid in
  let finally () = Runtime_events.free_cursor cursor in
  Fun.protect ~finally @@ fun () ->
  Runtime_events.read_poll cursor callbacks None

let parse_corrupted path_pid =
    try let (_:int) = parse path_pid in ()
    with Failure _ | Invalid_argument _ ->
      (* parsing corrupted rings, raises exceptions,
         this is expected *)
      ()

let pid = Unix.getpid ()
let file = Printf.sprintf "%d.events" pid
let run () =
  let () =
    try Sys.remove file with Sys_error _ -> ()
  in
  Unix.symlink Sys.argv.(1) file;
  Unix.truncate Sys.argv.(1) 68167744;
  parse_corrupted (Some (".", pid));
  Unix.unlink file

let () =
  AflPersistent.run run
EOF
../ocamlopt.opt -I ../stdlib -I ../otherlibs/unix -I ../otherlibs/runtime_events unix.cmxa aflPersistent.cmx runtime_events.cmxa aflPersistent.mli -c
../ocamlopt.opt -I ../stdlib -I ../otherlibs/unix -I ../otherlibs/runtime_events unix.cmxa aflPersistent.cmx runtime_events.cmxa aflPersistent.ml -c
./ocamlopt.opt -I ../stdlib -I ../otherlibs/unix -I ../otherlibs/runtime_events unix.cmxa aflPersistent.cmx runtime_events.cmxa parse0.ml -o parse0p

In a separate dir build MSAN instrumented AFL runtime:

unset AFL_USE_ASAN
export AFL_USE_MSAN=1
./configure CC=afl-clang-fast CFLAGS="-Og -g -fno-omit-frame-pointer" --without-zstd --disable-native-compiler
make -j32
../runtime/ocamlrun ../ocamlc -nostdlib -I ../stdlib -I ../otherlibs/unix -I ../otherlibs/runtime_events unix.cma runtime_events.cma aflPersistent.cmo parse0.ml -custom -ccopt -I../runtime -o parse0p

Input data: get an .events file from (modify it to remove unlink at the end), and move it to input/

make one TEST=tests/lib-runtime-events/test_external_preserve.ml

To run:

export ASAN_OPTIONS="detect_leaks=0,use_sigaltstack=0,abort_on_error=1,symbolize=0"
# important to set this, otherwise AFL will set it, and enable leak reporting, which will result in what AFL calls "unstable" results (where test binary output is different)
export LSAN_OPTIONS="detect_leaks=0,symbolize=0"
afl-fuzz -M fuzzer001 -i input -o output -- afl-persistent.1.4/parse0p @@
for i in $(seq 1 15); do AFL_NO_UI=1 afl-fuzz -P explore -S fuzzer01$i -i input/ -o output/ afl-persistent.1.4/parse0p @@& done
afl-fuzz -S fuzzer04M -i input/ -o output/ ../ocaml-fuzz-msan/afl-persistent.1.4/parse0p @@

Caveats:

When AFL aborts a run (e.g. due to a timeout) the symlink is not cleaned up and a lot of symlinks pile up in the current dir. In fact so many files can be created that cleaning up with 'rm *.events' is not possible anymore, and this has to be used instead: find . -name '*.events' -print0 | xargs -0 rm

@edwintorok
Copy link
Author

edwintorok commented Jul 14, 2024

Fedora 40 bug reported here https://bugzilla.redhat.com/show_bug.cgi?id=2297764 , should be fixable with a package rebuild (and perhaps could be future proofed by making afl-clang-fast call clang-<MAJORVER> instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment