In addition to the existing targets in Fuzzilli's GitHub repository, we can add new targets and make Fuzzilli work with more JavaScript engines.
Migrate Fuzzilli to the ChakraCore Engine
ChakraCore is an open-source JavaScript engine with a C API. Microsoft Edge previously used this engine, although it was no longer used after March 2021. In this post, I migrate Fuzzilli to ChakraCore and see whether Fuzzilli can be easily adapted to other targets.
Clone the Code
Make sure clang/clang++ and llvm-dev are installed. clang/clang++ is needed for SanitizerCoverage (documentation), which provides edge coverage for each execution.
First, change the Makefile. Add set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lrt") to bin/ch/CMakeLists.txt. Searching for the keyword shared library below in vim jumps directly to this location.
To avoid linking issues, I added the code in coverage.c under fuzzilli/Targets to the library in bin/ch/ch.cpp. I also added several header files at the beginning.
extern"C"void __sanitizer_cov_trace_pc_guard(uint32_t *guard) { // There's a small race condition here: if this function executes in two threads for the same // edge at the same time, the first thread might disable the edge (by setting the guard to zero) // before the second thread fetches the guard value (and thus the index). However, our // instrumentation ignores the first edge (see libcoverage.c) and so the race is unproblematic. uint32_t index = *guard; // If this function is called before coverage instrumentation is properly initialized we want to return early. if (!index) return; __shmem->edges[index / 8] |= 1 << (index % 8); *guard = 0; }
// // END FUZZING CODE //
Add REPRL
After this, I followed the instructions here to add REPRL (Read-Eval-Print-Reset-Loop) code to ch.cpp. REPRL reduces the overhead of starting a new JavaScript engine instance, and this loop needs to be triggered by a unique command-line option. Make sure to include it as a processArgument in the profile.
For the implementation, I referred to several patches under the Targets folder. You can also search for grep -rn "HELO" * or grep -rn "char helo" * under fuzzilli/Targets to find implementation examples, but make sure all JavaScript engine source code has been cloned under that folder.
After doing this, my implementation is shown below. The file is located at ChakraCore/bin/ch/ch.cpp:
/* Run the fuzzilli run-eval-print-repeat loop, and exit at the end. */ if (options.m_reprl) { /* REPRL: let parent know we are ready */ char helo[] = "HELO"; CHECK(write(REPRL_CWFD, helo, 4) == 4);//write "HELO" on REPRL_CWFD CHECK(read(REPRL_CRFD, helo, 4) == 4); //read 4 bytes on REPRL_CRFD
if (memcmp(helo, "HELO", 4) != 0) { fprintf(stderr, "[REPRL] Invalid response from parent\n"); exit(EXIT_FAILURE); //break if 4 read bytes do not equal "HELO" }
do {//while true // Keep indentation for easier diffing unsigned action; size_t script_size; char *buffer; CHECK(read(REPRL_CRFD, &action, 4) == 4); //read 4 bytes on REPRL_CRFD if (action != 'cexe') { fprintf(stderr, "[REPRL] Unknown action: %u\n", action); exit(EXIT_FAILURE);//break if 4 read bytes do not equal "cexe" } else{ //read 8 bytes on REPRL_CRFD, store as unsigned 64 bit integer size CHECK(read(REPRL_CRFD, &script_size, 8) == 8); }
char *ptr = buffer; //ptr is buffer size_t remaining = script_size; while (remaining > 0) { ssize_t rv = read(REPRL_DRFD, ptr, remaining);//read size bytes from REPRL_DRFD into allocated buffer if (rv <= 0) { fprintf(stderr, "Failed to load script\n"); _exit(-1); } remaining -= rv; ptr += rv; }
buffer[script_size] = '\0'; if (0 == (result = eval_buf(ctx, buffer, script_size, "reprl", 0))) { js_std_loop(ctx); //Execute buffer as javascript code } else { fprintf(stderr, "Failed to eval_buf reprl\n"); }
js_free(ctx, buffer); //Store return value from JS execution
// In REPRL mode, stdout and stderr may be regular files, so we need to fflush them here. fflush(stdout); //Flush stdout fflush(stderr); //Flush stderr
int status = (result & 0xff) << 8; //Mask return value with 0xff and shift it left by 8 CHECK(write(REPRL_CWFD, &status, 4) == 4); //write that value over REPRL_CWFD //Reset the Javascript engine __sanitizer_cov_reset_edgeguards();//Call __sanitizer_cov_reset_edgeguards to reset coverage } while (true); }
Add Built-In Function
The next step is to add a built-in function that crashes directly, which helps Fuzzilli test whether it can catch crashes in the target engine. The target file is lib/Runtime/Library/GlobalObject.cpp. I will not include the code here. I packaged it as a patch named ch.patch and used the following commands to patch the directory:
1 2
$cd Targets/ChakraCore $ patch -p1 < ch.patch
Create Fuzzilli Profile
Create ChakraCoreProfile.swift. This file tells Fuzzilli how to fuzz the target engine. Put the profile file in FuzzilliCli/Profiles.
// Copyright 2020 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import Fuzzilli letChakraCoreProfile=Profile( processArguments: ["-ForceJITCFGCheck", "-MaxJitThreadCount:1", "-collectgarbage"], // processEnv: [:], processEnv: ["UBSAN_OPTIONS":"handle_segv=0"], codePrefix: """ function main() { """, codeSuffix: """ CollectGarbage(); } main(); """, ecmaVersion: ECMAScriptVersion.es6, crashTests: ["fuzzilli('FUZZILLI_CRASH', 0)", "fuzzilli('FUZZILLI_CRASH', 1)"], additionalCodeGenerators: WeightedList<CodeGenerator>([]), additionalProgramTemplates: WeightedList<ProgramTemplate>([]), disabledCodeGenerators: [], additionalBuiltins: ["CollectGarbage" : .function([] => .undefined)] )
Add this profile to Profile.swift, located in Fuzzilli/Profiles/Profile.swift. My implementation is shown below.
Make sure to rebuild Fuzzilli after doing this:
1 2
$cd fuzzilli $ swift build -c release -Xlinker='-lrt'
Build succeeded!
Run Fuzzilli
Build ChakraCore first. The build instructions are available here. The executable file will be generated at out/Debug/bin/ch/ch. Install the dependencies for building ChakraCore first.
$cd fuzzilli/Targets/ChakraCore $export CFLAGS="-fsanitize-coverage=trace-pc-guard -O3 -fsanitize=address" $export CXXFLAGS="-fsanitize-coverage=trace-pc-guard -O3 -fsanitize=address" $ ./build.sh --static -d -j --cc=/path/to/clang --cxx=/path/to/clang++ # Can specify --debug or --test-build to build.sh to select Debug or Test build flavors respectively. Default is Release build. # Can specify --static to build ChakraCore as a static library. # Also can run as ./build.sh --static -d --cxx=/usr/bin/clang++-10 --cc=/usr/bin/clang-10 -j
If building with Ninja:
1 2 3
$ sudo apt-get install -y ninja-build $ ninja --version #Ensure ninjs has been installed correctly $ ./build.sh -n #Specify the -n flag to build.sh to build with ninja
Let's try fuzzing!
1 2 3
$cd fuzzilli/Targets/ChakraCore $ mkdir fuzz_ch_out $ swift run FuzzilliCli --jobs=2 --profile=chakracore --storagePath=fuzz_ch_out/ out/Debug/ch #or this "out/Debug/bin/ch/ch"
However, I ran into some problems here.
I tried to rebuild Fuzzilli and ChakraCore, but compilation failed again. To record the errors, use the following command:
1
$ script -f output.txt
After collecting the compilation output, press Ctrl + d to stop recording. I then fixed the issues and built the project successfully.