Reflecta

April 22, 2025 ยท View on GitHub

This repo holds the artifact for REFLECTA: Reflection-based Scalable and Semantic Scripting Language Fuzzing, published in AsiaCCS 2025.

Reflecta, our novel scripting language fuzzer, relies solely on a common introspection feature in programming languages, namely reflection, enabling a generic fuzzer design across different scripting languages.

Supported fuzzers include reflecta, nautilus, polyglot, polyglot-corpus, and fuzzilli, and supported targets are ruby, mruby, cpython, micropython, php, and v8.

reflectanautiluspolyglotpolyglot-corpusfuzzilli
rubyYYY--
mrubyYYY--
cpythonYYY--
micropythonYYY--
phpYYYY-
v8YYY-Y

We provide a docker image that contains everything and is available for everyone to use.

git clone https://github.com/HexHive/Reflecta
docker pull chibinz/reflecta:latest

We update specifications for nautilus and polyglot. The grammars we provided for nautilus and polyglot can be found at dockerimage:/targets/{nautilus,polyglot}/grammars. We use existing grammars provided by the authors if available, otherwise adapting them from the https://github.com/antlr/grammars-v4 following nautilus and polyglot's instructions, (mostly adding some builtin names and preambles).

Note that the syntax of Python is not context-free due to the indentation rule. We address this at the lexical stage by introducing two special tokens: INDENT and DEDENT, which act similarly to curly braces that enclose the indented block. We modify the Python grammar (for nautilus and polyglot) to produce INDENT and DEDENT verbatim in the generated code and then augment our fuzzing driver to replace these two tokens with the correct indentation before passing to the interpreter for execution.

Reproducing

Running fuzzing campaigns

To launch fuzzing campaigns for multiple fuzzer-target pairs, we provide the launch.sh script with three arguments.

  • the first are comma-delimited lists of fuzzers, without spaces after commas
  • the first are comma-delimited lists of targets, without spaces after commas
  • the third is an optional argument indicating the number of repetitions, which defaults to 1

The script automatically detects and binds containers to free CPU cores. For optimal performance, we recommend disabling hyperthreads and using only physical cores. Before running the command, ensure you are in the root directory of this repo.

cd Reflecta
# run fuzzilli-based fuzzers
sudo sysctl -w 'kernel.core_pattern=|/bin/false'
./scripts/launch.sh fuzzilli v8 1
./scripts/launch.sh reflecta ruby,mruby,cpython,micropython,php,v8 1

# run afl-based fuzzers
sudo su -c "echo core >/proc/sys/kernel/core_pattern"
./scripts/launch.sh nautilus,polyglot ruby,mruby,cpython,micropython,php,v8 1
./scripts/launch.sh polyglot-corpus php 1

By default, campaigns last 24 hours. Alternatively, run env duration=24h scripts/launch.sh to set a specific timeout.

The fuzzer output for each fuzzer-target pair is saved in the ./bench/ folder (automatically mounted into each docker container by scripts/launch.sh).

For running ablated reflecta variant Reflecta-Sem, comment out the reflection mutator in main.swift. For Reflecta+Ext, use JavaScriptEnvironment (provided by original fuzzilli) instead of ReflectionEnvironment (implemented by reflecta).

Collecting results

