From 56f3e7bd89e90bba6c6e3ad8680567dbfcc8b9ac Mon Sep 17 00:00:00 2001 From: Darya Parygina Date: Wed, 8 Nov 2023 20:59:20 +0300 Subject: [PATCH] Add tests for casr-js --- .github/workflows/amd64.yml | 9 +- .github/workflows/coverage.yaml | 9 +- .gitignore | 1 + casr/tests/casr_tests/js/binding.gyp | 12 + casr/tests/casr_tests/js/crash | 1 + 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 + casr/tests/tests.rs | 526 ++++++++++++++++++ 13 files changed, 654 insertions(+), 2 deletions(-) create mode 100644 casr/tests/casr_tests/js/binding.gyp create mode 100644 casr/tests/casr_tests/js/crash 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 diff --git a/.github/workflows/amd64.yml b/.github/workflows/amd64.yml index a405a1ba..2a5cd502 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 + npm install -g jsfuzz + 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 98f06dba..3e4c2134 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 + npm install -g jsfuzz + 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 496cbf91..a89614ff 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ Cargo.lock *.swp node_modules */node_modules/* +.vscode diff --git a/casr/tests/casr_tests/js/binding.gyp b/casr/tests/casr_tests/js/binding.gyp new file mode 100644 index 00000000..ef97f6d0 --- /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 -I/usr/lib/llvm-10/lib/clang/10.0.0/lib/linux/ -lclang_rt.fuzzer-x86_64" ], + "cflags_cc": [ "-fexceptions -fsanitize=address,fuzzer-no-link -O0 -g -fPIC -I/usr/lib/llvm-10/lib/clang/10.0.0/lib/linux/ -lclang_rt.fuzzer-x86_64" ], + "include_dirs" : [" +#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..4523c63a --- /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); +}; \ No newline at end of file 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/tests.rs b/casr/tests/tests.rs index 0e080722..704aa3f5 100644 --- a/casr/tests/tests.rs +++ b/casr/tests/tests.rs @@ -20,6 +20,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")); } @@ -4562,3 +4563,528 @@ 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 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."); + } + let _ = std::fs::remove_dir_all(&test_dir); +} + +#[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 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 _ = fs::copy( + abs_path("tests/casr_tests/js/crash"), + "tests/tmp_tests_casr/test_casr_js_jsfuzz/corpus/crash", + ); + + 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."); + } + let _ = std::fs::remove_dir_all(&test_dir); +} + +#[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 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 _ = fs::copy( + abs_path("tests/casr_tests/js/crash"), + "tests/tmp_tests_casr/test_casr_js_jazzer/corpus/crash", + ); + + 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."); + } + let _ = std::fs::remove_dir_all(&test_dir); +} + +#[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 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/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("bash") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .arg("-c") + .arg(format!("{} init -w {}", npm_path.display(), &paths[0])) + .spawn() + .expect("failed to run npm init"); + + let mut stdin = npm.stdin.take().expect("failed to open stdin"); + std::thread::spawn(move || { + 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") + .arg("-c") + .arg(format!("{} i node-addon-api bindings", npm_path.display())) + .status() + .expect("failed to add node-addon-api bindings"); + + assert!(npm.success()); + + let npm = Command::new("bash") + .env("CC", "clang") + .env("CXX", "clang++") + .arg("-c") + .arg(format!("{} install {}", npm_path.display(), &paths[0])) + .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[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!(report["Stacktrace"].as_array().unwrap().iter().count() == 9); + 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."); + } + let _ = std::fs::remove_dir_all(&test_dir); +} + +#[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 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/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("bash") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .arg("-c") + .arg(format!("{} init -w {}", npm_path.display(), &paths[0])) + .spawn() + .expect("failed to run npm init"); + + let mut stdin = npm.stdin.take().expect("failed to open stdin"); + std::thread::spawn(move || { + 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") + .arg("-c") + .arg(format!("{} i node-addon-api bindings", npm_path.display())) + .status() + .expect("failed to add node-addon-api bindings"); + + assert!(npm.success()); + + let npm = Command::new("bash") + .env("CC", "clang") + .env("CXX", "clang++") + .arg("-c") + .arg(format!("{} install {}", npm_path.display(), &paths[0])) + .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[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!(report["Stacktrace"].as_array().unwrap().iter().count() == 9); + 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."); + } + let _ = std::fs::remove_dir_all(&test_dir); +} + +#[test] +#[cfg(target_arch = "x86_64")] +fn test_casr_js_native_jazzer() { + // JS Jazzer.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_jazzer"); + + 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/tmp_tests_casr/test_casr_js_native_jazzer"), + "tests/tmp_tests_casr/test_casr_js_native_jazzer/test_casr_js_native_jazzer.js".to_string(), + ]; + + let Ok(npm_path) = which::which("npm") else { + panic!("No npm is found."); + }; + + let mut npm = Command::new("bash") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .arg("-c") + .arg(format!("{} init -w {}", npm_path.display(), &paths[0])) + .spawn() + .expect("failed to run npm init"); + + let mut stdin = npm.stdin.take().expect("failed to open stdin"); + std::thread::spawn(move || { + 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") + .arg("-c") + .arg(format!("{} i node-addon-api bindings", npm_path.display())) + .status() + .expect("failed to add node-addon-api bindings"); + + assert!(npm.success()); + + let npm = Command::new("bash") + .env("CC", "clang") + .env("CXX", "clang++") + .arg("-c") + .arg(format!("{} install {}", npm_path.display(), &paths[0])) + .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[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!(report["Stacktrace"].as_array().unwrap().iter().count() == 9); + 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."); + } + let _ = std::fs::remove_dir_all(&test_dir); +} \ No newline at end of file