Skip to content

Commit

Permalink
Support C# crash triage for SharpFuzz (#207)
Browse files Browse the repository at this point in the history
  • Loading branch information
headshog authored Apr 3, 2024
1 parent 4a8ad43 commit a680ce5
Show file tree
Hide file tree
Showing 40 changed files with 818 additions and 172 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/amd64.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ jobs:
sudo apt update && sudo apt install -y nodejs
sudo npm install -g jsfuzz
sudo npm install --save-dev @jazzer.js/core
wget -q https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
dpkg -i packages-microsoft-prod.deb && rm packages-microsoft-prod.deb
sudo apt update && sudo apt install -y --no-install-recommends dotnet-sdk-8.0
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 .github/workflows/coverage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ jobs:
sudo apt update && sudo apt install -y nodejs
sudo npm install -g jsfuzz
sudo npm install --save-dev @jazzer.js/core
wget -q https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
dpkg -i packages-microsoft-prod.deb && rm packages-microsoft-prod.deb
sudo apt update && sudo apt install -y --no-install-recommends dotnet-sdk-8.0
curl https://sh.rustup.rs -o rustup.sh && chmod +x rustup.sh && \
./rustup.sh -y && rm rustup.sh
rustup install nightly
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ Cargo.lock
*/target
*/Cargo.lock
*/tests/tmp_tests_casr
*/tests/casr_tests/csharp/*/bin
*/tests/casr_tests/csharp/*/obj
*.swp
node_modules
*/node_modules/*
Expand Down
27 changes: 22 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ java reports and get report from
to analyze JavaScript reports and get report from
[Jazzer.js](https://github.com/CodeIntelligenceTesting/jazzer.js) or
[jsfuzz](https://github.com/fuzzitdev/jsfuzz).
Use `casr-csharp` to analyze C# reports and get report from
[Sharpfuzz](https://github.com/Metalnem/sharpfuzz).

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 @@ -44,7 +46,8 @@ stored in JSON format. `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`.
Triage is based on stack trace comparison from [gdb-command](https://github.com/anfedotoff/gdb-command).
`casr-afl` is used to triage crashes found by [AFL++](https://github.com/AFLplusplus/AFLplusplus).
`casr-afl` is used to triage crashes found by [AFL++](https://github.com/AFLplusplus/AFLplusplus)
and AFL-based fuzzer [Sharpfuzz](https://github.com/Metalnem/sharpfuzz).
`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)
Expand Down Expand Up @@ -81,6 +84,7 @@ and program languages:
* Python
* Java
* JavaScript
* C#

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 @@ -161,6 +165,10 @@ Create report from JavaScript:

$ casr-js -o js.casrep -- node casr/tests/casr_tests/js/test_casr_js.js

Create report from C#:

$ casr-csharp -o csharp.casrep -- dotnet run --project casr/tests/casr_tests/csharp/test_casr_csharp/test_casr_csharp.csproj

View report:

$ casr-cli casr/tests/casr_tests/casrep/test_clustering_san/load_fuzzer_crash-120697a7f5b87c03020f321c8526adf0f4bcc2dc.casrep
Expand Down Expand Up @@ -193,6 +201,15 @@ Triage crashes after AFL++ fuzzing with casr-afl:
$ # You may also additionally generate crash reports for uninstrumented binary with casr-gdb
$ casr-afl -i casr/tests/casr_tests/casrep/afl-out-xlnt -o casr/tests/tmp_tests_casr/casr_afl_out -- /tmp/load_sydr @@

Triage crashes after Sharpfuzz fuzzing with casr-afl:

$ cp -r casr/tests/casr_tests/csharp/test_casr_afl_csharp /tmp/test_casr_afl_csharp
$ cp -r casr/tests/casr_tests/csharp/test_casr_afl_csharp_module /tmp/test_casr_afl_csharp_module
$ dotnet publish /tmp/test_casr_afl_csharp/test_casr_afl_csharp.csproj -c Debug -o /tmp/test_casr_afl_csharp/bin
$ casr-afl -i casr/tests/casr_tests/casrep/afl-out-sharpfuzz -o casr/tests/tmp_tests_casr/casr_afl_csharp_out
$ # You may force your own run arguments using --ignore-cmdline
$ casr-afl --ignore-cmdline -i casr/tests/casr_tests/casrep/afl-out-sharpfuzz -o casr/tests/tmp_tests_casr/casr_afl_csharp_out -- dotnet run --no-build --project /tmp/test_casr_afl_csharp/test_casr_afl_csharp.csproj @@

Triage libFuzzer crashes with casr-libfuzzer:

$ casr-libfuzzer -t 30 -i casr/tests/casr_tests/casrep/libfuzzer_crashes_xlnt -o casr/tests/tmp_tests_casr/casr_libfuzzer_out -- casr/tests/casr_tests/bin/load_fuzzer
Expand Down Expand Up @@ -227,23 +244,23 @@ 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`, `casr-java`, or `casr-js`.
are present), `casr-python`, `casr-java`, `casr-js`, or `casr-csharp`.
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`.
5. View reports from clusters using `casr-cli` or upload them to
[DefectDojo](https://github.com/DefectDojo/django-DefectDojo) with
`casr-dojo`.

If you use [AFL++](https://github.com/AFLplusplus/AFLplusplus), the pipeline
If you use [AFL++](https://github.com/AFLplusplus/AFLplusplus) or AFL-based
fuzzer [Sharpfuzz](https://www.llvm.org/docs/LibFuzzer.html), the pipeline
(without `casr-ubsan` and `casr-dojo`) could be done automatically by
`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)
/[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
[jsfuzz](https://github.com/fuzzitdev/jsfuzz)), the pipeline (without `casr-ubsan` and `casr-dojo`) could be done automatically
by `casr-libfuzzer`.

## Contributing
Expand Down
1 change: 1 addition & 0 deletions casr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ required-features = ["dojo"]

[dev-dependencies]
lazy_static = "1.4"
copy_dir = "0.1.3"
64 changes: 38 additions & 26 deletions casr/src/bin/casr-afl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use std::path::{Path, PathBuf};
fn main() -> Result<()> {
let matches = clap::Command::new("casr-afl")
.version(clap::crate_version!())
.about("Triage crashes found by AFL++")
.about("Triage crashes found by AFL++/Sharpfuzz")
.term_width(90)
.arg(
Arg::new("log-level")
Expand All @@ -40,7 +40,7 @@ fn main() -> Result<()> {
.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..))
.value_parser(clap::value_parser!(u64))
)
.arg(
Arg::new("input")
Expand Down Expand Up @@ -100,23 +100,23 @@ fn main() -> Result<()> {
.required(false)
.num_args(1..)
.last(true)
.help("Add \"-- ./gdb_fuzz_target <arguments>\" to generate additional crash reports with casr-gdb (e.g., test whether program crashes without sanitizers)"),
.help("Add \"-- ./gdb_fuzz_target <arguments>\" to generate additional crash reports with casr-gdb \
(for compiled binaries, e.g., test whether program crashes without sanitizers), \"-- dotnet <arguments>\" \
or \"-- mono <arguments>\" to triage C# crashes with additional options")
)
.get_matches();

// Init log.
util::initialize_logging(&matches);

let casr_san = util::get_path("casr-san")?;
let casr_gdb = util::get_path("casr-gdb")?;

let mut gdb_args = if let Some(argv) = matches.get_many::<String>("ARGS") {
let mut is_casr_gdb = true;
let mut args = if let Some(argv) = matches.get_many::<String>("ARGS") {
argv.cloned().collect()
} else {
Vec::new()
};

if gdb_args.is_empty() && matches.get_flag("ignore-cmdline") {
if args.is_empty() && matches.get_flag("ignore-cmdline") {
bail!("ARGS is empty, but \"ignore-cmdline\" option is provided.");
}

Expand All @@ -130,11 +130,10 @@ fn main() -> Result<()> {

// Get crashes from one node.
let mut crash_info = casr::triage::CrashInfo {
casr_tool: casr_gdb.clone(),
..Default::default()
};
crash_info.target_args = if matches.get_flag("ignore-cmdline") {
gdb_args.clone()
args.clone()
} else {
let cmdline_path = path.join("cmdline");
if let Ok(cmdline) = fs::read_to_string(&cmdline_path) {
Expand All @@ -144,29 +143,42 @@ fn main() -> Result<()> {
continue;
}
};
crash_info.casr_tool = if !crash_info.target_args.is_empty()
&& (crash_info.target_args[0].ends_with("dotnet")
|| crash_info.target_args[0].ends_with("mono"))
{
is_casr_gdb = false;
util::get_path("casr-csharp")?
} else {
util::get_path("casr-gdb")?
};
crash_info.at_index = crash_info
.target_args
.iter()
.skip(1)
.position(|s| s.contains("@@"))
.map(|x| x + 1);

if let Some(target) = crash_info.target_args.first() {
match util::symbols_list(Path::new(target)) {
Ok(list) => {
if list.contains("__asan") {
crash_info.casr_tool = casr_san.clone()
// When we triage crashes for binaries, use casr-san.
if is_casr_gdb {
if let Some(target) = crash_info.target_args.first() {
match util::symbols_list(Path::new(target)) {
Ok(list) => {
if list.contains("__asan") {
crash_info.casr_tool = util::get_path("casr-san")?.clone()
}
}
Err(e) => {
error!("{e}");
continue;
}
}
Err(e) => {
error!("{e}");
continue;
}
} else {
error!("Cmdline is empty. Path: {:?}", path.join("cmdline"));
continue;
}
} else {
error!("Cmdline is empty. Path: {:?}", path.join("cmdline"));
continue;
}

// Push crash paths.
for crash in path
.read_dir()?
Expand All @@ -183,10 +195,10 @@ fn main() -> Result<()> {
}
}

if matches.get_flag("ignore-cmdline") {
gdb_args = Vec::new();
if matches.get_flag("ignore-cmdline") || !is_casr_gdb {
args = Vec::new();
}

// Generate reports
fuzzing_crash_triage_pipeline(&matches, &crashes, &gdb_args)
// Generate reports.
fuzzing_crash_triage_pipeline(&matches, &crashes, &args)
}
2 changes: 1 addition & 1 deletion casr/src/bin/casr-cluster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ fn main() -> Result<()> {
.value_parser(clap::value_parser!(u32).range(1..))
)
.get_matches();
init_ignored_frames!("cpp", "rust", "python", "go", "java", "js");
init_ignored_frames!("cpp", "rust", "python", "go", "java", "js", "csharp");

// Get number of threads
let jobs = if let Some(jobs) = matches.get_one::<u32>("jobs") {
Expand Down
3 changes: 2 additions & 1 deletion casr/src/bin/casr-core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ fn main() -> Result<()> {
// Save report.
if let Ok(mut file) = OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(&report_path)
{
Expand All @@ -319,7 +320,7 @@ fn check_lock() -> Result<File> {
project_dir.push("Casr.lock");
let file = OpenOptions::new()
.create(true)
.write(true)
.append(true)
.open(project_dir)?;
let fd = file.as_raw_fd();
flock(fd, FlockArg::LockExclusive).unwrap();
Expand Down
Loading

0 comments on commit a680ce5

Please sign in to comment.