From 4644ee098a21956f2b1f573447fcfc0c20819444 Mon Sep 17 00:00:00 2001 From: PaDarochek <69221349+PaDarochek@users.noreply.github.com> Date: Tue, 12 Dec 2023 17:01:22 +0300 Subject: [PATCH] Support JavaScript crash triaging for Jazzer.js and jsfuzz via casr-libfuzzer (#176) --- .github/workflows/amd64.yml | 9 +- .github/workflows/coverage.yaml | 9 +- .gitignore | 3 + README.md | 31 +- casr/src/bin/casr-cli.rs | 14 + casr/src/bin/casr-cluster.rs | 3 +- casr/src/bin/casr-java.rs | 2 +- casr/src/bin/casr-js.rs | 170 ++++ casr/src/bin/casr-libfuzzer.rs | 7 +- casr/src/bin/casr-python.rs | 2 +- casr/tests/casr_tests/js/binding.gyp | 12 + casr/tests/casr_tests/js/crashes.zip | Bin 0 -> 460 bytes casr/tests/casr_tests/js/native.cpp | 20 + casr/tests/casr_tests/js/test_casr_js.js | 15 + .../casr_tests/js/test_casr_js_jazzer.js | 18 + .../casr_tests/js/test_casr_js_jsfuzz.js | 19 + .../casr_tests/js/test_casr_js_native.js | 5 + .../js/test_casr_js_native_jazzer.js | 10 + .../js/test_casr_js_native_jsfuzz.js | 11 + .../js/test_casr_libfuzzer_jazzer_js.js | 22 + .../test_casr_libfuzzer_jazzer_js_xml2js.js | 53 + .../js/test_casr_libfuzzer_jsfuzz.js | 23 + casr/tests/casr_tests/js/xml2js.zip | Bin 0 -> 27251 bytes casr/tests/tests.rs | 963 ++++++++++++++++++ docs/usage.md | 58 +- libcasr/src/constants.rs | 4 + libcasr/src/js.rs | 22 +- 27 files changed, 1474 insertions(+), 31 deletions(-) create mode 100644 casr/src/bin/casr-js.rs create mode 100644 casr/tests/casr_tests/js/binding.gyp create mode 100644 casr/tests/casr_tests/js/crashes.zip create mode 100644 casr/tests/casr_tests/js/native.cpp create mode 100644 casr/tests/casr_tests/js/test_casr_js.js create mode 100644 casr/tests/casr_tests/js/test_casr_js_jazzer.js create mode 100644 casr/tests/casr_tests/js/test_casr_js_jsfuzz.js create mode 100644 casr/tests/casr_tests/js/test_casr_js_native.js create mode 100644 casr/tests/casr_tests/js/test_casr_js_native_jazzer.js create mode 100644 casr/tests/casr_tests/js/test_casr_js_native_jsfuzz.js create mode 100644 casr/tests/casr_tests/js/test_casr_libfuzzer_jazzer_js.js create mode 100755 casr/tests/casr_tests/js/test_casr_libfuzzer_jazzer_js_xml2js.js create mode 100644 casr/tests/casr_tests/js/test_casr_libfuzzer_jsfuzz.js create mode 100644 casr/tests/casr_tests/js/xml2js.zip diff --git a/.github/workflows/amd64.yml b/.github/workflows/amd64.yml index c48b20e9..48c7fe84 100644 --- a/.github/workflows/amd64.yml +++ b/.github/workflows/amd64.yml @@ -20,8 +20,15 @@ jobs: - name: Run tests run: | sudo apt update && sudo apt install -y gdb pip curl python3.10-dev llvm \ - openjdk-17-jdk + openjdk-17-jdk ca-certificates gnupg pip3 install atheris + sudo mkdir -p /etc/apt/keyrings + curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg + export NODE_MAJOR=20 + echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list + sudo apt update && sudo apt install -y nodejs + sudo npm install -g jsfuzz + sudo npm install --save-dev @jazzer.js/core curl https://sh.rustup.rs -o rustup.sh && chmod +x rustup.sh && \ ./rustup.sh -y && rm rustup.sh rustup install nightly diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 67921dcd..9d806814 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -18,8 +18,15 @@ jobs: - name: Install Dependences run: | sudo apt update && sudo apt install -y gdb pip curl python3.10-dev llvm \ - openjdk-17-jdk + openjdk-17-jdk ca-certificates gnupg pip3 install atheris + sudo mkdir -p /etc/apt/keyrings + curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg + export NODE_MAJOR=20 + echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list + sudo apt update && sudo apt install -y nodejs + sudo npm install -g jsfuzz + sudo npm install --save-dev @jazzer.js/core curl https://sh.rustup.rs -o rustup.sh && chmod +x rustup.sh && \ ./rustup.sh -y && rm rustup.sh rustup install nightly diff --git a/.gitignore b/.gitignore index c3345b6c..a89614ff 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ Cargo.lock */Cargo.lock */tests/tmp_tests_casr *.swp +node_modules +*/node_modules/* +.vscode diff --git a/README.md b/README.md index a06278ae..8425d0a6 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,10 @@ ASAN reports or `casr-ubsan` to analyze UBSAN reports. Try `casr-gdb` to get reports from gdb. Use `casr-python` to analyze python reports and get report from [Atheris](https://github.com/google/atheris). Use `casr-java` to analyze java reports and get report from -[Jazzer](https://github.com/CodeIntelligenceTesting/jazzer). +[Jazzer](https://github.com/CodeIntelligenceTesting/jazzer). Use `casr-js` +to analyze JavaScript reports and get report from +[Jazzer.js](https://github.com/CodeIntelligenceTesting/jazzer.js) or +[jsfuzz](https://github.com/fuzzitdev/jsfuzz). Crash report contains many useful information: severity (like [exploitable](https://github.com/jfoote/exploitable)) for x86, x86\_64, arm32, aarch64, rv32g, rv64g architectures, @@ -44,7 +47,8 @@ Triage is based on stack trace comparison from [gdb-command](https://github.com/ `casr-libfuzzer` can triage crashes found by [libFuzzer](https://www.llvm.org/docs/LibFuzzer.html) based fuzzer (C/C++/[go-fuzz](https://github.com/dvyukov/go-fuzz)/[Atheris](https://github.com/google/atheris) -/[Jazzer](https://github.com/CodeIntelligenceTesting/jazzer)). +/[Jazzer](https://github.com/CodeIntelligenceTesting/jazzer)/[Jazzer.js](https://github.com/CodeIntelligenceTesting/jazzer.js)/ +[jsfuzz](https://github.com/fuzzitdev/jsfuzz)). `casr-dojo` allows to upload new and unique CASR reports to [DefectDojo](https://github.com/DefectDojo/django-DefectDojo) (available with `dojo` feature). @@ -75,6 +79,7 @@ and program languages: * Go * Python * Java +* JavaScript It could be built with `exploitable` feature for severity estimation crashes collected from gdb. To save crash reports as json use `serde` feature. @@ -137,6 +142,10 @@ Create report from java: $ casr-java -o java.casrep -- java casr/tests/casr_tests/java/Test1.java +Create report from JavaScript: + + $ casr-js -o js.casrep -- node casr/tests/casr_tests/js/test_casr_js.js + View report: $ casr-cli casr/tests/casr_tests/casrep/test_clustering_san/load_fuzzer_crash-120697a7f5b87c03020f321c8526adf0f4bcc2dc.casrep @@ -176,8 +185,16 @@ Triage libFuzzer crashes with casr-libfuzzer: Triage Atheris crashes with casr-libfuzzer: $ unzip casr/tests/casr_tests/python/ruamel.zip - $ cp casr/tests/casr_tests/python/yaml_fuzzer.py . - $ casr-libfuzzer -i casr/tests/casr_tests/casrep/atheris_crashes_ruamel_yaml -o casr/tests/tmp_tests_casr/casr_libfuzzer_atheris_out -- ./yaml_fuzzer.py + $ casr-libfuzzer -i casr/tests/casr_tests/casrep/atheris_crashes_ruamel_yaml -o casr/tests/tmp_tests_casr/casr_libfuzzer_atheris_out -- casr/tests/casr_tests/python/yaml_fuzzer.py + +Triage Jazzer.js crashes with casr-libfuzzer (Jazzer.js installation [guide](https://github.com/CodeIntelligenceTesting/jazzer.js#quickstart)): + + $ unzip casr/tests/casr_tests/js/xml2js.zip -d xml2js + $ mkdir -p casr/tests/tmp_tests_casr/xml2js_fuzzer_out + $ cp casr/tests/casr_tests/js/test_casr_libfuzzer_jazzer_js_xml2js.js casr/tests/tmp_tests_casr/xml2js_fuzzer_out/xml2js_fuzzer.js + $ sudo npm install xml2js + $ sudo npm install --save-dev @jazzer.js/core + $ casr-libfuzzer -i ./xml2js -o casr/tests/tmp_tests_casr/xml2js_fuzzer_out/out -- npx jazzer casr/tests/tmp_tests_casr/xml2js_fuzzer_out/xml2js_fuzzer.js Upload new and unique CASR reports to [DefectDojo](https://github.com/DefectDojo/django-DefectDojo): @@ -195,7 +212,7 @@ Upload new and unique CASR reports to When you have crashes from fuzzing you may do the following steps: 1. Create reports for all crashes via `casr-san`, `casr-gdb` (if no sanitizers - are present), `casr-python`, or `casr-java`. + are present), `casr-python`, `casr-java`, or `casr-js`. 2. Deduplicate collected crash reports via `casr-cluster -d`. 3. Cluster deduplicated crash reports via `casr-cluster -c`. 4. Create reports and deduplicate them for all UBSAN errors via `casr-ubsan`. @@ -208,7 +225,9 @@ If you use [AFL++](https://github.com/AFLplusplus/AFLplusplus), the pipeline `casr-afl`. If you use [libFuzzer](https://www.llvm.org/docs/LibFuzzer.html) based fuzzer -(C/C++/[go-fuzz](https://github.com/dvyukov/go-fuzz)/[Atheris](https://github.com/google/atheris)), +(C/C++/[go-fuzz](https://github.com/dvyukov/go-fuzz)/[Atheris](https://github.com/google/atheris) +/[Jazzer](https://github.com/CodeIntelligenceTesting/jazzer)/[Jazzer.js](https://github.com/CodeIntelligenceTesting/jazzer.js)/ +[jsfuzz](https://github.com/fuzzitdev/jsfuzz)), the pipeline (without `casr-ubsan` and `casr-dojo`) could be done automatically by `casr-libfuzzer`. diff --git a/casr/src/bin/casr-cli.rs b/casr/src/bin/casr-cli.rs index ea744849..79573598 100644 --- a/casr/src/bin/casr-cli.rs +++ b/casr/src/bin/casr-cli.rs @@ -447,6 +447,16 @@ fn build_tree_report( tree.collapse_item(row); } + if !report.js_report.is_empty() { + row = tree + .insert_container_item("JsReport".to_string(), Placement::After, row) + .unwrap(); + report.js_report.iter().for_each(|e| { + tree.insert_item(e.clone(), Placement::LastChild, row); + }); + tree.collapse_item(row); + } + if !report.source.is_empty() { row = tree .insert_container_item("Source".to_string(), Placement::After, row) @@ -629,6 +639,10 @@ fn build_slider_report( select.add_item("RustReport", report.rust_report.join("\n")); } + if !report.js_report.is_empty() { + select.add_item("JsReport", report.js_report.join("\n")); + } + if !report.source.is_empty() { select.add_item("Source", report.source.join("\n")); } diff --git a/casr/src/bin/casr-cluster.rs b/casr/src/bin/casr-cluster.rs index de74990b..521e420e 100644 --- a/casr/src/bin/casr-cluster.rs +++ b/casr/src/bin/casr-cluster.rs @@ -426,8 +426,7 @@ fn main() -> Result<()> { .value_parser(clap::value_parser!(u32).range(1..)) ) .get_matches(); - - init_ignored_frames!("cpp", "rust", "python", "go", "java"); + init_ignored_frames!("cpp", "rust", "python", "go", "java", "js"); // Get number of threads let jobs = if let Some(jobs) = matches.get_one::("jobs") { diff --git a/casr/src/bin/casr-java.rs b/casr/src/bin/casr-java.rs index c5361d99..7fef0b84 100644 --- a/casr/src/bin/casr-java.rs +++ b/casr/src/bin/casr-java.rs @@ -83,7 +83,7 @@ fn main() -> Result<()> { ) .get_matches(); - init_ignored_frames!("java"); //TODO + init_ignored_frames!("java", "cpp"); //TODO if let Some(path) = matches.get_one::("ignore") { util::add_custom_ignored_frames(path)?; } diff --git a/casr/src/bin/casr-js.rs b/casr/src/bin/casr-js.rs new file mode 100644 index 00000000..875b4eb7 --- /dev/null +++ b/casr/src/bin/casr-js.rs @@ -0,0 +1,170 @@ +use casr::util; +use libcasr::{ + exception::Exception, init_ignored_frames, js::*, report::CrashReport, stacktrace::*, +}; + +use anyhow::{bail, Result}; +use clap::{Arg, ArgAction, ArgGroup}; +use regex::Regex; +use std::path::{Path, PathBuf}; +use std::process::Command; + +fn main() -> Result<()> { + let matches = clap::Command::new("casr-js") + .version(clap::crate_version!()) + .about("Create CASR reports (.casrep) from JavaScript crash reports") + .term_width(90) + .arg( + Arg::new("output") + .short('o') + .long("output") + .action(ArgAction::Set) + .value_parser(clap::value_parser!(PathBuf)) + .value_name("REPORT") + .help( + "Path to save report. Path can be a directory, then report name is generated", + ), + ) + .arg( + Arg::new("stdout") + .action(ArgAction::SetTrue) + .long("stdout") + .help("Print CASR report to stdout"), + ) + .group( + ArgGroup::new("out") + .args(["stdout", "output"]) + .required(true), + ) + .arg( + Arg::new("stdin") + .long("stdin") + .action(ArgAction::Set) + .value_parser(clap::value_parser!(PathBuf)) + .value_name("FILE") + .help("Stdin file for program"), + ) + .arg( + Arg::new("timeout") + .short('t') + .long("timeout") + .action(ArgAction::Set) + .default_value("0") + .value_name("SECONDS") + .help("Timeout (in seconds) for target execution, 0 value means that timeout is disabled") + .value_parser(clap::value_parser!(u64).range(0..)) + ) + .arg( + Arg::new("ignore") + .long("ignore") + .action(ArgAction::Set) + .value_parser(clap::value_parser!(PathBuf)) + .value_name("FILE") + .help("File with regular expressions for functions and file paths that should be ignored"), + ) + .arg( + Arg::new("ARGS") + .action(ArgAction::Set) + .num_args(1..) + .last(true) + .help("Add \"-- \" to run"), + ) + .get_matches(); + + init_ignored_frames!("js", "cpp"); + if let Some(path) = matches.get_one::("ignore") { + util::add_custom_ignored_frames(path)?; + } + // Get program args. + let argv: Vec<&str> = if let Some(argvs) = matches.get_many::("ARGS") { + argvs.map(|s| s.as_str()).collect() + } else { + bail!("Wrong arguments for starting program"); + }; + + // Get stdin for target program. + let stdin_file = util::stdin_from_matches(&matches)?; + + // Get timeout + let timeout = *matches.get_one::("timeout").unwrap(); + + // Run program. + let mut js_cmd = Command::new(argv[0]); + if let Some(ref file) = stdin_file { + js_cmd.stdin(std::fs::File::open(file)?); + } + if argv.len() > 1 { + js_cmd.args(&argv[1..]); + } + let js_result = util::get_output(&mut js_cmd, timeout, true)?; + + let js_stderr = String::from_utf8_lossy(&js_result.stderr); + + // Create report. + let mut report = CrashReport::new(); + // Set executable path. + report.executable_path = argv[0].to_string(); + let mut path_to_tool = PathBuf::new(); + path_to_tool.push(argv[0]); + if argv.len() > 1 { + let fpath = Path::new(argv[0]); + if let Some(fname) = fpath.file_name() { + path_to_tool = if fname == fpath.as_os_str() { + let Ok(full_path_to_tool) = which::which(fname) else { + bail!("{} is not found in PATH", argv[0]); + }; + full_path_to_tool + } else { + fpath.to_path_buf() + }; + if !path_to_tool.exists() { + bail!("Could not find the tool in the specified path {}", argv[0]); + } + let fname = fname.to_string_lossy(); + if (fname == "node" || fname == "jsfuzz") && argv[1].ends_with(".js") { + report.executable_path = argv[1].to_string(); + } else if argv.len() > 2 + && fname == "npx" + && argv[1] == "jazzer" + && argv[2].ends_with(".js") + { + report.executable_path = argv[2].to_string(); + } + } + } + report.proc_cmdline = argv.join(" "); + let _ = report.add_os_info(); + let _ = report.add_proc_environ(); + + // Get JS report. + let js_stderr_list: Vec = js_stderr.split('\n').map(|l| l.to_string()).collect(); + let re = Regex::new(r"^(?:.*Error:(?:\s+.*)?|Thrown at:)$").unwrap(); + if let Some(start) = js_stderr_list.iter().position(|x| re.is_match(x)) { + report.js_report = js_stderr_list[start..].to_vec(); + report + .js_report + .retain(|x| !x.is_empty() && (x.trim().starts_with("at") || x.contains("Error:"))); + let report_str = report.js_report.join("\n"); + report.stacktrace = JsStacktrace::extract_stacktrace(&report_str)?; + if let Some(exception) = JsException::parse_exception(&report.js_report[0]) { + report.execution_class = exception; + } + } else { + // Call casr-san with absolute path to interpreter/fuzzer + let mut modified_argv = argv.clone(); + modified_argv[0] = path_to_tool.to_str().unwrap_or(argv[0]); + return util::call_casr_san(&matches, &modified_argv, "casr-js"); + } + + if let Ok(crash_line) = JsStacktrace::parse_stacktrace(&report.stacktrace)?.crash_line() { + report.crashline = crash_line.to_string(); + if let CrashLine::Source(debug) = crash_line { + if let Some(sources) = CrashReport::sources(&debug) { + report.source = sources; + } + } + } + + //Output report + util::output_report(&report, &matches, &argv) +} diff --git a/casr/src/bin/casr-libfuzzer.rs b/casr/src/bin/casr-libfuzzer.rs index 38626a93..34462044 100644 --- a/casr/src/bin/casr-libfuzzer.rs +++ b/casr/src/bin/casr-libfuzzer.rs @@ -14,7 +14,7 @@ use std::path::{Path, PathBuf}; fn main() -> Result<()> { let matches = clap::Command::new("casr-libfuzzer") .version(clap::crate_version!()) - .about("Triage crashes found by libFuzzer based fuzzer (C/C++/go-fuzz/Atheris/Jazzer)") + .about("Triage crashes found by libFuzzer based fuzzer (C/C++/go-fuzz/Atheris/Jazzer/Jazzer.js/jsfuzz)") .term_width(90) .arg( Arg::new("log-level") @@ -128,6 +128,11 @@ fn main() -> Result<()> { "casr-python" } else if argv[0].ends_with("jazzer") || argv[0].ends_with("java") { "casr-java" + } else if argv[0].ends_with("node") + || argv.len() > 1 && argv[0].ends_with("npx") && argv[1] == "jazzer" + || argv[0].ends_with("jsfuzz") + { + "casr-js" } else { let sym_list = util::symbols_list(Path::new(argv[0]))?; if sym_list.contains("__asan") || sym_list.contains("runtime.go") { diff --git a/casr/src/bin/casr-python.rs b/casr/src/bin/casr-python.rs index 26be758b..12a68975 100644 --- a/casr/src/bin/casr-python.rs +++ b/casr/src/bin/casr-python.rs @@ -75,7 +75,7 @@ fn main() -> Result<()> { ) .get_matches(); - init_ignored_frames!("python"); + init_ignored_frames!("python", "cpp"); if let Some(path) = matches.get_one::("ignore") { util::add_custom_ignored_frames(path)?; } diff --git a/casr/tests/casr_tests/js/binding.gyp b/casr/tests/casr_tests/js/binding.gyp new file mode 100644 index 00000000..5b43de77 --- /dev/null +++ b/casr/tests/casr_tests/js/binding.gyp @@ -0,0 +1,12 @@ +{ + "targets": [ + { + "cflags": [ "-fexceptions -fsanitize=address,fuzzer-no-link -O0 -g -fPIC" ], + "cflags_cc": [ "-fexceptions -fsanitize=address,fuzzer-no-link -O0 -g -fPIC" ], + "include_dirs" : ["6X^1h{5GJ4j z*bFf=0=k5eNsbwpha^D$WMBm1EsY=++ +#include + +void foo(const Napi::CallbackInfo &info) +{ + Napi::Env env = info.Env(); + uint8_t buf[] = {1, 2, 3}; + Napi::Buffer arr = Napi::Buffer::New(env, &buf[0], 3); + arr[5u] = 1; + printf("Number: %u\n", arr[5u]); + // throw Napi::String::New(env, "error in native lib"); +} + +Napi::Object init(Napi::Env env, Napi::Object exports) +{ + exports.Set(Napi::String::New(env, "foo"), Napi::Function::New(env, foo)); + return exports; +}; + +NODE_API_MODULE(native, init); diff --git a/casr/tests/casr_tests/js/test_casr_js.js b/casr/tests/casr_tests/js/test_casr_js.js new file mode 100644 index 00000000..294e3c63 --- /dev/null +++ b/casr/tests/casr_tests/js/test_casr_js.js @@ -0,0 +1,15 @@ +function bar() { + new Function(` + throw new Error('internal'); + `)(); +} + +function foo() { + bar(); +} + +function main() { + foo(); +} + +main() diff --git a/casr/tests/casr_tests/js/test_casr_js_jazzer.js b/casr/tests/casr_tests/js/test_casr_js_jazzer.js new file mode 100644 index 00000000..9bf924b5 --- /dev/null +++ b/casr/tests/casr_tests/js/test_casr_js_jazzer.js @@ -0,0 +1,18 @@ +function bar() { + new Function(` + throw new Error('internal'); + `)(); +} + +function foo() { + bar(); +} + +function fuzz(data) { + foo(); +} + +module.exports.fuzz = function (data /*: Buffer */) { + const fuzzerData = data.toString(); + fuzz(fuzzerData); +}; diff --git a/casr/tests/casr_tests/js/test_casr_js_jsfuzz.js b/casr/tests/casr_tests/js/test_casr_js_jsfuzz.js new file mode 100644 index 00000000..c7b0b636 --- /dev/null +++ b/casr/tests/casr_tests/js/test_casr_js_jsfuzz.js @@ -0,0 +1,19 @@ +function bar() { + new Function(` + throw new Error('internal'); + `)(); +} + +function foo() { + bar(); +} + +function fuzz(data) { + foo(); +} + +module.exports = { + fuzz +}; + +fuzz(process.argv[1]); diff --git a/casr/tests/casr_tests/js/test_casr_js_native.js b/casr/tests/casr_tests/js/test_casr_js_native.js new file mode 100644 index 00000000..fe63c5bd --- /dev/null +++ b/casr/tests/casr_tests/js/test_casr_js_native.js @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +const native_lib = require('bindings')('native') + +native_lib.foo(); diff --git a/casr/tests/casr_tests/js/test_casr_js_native_jazzer.js b/casr/tests/casr_tests/js/test_casr_js_native_jazzer.js new file mode 100644 index 00000000..9b4b1b1c --- /dev/null +++ b/casr/tests/casr_tests/js/test_casr_js_native_jazzer.js @@ -0,0 +1,10 @@ +const native_lib = require('bindings')('native') + +function fuzz(data) { + native_lib.foo(); +} + +module.exports.fuzz = function (data /*: Buffer */) { + const fuzzerData = data.toString(); + fuzz(fuzzerData); +}; diff --git a/casr/tests/casr_tests/js/test_casr_js_native_jsfuzz.js b/casr/tests/casr_tests/js/test_casr_js_native_jsfuzz.js new file mode 100644 index 00000000..b63e2495 --- /dev/null +++ b/casr/tests/casr_tests/js/test_casr_js_native_jsfuzz.js @@ -0,0 +1,11 @@ +const native_lib = require('bindings')('native') + +function fuzz(data) { + native_lib.foo(); +} + +module.exports = { + fuzz +}; + +fuzz(process.argv[1]); diff --git a/casr/tests/casr_tests/js/test_casr_libfuzzer_jazzer_js.js b/casr/tests/casr_tests/js/test_casr_libfuzzer_jazzer_js.js new file mode 100644 index 00000000..3c3e79d3 --- /dev/null +++ b/casr/tests/casr_tests/js/test_casr_libfuzzer_jazzer_js.js @@ -0,0 +1,22 @@ +function bar(data) { + if (data.length > 0 && data[0] > '1') { + throw new TypeError('First'); + } else if (data.length > 1 && data[1] > '1') { + throw new ReferenceError('Second'); + } else if (data.length > 2 && data[2] > '1') { + throw new RangeError('Third'); + } +} + +function foo(data) { + bar(data); +} + +function fuzz(data) { + foo(data); +} + +module.exports.fuzz = function (data /*: Buffer */) { + const fuzzerData = data.toString(); + fuzz(fuzzerData); +}; diff --git a/casr/tests/casr_tests/js/test_casr_libfuzzer_jazzer_js_xml2js.js b/casr/tests/casr_tests/js/test_casr_libfuzzer_jazzer_js_xml2js.js new file mode 100755 index 00000000..8712798a --- /dev/null +++ b/casr/tests/casr_tests/js/test_casr_libfuzzer_jazzer_js_xml2js.js @@ -0,0 +1,53 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * 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 + * + * http://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. + */ + +// The code in this file is based on the examples available in JSFuzz: +// https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/jsfuzz/-/blob/34a694a8c73bfe0895c4e24784ba5b6dfe964b94/examples/xml/fuzz.js +// The original code is available under the Apache License 2.0. + +const xml2js = require("xml2js"); + +/** + * @param { Buffer } data + */ +module.exports.fuzz = async function (data) { + try { + await xml2js.parseStringPromise(data.toString(), {}); + } catch (error) { + if (!ignoredError(error)) throw error; + } +}; + +function ignoredError(error) { + return !!ignored.find((message) => error.message.startsWith(message)); +} + +const ignored = [ + "Non-whitespace before first tag", + "Unencoded", + "Unexpected end", + "Invalid character", + "Invalid attribute name", + "Invalid tagname", + "Unclosed root tag", + "Attribute without value", + "Forward-slash in opening tag", + "Text data outside of root node", + "Unquoted attribute value", + "Unmatched closing tag", + "No whitespace between attributes", + "Unexpected close tag", +]; diff --git a/casr/tests/casr_tests/js/test_casr_libfuzzer_jsfuzz.js b/casr/tests/casr_tests/js/test_casr_libfuzzer_jsfuzz.js new file mode 100644 index 00000000..fb56420f --- /dev/null +++ b/casr/tests/casr_tests/js/test_casr_libfuzzer_jsfuzz.js @@ -0,0 +1,23 @@ +function bar(data) { + if (data.length > 0 && data[0] > '1') { + throw new Error('First'); + } else if (data.length > 1 && data[1] > '2') { + throw new Error('Second'); + } else if (data.length > 2 && data[2] > '3') { + throw new Error('Third'); + } +} + +function foo(data) { + bar(data); +} + +function fuzz(data) { + foo(data); +} + +module.exports = { + fuzz +}; + +fuzz(process.argv[1]); diff --git a/casr/tests/casr_tests/js/xml2js.zip b/casr/tests/casr_tests/js/xml2js.zip new file mode 100644 index 0000000000000000000000000000000000000000..bb5d12e1726eb240506ebc8a01fd3571b7da75ba GIT binary patch literal 27251 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 JK2S_Q{SU*BLID5( literal 0 HcmV?d00001 diff --git a/casr/tests/tests.rs b/casr/tests/tests.rs index f49de7b7..606baafc 100644 --- a/casr/tests/tests.rs +++ b/casr/tests/tests.rs @@ -4,6 +4,7 @@ extern crate serde_json; use regex::Regex; use serde_json::Value; +use std::env; use std::fs; use std::io::Write; @@ -20,6 +21,7 @@ lazy_static::lazy_static! { static ref EXE_CASR_UBSAN: RwLock<&'static str> = RwLock::new(env!("CARGO_BIN_EXE_casr-ubsan")); static ref EXE_CASR_PYTHON: RwLock<&'static str> = RwLock::new(env!("CARGO_BIN_EXE_casr-python")); static ref EXE_CASR_JAVA: RwLock<&'static str> = RwLock::new(env!("CARGO_BIN_EXE_casr-java")); + static ref EXE_CASR_JS: RwLock<&'static str> = RwLock::new(env!("CARGO_BIN_EXE_casr-js")); static ref EXE_CASR_GDB: RwLock<&'static str> = RwLock::new(env!("CARGO_BIN_EXE_casr-gdb")); static ref PROJECT_DIR: RwLock<&'static str> = RwLock::new(env!("CARGO_MANIFEST_DIR")); } @@ -4561,3 +4563,964 @@ fn test_casr_cluster_d_python() { let _ = std::fs::remove_dir_all(&paths[1]); } + +#[test] +#[cfg(target_arch = "x86_64")] +fn test_casr_js() { + let test_dir = abs_path("tests/tmp_tests_casr/test_casr_js"); + let test_path = abs_path("tests/casr_tests/js/test_casr_js.js"); + let _ = std::fs::remove_dir_all(test_dir); + let Ok(node_path) = which::which("node") else { + panic!("No node is found."); + }; + + let output = Command::new(*EXE_CASR_JS.read().unwrap()) + .args(["--stdout", "--", &node_path.to_str().unwrap(), &test_path]) + .output() + .expect("failed to start casr-js"); + + assert!( + output.status.success(), + "Stdout {}.\n Stderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + + let report: Result = serde_json::from_slice(&output.stdout); + if let Ok(report) = report { + let severity_type = report["CrashSeverity"]["Type"].as_str().unwrap(); + let severity_desc = report["CrashSeverity"]["ShortDescription"] + .as_str() + .unwrap() + .to_string(); + + assert_eq!(10, report["Stacktrace"].as_array().unwrap().iter().count()); + assert_eq!(severity_type, "NOT_EXPLOITABLE"); + assert_eq!(severity_desc, "Error"); + assert!(report["CrashLine"] + .as_str() + .unwrap() + .contains("test_casr_js.js:3:15")); + } else { + panic!("Couldn't parse json report file."); + } +} + +#[test] +#[cfg(target_arch = "x86_64")] +fn test_casr_js_jsfuzz() { + let test_dir = abs_path("tests/tmp_tests_casr/test_casr_js_jsfuzz"); + let _ = std::fs::remove_dir_all(test_dir); + let paths = [ + "tests/casr_tests/js/test_casr_js_jsfuzz.js".to_string(), + "tests/tmp_tests_casr/test_casr_js_jsfuzz/corpus".to_string(), + ]; + let Ok(jsfuzz_path) = which::which("jsfuzz") else { + panic!("No jsfuzz is found."); + }; + + // Create out dir + let output = Command::new("mkdir") + .args(["-p", &paths[1]]) + .output() + .expect("failed to create dir"); + assert!( + output.status.success(), + "Stdout {}.\n Stderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + let mut crash_file = fs::File::create(abs_path( + "tests/tmp_tests_casr/test_casr_js_jsfuzz/corpus/crash", + )) + .unwrap(); + crash_file.write_all(b"AAAAAAAAAAAAAAAAAAAAAAAA").unwrap(); + + let output = Command::new(*EXE_CASR_JS.read().unwrap()) + .args([ + "--stdout", + "--", + &jsfuzz_path.to_str().unwrap(), + &paths[0], + &paths[1], + ]) + .output() + .expect("failed to start casr-js"); + + assert!( + output.status.success(), + "Stdout {}.\n Stderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + + let report: Result = serde_json::from_slice(&output.stdout); + if let Ok(report) = report { + let severity_type = report["CrashSeverity"]["Type"].as_str().unwrap(); + let severity_desc = report["CrashSeverity"]["ShortDescription"] + .as_str() + .unwrap() + .to_string(); + + assert_eq!(10, report["Stacktrace"].as_array().unwrap().iter().count()); + assert_eq!(severity_type, "NOT_EXPLOITABLE"); + assert_eq!(severity_desc, "Error"); + assert!(report["CrashLine"] + .as_str() + .unwrap() + .contains("test_casr_js_jsfuzz.js:2:15")); + } else { + panic!("Couldn't parse json report file."); + } +} + +#[test] +#[cfg(target_arch = "x86_64")] +fn test_casr_js_jazzer() { + let test_dir = abs_path("tests/tmp_tests_casr/test_casr_js_jazzer"); + let _ = std::fs::remove_dir_all(test_dir); + let paths = [ + abs_path("tests/casr_tests/js/test_casr_js_jazzer.js"), + abs_path("tests/tmp_tests_casr/test_casr_js_jazzer/corpus"), + ]; + let Ok(npx_path) = which::which("npx") else { + panic!("No npx is found."); + }; + + // Create out dir + let output = Command::new("mkdir") + .args(["-p", &paths[1]]) + .output() + .expect("failed to create dir"); + assert!( + output.status.success(), + "Stdout {}.\n Stderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + let mut crash_file = fs::File::create(abs_path( + "tests/tmp_tests_casr/test_casr_js_jazzer/corpus/crash", + )) + .unwrap(); + crash_file.write_all(b"AAAAAAAAAAAAAAAAAAAAAAAA").unwrap(); + + let output = Command::new(*EXE_CASR_JS.read().unwrap()) + .args([ + "--stdout", + "--", + &npx_path.to_str().unwrap(), + "jazzer", + &paths[0], + &paths[1], + ]) + .output() + .expect("failed to start casr-js"); + + assert!( + output.status.success(), + "Stdout {}.\n Stderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + + let report: Result = serde_json::from_slice(&output.stdout); + if let Ok(report) = report { + let severity_type = report["CrashSeverity"]["Type"].as_str().unwrap(); + let severity_desc = report["CrashSeverity"]["ShortDescription"] + .as_str() + .unwrap() + .to_string(); + + assert_eq!(5, report["Stacktrace"].as_array().unwrap().iter().count()); + assert_eq!(severity_type, "NOT_EXPLOITABLE"); + assert_eq!(severity_desc, "Error"); + assert!(report["CrashLine"] + .as_str() + .unwrap() + .contains("test_casr_js_jazzer.js:3:15")); + } else { + panic!("Couldn't parse json report file."); + } +} + +#[test] +#[cfg(target_arch = "x86_64")] +fn test_casr_js_native() { + // JS C extension test + // Copy files to tmp dir + let work_dir = abs_path("tests/casr_tests/js"); + let test_dir = abs_path("tests/tmp_tests_casr/test_casr_js_native"); + let _ = std::fs::remove_dir_all(&test_dir); + + let output = Command::new("cp") + .args(["-r", &work_dir, &test_dir]) + .output() + .expect("failed to copy dir"); + + assert!( + output.status.success(), + "Stdout {}.\n Stderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + + let paths = [ + abs_path("tests"), + abs_path("tests/tmp_tests_casr/test_casr_js_native"), + abs_path("tests/tmp_tests_casr/test_casr_js_native/test_casr_js_native.js"), + ]; + + let Ok(npm_path) = which::which("npm") else { + panic!("No npm is found."); + }; + + let mut npm = Command::new(&npm_path) + .stdin(Stdio::piped()) + .stdout(Stdio::null()) + .current_dir(&paths[1]) + .env("CC", "clang") + .env("CXX", "clang++") + .arg("init") + .arg("-y") + .spawn() + .expect("failed to run npm init"); + + let mut stdin = npm.stdin.take().expect("failed to open stdin"); + stdin + .write_all("\n\n\n\n\n\n\n\n".as_bytes()) + .expect("failed to write to stdin"); + + npm.wait().expect("failed to run npm init"); + + let npm = Command::new("bash") + .current_dir(&paths[1]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .arg("-c") + .arg(format!( + "{} install node-addon-api bindings", + &npm_path.display() + )) + .status() + .expect("failed to add node-addon-api bindings"); + assert!(npm.success()); + + let npm = Command::new("bash") + .current_dir(&paths[1]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .env("CC", "clang") + .env("CXX", "clang++") + .arg("-c") + .arg(format!("{} install {}", &npm_path.display(), &paths[1])) + .status() + .expect("failed to run npm install"); + assert!(npm.success()); + + // Get path of asan lib + let output = Command::new("bash") + .arg("-c") + .arg("clang++ -print-file-name=libclang_rt.asan-x86_64.so") + .output() + .expect("failed to execute clang++"); + + assert!( + output.status.success(), + "Stdout {}.\n Stderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + + let clang_rt = String::from_utf8_lossy(&output.stdout); + assert!(Path::new(&clang_rt.trim().to_string()).exists()); + + let Ok(node_path) = which::which("node") else { + panic!("No node is found."); + }; + + let output = Command::new(*EXE_CASR_JS.read().unwrap()) + .env("ASAN_OPTIONS", "detect_leaks=0,symbolize=1") + .env("LD_PRELOAD", clang_rt.trim()) + .env( + "LD_LIBRARY_PATH", + Path::new(&clang_rt.trim().to_string()) + .parent() + .unwrap_or(Path::new("")), + ) + .args(["--stdout", "--", node_path.to_str().unwrap(), &paths[2]]) + .output() + .expect("failed to start casr-js"); + + assert!( + output.status.success(), + "Stdout {}.\n Stderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + + let report: Result = serde_json::from_slice(&output.stdout); + if let Ok(report) = report { + let severity_type = report["CrashSeverity"]["Type"].as_str().unwrap(); + let severity_desc = report["CrashSeverity"]["ShortDescription"] + .as_str() + .unwrap() + .to_string(); + + assert!(report["Stacktrace"].as_array().unwrap().iter().count() >= 4); + assert_eq!(severity_type, "EXPLOITABLE"); + assert_eq!(severity_desc, "stack-buffer-overflow(write)"); + assert!(report["CrashLine"] + .as_str() + .unwrap() + .contains("native.cpp:9:13")); + } else { + panic!("Couldn't parse json report file."); + } +} + +#[test] +#[cfg(target_arch = "x86_64")] +fn test_casr_js_native_jsfuzz() { + // JS jsfuzz C extension test + // Copy files to tmp dir + let work_dir = abs_path("tests/casr_tests/js"); + let test_dir = abs_path("tests/tmp_tests_casr/test_casr_js_native_jsfuzz"); + let _ = std::fs::remove_dir_all(&test_dir); + + let output = Command::new("cp") + .args(["-r", &work_dir, &test_dir]) + .output() + .expect("failed to copy dir"); + + assert!( + output.status.success(), + "Stdout {}.\n Stderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + + let paths = [ + abs_path("tests"), + abs_path("tests/tmp_tests_casr/test_casr_js_native_jsfuzz"), + "tests/tmp_tests_casr/test_casr_js_native_jsfuzz/test_casr_js_native_jsfuzz.js".to_string(), + ]; + + let Ok(npm_path) = which::which("npm") else { + panic!("No npm is found."); + }; + + let mut npm = Command::new(&npm_path) + .stdin(Stdio::piped()) + .stdout(Stdio::null()) + .current_dir(&paths[1]) + .env("CC", "clang") + .env("CXX", "clang++") + .arg("init") + .arg("-y") + .spawn() + .expect("failed to run npm init"); + + let mut stdin = npm.stdin.take().expect("failed to open stdin"); + stdin + .write_all("\n\n\n\n\n\n\n\n".as_bytes()) + .expect("failed to write to stdin"); + + npm.wait().expect("failed to run npm init"); + + let npm = Command::new("bash") + .current_dir(&paths[1]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .arg("-c") + .arg(format!( + "{} install node-addon-api bindings", + &npm_path.display() + )) + .status() + .expect("failed to add node-addon-api bindings"); + assert!(npm.success()); + + let npm = Command::new("bash") + .current_dir(&paths[1]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .env("CC", "clang") + .env("CXX", "clang++") + .arg("-c") + .arg(format!("{} install {}", &npm_path.display(), &paths[1])) + .status() + .expect("failed to run npm install"); + assert!(npm.success()); + + // Get path of asan lib + let output = Command::new("bash") + .arg("-c") + .arg("clang++ -print-file-name=libclang_rt.asan-x86_64.so") + .output() + .expect("failed to execute clang++"); + + assert!( + output.status.success(), + "Stdout {}.\n Stderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + + let clang_rt = String::from_utf8_lossy(&output.stdout); + assert!(Path::new(&clang_rt.trim().to_string()).exists()); + + let Ok(jsfuzz_path) = which::which("jsfuzz") else { + panic!("No jsfuzz is found."); + }; + + let output = Command::new(*EXE_CASR_JS.read().unwrap()) + .env("ASAN_OPTIONS", "detect_leaks=0,symbolize=1") + .env("LD_PRELOAD", clang_rt.trim()) + .env( + "LD_LIBRARY_PATH", + Path::new(&clang_rt.trim().to_string()) + .parent() + .unwrap_or(Path::new("")), + ) + .args(["--stdout", "--", jsfuzz_path.to_str().unwrap(), &paths[2]]) + .output() + .expect("failed to start casr-js"); + + assert!( + output.status.success(), + "Stdout {}.\n Stderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + + let report: Result = serde_json::from_slice(&output.stdout); + if let Ok(report) = report { + let severity_type = report["CrashSeverity"]["Type"].as_str().unwrap(); + let severity_desc = report["CrashSeverity"]["ShortDescription"] + .as_str() + .unwrap() + .to_string(); + + assert!(report["Stacktrace"].as_array().unwrap().iter().count() >= 4); + assert_eq!(severity_type, "EXPLOITABLE"); + assert_eq!(severity_desc, "stack-buffer-overflow(write)"); + assert!(report["CrashLine"] + .as_str() + .unwrap() + .contains("native.cpp:9:13")); + } else { + panic!("Couldn't parse json report file."); + } +} + +#[test] +#[cfg(target_arch = "x86_64")] +fn test_casr_js_native_jazzer() { + // JS jsfuzz C extension test + // Copy files to tmp dir + let work_dir = abs_path("tests/casr_tests/js"); + let test_dir = abs_path("tests/tmp_tests_casr/test_casr_js_native_jazzer"); + let _ = std::fs::remove_dir_all(&test_dir); + + let output = Command::new("cp") + .args(["-r", &work_dir, &test_dir]) + .output() + .expect("failed to copy dir"); + + assert!( + output.status.success(), + "Stdout {}.\n Stderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + + let paths = [ + abs_path("tests"), + abs_path("tests/tmp_tests_casr/test_casr_js_native_jazzer"), + abs_path("tests/tmp_tests_casr/test_casr_js_native_jazzer/test_casr_js_native_jazzer.js"), + ]; + + let Ok(npm_path) = which::which("npm") else { + panic!("No npm is found."); + }; + + let mut npm = Command::new(&npm_path) + .stdin(Stdio::piped()) + .stdout(Stdio::null()) + .current_dir(&paths[1]) + .env("CC", "clang") + .env("CXX", "clang++") + .arg("init") + .arg("-y") + .spawn() + .expect("failed to run npm init"); + + let mut stdin = npm.stdin.take().expect("failed to open stdin"); + stdin + .write_all("\n\n\n\n\n\n\n\n".as_bytes()) + .expect("failed to write to stdin"); + + npm.wait().expect("failed to run npm init"); + + let npm = Command::new("bash") + .current_dir(&paths[1]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .arg("-c") + .arg(format!( + "{} install node-addon-api bindings", + &npm_path.display() + )) + .status() + .expect("failed to add node-addon-api bindings"); + assert!(npm.success()); + + let npm = Command::new("bash") + .current_dir(&paths[1]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .env("CC", "clang") + .env("CXX", "clang++") + .arg("-c") + .arg(format!("{} install {}", &npm_path.display(), &paths[1])) + .status() + .expect("failed to run npm install"); + assert!(npm.success()); + + // Get path of asan lib + let output = Command::new("bash") + .arg("-c") + .arg("clang++ -print-file-name=libclang_rt.asan-x86_64.so") + .output() + .expect("failed to execute clang++"); + + assert!( + output.status.success(), + "Stdout {}.\n Stderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + + let clang_rt = String::from_utf8_lossy(&output.stdout); + assert!(Path::new(&clang_rt.trim().to_string()).exists()); + + let Ok(npx_path) = which::which("npx") else { + panic!("No npx is found."); + }; + + let output = Command::new(*EXE_CASR_JS.read().unwrap()) + .env("ASAN_OPTIONS", "detect_leaks=0,symbolize=1") + .env("LD_PRELOAD", clang_rt.trim()) + .env( + "LD_LIBRARY_PATH", + Path::new(&clang_rt.trim().to_string()) + .parent() + .unwrap_or(Path::new("")), + ) + .args([ + "--stdout", + "--", + npx_path.to_str().unwrap(), + "jazzer", + &paths[2], + ]) + .output() + .expect("failed to start casr-js"); + + assert!( + output.status.success(), + "Stdout {}.\n Stderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + + let report: Result = serde_json::from_slice(&output.stdout); + if let Ok(report) = report { + let severity_type = report["CrashSeverity"]["Type"].as_str().unwrap(); + let severity_desc = report["CrashSeverity"]["ShortDescription"] + .as_str() + .unwrap() + .to_string(); + + assert!(report["Stacktrace"].as_array().unwrap().iter().count() >= 4); + assert_eq!(severity_type, "EXPLOITABLE"); + assert_eq!(severity_desc, "stack-buffer-overflow(write)"); + assert!(report["CrashLine"] + .as_str() + .unwrap() + .contains("native.cpp:9:13")); + } else { + panic!("Couldn't parse json report file."); + } +} + +// Jsfuzz is available only in very old version and it behaves very strangely. +// Launching jsfuzz on a simple example doesn't work correctly, so this test +// also produces very strange results. +// There are problems in updating jsfuzz to the newer version due to its moving +// to gitlab (they have several open issues about it). +#[test] +#[cfg(target_arch = "x86_64")] +fn test_casr_libfuzzer_jsfuzz() { + let test_dir = abs_path("tests/tmp_tests_casr/test_casr_libfuzzer_jsfuzz"); + let _ = std::fs::remove_dir_all(test_dir); + let paths = [ + "tests/casr_tests/js/test_casr_libfuzzer_jsfuzz.js".to_string(), + abs_path("tests/tmp_tests_casr/test_casr_libfuzzer_jsfuzz/crashes"), + abs_path("tests/tmp_tests_casr/test_casr_libfuzzer_jsfuzz/casr_out"), + "tests/tmp_tests_casr/test_casr_libfuzzer_jsfuzz/test_casr_libfuzzer_jsfuzz.js".to_string(), + ]; + + // Create crashes dir + let output = Command::new("mkdir") + .args(["-p", &paths[1]]) + .output() + .expect("failed to create dir"); + assert!( + output.status.success(), + "Stdout {}.\n Stderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + + let _ = fs::copy(&paths[0], &paths[3]); + + let Ok(jsfuzz_path) = which::which("jsfuzz") else { + panic!("No jsfuzz is found."); + }; + + Command::new("unzip") + .arg(abs_path("tests/casr_tests/js/crashes.zip")) + .args(["-d", &paths[1]]) + .stdout(Stdio::null()) + .status() + .expect("failed to unzip crashes.zip"); + + let bins = Path::new(*EXE_CASR_LIBFUZZER.read().unwrap()) + .parent() + .unwrap(); + let mut cmd = Command::new(*EXE_CASR_LIBFUZZER.read().unwrap()); + cmd.args([ + "-i", + &paths[1], + "-o", + &paths[2], + "--", + jsfuzz_path.to_str().unwrap(), + &paths[3], + ]) + .env( + "PATH", + format!("{}:{}", bins.display(), std::env::var("PATH").unwrap()), + ); + let output = cmd.output().expect("failed to start casr-libfuzzer"); + + assert!( + output.status.success(), + "Stdout {}.\n Stderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + let err = String::from_utf8_lossy(&output.stderr); + + assert!(!err.is_empty()); + + assert!(err.contains("NOT_EXPLOITABLE")); + assert!(err.contains("Error")); + assert!(err.contains("test_casr_libfuzzer_jsfuzz.js")); + + let re = Regex::new(r"Number of reports after deduplication: (?P\d+)").unwrap(); + let unique_cnt = re + .captures(&err) + .unwrap() + .name("unique") + .map(|x| x.as_str()) + .unwrap() + .parse::() + .unwrap(); + + assert_eq!(unique_cnt, 1, "Invalid number of deduplicated reports"); +} + +#[test] +#[cfg(target_arch = "x86_64")] +fn test_casr_libfuzzer_jazzer_js() { + use std::collections::HashMap; + + let test_dir = abs_path("tests/tmp_tests_casr/test_casr_libfuzzer_jazzer_js"); + let _ = std::fs::remove_dir_all(test_dir); + let paths = [ + abs_path("tests/casr_tests/js/test_casr_libfuzzer_jazzer_js.js"), + abs_path("tests/tmp_tests_casr/test_casr_libfuzzer_jazzer_js/crashes"), + abs_path("tests/tmp_tests_casr/test_casr_libfuzzer_jazzer_js/casr_out"), + abs_path( + "tests/tmp_tests_casr/test_casr_libfuzzer_jazzer_js/test_casr_libfuzzer_jazzer_js.js", + ), + ]; + + // Create crashes dir + let output = Command::new("mkdir") + .args(["-p", &paths[1]]) + .output() + .expect("failed to create dir"); + assert!( + output.status.success(), + "Stdout {}.\n Stderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + + let _ = fs::copy(&paths[0], &paths[3]); + + let Ok(npx_path) = which::which("npx") else { + panic!("No npx is found."); + }; + + Command::new("unzip") + .arg(abs_path("tests/casr_tests/js/crashes.zip")) + .args(["-d", &paths[1]]) + .stdout(Stdio::null()) + .status() + .expect("failed to unzip crashes.zip"); + + let bins = Path::new(*EXE_CASR_LIBFUZZER.read().unwrap()) + .parent() + .unwrap(); + let mut cmd = Command::new(*EXE_CASR_LIBFUZZER.read().unwrap()); + cmd.args([ + "-i", + &paths[1], + "-o", + &paths[2], + "--", + npx_path.to_str().unwrap(), + "jazzer", + &paths[3], + ]) + .env( + "PATH", + format!("{}:{}", bins.display(), std::env::var("PATH").unwrap()), + ); + let output = cmd.output().expect("failed to start casr-libfuzzer"); + + assert!( + output.status.success(), + "Stdout {}.\n Stderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + let err = String::from_utf8_lossy(&output.stderr); + + assert!(!err.is_empty()); + + assert!(err.contains("NOT_EXPLOITABLE")); + assert!(err.contains("TypeError")); + assert!(err.contains("ReferenceError")); + assert!(err.contains("RangeError")); + assert!(err.contains("test_casr_libfuzzer_jazzer_js.js")); + + let re = Regex::new(r"Number of reports after deduplication: (?P\d+)").unwrap(); + let unique_cnt = re + .captures(&err) + .unwrap() + .name("unique") + .map(|x| x.as_str()) + .unwrap() + .parse::() + .unwrap(); + + assert_eq!(unique_cnt, 3, "Invalid number of deduplicated reports"); + + let re = Regex::new(r"Number of clusters: (?P\d+)").unwrap(); + let clusters_cnt = re + .captures(&err) + .unwrap() + .name("clusters") + .map(|x| x.as_str()) + .unwrap() + .parse::() + .unwrap(); + + assert_eq!(clusters_cnt, 1, "Invalid number of clusters"); + + let mut storage: HashMap = HashMap::new(); + for entry in fs::read_dir(&paths[2]).unwrap() { + let e = entry.unwrap().path(); + let fname = e.file_name().unwrap().to_str().unwrap(); + if fname.starts_with("cl") && e.is_dir() { + for file in fs::read_dir(e).unwrap() { + let mut e = file.unwrap().path(); + if e.is_file() && e.extension().is_some() && e.extension().unwrap() == "casrep" { + e = e.with_extension(""); + } + let fname = e.file_name().unwrap().to_str().unwrap(); + if let Some(v) = storage.get_mut(fname) { + *v += 1; + } else { + storage.insert(fname.to_string(), 1); + } + } + } + } + + assert!(storage.values().all(|x| *x > 1)); +} + +#[test] +#[cfg(target_arch = "x86_64")] +fn test_casr_libfuzzer_jazzer_js_xml2js() { + use std::collections::HashMap; + + let test_dir = abs_path("tests/tmp_tests_casr/test_casr_libfuzzer_jazzer_js_xml2js"); + let _ = std::fs::remove_dir_all(&test_dir); + let paths = [ + abs_path("tests/casr_tests/js/test_casr_libfuzzer_jazzer_js_xml2js.js"), + abs_path("tests/tmp_tests_casr/test_casr_libfuzzer_jazzer_js_xml2js/crashes"), + abs_path("tests/tmp_tests_casr/test_casr_libfuzzer_jazzer_js_xml2js/casr_out"), + abs_path( + "tests/tmp_tests_casr/test_casr_libfuzzer_jazzer_js_xml2js/test_casr_libfuzzer_jazzer_js_xml2js.js", + ), + ]; + + // Create crashes dir + let output = Command::new("mkdir") + .args(["-p", &paths[1]]) + .output() + .expect("failed to create dir"); + assert!( + output.status.success(), + "Stdout: {}.\n Stderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + + let _ = fs::copy(&paths[0], &paths[3]); + + Command::new("unzip") + .arg(abs_path("tests/casr_tests/js/xml2js.zip")) + .args(["-d", &paths[1]]) + .stdout(Stdio::null()) + .status() + .expect("failed to unzip xml2js.zip"); + + let Ok(npm_path) = which::which("npm") else { + panic!("No npm is found."); + }; + + let mut npm = Command::new(&npm_path) + .stdin(Stdio::piped()) + .stdout(Stdio::null()) + .current_dir(&test_dir) + .env("CC", "clang") + .env("CXX", "clang++") + .arg("init") + .arg("-y") + .spawn() + .expect("failed to run npm init"); + + let mut stdin = npm.stdin.take().expect("failed to open stdin"); + stdin + .write_all("\n\n\n\n\n\n\n\n".as_bytes()) + .expect("failed to write to stdin"); + + npm.wait().expect("failed to run npm init"); + + let npm = Command::new("bash") + .current_dir(&test_dir) + .arg("-c") + .arg(format!("{} install xml2js", &npm_path.display())) + .output() + .expect("failed to install xml2js"); + assert!( + npm.status.success(), + "Stdout: {}.\n Stderr: {}", + String::from_utf8_lossy(&npm.stdout), + String::from_utf8_lossy(&npm.stderr) + ); + + let Ok(npx_path) = which::which("npx") else { + panic!("No npx is found."); + }; + + let bins = Path::new(*EXE_CASR_LIBFUZZER.read().unwrap()) + .parent() + .unwrap(); + let mut cmd = Command::new(*EXE_CASR_LIBFUZZER.read().unwrap()); + cmd.args([ + "-i", + &paths[1], + "-o", + &paths[2], + "--", + npx_path.to_str().unwrap(), + "jazzer", + &paths[3], + ]) + .env( + "PATH", + format!("{}:{}", bins.display(), std::env::var("PATH").unwrap()), + ); + let output = cmd.output().expect("failed to start casr-libfuzzer"); + + assert!( + output.status.success(), + "Stdout {}.\n Stderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + let err = String::from_utf8_lossy(&output.stderr); + + assert!(!err.is_empty()); + + assert!(err.contains("NOT_EXPLOITABLE")); + assert!(err.contains("TypeError")); + assert!(err.contains("xml2js/lib/parser.js")); + assert!(err.contains("Error")); + assert!(err.contains("sax/lib/sax.js")); + + let re = Regex::new(r"Number of reports after deduplication: (?P\d+)").unwrap(); + let unique_cnt = re + .captures(&err) + .unwrap() + .name("unique") + .map(|x| x.as_str()) + .unwrap() + .parse::() + .unwrap(); + + assert_eq!(unique_cnt, 3, "Invalid number of deduplicated reports"); + + let re = Regex::new(r"Number of clusters: (?P\d+)").unwrap(); + let clusters_cnt = re + .captures(&err) + .unwrap() + .name("clusters") + .map(|x| x.as_str()) + .unwrap() + .parse::() + .unwrap(); + + assert_eq!(clusters_cnt, 2, "Invalid number of clusters"); + + let mut storage: HashMap = HashMap::new(); + for entry in fs::read_dir(&paths[2]).unwrap() { + let e = entry.unwrap().path(); + let fname = e.file_name().unwrap().to_str().unwrap(); + if fname.starts_with("cl") && e.is_dir() { + for file in fs::read_dir(e).unwrap() { + let mut e = file.unwrap().path(); + if e.is_file() && e.extension().is_some() && e.extension().unwrap() == "casrep" { + e = e.with_extension(""); + } + let fname = e.file_name().unwrap().to_str().unwrap(); + if let Some(v) = storage.get_mut(fname) { + *v += 1; + } else { + storage.insert(fname.to_string(), 1); + } + } + } + } + + assert!(storage.values().all(|x| *x > 1)); +} diff --git a/docs/usage.md b/docs/usage.md index 2646d9c2..b36e8d16 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -6,14 +6,17 @@ ASAN reports or `casr-ubsan` to analyze UBSAN reports. Try `casr-gdb` to get reports from gdb. Use `casr-python` to analyze python reports and get report from [Atheris](https://github.com/google/atheris). Use `casr-java` to analyze java reports and get report from -[Jazzer](https://github.com/CodeIntelligenceTesting/jazzer). `casr-afl` is used +[Jazzer](https://github.com/CodeIntelligenceTesting/jazzer). Use `casr-js` +to analyze JavaScript reports and get report from +[Jazzer.js](https://github.com/CodeIntelligenceTesting/jazzer.js) or +[jsfuzz](https://github.com/fuzzitdev/jsfuzz). `casr-afl` is used to triage crashes found by [AFL++](https://github.com/AFLplusplus/AFLplusplus). `casr-libfuzzer` can triage crashes found by [libFuzzer](https://www.llvm.org/docs/LibFuzzer.html) (libFuzzer, go-fuzz, -Atheris, Jazzer). `casr-dojo` allows to upload new and unique CASR reports to -[DefectDojo](https://github.com/DefectDojo/django-DefectDojo). `casr-cli` is -meant to provide TUI for viewing reports and converting them into SARIF report. -Reports triage (deduplication, clustering) is done by `casr-cluster`. +Atheris, Jazzer, Jazzer.js, jsfuzz). `casr-dojo` allows to upload new and +unique CASR reports to [DefectDojo](https://github.com/DefectDojo/django-DefectDojo). +`casr-cli` is meant to provide TUI for viewing reports and converting them into +SARIF report. Reports triage (deduplication, clustering) is done by `casr-cluster`. ## casr-gdb @@ -165,6 +168,31 @@ Run casr-java: $ casr-java -o java.casrep -- java casr/tests/casr_tests/java/Test1.java +## casr-js + +Create CASR reports (.casrep) from JavaScript reports + + Usage: casr-js [OPTIONS] <--stdout|--output > [-- ...] + + Arguments: + [ARGS]... Add "-- " to run + + Options: + -o, --output Path to save report. Path can be a directory, then report + name is generated + --stdout Print CASR report to stdout + --stdin Stdin file for program + -t, --timeout Timeout (in seconds) for target execution, 0 value means + that timeout is disabled [default: 0] + --ignore File with regular expressions for functions and file paths + that should be ignored + -h, --help Print help + -V, --version Print version + +Run casr-js: + + $ casr-js -o js.casrep -- node casr/tests/casr_tests/js/test_casr_js.js + ## casr-core Analyze coredump for security goals and provide detailed report with severity estimation @@ -345,7 +373,7 @@ Convert reports to SARIF report: $ casr-cli --sarif out.sarif --tool libfuzzer --source-root /xlnt casr/tests/casr_tests/casrep/test_clustering_san -### Screnshots +### Screenshots ![casrep](/docs/images/casr_report.png) @@ -463,7 +491,8 @@ variable may be used by [casr-san](#casr-san). ## casr-libfuzzer -Triage crashes found by libFuzzer based fuzzer (C/C++/go-fuzz/Atheris/Jazzer) +Triage crashes found by libFuzzer based fuzzer +(C/C++/go-fuzz/Atheris/Jazzer/Jazzer.js/jsfuzz) Usage: casr-libfuzzer [OPTIONS] --output [-- ...] @@ -498,7 +527,8 @@ Triage crashes found by libFuzzer based fuzzer (C/C++/go-fuzz/Atheris/Jazzer) `casr-libfuzzer` provides integration with [libFuzzer](https://www.llvm.org/docs/LibFuzzer.html) based fuzzers (C/C++/[go-fuzz](https://github.com/dvyukov/go-fuzz)/[Atheris](https://github.com/google/atheris)/ -[Jazzer](https://github.com/CodeIntelligenceTesting/jazzer)). +[Jazzer](https://github.com/CodeIntelligenceTesting/jazzer)/[Jazzer.js](https://github.com/CodeIntelligenceTesting/jazzer.js)/ +[jsfuzz](https://github.com/fuzzitdev/jsfuzz)). It is pretty much like `casr-afl`. libFuzzer example: @@ -513,8 +543,16 @@ binary with `casr-gdb`: Atheris example: $ unzip casr/tests/casr_tests/python/ruamel.zip - $ cp casr/tests/casr_tests/python/yaml_fuzzer.py . - $ casr-libfuzzer -i casr/tests/casr_tests/casrep/atheris_crashes_ruamel_yaml -o casr/tests/tmp_tests_casr/casr_libfuzzer_atheris_out -- ./yaml_fuzzer.py + $ casr-libfuzzer -i casr/tests/casr_tests/casrep/atheris_crashes_ruamel_yaml -o casr/tests/tmp_tests_casr/casr_libfuzzer_atheris_out -- casr/tests/casr_tests/python/yaml_fuzzer.py + +Jazzer.js example (Jazzer.js installation [guide](https://github.com/CodeIntelligenceTesting/jazzer.js#quickstart)): + + $ unzip casr/tests/casr_tests/js/xml2js.zip -d xml2js + $ mkdir -p casr/tests/tmp_tests_casr/xml2js_fuzzer_out + $ cp casr/tests/casr_tests/js/test_casr_libfuzzer_jazzer_js_xml2js.js casr/tests/tmp_tests_casr/xml2js_fuzzer_out/xml2js_fuzzer.js + $ sudo npm install xml2js + $ sudo npm install --save-dev @jazzer.js/core + $ casr-libfuzzer -i ./xml2js -o casr/tests/tmp_tests_casr/xml2js_fuzzer_out/out -- npx jazzer casr/tests/tmp_tests_casr/xml2js_fuzzer_out/xml2js_fuzzer.js You can set environment variable `RUST_BACKTRACE=(1|full)` for `casr-libfuzzer`. This variable may be used by [casr-san](#casr-san). diff --git a/libcasr/src/constants.rs b/libcasr/src/constants.rs index 7be1f1c9..17feb4ed 100644 --- a/libcasr/src/constants.rs +++ b/libcasr/src/constants.rs @@ -244,6 +244,10 @@ pub const STACK_FRAME_FILEPATH_IGNORE_REGEXES_JS: &[&str] = &[ // JS internal modules r"^(|node:)internal/?", r"^(|node:)events/?", + // Jazzer.js internal modules + r"node_modules/@jazzer.js", + // jsfuzz internal modules + r"node_modules/jsfuzz", ]; /// Regular expressions for paths to python files that should be ignored. diff --git a/libcasr/src/js.rs b/libcasr/src/js.rs index 3ea798f0..31b5167e 100644 --- a/libcasr/src/js.rs +++ b/libcasr/src/js.rs @@ -241,53 +241,59 @@ mod tests { at new Uint8Array () at Object.decode (/fuzz/node_modules/jpeg-js/lib/decoder.js:1110:13) at fuzz (/fuzz/FuzzTarget.js:6:14) - at result (/fuzz/node_modules/@jazzer.js/core/core.ts:335:15) + at result (/fuzz/node_modules/fuzzer/core/core.ts:335:15) at Worker.fuzz [as fn] (/home/user/test_js_stacktrace/main.js:1:2017) - at process. (/home/user/.nvm/versions/node/v16.15.1/lib/node_modules/jsfuzz/build/src/worker.js:55:30) + at process. (/home/user/.nvm/versions/node/v16.15.1/lib/node_modules/fuzzer/build/src/worker.js:55:30) at process.emit (node:events:527:28) at at bootstrap_node.js:609:3 at file:///home/user/node/offset.js:3:37 + at fuzz (/home/user/.nvm/versions/node/v16.15.1/lib/node_modules/jsfuzz/build/src/worker.js:55:30) at async Loader.import (internal/modules/esm/loader.js:178:24) at eval (eval at (eval at g (/fuzz/FuzzTarget.js:7:7)), :4:23) at eval (eval at (file:///home/user/node/offset.js:3:3), :3:7) at eval (eval at g (/fuzz/FuzzTarget.js:7:7), :8:13) at eval (/.svelte-kit/runtime/components/layout.svelte:8:41) + at fuzz (/home/user/.nvm/versions/node/v16.15.1/lib/node_modules/@jazzer.js/build/src/worker.js:55:30) at handler (:3:10) Uncaught ReferenceError: var is not defined at Object.decode (/fuzz/node_modules/jpeg-js/lib/decoder.js:1110:13) at fuzz (/fuzz/FuzzTarget.js:6:14) - at result (/fuzz/node_modules/@jazzer.js/core/core.ts:335:15) + at result (/fuzz/node_modules/fuzzer/core/core.ts:335:15) at Worker.fuzz [as fn] (/home/user/test_js_stacktrace/main.js:1:2017) - at process. (/home/user/.nvm/versions/node/v16.15.1/lib/node_modules/jsfuzz/build/src/worker.js:55:30) + at process. (/home/user/.nvm/versions/node/v16.15.1/lib/node_modules/fuzzer/build/src/worker.js:55:30) at process.emit (node:events:527:28) at at bootstrap_node.js:609:3 at file:///home/user/node/offset.js:3:37 + at fuzz (/home/user/.nvm/versions/node/v16.15.1/lib/node_modules/jsfuzz/build/src/worker.js:55:30) at async Loader.import (internal/modules/esm/loader.js:178:24) at eval (eval at (eval at g (/fuzz/FuzzTarget.js:7:7)), :4:23) at eval (eval at (file:///home/user/node/offset.js:3:3), :3:7) at eval (eval at g (/fuzz/FuzzTarget.js:7:7), :8:13) at eval (/.svelte-kit/runtime/components/layout.svelte:8:41) + at fuzz (/home/user/.nvm/versions/node/v16.15.1/lib/node_modules/@jazzer.js/build/src/worker.js:55:30) at handler (:3:10)"#; let raw_stacktrace = &[ "at new Uint8Array ()", "at Object.decode (/fuzz/node_modules/jpeg-js/lib/decoder.js:1110:13)", "at fuzz (/fuzz/FuzzTarget.js:6:14)", - "at result (/fuzz/node_modules/@jazzer.js/core/core.ts:335:15)", + "at result (/fuzz/node_modules/fuzzer/core/core.ts:335:15)", "at Worker.fuzz [as fn] (/home/user/test_js_stacktrace/main.js:1:2017)", - "at process. (/home/user/.nvm/versions/node/v16.15.1/lib/node_modules/jsfuzz/build/src/worker.js:55:30)", + "at process. (/home/user/.nvm/versions/node/v16.15.1/lib/node_modules/fuzzer/build/src/worker.js:55:30)", "at process.emit (node:events:527:28)", "at ", "at bootstrap_node.js:609:3", "at file:///home/user/node/offset.js:3:37", + "at fuzz (/home/user/.nvm/versions/node/v16.15.1/lib/node_modules/jsfuzz/build/src/worker.js:55:30)", "at async Loader.import (internal/modules/esm/loader.js:178:24)", "at eval (eval at (eval at g (/fuzz/FuzzTarget.js:7:7)), :4:23)", "at eval (eval at (file:///home/user/node/offset.js:3:3), :3:7)", "at eval (eval at g (/fuzz/FuzzTarget.js:7:7), :8:13)", "at eval (/.svelte-kit/runtime/components/layout.svelte:8:41)", + "at fuzz (/home/user/.nvm/versions/node/v16.15.1/lib/node_modules/@jazzer.js/build/src/worker.js:55:30)", "at handler (:3:10)", ]; @@ -338,7 +344,7 @@ Uncaught ReferenceError: var is not defined assert_eq!(stacktrace[1].function, "fuzz".to_string()); assert_eq!( stacktrace[2].debug.file, - "/fuzz/node_modules/@jazzer.js/core/core.ts".to_string() + "/fuzz/node_modules/fuzzer/core/core.ts".to_string() ); assert_eq!(stacktrace[2].debug.line, 335); assert_eq!(stacktrace[2].debug.column, 15); @@ -352,7 +358,7 @@ Uncaught ReferenceError: var is not defined assert_eq!(stacktrace[3].function, "Worker.fuzz [as fn]".to_string()); assert_eq!( stacktrace[4].debug.file, - "/home/user/.nvm/versions/node/v16.15.1/lib/node_modules/jsfuzz/build/src/worker.js" + "/home/user/.nvm/versions/node/v16.15.1/lib/node_modules/fuzzer/build/src/worker.js" .to_string() ); assert_eq!(stacktrace[4].debug.line, 55);