diff --git a/.github/workflows/amd64.yml b/.github/workflows/amd64.yml index 5d460f13..6bf3868f 100644 --- a/.github/workflows/amd64.yml +++ b/.github/workflows/amd64.yml @@ -22,7 +22,20 @@ jobs: - name: Run tests run: | sudo apt update && sudo apt install -y gdb pip curl python3-dev llvm \ - openjdk-17-jdk ca-certificates gnupg lua5.4 + openjdk-17-jdk ca-certificates gnupg libcurl4-gnutls-dev + wget https://www.lua.org/ftp/lua-5.4.7.tar.gz && tar zxf lua-5.4.7.tar.gz && cd lua-5.4.7 && \ + sed -i 's/std=gnu99/std=gnu99 -fPIC/1' src/Makefile && \ + sed -i 's/Wextra/Wextra -fPIC/1' src/Makefile && make all test && sudo make install + ln -s $(which lua) $(which lua)5.4 + wget https://luarocks.org/releases/luarocks-3.11.1.tar.gz && tar zxpf luarocks-3.11.1.tar.gz && \ + cd luarocks-3.11.1 && ./configure && make && sudo make install && cd .. && rm -rf luarocks-3.11.1.tar.gz luarocks-3.11.1 + eval $(luarocks path) + wget https://github.com/tarantool/luajit/archive/refs/tags/v2.1.0-beta3.tar.gz && \ + tar zxpf v2.1.0-beta3.tar.gz && rm v2.1.0-beta3.tar.gz && cd luajit-2.1.0-beta3 && \ + make && sudo make install && sudo ln -sf luajit-2.1.0-beta3 /usr/local/bin/luajit && cd .. && rm -rf luajit-2.1.0-beta3 + git clone https://github.com/ligurio/luzer.git && \ + cd luzer && git checkout 649289aa945b7d6c1e2c29edccc83fd3fbe70568 && luarocks build --pack-binary-rock && cd .. + export LUA_CPATH="/home/runner/work/casr/casr/luzer/build.luarocks/luzer/?.so;$LUA_CPATH" # Atheris fails to install on Ubuntu 24.04, see https://github.com/google/atheris/issues/82 # pip3 install atheris sudo mkdir -p /etc/apt/keyrings diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index f02ba453..66195b2b 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -45,8 +45,8 @@ jobs: run: | rustup component add llvm-tools-preview cargo +nightly build --all-features --verbose - cargo +nightly test --verbose --lib -- --test-threads 1 - cargo +nightly test --verbose --package casr + cargo +nightly test --verbose --lib -- --test-threads 1 --skip test_casr_libfuzzer_luzer + cargo +nightly test --verbose --package casr -- --skip test_casr_libfuzzer_luzer - name: Collect Coverage run: | mkdir target/coverage diff --git a/README.md b/README.md index 02c72bbd..7b5285c2 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,8 @@ and AFL-based fuzzer [Sharpfuzz](https://github.com/Metalnem/sharpfuzz). [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)) or [LibAFL](https://github.com/AFLplusplus/LibAFL) +[jsfuzz](https://github.com/fuzzitdev/jsfuzz)/[luzer](https://github.com/ligurio/luzer)) +or [LibAFL](https://github.com/AFLplusplus/LibAFL) based [fuzzers](https://github.com/AFLplusplus/LibAFL/tree/main/fuzzers). `casr-dojo` allows to upload new and unique CASR reports to [DefectDojo](https://github.com/DefectDojo/django-DefectDojo) (available with @@ -242,6 +243,14 @@ Triage Jazzer.js crashes with casr-libfuzzer (Jazzer.js installation [guide](htt $ 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 +Triage luzer crashes with casr-libfuzzer: + + $ unzip casr/tests/casr_tests/lua/xml2lua.zip && cd xml2lua && luarocks --local build && cd .. && rm -rf xml2lua + $ git clone https://github.com/azanegin/luzer.git && \ + cd luzer && git checkout 77642ba37430eded66d171a68d7e9c3f6347d625 && luarocks --local build && cd .. && rm -rf luzer + $ mkdir -p casr/tests/tmp_tests_casr/casr_libfuzzer_luzer_out + $ casr-libfuzzer -i casr/tests/casr_tests/casrep/luzer_crashes_xml2lua -o casr/tests/tmp_tests_casr/casr_libfuzzer_luzer_out -- casr/tests/casr_tests/lua/stdin_parse_xml.lua + Triage LibAFL crashes with casr-libfuzzer: $ casr-libfuzzer -i casr/tests/casr_tests/casrep/test_libafl_crashes -o casr/tests/tmp_tests_casr/casr_libafl_out -- casr/tests/casr_tests/bin/test_libafl_fuzzer @@ diff --git a/casr/src/bin/casr-libfuzzer.rs b/casr/src/bin/casr-libfuzzer.rs index f484b620..d7f7f544 100644 --- a/casr/src/bin/casr-libfuzzer.rs +++ b/casr/src/bin/casr-libfuzzer.rs @@ -161,6 +161,14 @@ fn main() -> Result<()> { || argv[0].ends_with("jsfuzz")) { "casr-js" + } else if hint == "lua" + || hint == "auto" + && (argv[0].ends_with(".lua") + || argv[0] == "lua" + || argv[0] == "luajit" + || argv.len() > 1 && argv[1].ends_with(".lua")) + { + "casr-lua" } else { let sym_list = util::symbols_list(Path::new(argv[0]))?; if hint == "san" @@ -203,7 +211,7 @@ fn main() -> Result<()> { // Get input file argument index. let at_index = if let Some(idx) = argv.iter().skip(1).position(|s| s.contains("@@")) { Some(idx + 1) - } else if is_libafl_based { + } else if is_libafl_based || tool.eq("casr-lua") { None } else { argv.push("@@"); diff --git a/casr/src/triage.rs b/casr/src/triage.rs index 7c241fd2..910537b8 100644 --- a/casr/src/triage.rs +++ b/casr/src/triage.rs @@ -51,7 +51,7 @@ impl<'a> CrashInfo { } else { args.push(format!("{}.casrep", report_path.display())); } - if self.at_index.is_none() { + if self.at_index.is_none() || self.at_index == Some(0) { args.push("--stdin".to_string()); args.push(self.path.to_str().unwrap().to_string()); } diff --git a/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/001ae4daf6ab4e2c2b562a79b608e6eb5628ff66 b/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/001ae4daf6ab4e2c2b562a79b608e6eb5628ff66 new file mode 100644 index 00000000..ff68be47 --- /dev/null +++ b/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/001ae4daf6ab4e2c2b562a79b608e6eb5628ff66 @@ -0,0 +1,3 @@ +yl< />/< +:/>< */>< +'>p \ No newline at end of file diff --git a/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/08c81694662536d2ef00a1995b34e25b397c6be2 b/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/08c81694662536d2ef00a1995b34e25b397c6be2 new file mode 100644 index 00000000..296ef238 --- /dev/null +++ b/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/08c81694662536d2ef00a1995b34e25b397c6be2 @@ -0,0 +1,6 @@ +<>,/< +>/,< />>< />//<>>P< + />,< />/< +>/,< />/< +>/<>>P<>Q< +>/<>>P> \ No newline at end of file diff --git a/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/crash-00392e7545ad23ce26c768fafa8c14291b91eec0 b/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/crash-00392e7545ad23ce26c768fafa8c14291b91eec0 new file mode 100644 index 00000000..5112a640 Binary files /dev/null and b/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/crash-00392e7545ad23ce26c768fafa8c14291b91eec0 differ diff --git a/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/crash-00c984770c98536dc510ad22731f20854a350b01 b/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/crash-00c984770c98536dc510ad22731f20854a350b01 new file mode 100644 index 00000000..d27facd9 --- /dev/null +++ b/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/crash-00c984770c98536dc510ad22731f20854a350b01 @@ -0,0 +1,2 @@ +Qy> + diff --git a/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/crash-06d2814ffd0a9eb5be1199c7a522337efe29f805 b/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/crash-06d2814ffd0a9eb5be1199c7a522337efe29f805 new file mode 100644 index 00000000..1b5a18bf Binary files /dev/null and b/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/crash-06d2814ffd0a9eb5be1199c7a522337efe29f805 differ diff --git a/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/crash-0c2577e1a76131d5d264c705734ce4f5a3225e94 b/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/crash-0c2577e1a76131d5d264c705734ce4f5a3225e94 new file mode 100644 index 00000000..e41db532 Binary files /dev/null and b/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/crash-0c2577e1a76131d5d264c705734ce4f5a3225e94 differ diff --git a/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/crash-161cf7d38d7749f00c2cca6bf5f44a68804bbe0f b/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/crash-161cf7d38d7749f00c2cca6bf5f44a68804bbe0f new file mode 100644 index 00000000..64529c73 --- /dev/null +++ b/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/crash-161cf7d38d7749f00c2cca6bf5f44a68804bbe0f @@ -0,0 +1,119 @@ +< +p/>Q>< />>

