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

Add casr-lua to casr-libfuzzer #242

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
11 changes: 10 additions & 1 deletion .github/workflows/amd64.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,16 @@ 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 lua5.4 liblua5.4-dev libcurl4-gnutls-dev
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 6e3276c5e6faf18c8c98637162bea0a23f0cd1d3 && luarocks --local build && 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
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/coverage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 @@
Expand Down
10 changes: 9 additions & 1 deletion casr/src/bin/casr-libfuzzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,14 @@
|| 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"

Check warning on line 171 in casr/src/bin/casr-libfuzzer.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-libfuzzer.rs#L171

Added line #L171 was not covered by tests
} else {
let sym_list = util::symbols_list(Path::new(argv[0]))?;
if hint == "san"
Expand Down Expand Up @@ -203,7 +211,7 @@
// 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("@@");
Expand Down
2 changes: 1 addition & 1 deletion casr/src/triage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
yl< />/<
«:/>< */>Ú<
'>­p
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<>,</>/<
>/</>,< />>< />//<>>P­</></><
/>,< />/<
>/</>,< />/<
>/<>>P<>Q<
>/<>>P­>
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<pee=?"nwÿÿÿÿÿÿÿQ­<>Ð<p 1/>Qÿÿÿÿy>
</pers/<citye>
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<peop<
/Â/><
p/><p 0/>Q>< />><p/>Î pe= šs/>><)­l<šsne <
òp/>><
p/>e< <
p/> <
p/>e­ll <
/>šq/>n)e= 2iy$ <>e <
p/>e­l <
/>e­l<šq/>/><
:òp/>><iy /<=/ààààààààààààààààààààààààààààààààààààààà>><
4/>QÚ<
/>/­<:
p,> <
>/<>><

>e<"/>/<
p/>e /šq/>/<
:òp/>><

p3/>Q<Q­<>­e <
òp/>><
p/>e< <
p/> <
pšq/>/<
:òp/>><

p3/>Q­<<> ­e
òp/>><
p/>e< <
p/> <
p/>e­l<MMM<
:òp/>><

p3/>Q­<> p/>e­l<>><
p/>e< />šq/­<3/MÞa>n)e= 2iy$ <>e <
p/>e­l <
/>e­l<šq/>/><
:òp/>><iy <
p/>e /šq/>/<
:òp/>><

<>­Q3/p>­e <
òp/>><
p/>e< <
p/> <
pšq/>/<
:òp/>><

p3/>Q­<<> ­e
òp/>><
p/>e< <
p/> <
p/>e­l<MMM /><p//>Q­</>±< />><p/><
e­òp/>><
p/> <
p/>e­ll <
/>šq/>n)e= 2iy$ <>e <
p/>e­l <
/>e­l<šq/>/><
:òp/>><iy /<=/>><
4/>QÚ<
/>/­<:
p,> <
>/<>><

>e<"/>/<
p/>e /šq/>/<
:òp/>><

p3/>Q<Q­<>­e <
òp/>><
p/>e< <
p/> <
pšq/>/<
:òp/>><

p3/>Q­<<> ­e
òp/>><
p/>e< <
p/> <
p/>e­l<MMM<
:òp<pee=?"naÿÿÿQ­<>/>><

p3/>Q­<> p/>e­l<>><
p/>e< />šq/­<3/MÞa>n)e= 2iy$ <>e <
p/>e­l <
/>e­l<šôq/>/><
:òp/>><iy <
p/>e /šq/>/<
:òp/>><

p3/>Q­<>­e <
òp/>><
p/>e< <
p/> <
pšq/>/<
:òp/>><

p3/>Q­<<> ­e
òp/>><
p/>e< <
p/> <
p/>e­l<MMM /><p//>Q­</>±< />><p/><
e­e< <
p/> <
p/>e­l<>><
p/>e< <
p/> <
pšq><
:òp/>><iy <
p/>e /šq/>/<
:òp/>/ /><p//>Q­</>±< />><p/><
e­e< <
p/> <
p/>e­l<>><
p/ òp/>><
p/>e< <
p/>>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
‹<i-
p /><r/> <Ûœ† />

e<ie=?"laeg p /><r/> <Ûœ† />/><r/> <Ûœ† />

|<ie<it
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
</a=?">m
33 changes: 33 additions & 0 deletions casr/tests/casr_tests/lua/stdin_parse_xml.lua
Original file line number Diff line number Diff line change
@@ -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)
Binary file added casr/tests/casr_tests/lua/xml2lua.zip
Binary file not shown.
120 changes: 120 additions & 0 deletions casr/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<unique>\d+)").unwrap();
let unique_cnt = re
.captures(&err)
.unwrap()
.name("unique")
.map(|x| x.as_str())
.unwrap()
.parse::<u32>()
.unwrap();

assert_eq!(unique_cnt, 3, "Invalid number of deduplicated reports");

let re = Regex::new(r"Number of clusters: (?P<clusters>\d+)").unwrap();
let clusters_cnt = re
.captures(&err)
.unwrap()
.name("clusters")
.map(|x| x.as_str())
.unwrap()
.parse::<u32>()
.unwrap();

assert_eq!(clusters_cnt, 3, "Invalid number of clusters");

let mut storage: HashMap<String, u32> = 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() {
Expand Down
Loading
Loading