To collect the coverage, we replay the corpus of each fuzzer on a separately built SanitizerCoverage binary. This process is automated by our scripts.

  • To obtain the correctness rate for Reflecta, lookup stats/*.json in Reflecta's outputs in the bench folder.
  • The bugs for each fuzzer are located in the crashes folder within each fuzzer's output directory.
  • Coverage results and plots are saved in the cov folder of each fuzzer output.

To visualize the results, please run this notebook and run all cells.

jupyter notebook scripts/visualize-results.ipynb

To manually perform this analysis, run the following commands, which will analyze all the fuzzing compaigns inside bench.

docker run \
    -w /workspaces/Reflecta \
    -v $PWD:/workspaces/Reflecta \
    -it chibinz/reflecta:latest /usr/bin/fish
scripts/collect.fish coverage

Developing / Building from scratch

We provide a Dockerfile for automating dependencies setup. VSCode should automatically detect the presence of .devcontainer/ folder and automatically shows a pop up, prompting you to build and a launch container. If you're using a different editor, you can docker build the image and mount the workspace folder accordingly. Scripts for building targets or fuzzers can be found at scripts/build.fish. The persistent fuzz drivers source can be found at drivers/. For example, if you want to build polyglot fuzzer, run scripts/build.fish polyglot-fuzzer. If you want build afl instrumented binary for mruby (which is used by nautilus), run scripts/build.fish aflplusplus mruby. The installed binary together with fuzz driver can be found in the targets/ directory.

Coverage treemap of the tested targets

One key insight while developing reflecta was that scripting languages has a very large standard library (or runtime) shipped together with it. Earlier work seems to realize the importance of builtins and standard library functionalies for achieving good coverage, but this was not spoken out explicitly :(. When people think of language features, they tend to think of syntactic or control flow features such as (yield generators, with statements, async functions, inheritance). While important, the implementation of these constructs in terms of lines of C code is smaller compared to functionalities implemented in the standard library. A great to way to visualize this is using a coverage treemap. The block size in the treemap denotes the number of edges instrumented by Sancov in each file / function, and the color indicates the percentage of covered edges (green means a majority is covered). The script for plotting the treemap can be found at scripts/plot.py. Note the treemap has three hierarchies: directory, file, and function. The script also outputs an html file which allows you click on each tile and see the coverage percentage for each function. You can also compare the coverage differential of two fuzzers, as shown in paper.

  • MRuby

  • Ruby

  • Micropython

  • CPython

  • PHP

  • V8

Bugs Found by Reflecta

  • MRuby
Bug typeLocation functionStatusReport linkReport dateFix linkFix dateComment
null-dereferenceio_buf_fillConfirmedhttps://github.com/mruby/mruby/issues/604915,09.2023--Already fixed in the upstream.
stack-overflowmrb_json_generateFixedhttps://github.com/mruby/mruby/issues/605015,09.2023--Already fixed in the upstream.
null-dereferencemrb_addrinfo_unix_pathFixedhttps://github.com/mruby/mruby/issues/605116.09.2023https://github.com/mruby/mruby/commit/8afb73f3e2a0eac8e064a5f83f5ca1a8e6a29b3919.09.2023While handling the Addrinfo class.
null-dereferencemrb_vm_execFixedhttps://github.com/mruby/mruby/issues/605216.09.2023https://github.com/mruby/mruby/commit/75e8a726497813af1fd2c11f1cd8560abf0b890019.09.2023While handling the Proc class.
null-dereferenceeval_underFixedhttps://github.com/mruby/mruby/issues/606525.09.2023https://github.com/mruby/mruby/commit/d298da8e72d930ed63d2d68c1c21b12d36e1348d28.09.2023Suspected same root cause to #4.
null-dereferencemrb_struct_to_hFixedhttps://github.com/mruby/mruby/issues/606625.09.2023https://github.com/mruby/mruby/commit/0ac917e2285e36391a142ab4adab9f6a7177531628.09.2023While handling Struct.new().
use-after-freeary_rotate_bangFixed-25.09.2023https://github.com/mruby/mruby/commit/a80f394d971834c4ea9380a49ebd218c00dbd25727.09.2023While operating on an object treated with rotate!().
use-after-freeary_compact_bangFixed-25.09.2023https://github.com/mruby/mruby/commit/a80f394d971834c4ea9380a49ebd218c00dbd25727.09.2023While operating on an object treated with compact!(). Same fix to #7.
  • MicroPython
Bug typeLocation functionStatusReport linkReport dateFix linkFix dateComment
null-dereferencemp_reader_new_fileConfirmedhttps://github.com/micropython/micropython/issues/1252226.09.2023https://github.com/micropython/micropython/pull/1252627.09.2023While handling builtins.execfile().
negative-size-parammpz_as_bytesConfirmedhttps://github.com/micropython/micropython/issues/1252827.09.2023--Feeding a wrong-typed "byteorder" parameter to to_bytes().
generic-Confirmedhttps://github.com/micropython/micropython/issues/1253127.09.2023https://github.com/micropython/micropython/pull/1253528.09.2023MemorySanitizer compatibility issue. Not a crash by fuzzer.
global-buffer-overflowmp_get_stream_raiseConfirmedhttps://github.com/micropython/micropython/issues/1253227.09.2023-Using the return value of "os.VfsPosix()".
heap-use-after-free_bt*Confirmedhttps://github.com/micropython/micropython/issues/1254328.09.2023--Reusing a closed btree object.
null-dereference{mvlsb,rgb565}_fill_rectConfirmedhttps://github.com/micropython/micropython/issues/1256202.10.2023https://github.com/micropython/micropython/pull/12585/commits/c57dc8b418e7a200ececf1df217cdcfae2dd5a7804.10.2023Feeding an improper fill value to a FrameBuffer instance.
null-dereference{mvlsb,rgb565}_getpixelFixedhttps://github.com/micropython/micropython/issues/1256202.10.2023https://github.com/micropython/micropython/pull/12585/commits/c57dc8b418e7a200ececf1df217cdcfae2dd5a7804.10.2023Reported together with #6. (Same fix)
null-dereferencestringio_writeFixedhttps://github.com/micropython/micropython/issues/1256302.10.2023https://github.com/micropython/micropython/pull/12585/commits/c57dc8b418e7a200ececf1df217cdcfae2dd5a7804.10.2023Involving framebuf.FrameBuffer1(). Some PoCs crash at different functions. (Same fix as #6)
null-dereferencemp_obj_bytes_hexFixedhttps://github.com/micropython/micropython/issues/1256302.10.2023https://github.com/micropython/micropython/pull/12585/commits/c57dc8b418e7a200ececf1df217cdcfae2dd5a7804.10.2023Reported together with #8. (Same fix as #6)
null-dereferencemp_reader_mem_readbyteFixedhttps://github.com/micropython/micropython/issues/1256302.10.2023https://github.com/micropython/micropython/pull/12585/commits/c57dc8b418e7a200ececf1df217cdcfae2dd5a7804.10.2023Reported together with #8. (Same fix as #6)
global-buffer-overflowmp_vfs_umountFixedhttps://github.com/micropython/micropython/issues/1258704.10.2023--While unmounting a bogus path.
heap-buffer-overflowmp_seq_multiplyFixedhttps://github.com/micropython/micropython/issues/1260506.10.2023--While operating on a namedtuple object.
null-dereferencemp_obj_equal_not_equalFixedhttps://github.com/micropython/micropython/issues/1260506.10.2023--Reported together with #12. Turned out unrelated to 'namedtuple.'
heap-buffer-overflowmpz_hashFixedhttps://github.com/micropython/micropython/issues/1266011.10.2023https://github.com/micropython/micropython/pull/1266912.10.2023Using a module as an argument to "uctypes.struct()"
null-dereferencempz_hashFixedhttps://github.com/micropython/micropython/issues/1266011.10.2023https://github.com/micropython/micropython/pull/1266912.10.2023Using a value as an argument to "uctypes.struct()". Reported together with #14.
segmentation-faultvfs_posix_file_ioctlFixedhttps://github.com/micropython/micropython/issues/1267012.10.2023--While closing stdin/stdout.
global-buffer-overflowuctypes_struct_agg_sizeFixedhttps://github.com/micropython/micropython/issues/1270216.10.2023--While executing uctypes.sizeof().
null-dereferencemp_obj_is_dict_or_ordereddictFixedhttps://github.com/micropython/micropython/issues/1270216.10.2023--Reported together with #17.
segmentation-faultdecompress_error_text_maybeFixedhttps://github.com/micropython/micropython/issues/1273519.10.2023--While referencing an Error object, initialized with sys.version.
global-buffer-overflowtask_push_queueFixedhttps://github.com/micropython/micropython/issues/1277623.10.2023--While pushing an object to "asyncio.TaskQueue()".
  • PHP
Bug typeLocation functionStatusReport linkReport dateFix linkFix dateComment
null-dereferencezend_hash_cleanFixedhttps://github.com/php/php-src/security/advisories/GHSA-hh2m-7xq2-44p324.11.2023--Calling "spl_autoload_unregister" with a string parameter.
null-dereferencezif_forward_static_callFixedhttps://github.com/php/php-src/security/advisories/GHSA-w3f6-fp6r-7h8224.11.2024--Calling "register_shutdown_function" with string parameters.
null-dereferencezif_func_{num,get}_argsFixedhttps://github.com/php/php-src/security/advisories/GHSA-c2qc-q2pj-w5p924.11.2025--Passing "func_{num,get}_args" to a crashing function.