pe= s/>><)l>< + p/>e< < + p/> < + p/>ell < + />q/>n)e= 2iy$ <>e < + p/>el < + />el/>< +:p/>>>< + 4/>Q< +/>/<: + p,> < +>/<>>< + +>e<"/>/< + p/>e /šq/>/< +:p/>>< + + p3/>Qe < + p/>>< + p/>e< < + p/> < + pšq/>/< +:p/>>< + + p3/>Q<<> e + p/>>< + p/>e< < + p/> < + p/>el>< + + p3/>Q<> p/>el<>>< + p/>e< />q/<3/Ma>n)e= 2iy$ <>e < + p/>el < + />el/>< +:p/>>e /šq/>/< +:p/>>< + + <>Q3/p>e < + p/>>< + p/>e< < + p/> < + pšq/>/< +:p/>>< + + p3/>Q<<> e + p/>>< + p/>e< < + p/> < + p/>el

Q< />>

< + ep/>>< + p/> < + p/>ell < + />q/>n)e= 2iy$ <>e < + p/>el < + />el/>< +:p/>>>< + 4/>Q< +/>/<: + p,> < +>/<>>< + +>e<"/>/< + p/>e /šq/>/< +:p/>>< + + p3/>Qe < + p/>>< + p/>e< < + p/> < + pšq/>/< +:p/>>< + + p3/>Q<<> e + p/>>< + p/>e< < + p/> < + p/>el/>>< + + p3/>Q<> p/>el<>>< + p/>e< />q/<3/Ma>n)e= 2iy$ <>e < + p/>el < + />el/>< +:p/>>e /šq/>/< +:p/>>< + + p3/>Q<>e < + p/>>< + p/>e< < + p/> < + pšq/>/< +:p/>>< + + p3/>Q<<> e + p/>>< + p/>e< < + p/> < + p/>el

