Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Casr-js #176

Merged
merged 23 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from 15 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
26 changes: 20 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,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).
hkctkuy marked this conversation as resolved.
Show resolved Hide resolved

Crash report contains many useful information: severity (like [exploitable](https://github.com/jfoote/exploitable))
for x86, x86\_64, arm32, aarch64, rv32g, rv64g architectures,
Expand All @@ -43,7 +46,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)).
hkctkuy marked this conversation as resolved.
Show resolved Hide resolved
`casr-dojo` allows to upload new and unique CASR reports to
[DefectDojo](https://github.com/DefectDojo/django-DefectDojo) (available with
`dojo` feature).
Expand Down Expand Up @@ -74,6 +78,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.
Expand Down Expand Up @@ -136,6 +141,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
Expand Down Expand Up @@ -175,8 +184,11 @@ 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
SweetVishnya marked this conversation as resolved.
Show resolved Hide resolved

Triage Jazzer.js crashes with casr-libfuzzer:
SweetVishnya marked this conversation as resolved.
Show resolved Hide resolved
$ unzip casr/tests/casr_tests/js/crashes.zip -d crashes
$ casr-libfuzzer -i ./crashes -o casr/tests/tmp_tests_casr/casr_libfuzzer_jazzer_js_out -- npx jazzer casr/tests/casr_tests/js/test_casr_libfuzzer_jazzer_js.js

Upload new and unique CASR reports to
[DefectDojo](https://github.com/DefectDojo/django-DefectDojo):
Expand All @@ -194,7 +206,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`.
Expand All @@ -207,7 +219,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)
anfedotoff marked this conversation as resolved.
Show resolved Hide resolved
/[Jazzer](https://github.com/CodeIntelligenceTesting/jazzer)/[Jazzer.js](https://github.com/CodeIntelligenceTesting/jazzer.js)/
[jsfuzz](https://github.com/fuzzitdev/jsfuzz)),
hkctkuy marked this conversation as resolved.
Show resolved Hide resolved
the pipeline (without `casr-ubsan` and `casr-dojo`) could be done automatically
by `casr-libfuzzer`.

Expand Down
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() {
Avgor46 marked this conversation as resolved.
Show resolved Hide resolved
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
162 changes: 162 additions & 0 deletions casr/src/bin/casr-js.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
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 \"-- <path> <arguments>\" to run"),
)
.get_matches();

init_ignored_frames!("js");
SweetVishnya marked this conversation as resolved.
Show resolved Hide resolved
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() {
SweetVishnya marked this conversation as resolved.
Show resolved Hide resolved
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 == "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<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)) {
anfedotoff marked this conversation as resolved.
Show resolved Hide resolved
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"
SweetVishnya marked this conversation as resolved.
Show resolved Hide resolved
|| 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' ]
}
]
}
Binary file added casr/tests/casr_tests/js/crashes.zip
Binary file not shown.
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()
Loading
Loading