Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/amd64.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/coverage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ Cargo.lock
*/Cargo.lock
*/tests/tmp_tests_casr
*.swp
node_modules
*/node_modules/*
.vscode
14 changes: 14 additions & 0 deletions casr/src/bin/casr-cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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"));
}
Expand Down
3 changes: 1 addition & 2 deletions casr/src/bin/casr-cluster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -429,8 +429,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::<u32>("jobs") {
Expand Down
166 changes: 166 additions & 0 deletions casr/src/bin/casr-js.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
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 JS 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 \"-- <path> <arguments>\" to run"),
)
.get_matches();

init_ignored_frames!("js"); //TODO
if let Some(path) = matches.get_one::<PathBuf>("ignore") {
util::add_custom_ignored_frames(path)?;
}
// Get program args.
let argv: Vec<&str> = if let Some(argvs) = matches.get_many::<String>("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::<u64>("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 {
if let Some(fname) = Path::new(argv[0]).file_name() {
let Ok(full_path_to_tool) = which::which(fname) else {
bail!("Could not get the full path of {}", argv[0]);
};
path_to_tool = full_path_to_tool;
let fname = fname.to_string_lossy();
if (fname.ends_with("node") || fname.ends_with("jsfuzz"))
&& !fname.ends_with(".js")
&& argv[1].ends_with(".js")
{
report.executable_path = argv[1].to_string();
} else if argv.len() > 2
&& fname.ends_with("npx")
&& !fname.ends_with(".js")
&& 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<String> = 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)
}
7 changes: 6 additions & 1 deletion casr/src/bin/casr-libfuzzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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") {
Expand Down
12 changes: 12 additions & 0 deletions casr/tests/casr_tests/js/binding.gyp
Original file line number Diff line number Diff line change
@@ -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" : ["<!@(node -p \"require('node-addon-api').include\")"],
"target_name": "native",
"sources": [ "native.cpp" ],
'defines': [ 'NAPI_CPP_EXCEPTIONS' ]
}
]
}
1 change: 1 addition & 0 deletions casr/tests/casr_tests/js/crash
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
20 changes: 20 additions & 0 deletions casr/tests/casr_tests/js/native.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include <napi.h>
#include <stdio.h>

void foo(const Napi::CallbackInfo &info)
{
Napi::Env env = info.Env();
uint8_t buf[] = {1, 2, 3};
Napi::Buffer<uint8_t> arr = Napi::Buffer<uint8_t>::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);
15 changes: 15 additions & 0 deletions casr/tests/casr_tests/js/test_casr_js.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
function bar() {
new Function(`
throw new Error('internal');
`)();
}

function foo() {
bar();
}

function main() {
foo();
}

main()
18 changes: 18 additions & 0 deletions casr/tests/casr_tests/js/test_casr_js_jazzer.js
Original file line number Diff line number Diff line change
@@ -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);
};
19 changes: 19 additions & 0 deletions casr/tests/casr_tests/js/test_casr_js_jsfuzz.js
Original file line number Diff line number Diff line change
@@ -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]);
5 changes: 5 additions & 0 deletions casr/tests/casr_tests/js/test_casr_js_native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env node

const native_lib = require('bindings')('native')

native_lib.foo();
10 changes: 10 additions & 0 deletions casr/tests/casr_tests/js/test_casr_js_native_jazzer.js
Original file line number Diff line number Diff line change
@@ -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);
};
11 changes: 11 additions & 0 deletions casr/tests/casr_tests/js/test_casr_js_native_jsfuzz.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const native_lib = require('bindings')('native')

function fuzz(data) {
native_lib.foo();
}

module.exports = {
fuzz
};

fuzz(process.argv[1]);
Loading