Q< />>

< + ee< < + p/> < + p/>el<>>< + p/>e< < + p/> < + pšq>< +:p/>>e /šq/>/< +:p/>/ />

Q< />>

< + ee< < + p/> < + p/>el<>>< + p/ p/>>< + p/>e< < + p/>> diff --git a/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/crash-a922410c3705cb2a29c327dfae9d61d37ce7f7c3 b/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/crash-a922410c3705cb2a29c327dfae9d61d37ce7f7c3 new file mode 100644 index 00000000..d80f3705 --- /dev/null +++ b/casr/tests/casr_tests/casrep/luzer_crashes_xml2lua/crash-a922410c3705cb2a29c327dfae9d61d37ce7f7c3 @@ -0,0 +1,6 @@ + <ۜ /> + +e <ۜ />/> <ۜ /> + +|m \ No newline at end of file diff --git a/casr/tests/casr_tests/lua/stdin_parse_xml.lua b/casr/tests/casr_tests/lua/stdin_parse_xml.lua new file mode 100755 index 00000000..2c72b567 --- /dev/null +++ b/casr/tests/casr_tests/lua/stdin_parse_xml.lua @@ -0,0 +1,33 @@ +#!/usr/bin/env lua +-- +-- Copyright 2025 ISP RAS +-- +-- 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. +-- +-------------------------------------------------------------------------- + +local xml2lua = require("xml2lua") +local handler = require("xmlhandler.tree") +local luzer = require("luzer") + +local function TestOneInput(buf) + local fdp = luzer.FuzzedDataProvider(buf) + if #buf < 2 then return nil end + local str = fdp:consume_string(#buf - 1) + local parser = xml2lua.parser(handler) + parser:parse(str) +end + +local buf = io.read("*all") +if not buf then return nil end +TestOneInput(buf) diff --git a/casr/tests/casr_tests/lua/xml2lua.zip b/casr/tests/casr_tests/lua/xml2lua.zip new file mode 100644 index 00000000..69f5906e Binary files /dev/null and b/casr/tests/casr_tests/lua/xml2lua.zip differ diff --git a/casr/tests/tests.rs b/casr/tests/tests.rs index 0eccf121..20179484 100644 --- a/casr/tests/tests.rs +++ b/casr/tests/tests.rs @@ -5017,6 +5017,126 @@ fn test_casr_lua() { } } +#[test] +#[cfg(target_arch = "x86_64")] +fn test_casr_libfuzzer_luzer() { + use std::collections::HashMap; + + let paths = [ + abs_path("tests/casr_tests/casrep/luzer_crashes_xml2lua"), + abs_path("tests/tmp_tests_casr/test_casr_libfuzzer_luzer/casr_libfuzzer_luzer_out"), + abs_path("tests/tmp_tests_casr/test_casr_libfuzzer_luzer/stdin_parse_xml.lua"), + abs_path("tests/tmp_tests_casr/test_casr_libfuzzer_luzer/xml2lua"), + ]; + + let _ = fs::remove_dir_all(&paths[1]); + let _ = fs::remove_file(&paths[2]); + let _ = fs::remove_dir_all(&paths[3]); + let _ = fs::create_dir_all(abs_path("tests/tmp_tests_casr/test_casr_libfuzzer_luzer")); + + fs::copy( + abs_path("tests/casr_tests/lua/stdin_parse_xml.lua"), + &paths[2], + ) + .unwrap(); + + Command::new("unzip") + .arg(abs_path("tests/casr_tests/lua/xml2lua.zip")) + .current_dir(abs_path("tests/tmp_tests_casr/test_casr_libfuzzer_luzer")) + .stdout(Stdio::null()) + .status() + .expect("failed to unzip xml2lua.zip"); + + let Ok(luarocks_path) = which::which("luarocks") else { + panic!("No luarocks is found."); + }; + + let luarocks = Command::new(&luarocks_path) + .current_dir(&paths[3]) + .args(["--local", "build"]) + .output() + .expect("failed to run luarocks build"); + assert!( + luarocks.status.success(), + "Stdout: {}.\n Stderr: {}", + String::from_utf8_lossy(&luarocks.stdout), + String::from_utf8_lossy(&luarocks.stderr) + ); + + let bins = Path::new(EXE_CASR_LIBFUZZER).parent().unwrap(); + let mut cmd = Command::new(EXE_CASR_LIBFUZZER); + cmd.args(["-i", &paths[0], "-o", &paths[1], "--", &paths[2]]) + .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); + println!("{}", err); + + assert!(!err.is_empty()); + + assert!(err.contains("NOT_EXPLOITABLE")); + assert!(err.contains("attempt to perform arithmetic")); + assert!(err.contains("attempt to index")); + assert!(err.contains("bad argument #1 to 'insert'")); + assert!(err.contains("XmlParser.lua")); + assert!(err.contains("tree.lua")); + + 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, 3, "Invalid number of clusters"); + + let mut storage: HashMap = HashMap::new(); + for entry in fs::read_dir(&paths[1]).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_js() { diff --git a/docs/usage.md b/docs/usage.md index aced46a3..c0647775 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -15,7 +15,7 @@ Use `casr-lua` to analyze Lua reports. `casr-afl` can 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) -(libFuzzer, go-fuzz, Atheris, Jazzer, Jazzer.js, jsfuzz) or by +(libFuzzer, go-fuzz, Atheris, Jazzer, Jazzer.js, jsfuzz, luzer) or by [LibAFL](https://github.com/AFLplusplus/LibAFL) based [fuzzers](https://github.com/AFLplusplus/LibAFL/tree/main/fuzzers). `casr-dojo` allows to upload new and unique CASR reports to @@ -613,7 +613,7 @@ your project before (via `dotnet build` or `dotnet publish`) and specify `--no-b ## casr-libfuzzer Triage crashes found by libFuzzer based fuzzer -(C/C++/go-fuzz/Atheris/Jazzer/Jazzer.js/jsfuzz) or LibAFL based fuzzer +(C/C++/go-fuzz/Atheris/Jazzer/Jazzer.js/jsfuzz/luzer) or LibAFL based fuzzer Usage: casr-libfuzzer [OPTIONS] --output -- ... @@ -655,7 +655,8 @@ Triage crashes found by libFuzzer based fuzzer [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.js](https://github.com/CodeIntelligenceTesting/jazzer.js)/ -[jsfuzz](https://github.com/fuzzitdev/jsfuzz)) or [LibAFL](https://github.com/AFLplusplus/LibAFL) based +[jsfuzz](https://github.com/fuzzitdev/jsfuzz)/[luzer](https://github.com/ligurio/luzer)) +or [LibAFL](https://github.com/AFLplusplus/LibAFL) based [fuzzers](https://github.com/AFLplusplus/LibAFL/tree/main/fuzzers). It is pretty much like `casr-afl`. @@ -682,6 +683,14 @@ Jazzer.js example (Jazzer.js installation [guide](https://github.com/CodeIntelli $ 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 +Luzer example: + + $ unzip casr/tests/casr_tests/lua/xml2lua.zip && cd xml2lua && luarocks --local build && cd .. && rm -rf xml2lua + $ git clone https://github.com/azanegin/luzer.git && \ + cd luzer && git checkout 77642ba37430eded66d171a68d7e9c3f6347d625 && luarocks --local build && cd .. && rm -rf luzer + $ mkdir -p casr/tests/tmp_tests_casr/casr_libfuzzer_luzer_out + $ casr-libfuzzer -i casr/tests/casr_tests/casrep/luzer_crashes_xml2lua -o casr/tests/tmp_tests_casr/casr_libfuzzer_luzer_out -- casr/tests/casr_tests/lua/stdin_parse_xml.lua + LibAFL example: $ casr-libfuzzer -i casr/tests/casr_tests/casrep/test_libafl_crashes -o casr/tests/tmp_tests_casr/casr_libafl_out -- casr/tests/casr_tests/bin/test_libafl_fuzzer @@