From d4eeeea66a8c23dc3aa7c6b9698ee9e3510b37e3 Mon Sep 17 00:00:00 2001 From: hkctkuy Date: Fri, 29 Nov 2024 20:36:20 +0300 Subject: [PATCH 01/15] casr-lua initial commit: add LuaStacktrace --- libcasr/src/lib.rs | 8 +- libcasr/src/lua.rs | 175 ++++++++++++++++++++++++++++++++++++++++++ libcasr/src/python.rs | 2 +- 3 files changed, 181 insertions(+), 4 deletions(-) create mode 100644 libcasr/src/lua.rs diff --git a/libcasr/src/lib.rs b/libcasr/src/lib.rs index 0dc1d90f..b6d0fb04 100644 --- a/libcasr/src/lib.rs +++ b/libcasr/src/lib.rs @@ -12,12 +12,13 @@ //! 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 (.casrep/.sarif) use `serde` feature. @@ -34,6 +35,7 @@ pub mod gdb; pub mod go; pub mod java; pub mod js; +pub mod lua; pub mod python; pub mod report; pub mod rust; diff --git a/libcasr/src/lua.rs b/libcasr/src/lua.rs new file mode 100644 index 00000000..1b302e7b --- /dev/null +++ b/libcasr/src/lua.rs @@ -0,0 +1,175 @@ +//! Lua module implements `ParseStacktrace`, `Exception` traits for Lua reports. +use crate::error::*; +use crate::exception::Exception; +use crate::execution_class::ExecutionClass; +use crate::severity::Severity; +use crate::stacktrace::{CrashLine, CrashLineExt, DebugInfo}; +use crate::stacktrace::{ParseStacktrace, Stacktrace, StacktraceEntry}; + +use regex::Regex; + +/// Structure provides an interface for processing the stack trace. +pub struct LuaStacktrace; + +impl ParseStacktrace for LuaStacktrace { + fn extract_stacktrace(stream: &str) -> Result> { + let stacktrace = stream + .split('\n') + .map(|l| l.to_string()) + .collect::>(); + let Some(first) = stacktrace + .iter() + .position(|line| line.trim().starts_with("stack traceback:")) + else { + return Err(Error::Casr( + "Couldn't find traceback in lua report".to_string(), + )); + }; + + let re = Regex::new(r#"\S+:(?:|\d+:) in .+"#).unwrap(); + Ok(stacktrace[first..] + .iter() + .map(|s| s.trim().to_string()) + .filter(|s| re.is_match(s)) + .collect::>()) + } + + fn parse_stacktrace_entry(entry: &str) -> Result { + let mut stentry = StacktraceEntry::default(); + let re = Regex::new(r#"(\S+):(\d+): in (\S+ )(\S+)"#).unwrap(); + let Some(cap) = re.captures(entry) else { + return Err(Error::Casr(format!( + "Couldn't parse stacktrace line: {entry}" + ))); + }; + stentry.debug.file = cap.get(1).unwrap().as_str().to_string(); + if let Ok(line) = cap.get(2).unwrap().as_str().parse::() { + stentry.debug.line = line; + } else { + return Err(Error::Casr(format!( + "Couldn't parse stacktrace line number: {entry}" + ))); + }; + stentry.function = if let Some(func) = cap.get(4) { + func.as_str() + .to_string() + .trim_matches('\'') + .trim_start_matches('<') + .trim_end_matches('>') + .to_string() + } else { + return Err(Error::Casr(format!( + "Couldn't parse stacktrace function: {entry}" + ))); + }; + if let Some(appendix) = cap.get(3) { + let appendix = appendix.as_str().to_string(); + if !appendix.starts_with("function") + && !appendix.starts_with("upvalue") + && !appendix.starts_with("local") + { + stentry.function = appendix + &stentry.function; + } + } + Ok(stentry) + } + + fn parse_stacktrace(entries: &[String]) -> Result { + let mut stacktrace = Stacktrace::new(); + for entry in entries.iter() { + if entry.starts_with("[C]:") { + continue; + } + stacktrace.push(Self::parse_stacktrace_entry(entry)?); + } + Ok(stacktrace) + } +} + +/// Structure provides an interface for parsing lua exception message. +pub struct LuaException; +impl Exception for LuaException { + fn parse_exception(stderr: &str) -> Option { + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_lua_stacktrace() { + let raw_stacktrace = " + lua: (error object is a table value) + stack traceback: + [C]: in function 'error' + /usr/local/share/lua/5.4/luacheck/parser.lua:28: in function 'luacheck.parser.syntax_error' + /usr/local/share/lua/5.4/luacheck/parser.lua:96: in upvalue 'parse_error' + /usr/local/share/lua/5.4/luacheck/parser.lua:535: in upvalue 'parse_simple_expression' + /usr/local/share/lua/5.4/luacheck/parser.lua:894: in function + (...tail calls...) + /usr/local/share/lua/5.4/luacheck/parser.lua:974: in upvalue 'parse_block' + /usr/local/share/lua/5.4/luacheck/parser.lua:1020: in function 'luacheck.parser.parse' + luacheck_parser_parse.lua:6: in local 'TestOneInput' + luacheck_parser_parse.lua:18: in main chunk + [C]: in ? + "; + let sttr = LuaStacktrace::extract_stacktrace(raw_stacktrace); + let Ok(sttr) = sttr else { + panic!("{}", sttr.err().unwrap()); + }; + assert_eq!(sttr.len(), 10); + + let sttr = LuaStacktrace::parse_stacktrace(&sttr); + let Ok(sttr) = sttr else { + panic!("{}", sttr.err().unwrap()); + }; + assert_eq!(sttr.len(), 8); + assert_eq!( + sttr[0].debug.file, + "/usr/local/share/lua/5.4/luacheck/parser.lua".to_string() + ); + assert_eq!(sttr[0].debug.line, 28); + assert_eq!(sttr[0].function, "luacheck.parser.syntax_error".to_string()); + assert_eq!( + sttr[1].debug.file, + "/usr/local/share/lua/5.4/luacheck/parser.lua".to_string() + ); + assert_eq!(sttr[1].debug.line, 96); + assert_eq!(sttr[1].function, "parse_error".to_string()); + assert_eq!( + sttr[2].debug.file, + "/usr/local/share/lua/5.4/luacheck/parser.lua".to_string() + ); + assert_eq!(sttr[2].debug.line, 535); + assert_eq!(sttr[2].function, "parse_simple_expression".to_string()); + assert_eq!( + sttr[3].debug.file, + "/usr/local/share/lua/5.4/luacheck/parser.lua".to_string() + ); + assert_eq!(sttr[3].debug.line, 894); + assert_eq!( + sttr[3].function, + "/usr/local/share/lua/5.4/luacheck/parser.lua:886".to_string() + ); + assert_eq!( + sttr[4].debug.file, + "/usr/local/share/lua/5.4/luacheck/parser.lua".to_string() + ); + assert_eq!(sttr[4].debug.line, 974); + assert_eq!(sttr[4].function, "parse_block".to_string()); + assert_eq!( + sttr[5].debug.file, + "/usr/local/share/lua/5.4/luacheck/parser.lua".to_string() + ); + assert_eq!(sttr[5].debug.line, 1020); + assert_eq!(sttr[5].function, "luacheck.parser.parse".to_string()); + assert_eq!(sttr[6].debug.file, "luacheck_parser_parse.lua".to_string()); + assert_eq!(sttr[6].debug.line, 6); + assert_eq!(sttr[6].function, "TestOneInput".to_string()); + assert_eq!(sttr[7].debug.file, "luacheck_parser_parse.lua".to_string()); + assert_eq!(sttr[7].debug.line, 18); + assert_eq!(sttr[7].function, "main chunk".to_string()); + } +} diff --git a/libcasr/src/python.rs b/libcasr/src/python.rs index bd2b60d6..759a2d9e 100644 --- a/libcasr/src/python.rs +++ b/libcasr/src/python.rs @@ -59,7 +59,7 @@ impl ParseStacktrace for PythonStacktrace { stentry.debug.line = line; } else { return Err(Error::Casr(format!( - "Couldn't parse stacktrace line num: {entry}" + "Couldn't parse stacktrace line number: {entry}" ))); }; stentry.function = cap.get(3).unwrap().as_str().to_string(); From 68aba4ccd2f5379b40938eb5c4c5466274dc73be Mon Sep 17 00:00:00 2001 From: hkctkuy Date: Fri, 29 Nov 2024 22:48:39 +0300 Subject: [PATCH 02/15] Add CrashLineExt impl --- .gitignore | 1 + libcasr/src/lua.rs | 119 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 111 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index b3cec92b..10ea9c40 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ Cargo.lock */tests/casr_tests/csharp/*/bin */tests/casr_tests/csharp/*/obj *.swp +*.swo node_modules */node_modules/* .vscode diff --git a/libcasr/src/lua.rs b/libcasr/src/lua.rs index 1b302e7b..adc3567b 100644 --- a/libcasr/src/lua.rs +++ b/libcasr/src/lua.rs @@ -1,6 +1,5 @@ -//! Lua module implements `ParseStacktrace`, `Exception` traits for Lua reports. +//! Lua module implements `ParseStacktrace`, `CrashLineExt` and `Severity` traits for Lua reports. use crate::error::*; -use crate::exception::Exception; use crate::execution_class::ExecutionClass; use crate::severity::Severity; use crate::stacktrace::{CrashLine, CrashLineExt, DebugInfo}; @@ -8,6 +7,42 @@ use crate::stacktrace::{ParseStacktrace, Stacktrace, StacktraceEntry}; use regex::Regex; +// TODO: Adjust terms: Error? Exception? Warnings? Etc? +/// Structure provides an interface for save parsing lua exception. +#[derive(Clone, Debug)] +pub struct LuaException { + message: String, +} + +impl LuaException { + /// Create new `LuaException` instance from stream + pub fn new(stream: &str) -> Option { + let re = Regex::new( + r#"(?:lua|luajit):.+\n(\s+)stack traceback:\n(?:.*\n)*(\s+)\[C\]: (?:in|at) .+"#, + ) + .unwrap(); + let mat = re.find(stream).unwrap(); + Some(LuaException { + message: mat.as_str().to_string(), + }) + } + /// Extract stack trace from lua message. + pub fn extract_stacktrace(&self) -> Result> { + LuaStacktrace::extract_stacktrace(&self.message) + } + /// Get lua runtime error message as a vector of lines. + pub fn parse_stacktrace(&self) -> Result { + LuaStacktrace::parse_stacktrace(&self.extract_stacktrace()?) + } + /// Get lua runtime error message as a vector of lines. + pub fn lines(&self) -> Vec { + self.message + .split('\n') + .map(|s| s.trim().to_string()) + .collect() + } +} + /// Structure provides an interface for processing the stack trace. pub struct LuaStacktrace; @@ -26,7 +61,7 @@ impl ParseStacktrace for LuaStacktrace { )); }; - let re = Regex::new(r#"\S+:(?:|\d+:) in .+"#).unwrap(); + let re = Regex::new(r#".+:(?:|\d+:) in .+"#).unwrap(); Ok(stacktrace[first..] .iter() .map(|s| s.trim().to_string()) @@ -36,7 +71,7 @@ impl ParseStacktrace for LuaStacktrace { fn parse_stacktrace_entry(entry: &str) -> Result { let mut stentry = StacktraceEntry::default(); - let re = Regex::new(r#"(\S+):(\d+): in (\S+ )(\S+)"#).unwrap(); + let re = Regex::new(r#"(.+):(\d+): in (\S+ )(\S+)"#).unwrap(); let Some(cap) = re.captures(entry) else { return Err(Error::Casr(format!( "Couldn't parse stacktrace line: {entry}" @@ -86,11 +121,39 @@ impl ParseStacktrace for LuaStacktrace { } } -/// Structure provides an interface for parsing lua exception message. -pub struct LuaException; -impl Exception for LuaException { - fn parse_exception(stderr: &str) -> Option { - None +impl CrashLineExt for LuaException { + fn crash_line(&self) -> Result { + let lines = self.lines(); + let re = Regex::new(r#"(?:lua|luajit): (.+):(\d+):"#).unwrap(); + let mut cap = re.captures(&lines[0]); + if cap.is_none() { + let re = Regex::new(r#"(.+):(\d+):"#).unwrap(); + for line in &lines[2..] { + cap = re.captures(&line); + if cap.is_some() { + break; + } + } + } + if let Some(cap) = cap { + let file = cap.get(1).unwrap().as_str().to_string(); + let Ok(line) = cap.get(2).unwrap().as_str().parse::() else { + return Err(Error::Casr(format!("Couldn't crashline line number"))); + }; + Ok(CrashLine::Source(DebugInfo { + file, + line, + column: 0, + })) + } else { + Err(Error::Casr(format!("Crashline is not found"))) + } + } +} + +impl Severity for LuaException { + fn severity(&self) -> Result { + Err(Error::Casr(format!("WRITE ME!"))) } } @@ -172,4 +235,42 @@ mod tests { assert_eq!(sttr[7].debug.line, 18); assert_eq!(sttr[7].function, "main chunk".to_string()); } + #[test] + fn test_lua_exception() { + let stream = " + luajit: (command line):1: crash + stack traceback: + [C]: in function 'error' + (command line):1: in main chunk + [C]: at 0x607f3df872e0 + "; + let exception = LuaException::new(stream); + let Some(exception) = exception else { + panic!("{:?}", exception); + }; + + let lines = exception.lines(); + assert_eq!(lines.len(), 5); + + let sttr = exception.extract_stacktrace(); + let Ok(sttr) = sttr else { + panic!("{}", sttr.err().unwrap()); + }; + assert_eq!(sttr.len(), 2); + + let sttr = exception.parse_stacktrace(); + let Ok(sttr) = sttr else { + panic!("{}", sttr.err().unwrap()); + }; + assert_eq!(sttr.len(), 1); + assert_eq!(sttr[0].debug.file, "(command line)".to_string()); + assert_eq!(sttr[0].debug.line, 1); + assert_eq!(sttr[0].function, "main chunk".to_string()); + + let crashline = exception.crash_line(); + let Ok(crashline) = crashline else { + panic!("{}", crashline.err().unwrap()); + }; + assert_eq!(crashline.to_string(), "(command line):1"); + } } From 0b1c712c26eed225baf7c57e6d78a8778269be5c Mon Sep 17 00:00:00 2001 From: hkctkuy Date: Fri, 29 Nov 2024 23:06:57 +0300 Subject: [PATCH 03/15] Add Severity impl --- libcasr/src/lua.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/libcasr/src/lua.rs b/libcasr/src/lua.rs index adc3567b..0c87d75a 100644 --- a/libcasr/src/lua.rs +++ b/libcasr/src/lua.rs @@ -153,7 +153,21 @@ impl CrashLineExt for LuaException { impl Severity for LuaException { fn severity(&self) -> Result { - Err(Error::Casr(format!("WRITE ME!"))) + let re = Regex::new(r#"(?:lua|luajit):(?: .+:)? (.+)"#).unwrap(); + let lines = self.lines(); + let description = lines.first().unwrap(); + let Some(cap) = re.captures(description) else { + return Err(Error::Casr(format!( + "Couldn't parse exception description: {description}" + ))); + }; + let description = cap.get(1).unwrap().as_str().to_string(); + Ok(ExecutionClass::new(( + "NOT_EXPLOITABLE", + &description, + &description, + "", + ))) } } @@ -272,5 +286,14 @@ mod tests { panic!("{}", crashline.err().unwrap()); }; assert_eq!(crashline.to_string(), "(command line):1"); + + let execution_class = exception.severity(); + let Ok(execution_class) = execution_class else { + panic!("{}", execution_class.err().unwrap()); + }; + assert_eq!(execution_class.severity, "NOT_EXPLOITABLE"); + assert_eq!(execution_class.short_description, "crash"); + assert_eq!(execution_class.description, "crash"); + assert_eq!(execution_class.explanation, ""); } } From 8287fb07fd2242438313c6601669ff0104e322af Mon Sep 17 00:00:00 2001 From: hkctkuy Date: Fri, 29 Nov 2024 23:51:54 +0300 Subject: [PATCH 04/15] FIx clippy --- libcasr/src/lua.rs | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/libcasr/src/lua.rs b/libcasr/src/lua.rs index 0c87d75a..3941aa7f 100644 --- a/libcasr/src/lua.rs +++ b/libcasr/src/lua.rs @@ -129,25 +129,27 @@ impl CrashLineExt for LuaException { if cap.is_none() { let re = Regex::new(r#"(.+):(\d+):"#).unwrap(); for line in &lines[2..] { - cap = re.captures(&line); + cap = re.captures(line); if cap.is_some() { break; } } } - if let Some(cap) = cap { - let file = cap.get(1).unwrap().as_str().to_string(); - let Ok(line) = cap.get(2).unwrap().as_str().parse::() else { - return Err(Error::Casr(format!("Couldn't crashline line number"))); - }; - Ok(CrashLine::Source(DebugInfo { - file, - line, - column: 0, - })) - } else { - Err(Error::Casr(format!("Crashline is not found"))) - } + let Some(cap) = cap else { + return Err(Error::Casr(format!("Crashline is not found: {:?}", lines))); + }; + let file = cap.get(1).unwrap().as_str().to_string(); + let line = cap.get(2).unwrap().as_str(); + let Ok(line) = line.parse::() else { + return Err(Error::Casr(format!( + "Couldn't crashline line number: {line}" + ))); + }; + Ok(CrashLine::Source(DebugInfo { + file, + line, + column: 0, + })) } } From 0edebae54e96180c0ed08441619e2529a40462ba Mon Sep 17 00:00:00 2001 From: hkctkuy Date: Sat, 30 Nov 2024 01:06:17 +0300 Subject: [PATCH 05/15] Add casr-lua --- casr/src/bin/casr-lua.rs | 155 ++++++++++++++++++++++++++++++++++++++ libcasr/src/constants.rs | 12 +++ libcasr/src/lua.rs | 24 +++--- libcasr/src/report.rs | 32 ++++++++ libcasr/src/stacktrace.rs | 38 ++++++---- libcasr/src/ubsan.rs | 4 +- 6 files changed, 234 insertions(+), 31 deletions(-) create mode 100644 casr/src/bin/casr-lua.rs diff --git a/casr/src/bin/casr-lua.rs b/casr/src/bin/casr-lua.rs new file mode 100644 index 00000000..194b52ef --- /dev/null +++ b/casr/src/bin/casr-lua.rs @@ -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 \"-- \" to run"), + ) + .get_matches(); + + init_ignored_frames!("lua"); + if let Some(path) = matches.get_one::("ignore") { + util::add_custom_ignored_frames(path)?; + } + // Get program args. + let argv: Vec<&str> = if let Some(argvs) = matches.get_many::("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::("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)?); + } + if argv.len() > 1 { + cmd.args(&argv[1..]); + } + 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(); + } + } + } + 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!"); + }; + + // 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; + } + } + } + let stacktrace = exception.parse_stacktrace()?; + if let Some(path) = matches.get_one::("strip-path") { + util::strip_paths(&mut report, &stacktrace, path); + } + + //Output report + util::output_report(&report, &matches, &argv) +} diff --git a/libcasr/src/constants.rs b/libcasr/src/constants.rs index 6fe55b9f..e8f79020 100644 --- a/libcasr/src/constants.rs +++ b/libcasr/src/constants.rs @@ -37,6 +37,12 @@ pub const STACK_FRAME_FUNCTION_IGNORE_REGEXES_JS: &[&str] = &[ r"^$", ]; +/// Regular expressions for lua functions to be ignored. +pub const STACK_FRAME_FUNCTION_IGNORE_REGEXES_LUA: &[&str] = &[ + // TODO + r"^[^.]$", +]; + /// Regular expressions for python functions to be ignored. pub const STACK_FRAME_FUNCTION_IGNORE_REGEXES_PYTHON: &[&str] = &[ // TODO @@ -256,6 +262,12 @@ pub const STACK_FRAME_FILEPATH_IGNORE_REGEXES_JS: &[&str] = &[ r"node_modules/jsfuzz", ]; +/// Regular expressions for paths to lua files that should be ignored. +pub const STACK_FRAME_FILEPATH_IGNORE_REGEXES_LUA: &[&str] = &[ + // TODO + r"^[^.]$", +]; + /// Regular expressions for paths to python files that should be ignored. pub const STACK_FRAME_FILEPATH_IGNORE_REGEXES_PYTHON: &[&str] = &[ // TODO diff --git a/libcasr/src/lua.rs b/libcasr/src/lua.rs index 3941aa7f..589d03cf 100644 --- a/libcasr/src/lua.rs +++ b/libcasr/src/lua.rs @@ -17,25 +17,23 @@ pub struct LuaException { impl LuaException { /// Create new `LuaException` instance from stream pub fn new(stream: &str) -> Option { - let re = Regex::new( - r#"(?:lua|luajit):.+\n(\s+)stack traceback:\n(?:.*\n)*(\s+)\[C\]: (?:in|at) .+"#, - ) - .unwrap(); + let re = Regex::new(r#"\S+:.+\n(\s+)stack traceback:\n(?:.*\n)*(\s+)\[C\]: (?:in|at) .+"#) + .unwrap(); let mat = re.find(stream).unwrap(); Some(LuaException { message: mat.as_str().to_string(), }) } - /// Extract stack trace from lua message. + /// Extract stack trace from lua exception. pub fn extract_stacktrace(&self) -> Result> { LuaStacktrace::extract_stacktrace(&self.message) } - /// Get lua runtime error message as a vector of lines. + /// Transform lua exception into `Stacktrace` type. pub fn parse_stacktrace(&self) -> Result { LuaStacktrace::parse_stacktrace(&self.extract_stacktrace()?) } - /// Get lua runtime error message as a vector of lines. - pub fn lines(&self) -> Vec { + /// Get lua exception as a vector of lines. + pub fn lua_report(&self) -> Vec { self.message .split('\n') .map(|s| s.trim().to_string()) @@ -123,8 +121,8 @@ impl ParseStacktrace for LuaStacktrace { impl CrashLineExt for LuaException { fn crash_line(&self) -> Result { - let lines = self.lines(); - let re = Regex::new(r#"(?:lua|luajit): (.+):(\d+):"#).unwrap(); + let lines = self.lua_report(); + let re = Regex::new(r#"\S+: (.+):(\d+):"#).unwrap(); let mut cap = re.captures(&lines[0]); if cap.is_none() { let re = Regex::new(r#"(.+):(\d+):"#).unwrap(); @@ -155,8 +153,8 @@ impl CrashLineExt for LuaException { impl Severity for LuaException { fn severity(&self) -> Result { - let re = Regex::new(r#"(?:lua|luajit):(?: .+:)? (.+)"#).unwrap(); - let lines = self.lines(); + let re = Regex::new(r#"\S+:(?: .+:)? (.+)"#).unwrap(); + let lines = self.lua_report(); let description = lines.first().unwrap(); let Some(cap) = re.captures(description) else { return Err(Error::Casr(format!( @@ -265,7 +263,7 @@ mod tests { panic!("{:?}", exception); }; - let lines = exception.lines(); + let lines = exception.lua_report(); assert_eq!(lines.len(), 5); let sttr = exception.extract_stacktrace(); diff --git a/libcasr/src/report.rs b/libcasr/src/report.rs index 52ae6e03..646c3898 100644 --- a/libcasr/src/report.rs +++ b/libcasr/src/report.rs @@ -8,6 +8,7 @@ use crate::gdb::GdbStacktrace; use crate::go::GoStacktrace; use crate::java::JavaStacktrace; use crate::js::JsStacktrace; +use crate::lua::LuaStacktrace; use crate::python::PythonStacktrace; use crate::rust::RustStacktrace; use crate::stacktrace::*; @@ -192,6 +193,13 @@ pub struct CrashReport { )] #[cfg_attr(feature = "serde", serde(default))] pub ubsan_report: Vec, + /// Lua report. + #[cfg_attr( + feature = "serde", + serde(rename(serialize = "LuaReport", deserialize = "LuaReport")) + )] + #[cfg_attr(feature = "serde", serde(default))] + pub lua_report: Vec, /// Python report. #[cfg_attr( feature = "serde", @@ -590,6 +598,8 @@ impl CrashReport { JsStacktrace::parse_stacktrace(&self.stacktrace)? } else if !self.csharp_report.is_empty() { CSharpStacktrace::parse_stacktrace(&self.stacktrace)? + } else if !self.lua_report.is_empty() { + LuaStacktrace::parse_stacktrace(&self.stacktrace)? } else { GdbStacktrace::parse_stacktrace(&self.stacktrace)? }; @@ -739,6 +749,14 @@ impl fmt::Display for CrashReport { report += &(self.ubsan_report.join("\n") + "\n"); } + // LuaReport + if !self.lua_report.is_empty() { + report += "\n===LuaReport===\n"; + for e in self.lua_report.iter() { + report += &format!("{e}\n"); + } + } + // PythonReport if !self.python_report.is_empty() { report += "\n===PythonReport===\n"; @@ -878,6 +896,13 @@ mod tests { "/home/hkctkuy/github/casr/casr/tests/tmp_tests_casr/test_casr_ubsan/test_ubsan.cpp:4:29: runtime error: signed integer overflow: 65535 * 32769 cannot be represented in type 'int'".to_string(), "SUMMARY: UndefinedBehaviorSanitizer: signed-integer-overflow /home/hkctkuy/github/casr/casr/tests/tmp_tests_casr/test_casr_ubsan/test_ubsan.cpp:4:29 in".to_string(), ]; + report.lua_report = vec![ + "luajit: (command line):1: crash".to_string(), + "stack traceback:".to_string(), + "[C]: in function 'error'".to_string(), + "(command line):1: in main chunk".to_string(), + "[C]: at 0x607f3df872e0".to_string(), + ]; report.python_report = vec![ " === Uncaught Python exception: ===".to_string(), "TypeError: unhashable type: 'list'".to_string(), @@ -965,6 +990,13 @@ mod tests { "/home/hkctkuy/github/casr/casr/tests/tmp_tests_casr/test_casr_ubsan/test_ubsan.cpp:4:29: runtime error: signed integer overflow: 65535 * 32769 cannot be represented in type 'int'".to_string(), "SUMMARY: UndefinedBehaviorSanitizer: signed-integer-overflow /home/hkctkuy/github/casr/casr/tests/tmp_tests_casr/test_casr_ubsan/test_ubsan.cpp:4:29 in".to_string(), "".to_string(), + "===LuaReport===".to_string(), + "luajit: (command line):1: crash".to_string(), + "stack traceback:".to_string(), + "[C]: in function 'error'".to_string(), + "(command line):1: in main chunk".to_string(), + "[C]: at 0x607f3df872e0".to_string(), + "".to_string(), "===PythonReport===".to_string(), " === Uncaught Python exception: ===".to_string(), "TypeError: unhashable type: 'list'".to_string(), diff --git a/libcasr/src/stacktrace.rs b/libcasr/src/stacktrace.rs index e52ad5f8..c16e4905 100644 --- a/libcasr/src/stacktrace.rs +++ b/libcasr/src/stacktrace.rs @@ -5,12 +5,14 @@ extern crate lazy_static; use crate::constants::{ STACK_FRAME_FILEPATH_IGNORE_REGEXES_CPP, STACK_FRAME_FILEPATH_IGNORE_REGEXES_CSHARP, STACK_FRAME_FILEPATH_IGNORE_REGEXES_GO, STACK_FRAME_FILEPATH_IGNORE_REGEXES_JAVA, - STACK_FRAME_FILEPATH_IGNORE_REGEXES_JS, STACK_FRAME_FILEPATH_IGNORE_REGEXES_PYTHON, - STACK_FRAME_FILEPATH_IGNORE_REGEXES_RUST, STACK_FRAME_FUNCTION_IGNORE_REGEXES_CPP, - STACK_FRAME_FUNCTION_IGNORE_REGEXES_CSHARP, STACK_FRAME_FUNCTION_IGNORE_REGEXES_GO, - STACK_FRAME_FUNCTION_IGNORE_REGEXES_JAVA, STACK_FRAME_FUNCTION_IGNORE_REGEXES_JS, + STACK_FRAME_FILEPATH_IGNORE_REGEXES_JS, STACK_FRAME_FILEPATH_IGNORE_REGEXES_LUA, + STACK_FRAME_FILEPATH_IGNORE_REGEXES_PYTHON, STACK_FRAME_FILEPATH_IGNORE_REGEXES_RUST, + STACK_FRAME_FUNCTION_IGNORE_REGEXES_CPP, STACK_FRAME_FUNCTION_IGNORE_REGEXES_CSHARP, + STACK_FRAME_FUNCTION_IGNORE_REGEXES_GO, STACK_FRAME_FUNCTION_IGNORE_REGEXES_JAVA, + STACK_FRAME_FUNCTION_IGNORE_REGEXES_JS, STACK_FRAME_FUNCTION_IGNORE_REGEXES_LUA, STACK_FRAME_FUNCTION_IGNORE_REGEXES_PYTHON, STACK_FRAME_FUNCTION_IGNORE_REGEXES_RUST, }; + use crate::error::*; use kodama::{linkage, Method}; use regex::Regex; @@ -323,18 +325,14 @@ pub trait Filter { let (funcs, files): (Vec<_>, Vec<_>) = languages .iter() .map(|&x| match x { - "python" => ( - STACK_FRAME_FUNCTION_IGNORE_REGEXES_PYTHON, - STACK_FRAME_FILEPATH_IGNORE_REGEXES_PYTHON, - ), - "rust" => ( - STACK_FRAME_FUNCTION_IGNORE_REGEXES_RUST, - STACK_FRAME_FILEPATH_IGNORE_REGEXES_RUST, - ), "cpp" => ( STACK_FRAME_FUNCTION_IGNORE_REGEXES_CPP, STACK_FRAME_FILEPATH_IGNORE_REGEXES_CPP, ), + "csharp" => ( + STACK_FRAME_FUNCTION_IGNORE_REGEXES_CSHARP, + STACK_FRAME_FILEPATH_IGNORE_REGEXES_CSHARP, + ), "go" => ( STACK_FRAME_FUNCTION_IGNORE_REGEXES_GO, STACK_FRAME_FILEPATH_IGNORE_REGEXES_GO, @@ -347,9 +345,17 @@ pub trait Filter { STACK_FRAME_FUNCTION_IGNORE_REGEXES_JS, STACK_FRAME_FILEPATH_IGNORE_REGEXES_JS, ), - "csharp" => ( - STACK_FRAME_FUNCTION_IGNORE_REGEXES_CSHARP, - STACK_FRAME_FILEPATH_IGNORE_REGEXES_CSHARP, + "lua" => ( + STACK_FRAME_FUNCTION_IGNORE_REGEXES_LUA, + STACK_FRAME_FILEPATH_IGNORE_REGEXES_LUA, + ), + "python" => ( + STACK_FRAME_FUNCTION_IGNORE_REGEXES_PYTHON, + STACK_FRAME_FILEPATH_IGNORE_REGEXES_PYTHON, + ), + "rust" => ( + STACK_FRAME_FUNCTION_IGNORE_REGEXES_RUST, + STACK_FRAME_FILEPATH_IGNORE_REGEXES_RUST, ), &_ => (["^[^.]$"].as_slice(), ["^[^.]$"].as_slice()), }) @@ -474,7 +480,7 @@ pub mod tests { let mut is_inited = INITED_STACKFRAMES_FILTER.write().unwrap(); if !*is_inited { *is_inited = true; - init_ignored_frames!("cpp", "rust", "python", "go", "java", "js", "csharp"); + init_ignored_frames!("cpp", "csharp", "go", "java", "js", "lua", "python", "rust"); } } diff --git a/libcasr/src/ubsan.rs b/libcasr/src/ubsan.rs index 4d014b71..b92c3d66 100644 --- a/libcasr/src/ubsan.rs +++ b/libcasr/src/ubsan.rs @@ -1,11 +1,11 @@ //! UndefinedBehaviorSanitizer module implements `Severity` and `CrashLineExt` traits for UndefinedBehaviorSanitizer warnings. use crate::asan::AsanStacktrace; +use crate::error::{Error, Result}; +use crate::execution_class::ExecutionClass; use crate::severity::Severity; use crate::stacktrace::{CrashLine, CrashLineExt, DebugInfo}; use crate::stacktrace::{ParseStacktrace, StacktraceEntry}; -use crate::error::*; -use crate::execution_class::ExecutionClass; use regex::Regex; /// Structure provides an interface for parsing ubsan runtime error message. From 0d8c6e594ea91811c3b2459539074e95385f17ec Mon Sep 17 00:00:00 2001 From: hkctkuy Date: Sat, 30 Nov 2024 01:41:30 +0300 Subject: [PATCH 06/15] Add bin test --- casr/tests/casr_tests/lua/test_casr_lua.lua | 23 ++++++++++++ casr/tests/tests.rs | 41 +++++++++++++++++++++ libcasr/src/lua.rs | 7 ++-- 3 files changed, 68 insertions(+), 3 deletions(-) create mode 100755 casr/tests/casr_tests/lua/test_casr_lua.lua diff --git a/casr/tests/casr_tests/lua/test_casr_lua.lua b/casr/tests/casr_tests/lua/test_casr_lua.lua new file mode 100755 index 00000000..f1db751e --- /dev/null +++ b/casr/tests/casr_tests/lua/test_casr_lua.lua @@ -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()) diff --git a/casr/tests/tests.rs b/casr/tests/tests.rs index 1e7fe468..fe4a9863 100644 --- a/casr/tests/tests.rs +++ b/casr/tests/tests.rs @@ -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")); @@ -4856,6 +4857,46 @@ 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 = 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() { diff --git a/libcasr/src/lua.rs b/libcasr/src/lua.rs index 589d03cf..dd12c74d 100644 --- a/libcasr/src/lua.rs +++ b/libcasr/src/lua.rs @@ -7,7 +7,6 @@ use crate::stacktrace::{ParseStacktrace, Stacktrace, StacktraceEntry}; use regex::Regex; -// TODO: Adjust terms: Error? Exception? Warnings? Etc? /// Structure provides an interface for save parsing lua exception. #[derive(Clone, Debug)] pub struct LuaException { @@ -17,9 +16,11 @@ pub struct LuaException { impl LuaException { /// Create new `LuaException` instance from stream pub fn new(stream: &str) -> Option { - let re = Regex::new(r#"\S+:.+\n(\s+)stack traceback:\n(?:.*\n)*(\s+)\[C\]: (?:in|at) .+"#) + let re = Regex::new(r#"\S+: .+\n\s*stack traceback:\n(?:.*\n)*\s+\[C\]: (?:in|at) .+"#) .unwrap(); - let mat = re.find(stream).unwrap(); + let Some(mat) = re.find(stream) else { + return None; + }; Some(LuaException { message: mat.as_str().to_string(), }) From 15de25f67d8c79dc01b81d9d106ac98d6f6100d9 Mon Sep 17 00:00:00 2001 From: hkctkuy Date: Sat, 30 Nov 2024 01:48:24 +0300 Subject: [PATCH 07/15] Update workflows --- .github/workflows/aarch64.yml | 3 ++- .github/workflows/amd64.yml | 2 +- .github/workflows/coverage.yaml | 2 +- .github/workflows/darwin-arm64.yml | 2 +- .github/workflows/riscv64.yml | 4 ++-- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/aarch64.yml b/.github/workflows/aarch64.yml index ecd48192..ea02c069 100644 --- a/.github/workflows/aarch64.yml +++ b/.github/workflows/aarch64.yml @@ -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: | diff --git a/.github/workflows/amd64.yml b/.github/workflows/amd64.yml index bd264b87..24466478 100644 --- a/.github/workflows/amd64.yml +++ b/.github/workflows/amd64.yml @@ -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 diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 7fae8fa4..3d641a47 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -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 diff --git a/.github/workflows/darwin-arm64.yml b/.github/workflows/darwin-arm64.yml index 3180fd98..9e24b064 100644 --- a/.github/workflows/darwin-arm64.yml +++ b/.github/workflows/darwin-arm64.yml @@ -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 diff --git a/.github/workflows/riscv64.yml b/.github/workflows/riscv64.yml index 9cee8adb..b7141c5d 100644 --- a/.github/workflows/riscv64.yml +++ b/.github/workflows/riscv64.yml @@ -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: | From 186078b6e7e8e8df1387c329ff6ef4144e6cb764 Mon Sep 17 00:00:00 2001 From: hkctkuy Date: Sat, 30 Nov 2024 01:58:38 +0300 Subject: [PATCH 08/15] Update docs --- README.md | 21 ++++++++++++------- casr/src/bin/casr-lua.rs | 2 +- docs/usage.md | 45 ++++++++++++++++++++++++++++++++-------- 3 files changed, 50 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index a6d6f6d4..fc638cbc 100644 --- a/README.md +++ b/README.md @@ -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, @@ -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. @@ -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 diff --git a/casr/src/bin/casr-lua.rs b/casr/src/bin/casr-lua.rs index 194b52ef..72c318d4 100644 --- a/casr/src/bin/casr-lua.rs +++ b/casr/src/bin/casr-lua.rs @@ -17,7 +17,7 @@ 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") + .about("Create CASR reports (.casrep) from Lua reports") .term_width(90) .arg( Arg::new("output") diff --git a/docs/usage.md b/docs/usage.md index be98355f..2251f8d8 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -11,15 +11,16 @@ 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). `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). `casr-dojo` allows to upload new and -unique CASR reports to [DefectDojo](https://github.com/DefectDojo/django-DefectDojo). -`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`. +[Sharpfuzz](https://github.com/Metalnem/sharpfuzz). 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). `casr-dojo` allows to +upload new and unique CASR reports to +[DefectDojo](https://github.com/DefectDojo/django-DefectDojo). `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`. ## casr-gdb @@ -232,6 +233,32 @@ Run casr-csharp: $ casr-csharp -o csharp.casrep -- dotnet run --project casr/tests/casr_tests/csharp/test_casr_csharp/test_casr_csharp.csproj +## casr-lua + +Create CASR reports (.casrep) from Lua reports + + Usage: casr-lua [OPTIONS] <--stdout|--output > -- ... + + Arguments: + ... Add "-- " to run + + Options: + -o, --output Path to save report. Path can be a directory, then report + name is generated + --stdout Print CASR report to stdout + --stdin Stdin file for program + -t, --timeout Timeout (in seconds) for target execution, 0 value means that + timeout is disabled [default: 0] + --ignore File with regular expressions for functions and file paths + that should be ignored + --strip-path Path prefix to strip from stacktrace [env: CASR_STRIP_PATH=] + -h, --help Print help + -V, --version Print version + +Run casr-lua: + + $ casr-lua -o lua.casrep -- casr/tests/casr_tests/lua/test_casr_lua.lua + ## casr-core Analyze coredump for security goals and provide detailed report with severity estimation From d644b14eb8af0b288917ad033e8e73b4d5572df1 Mon Sep 17 00:00:00 2001 From: hkctkuy Date: Sat, 30 Nov 2024 02:05:31 +0300 Subject: [PATCH 09/15] Update casr-cli --- casr/src/bin/casr-cli.rs | 14 ++++++++++++++ libcasr/src/lua.rs | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/casr/src/bin/casr-cli.rs b/casr/src/bin/casr-cli.rs index 2676c0b4..3eed3ff7 100644 --- a/casr/src/bin/casr-cli.rs +++ b/casr/src/bin/casr-cli.rs @@ -429,6 +429,16 @@ fn build_tree_report( 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); + } + if !report.java_report.is_empty() { row = tree .insert_container_item("JavaReport".to_string(), Placement::After, row) @@ -649,6 +659,10 @@ fn build_slider_report( select.add_item("PythonReport", report.python_report.join("\n")); } + if !report.lua_report.is_empty() { + select.add_item("LuaReport", report.lua_report.join("\n")); + } + if !report.java_report.is_empty() { select.add_item("JavaReport", report.java_report.join("\n")); } diff --git a/libcasr/src/lua.rs b/libcasr/src/lua.rs index dd12c74d..5bd4276e 100644 --- a/libcasr/src/lua.rs +++ b/libcasr/src/lua.rs @@ -37,7 +37,7 @@ impl LuaException { pub fn lua_report(&self) -> Vec { self.message .split('\n') - .map(|s| s.trim().to_string()) + .map(|s| s.trim_end().to_string()) .collect() } } From 9542a371fab9672233c180c4159105bd6e59c23f Mon Sep 17 00:00:00 2001 From: hkctkuy Date: Sat, 30 Nov 2024 02:08:24 +0300 Subject: [PATCH 10/15] Update gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 10ea9c40..91217064 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,8 @@ Cargo.lock */tests/tmp_tests_casr */tests/casr_tests/csharp/*/bin */tests/casr_tests/csharp/*/obj -*.swp *.swo +*.swp node_modules */node_modules/* .vscode From e3e8c3a123929c1d13d9c3c3507d51fb27452fe0 Mon Sep 17 00:00:00 2001 From: hkctkuy Date: Sat, 30 Nov 2024 02:15:18 +0300 Subject: [PATCH 11/15] FIx clippy --- casr/tests/tests.rs | 1 - libcasr/src/lua.rs | 8 +++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/casr/tests/tests.rs b/casr/tests/tests.rs index fe4a9863..ed79e335 100644 --- a/casr/tests/tests.rs +++ b/casr/tests/tests.rs @@ -4896,7 +4896,6 @@ fn test_casr_lua() { } } - #[test] #[cfg(target_arch = "x86_64")] fn test_casr_js() { diff --git a/libcasr/src/lua.rs b/libcasr/src/lua.rs index 5bd4276e..cc73ae0d 100644 --- a/libcasr/src/lua.rs +++ b/libcasr/src/lua.rs @@ -16,11 +16,9 @@ pub struct LuaException { impl LuaException { /// Create new `LuaException` instance from stream pub fn new(stream: &str) -> Option { - let re = Regex::new(r#"\S+: .+\n\s*stack traceback:\n(?:.*\n)*\s+\[C\]: (?:in|at) .+"#) - .unwrap(); - let Some(mat) = re.find(stream) else { - return None; - }; + let re = + Regex::new(r#"\S+: .+\n\s*stack traceback:\n(?:.*\n)*\s+\[C\]: (?:in|at) .+"#).unwrap(); + let mat = re.find(stream)?; Some(LuaException { message: mat.as_str().to_string(), }) From 7c7c7c2473c4020ee30090109462cf2cf9cd2038 Mon Sep 17 00:00:00 2001 From: hkctkuy Date: Sat, 30 Nov 2024 23:04:33 +0300 Subject: [PATCH 12/15] Add fuzzing --- libcasr/fuzz/fuzz_targets/parse_stacktrace.rs | 31 ++++++++++++------- libcasr/fuzz/init_corpus/lua | 13 ++++++++ 2 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 libcasr/fuzz/init_corpus/lua diff --git a/libcasr/fuzz/fuzz_targets/parse_stacktrace.rs b/libcasr/fuzz/fuzz_targets/parse_stacktrace.rs index 468209db..6f765f32 100644 --- a/libcasr/fuzz/fuzz_targets/parse_stacktrace.rs +++ b/libcasr/fuzz/fuzz_targets/parse_stacktrace.rs @@ -4,13 +4,14 @@ use libfuzzer_sys::fuzz_target; use libcasr::{ asan::AsanStacktrace, + csharp::CSharpStacktrace, gdb::GdbStacktrace, go::GoStacktrace, init_ignored_frames, java::JavaStacktrace, - python::PythonStacktrace, js::JsStacktrace, - csharp::CSharpStacktrace, + lua::LuaStacktrace, + python::PythonStacktrace, stacktrace::{CrashLineExt, Filter, ParseStacktrace, Stacktrace}, }; @@ -30,17 +31,17 @@ fuzz_target!(|data: &[u8]| { } } 1 => { - // Go - if let Ok(raw) = GoStacktrace::extract_stacktrace(&s) { - if let Ok(st) = GoStacktrace::parse_stacktrace(&raw) { + // C# + if let Ok(raw) = CSharpStacktrace::extract_stacktrace(&s) { + if let Ok(st) = CSharpStacktrace::parse_stacktrace(&raw) { let _ = st.crash_line(); } } } 2 => { - // Python - if let Ok(raw) = PythonStacktrace::extract_stacktrace(&s) { - if let Ok(st) = PythonStacktrace::parse_stacktrace(&raw) { + // Go + if let Ok(raw) = GoStacktrace::extract_stacktrace(&s) { + if let Ok(st) = GoStacktrace::parse_stacktrace(&raw) { let _ = st.crash_line(); } } @@ -62,9 +63,17 @@ fuzz_target!(|data: &[u8]| { } } 5 => { - // C# - if let Ok(raw) = CSharpStacktrace::extract_stacktrace(&s) { - if let Ok(st) = CSharpStacktrace::parse_stacktrace(&raw) { + // Lua + if let Ok(raw) = LuaStacktrace::extract_stacktrace(&s) { + if let Ok(st) = LuaStacktrace::parse_stacktrace(&raw) { + let _ = st.crash_line(); + } + } + } + 6 => { + // Python + if let Ok(raw) = PythonStacktrace::extract_stacktrace(&s) { + if let Ok(st) = PythonStacktrace::parse_stacktrace(&raw) { let _ = st.crash_line(); } } diff --git a/libcasr/fuzz/init_corpus/lua b/libcasr/fuzz/init_corpus/lua new file mode 100644 index 00000000..31821b31 --- /dev/null +++ b/libcasr/fuzz/init_corpus/lua @@ -0,0 +1,13 @@ +lua: /usr/local/share/lua/5.4/luacheck/parser.lua:28: error +stack traceback: + [C]: in function 'error' + /usr/local/share/lua/5.4/luacheck/parser.lua:28: in function 'luacheck.parser.syntax_error' + /usr/local/share/lua/5.4/luacheck/parser.lua:96: in upvalue 'parse_error' + /usr/local/share/lua/5.4/luacheck/parser.lua:535: in upvalue 'parse_simple_expression' + /usr/local/share/lua/5.4/luacheck/parser.lua:894: in function + (...tail calls...) + /usr/local/share/lua/5.4/luacheck/parser.lua:974: in upvalue 'parse_block' + /usr/local/share/lua/5.4/luacheck/parser.lua:1020: in function 'luacheck.parser.parse' + luacheck_parser_parse.lua:6: in local 'TestOneInput' + luacheck_parser_parse.lua:18: in main chunk + [C]: in ? From 94a023507e9fb6053e9c04a3c8c86c664de56460 Mon Sep 17 00:00:00 2001 From: hkctkuy Date: Mon, 2 Dec 2024 22:20:51 +0300 Subject: [PATCH 13/15] Rework regexps --- libcasr/src/lua.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/libcasr/src/lua.rs b/libcasr/src/lua.rs index cc73ae0d..55fe84df 100644 --- a/libcasr/src/lua.rs +++ b/libcasr/src/lua.rs @@ -16,8 +16,7 @@ pub struct LuaException { impl LuaException { /// Create new `LuaException` instance from stream pub fn new(stream: &str) -> Option { - let re = - Regex::new(r#"\S+: .+\n\s*stack traceback:\n(?:.*\n)*\s+\[C\]: (?:in|at) .+"#).unwrap(); + let re = Regex::new(r#"\S+: .+\n\s*stack traceback:\s*\n(?:.*\n)*.+: .+"#).unwrap(); let mat = re.find(stream)?; Some(LuaException { message: mat.as_str().to_string(), @@ -51,7 +50,7 @@ impl ParseStacktrace for LuaStacktrace { .collect::>(); let Some(first) = stacktrace .iter() - .position(|line| line.trim().starts_with("stack traceback:")) + .position(|line| line.trim().ends_with("stack traceback:")) else { return Err(Error::Casr( "Couldn't find traceback in lua report".to_string(), @@ -125,7 +124,16 @@ impl CrashLineExt for LuaException { let mut cap = re.captures(&lines[0]); if cap.is_none() { let re = Regex::new(r#"(.+):(\d+):"#).unwrap(); - for line in &lines[2..] { + let Some(index) = lines + .iter() + .rev() + .position(|line| line.trim().ends_with("stack traceback:")) + else { + return Err(Error::Casr( + "Couldn't find traceback in lua report".to_string(), + )); + }; + for line in &lines[index..] { cap = re.captures(line); if cap.is_some() { break; @@ -251,8 +259,10 @@ mod tests { #[test] fn test_lua_exception() { let stream = " - luajit: (command line):1: crash + custom-lua-interpreter: (command line):1: crash stack traceback: + some custom error message + stack traceback: [C]: in function 'error' (command line):1: in main chunk [C]: at 0x607f3df872e0 @@ -263,7 +273,7 @@ mod tests { }; let lines = exception.lua_report(); - assert_eq!(lines.len(), 5); + assert_eq!(lines.len(), 7); let sttr = exception.extract_stacktrace(); let Ok(sttr) = sttr else { From d4606fde8dc29d6b83c74dddb1ea51b9a29746af Mon Sep 17 00:00:00 2001 From: hkctkuy Date: Mon, 2 Dec 2024 22:43:56 +0300 Subject: [PATCH 14/15] Merge tests --- libcasr/src/lua.rs | 53 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/libcasr/src/lua.rs b/libcasr/src/lua.rs index 55fe84df..a6f79e8c 100644 --- a/libcasr/src/lua.rs +++ b/libcasr/src/lua.rs @@ -34,7 +34,7 @@ impl LuaException { pub fn lua_report(&self) -> Vec { self.message .split('\n') - .map(|s| s.trim_end().to_string()) + .map(|s| s.trim().to_string()) .collect() } } @@ -123,17 +123,16 @@ impl CrashLineExt for LuaException { let re = Regex::new(r#"\S+: (.+):(\d+):"#).unwrap(); let mut cap = re.captures(&lines[0]); if cap.is_none() { - let re = Regex::new(r#"(.+):(\d+):"#).unwrap(); let Some(index) = lines .iter() - .rev() - .position(|line| line.trim().ends_with("stack traceback:")) + .rposition(|line| line.ends_with("stack traceback:")) else { return Err(Error::Casr( "Couldn't find traceback in lua report".to_string(), )); }; - for line in &lines[index..] { + let re = Regex::new(r#"(.+):(\d+):"#).unwrap(); + for line in &lines[index + 1..] { cap = re.captures(line); if cap.is_some() { break; @@ -183,8 +182,8 @@ mod tests { use super::*; #[test] - fn test_lua_stacktrace() { - let raw_stacktrace = " + fn test_lua_exception() { + let stream = " lua: (error object is a table value) stack traceback: [C]: in function 'error' @@ -199,13 +198,21 @@ mod tests { luacheck_parser_parse.lua:18: in main chunk [C]: in ? "; - let sttr = LuaStacktrace::extract_stacktrace(raw_stacktrace); + let exception = LuaException::new(stream); + let Some(exception) = exception else { + panic!("{:?}", exception); + }; + + let lines = exception.lua_report(); + assert_eq!(lines.len(), 13); + + let sttr = exception.extract_stacktrace(); let Ok(sttr) = sttr else { panic!("{}", sttr.err().unwrap()); }; assert_eq!(sttr.len(), 10); - let sttr = LuaStacktrace::parse_stacktrace(&sttr); + let sttr = exception.parse_stacktrace(); let Ok(sttr) = sttr else { panic!("{}", sttr.err().unwrap()); }; @@ -255,9 +262,31 @@ mod tests { assert_eq!(sttr[7].debug.file, "luacheck_parser_parse.lua".to_string()); assert_eq!(sttr[7].debug.line, 18); assert_eq!(sttr[7].function, "main chunk".to_string()); - } - #[test] - fn test_lua_exception() { + + let crashline = exception.crash_line(); + let Ok(crashline) = crashline else { + panic!("{}", crashline.err().unwrap()); + }; + assert_eq!( + crashline.to_string(), + "/usr/local/share/lua/5.4/luacheck/parser.lua:28" + ); + + let execution_class = exception.severity(); + let Ok(execution_class) = execution_class else { + panic!("{}", execution_class.err().unwrap()); + }; + assert_eq!(execution_class.severity, "NOT_EXPLOITABLE"); + assert_eq!( + execution_class.short_description, + "(error object is a table value)" + ); + assert_eq!( + execution_class.description, + "(error object is a table value)" + ); + assert_eq!(execution_class.explanation, ""); + let stream = " custom-lua-interpreter: (command line):1: crash stack traceback: From 017113955d77f30d274c13b704e6608defd3e834 Mon Sep 17 00:00:00 2001 From: hkctkuy Date: Sun, 8 Dec 2024 18:09:48 +0300 Subject: [PATCH 15/15] Fixes --- libcasr/src/lua.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/libcasr/src/lua.rs b/libcasr/src/lua.rs index a6f79e8c..bc11adeb 100644 --- a/libcasr/src/lua.rs +++ b/libcasr/src/lua.rs @@ -46,21 +46,21 @@ impl ParseStacktrace for LuaStacktrace { fn extract_stacktrace(stream: &str) -> Result> { let stacktrace = stream .split('\n') - .map(|l| l.to_string()) + .map(|l| l.trim().to_string()) .collect::>(); let Some(first) = stacktrace .iter() - .position(|line| line.trim().ends_with("stack traceback:")) + .position(|line| line.ends_with("stack traceback:")) else { return Err(Error::Casr( "Couldn't find traceback in lua report".to_string(), )); }; - let re = Regex::new(r#".+:(?:|\d+:) in .+"#).unwrap(); + let re = Regex::new(r#".+: in .+"#).unwrap(); Ok(stacktrace[first..] .iter() - .map(|s| s.trim().to_string()) + .map(|s| s.to_string()) .filter(|s| re.is_match(s)) .collect::>()) } @@ -95,10 +95,7 @@ impl ParseStacktrace for LuaStacktrace { }; if let Some(appendix) = cap.get(3) { let appendix = appendix.as_str().to_string(); - if !appendix.starts_with("function") - && !appendix.starts_with("upvalue") - && !appendix.starts_with("local") - { + if appendix.starts_with("main") { stentry.function = appendix + &stentry.function; } }