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 #231

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
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
3 changes: 2 additions & 1 deletion .github/workflows/aarch64.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ jobs:
install: |
export CARGO_TERM_COLOR=always
export CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
apt-get update && apt-get install -y gdb pip curl python3.12-dev clang llvm build-essential
apt-get update && apt-get install -y gdb pip curl python3.12-dev clang \
llvm build-essential lua5.4
curl https://sh.rustup.rs -o rustup.sh && chmod +x rustup.sh && \
./rustup.sh -y && rm rustup.sh
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/amd64.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Run tests
run: |
sudo apt update && sudo apt install -y gdb pip curl python3.10-dev llvm \
openjdk-17-jdk ca-certificates gnupg
openjdk-17-jdk ca-certificates gnupg lua5.4
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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- name: Install Dependences
run: |
sudo apt update && sudo apt install -y gdb pip curl python3.10-dev llvm \
openjdk-17-jdk ca-certificates gnupg
openjdk-17-jdk ca-certificates gnupg lua5.4
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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/darwin-arm64.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
run: |
arch -x86_64 /usr/local/bin/brew update
arch -x86_64 /usr/local/bin/brew install gdb curl python llvm \
openjdk ca-certificates gnupg nodejs --overwrite
openjdk ca-certificates gnupg nodejs lua5.4 --overwrite
- name: Build
run: cargo build --all-features --verbose
- name: NPM packages
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/riscv64.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ jobs:
install: |
export CARGO_TERM_COLOR=always
export CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
apt-get update \
&& apt-get install -y gdb pip curl python3-dev clang llvm build-essential
apt-get update && apt-get install -y gdb pip curl python3-dev clang llvm \
build-essential lua5.4
curl https://sh.rustup.rs -o rustup.sh && chmod +x rustup.sh && \
./rustup.sh -y && rm rustup.sh
run: |
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Cargo.lock
*/tests/tmp_tests_casr
*/tests/casr_tests/csharp/*/bin
*/tests/casr_tests/csharp/*/obj
*.swo
*.swp
node_modules
*/node_modules/*
Expand Down
21 changes: 13 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ 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). Use `casr-js`
to analyze JavaScript reports and get report from
[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).
Use `casr-csharp` to analyze C# reports and get report from
[Sharpfuzz](https://github.com/Metalnem/sharpfuzz).
[jsfuzz](https://github.com/fuzzitdev/jsfuzz). Use `casr-csharp` to analyze C#
reports and get report from [Sharpfuzz](https://github.com/Metalnem/sharpfuzz).
Use `casr-lua` to analyze Lua reports.

Crash report contains many useful information: severity (like [exploitable](https://github.com/jfoote/exploitable))
for x86, x86\_64, arm32, aarch64, rv32g, rv64g architectures,
Expand Down Expand Up @@ -79,12 +79,13 @@ It can analyze crashes from different sources:
and program languages:

* C/C++
* Rust
* C#
* Go
* Python
* Java
* JavaScript
* C#
* Lua
* Python
* Rust

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 @@ -169,6 +170,10 @@ Create report from C#:

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

Create report from Lua:

$ casr-lua -o lua.casrep -- casr/tests/casr_tests/lua/test_casr_lua.lua

View report:

$ casr-cli casr/tests/casr_tests/casrep/test_clustering_san/load_fuzzer_crash-120697a7f5b87c03020f321c8526adf0f4bcc2dc.casrep
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 @@ -429,6 +429,16 @@
tree.collapse_item(row);
}

if !report.lua_report.is_empty() {
row = tree
.insert_container_item("LuaReport".to_string(), Placement::After, row)
.unwrap();
report.lua_report.iter().for_each(|e| {
tree.insert_item(e.clone(), Placement::LastChild, row);
});
tree.collapse_item(row);
}

Check warning on line 440 in casr/src/bin/casr-cli.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-cli.rs#L432-L440

Added lines #L432 - L440 were not covered by tests

if !report.java_report.is_empty() {
row = tree
.insert_container_item("JavaReport".to_string(), Placement::After, row)
Expand Down Expand Up @@ -649,6 +659,10 @@
select.add_item("PythonReport", report.python_report.join("\n"));
}

if !report.lua_report.is_empty() {
select.add_item("LuaReport", report.lua_report.join("\n"));
}

Check warning on line 664 in casr/src/bin/casr-cli.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-cli.rs#L662-L664

Added lines #L662 - L664 were not covered by tests

if !report.java_report.is_empty() {
select.add_item("JavaReport", report.java_report.join("\n"));
}
Expand Down
155 changes: 155 additions & 0 deletions casr/src/bin/casr-lua.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
use casr::util;
use libcasr::{
init_ignored_frames,
lua::LuaException,
report::CrashReport,
severity::Severity,
stacktrace::Filter,
stacktrace::Stacktrace,
stacktrace::{CrashLine, CrashLineExt},
};

use anyhow::{bail, Result};
use clap::{Arg, ArgAction, ArgGroup};
use std::path::{Path, PathBuf};
use std::process::Command;

fn main() -> Result<()> {
let matches = clap::Command::new("casr-lua")
.version(clap::crate_version!())
.about("Create CASR reports (.casrep) from Lua 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))
)
.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("strip-path")
.long("strip-path")
.env("CASR_STRIP_PATH")
.action(ArgAction::Set)
.value_name("PREFIX")
.help("Path prefix to strip from stacktrace"),
)
.arg(
Arg::new("ARGS")
.action(ArgAction::Set)
.num_args(1..)
.last(true)
.required(true)
.help("Add \"-- <path> <arguments>\" to run"),
)
.get_matches();

init_ignored_frames!("lua");
if let Some(path) = matches.get_one::<PathBuf>("ignore") {
util::add_custom_ignored_frames(path)?;

Check warning on line 90 in casr/src/bin/casr-lua.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-lua.rs#L90

Added line #L90 was not covered by tests
}
// 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");

Check warning on line 96 in casr/src/bin/casr-lua.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-lua.rs#L96

Added line #L96 was not covered by tests
};

// 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 cmd = Command::new(argv[0]);
if let Some(ref file) = stdin_file {
cmd.stdin(std::fs::File::open(file)?);

Check warning on line 108 in casr/src/bin/casr-lua.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-lua.rs#L108

Added line #L108 was not covered by tests
}
if argv.len() > 1 {
cmd.args(&argv[1..]);

Check warning on line 111 in casr/src/bin/casr-lua.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-lua.rs#L111

Added line #L111 was not covered by tests
}
let result = util::get_output(&mut cmd, timeout, true)?;
let stderr = String::from_utf8_lossy(&result.stderr);

// Create report.
let mut report = CrashReport::new();
report.executable_path = argv[0].to_string();
if argv.len() > 1 {
if let Some(fname) = Path::new(argv[0]).file_name() {
let fname = fname.to_string_lossy();
if fname.starts_with("lua") && !fname.ends_with(".lua") && argv[1].ends_with(".lua") {
report.executable_path = argv[1].to_string();
}
}

Check warning on line 125 in casr/src/bin/casr-lua.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-lua.rs#L120-L125

Added lines #L120 - L125 were not covered by tests
}
report.proc_cmdline = argv.join(" ");
let _ = report.add_os_info();
let _ = report.add_proc_environ();

// Extract lua exception
let Some(exception) = LuaException::new(&stderr) else {
bail!("Lua exception is not found!");

Check warning on line 133 in casr/src/bin/casr-lua.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-lua.rs#L133

Added line #L133 was not covered by tests
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imagine a Lua code like the following:

[0] ~/sources/casr $ cat exc6.lua 
#!/bin/env lua

function a()
  error("Boom!", 3); 
end

function b() a(); end

function c() b(); end

function d() c(); end

function e() d(); end

function f() e(); end

function errhandler(msg)
  print (msg .. "\nstack traceback: \n" .. debug.traceback(2, 3, 2));
end

xpcall(f, errhandler);

that produces an output:

[0] ~/sources/casr $ ./exc6.lua 
./exc6.lua:9: Boom!
stack traceback: 
2
stack traceback:
        ./exc6.lua:4: in function 'a'
        ./exc6.lua:7: in function 'b'
        ./exc6.lua:9: in function 'c'
        ./exc6.lua:11: in function 'd'
        ./exc6.lua:13: in function 'e'
        ./exc6.lua:15: in function <./exc6.lua:15>
        [C]: in function 'xpcall'
        ./exc6.lua:21: in main chunk
        [C]: in ?

casr-lua will not find traceback there:

$ ./target/debug/casr-lua -o lua.casrep -- ./exc6.lua 
Error: Lua exception is not found!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error occurs only due to extra spaces after traceback

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can think about storing custom (maybe multiline) error as Explanation or Description fields, but it's not clear how to extract ShortDescription or etc

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lua code:

#!/bin/env lua

local function f1()
  error('error is triggered', 1)
end

local function f2()
  f1()
end

local function f3()
  f2()
end

local function f4()
  f3()
end

local function f5()
  f4()
end

local thread = coroutine.create(f5)
local executed_ok, message = coroutine.resume(thread)

if not executed_ok then
  print("Error: "..message.." in ".. debug.traceback(thread))
end
[0] ~/sources/casr $ lua exc2.lua 
Error: exc2.lua:4: error is triggered in stack traceback:
        [C]: in function 'error'
        exc2.lua:4: in function 'f1'
        exc2.lua:8: in function 'f2'
        exc2.lua:12: in function 'f3'
        exc2.lua:16: in function 'f4'
        exc2.lua:20: in function <exc2.lua:19>
[0] ~/sources/casr $ ./target/debug/casr-lua -o lua.casrep -- ./exc2.lua 
Error: Lua exception is not found!
[0] ~/sources/casr $ 

Should it be supported?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm... I assumed there was always going to be something like [C] in ? or [C] at 0xdeadbeaf at the end of traceback, but apparently it's not

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait... That's just debug info in stdout, not error message (although it contains word Error)
Is it possible to rewrite this logging message to stderr?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second issue is multiple call of debug.traceback()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To sum up the above, I think we should not to support it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1
Custom error messages are not something that we want to support

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stack trace for assertions does not work:

[1] ~/sources/casr $ cat exc7.lua 
#!/bin/env lua

assert(nil, 'dddd')
[1] ~/sources/casr $ lua exc7.lua 
lua: exc7.lua:3: dddd
stack traceback:
        [C]: in function 'assert'
        exc7.lua:3: in main chunk
        [C]: in ?
[1] ~/sources/casr $ ./target/debug/casr-lua -o lua.casrep -- ./exc7.lua 
(empty)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idk, It works fine for me

};

// Parse exception
report.lua_report = exception.lua_report();
report.stacktrace = exception.extract_stacktrace()?;
report.execution_class = exception.severity()?;
if let Ok(crashline) = exception.crash_line() {
report.crashline = crashline.to_string();
if let CrashLine::Source(debug) = crashline {
if let Some(sources) = CrashReport::sources(&debug) {
report.source = sources;

Check warning on line 144 in casr/src/bin/casr-lua.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-lua.rs#L144

Added line #L144 was not covered by tests
}
}
}

Check warning on line 147 in casr/src/bin/casr-lua.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-lua.rs#L146-L147

Added lines #L146 - L147 were not covered by tests
let stacktrace = exception.parse_stacktrace()?;
if let Some(path) = matches.get_one::<String>("strip-path") {
util::strip_paths(&mut report, &stacktrace, path);

Check warning on line 150 in casr/src/bin/casr-lua.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-lua.rs#L150

Added line #L150 was not covered by tests
}

//Output report
util::output_report(&report, &matches, &argv)
}
23 changes: 23 additions & 0 deletions casr/tests/casr_tests/lua/test_casr_lua.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/env lua

function f(a, b)
a = a .. 'qwer'
b = b * 123
c = a / b
return c
end

function g(a)
a = a .. 'qwer'
b = 123
c = f(a, b)
return c
end

function h()
a = 'qwer'
c = g(a)
return c
end

print(h())
40 changes: 40 additions & 0 deletions casr/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ lazy_static::lazy_static! {
static ref EXE_CASR_SAN: RwLock<&'static str> = RwLock::new(env!("CARGO_BIN_EXE_casr-san"));
static ref EXE_CASR_UBSAN: RwLock<&'static str> = RwLock::new(env!("CARGO_BIN_EXE_casr-ubsan"));
static ref EXE_CASR_PYTHON: RwLock<&'static str> = RwLock::new(env!("CARGO_BIN_EXE_casr-python"));
static ref EXE_CASR_LUA: RwLock<&'static str> = RwLock::new(env!("CARGO_BIN_EXE_casr-lua"));
static ref EXE_CASR_JAVA: RwLock<&'static str> = RwLock::new(env!("CARGO_BIN_EXE_casr-java"));
static ref EXE_CASR_JS: RwLock<&'static str> = RwLock::new(env!("CARGO_BIN_EXE_casr-js"));
static ref EXE_CASR_CSHARP: RwLock<&'static str> = RwLock::new(env!("CARGO_BIN_EXE_casr-csharp"));
Expand Down Expand Up @@ -4856,6 +4857,45 @@ fn test_casr_cluster_d_python() {
let _ = std::fs::remove_dir_all(&paths[1]);
}

#[test]
#[cfg(target_arch = "x86_64")]
fn test_casr_lua() {
let test_dir = abs_path("tests/tmp_tests_casr/test_casr_lua");
let test_path = abs_path("tests/casr_tests/lua/test_casr_lua.lua");
let _ = std::fs::remove_dir_all(test_dir);

let output = Command::new(*EXE_CASR_LUA.read().unwrap())
.args(["--stdout", "--", &test_path])
.output()
.expect("failed to start casr-lua");

assert!(
output.status.success(),
"Stdout {}.\n Stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);

let report: Result<Value, _> = serde_json::from_slice(&output.stdout);
if let Ok(report) = report {
let severity_type = report["CrashSeverity"]["Type"].as_str().unwrap();
let severity_desc = report["CrashSeverity"]["ShortDescription"]
.as_str()
.unwrap()
.to_string();

assert_eq!(6, report["Stacktrace"].as_array().unwrap().iter().count());
assert_eq!(severity_type, "NOT_EXPLOITABLE");
assert_eq!(severity_desc, "attempt to div a 'string' with a 'number'");
assert!(report["CrashLine"]
.as_str()
.unwrap()
.contains("test_casr_lua.lua:6"));
} else {
panic!("Couldn't parse json report file.");
}
}

#[test]
#[cfg(target_arch = "x86_64")]
fn test_casr_js() {
Expand Down
Loading
Loading