From 2dad3cae47a7b6a9c9fdbfc8f3508c9ff41854dc Mon Sep 17 00:00:00 2001 From: Simon Kowallik Date: Sat, 21 Jan 2023 21:23:04 +0000 Subject: [PATCH] irulescan 1.1.0 - preprocess irules to support laxer syntax (compared to tcl) - add `table` command support - fixed typo - support rand() in expressions - avoid crashes --- files/apiserver.py | 2 +- irulescan/Cargo.lock | 39 +++++++++++++++++++++++- irulescan/Cargo.toml | 3 +- irulescan/src/lib.rs | 67 ++++++++++++++++++++++++++++++++++++++++-- irulescan/src/main.rs | 22 +++++++------- irulescan/src/rstcl.rs | 1 + 6 files changed, 119 insertions(+), 15 deletions(-) diff --git a/files/apiserver.py b/files/apiserver.py index 43f6fa5..27fad92 100644 --- a/files/apiserver.py +++ b/files/apiserver.py @@ -90,7 +90,7 @@ def decode(data: bytes) -> str: ], description="[irulescan homepage](https://github.com/simonkowallik/irulescan/)", title="irulescan", - version="1.0.0", + version="1.1.0", docs_url="/", ) diff --git a/irulescan/Cargo.lock b/irulescan/Cargo.lock index fa57bbf..4710b46 100644 --- a/irulescan/Cargo.lock +++ b/irulescan/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -30,6 +39,21 @@ dependencies = [ "which", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" @@ -89,6 +113,16 @@ dependencies = [ "num-traits 0.1.43", ] +[[package]] +name = "fancy-regex" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +dependencies = [ + "bit-set", + "regex", +] + [[package]] name = "glob" version = "0.3.0" @@ -97,11 +131,12 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "irulescan" -version = "1.0.0" +version = "1.1.0" dependencies = [ "bindgen", "docopt", "enum_primitive", + "fancy-regex", "num", "rustc-serialize", ] @@ -286,6 +321,8 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" dependencies = [ + "aho-corasick", + "memchr", "regex-syntax", ] diff --git a/irulescan/Cargo.toml b/irulescan/Cargo.toml index 98ba78f..b0c74d0 100644 --- a/irulescan/Cargo.toml +++ b/irulescan/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "irulescan" -version = "1.0.0" +version = "1.1.0" authors = ["Simon Kowallik "] build = "src/build.rs" @@ -14,6 +14,7 @@ docopt = "1.1" enum_primitive = "0.1" num = "0.4" rustc-serialize = "0.3" +fancy-regex = "0" [[bin]] diff --git a/irulescan/src/lib.rs b/irulescan/src/lib.rs index 394214b..5febbe8 100644 --- a/irulescan/src/lib.rs +++ b/irulescan/src/lib.rs @@ -5,9 +5,11 @@ extern crate libc; // https://github.com/rust-lang/rust/issues/16920 #[macro_use] extern crate enum_primitive; extern crate num; +extern crate fancy_regex; use std::iter; use std::fmt; +use fancy_regex::Regex; use self::CheckResult::*; // TODO: why does swapping this line with one below break? use rstcl::TokenType; @@ -263,7 +265,7 @@ pub fn check_command<'a, 'b>(ctx: &'a str, tokens: &'b Vec>) results.push(Danger(ctx, "missing options terminator `--` permits argument injection", tokens[i].val)); } if (tokens.len() - i) != 2 { - results.push(Danger(ctx, "Dangerous unqoted switch body", tokens[i].val)); + results.push(Danger(ctx, "Dangerous unquoted switch body", tokens[i].val)); } param_types.extend_from_slice(&vec![Code::Normal, Code::SwitchBody]); param_types @@ -325,11 +327,42 @@ pub fn check_command<'a, 'b>(ctx: &'a str, tokens: &'b Vec>) } iter::repeat(Code::Normal).take(tokens.len()-1).collect() }, + // table set [-notouch] [-subtable | -georedundancy] [-mustexist|-excl] [ []] + // table add [-notouch] [-subtable | -georedundancy] [ []] + // table replace [-notouch] [-subtable | -georedundancy] [ []] + // table lookup [-notouch] [-subtable | -georedundancy] + // table incr [-notouch] [-subtable | -georedundancy] [-mustexist] [] + // table append [-notouch] [-subtable | -georedundancy] [-mustexist] + // table delete [-subtable | -georedundancy] |-all + // table timeout [-subtable | -georedundancy] [-remaining] + // table timeout [-subtable | -georedundancy] [] + // table lifetime [-subtable | -georedundancy] [-remaining] + // table lifetime [-subtable | -georedundancy] [] + // table keys -subtable [-count|-notouch] + "table" => { + let mut options_terminated = false; + let mut i = 1; + while i < tokens.len() { + match tokens[i].val { + "set"|"add"|"replace"|"lookup"|"incr"|"append"|"delete"|"timeout"|"lifetime"| + "-notouch"|"-georedundancy"|"-mustexist"|"-count"|"-remaining"| + "-excl" => { i += 1; }, + "-subtable" => { i += 2; }, + "keys" => { options_terminated = true; break; }, + "--" => { options_terminated = true; break; }, + _ => {break;}, + }; + }; + if ! options_terminated { + results.push(Danger(ctx, "missing options terminator `--` permits argument injection", tokens[i].val)); + } + iter::repeat(Code::Normal).take(tokens.len()-1).collect() + }, // default _ => iter::repeat(Code::Normal).take(tokens.len()-1).collect(), }; if param_types.len() != tokens.len() - 1 { - results.push(Warn(ctx, "badly formed command", tokens[0].val)); + results.push(Danger(ctx, "badly formed command, cannot scan code", tokens[0].val)); return results; } for (param_type, param) in param_types.iter().zip(tokens[1..].iter()) { @@ -426,3 +459,33 @@ pub fn scan_script<'a>(string: &'a str) -> Vec> { } return all_results; } + +/// Preprocess iRules to sanitize lax irule syntax +pub fn preprocess_script(string: &str) -> String { + fn re_replacer(s: &str, re: &Regex, t: &str) -> String { + re.replace_all(s, t).into() + } + let processed_script = &string; + //let processed_script = re_replacer( + // &processed_script, + // &Regex::new(r"(?<=[^\\])\\\s+\n").unwrap(), + // &r"\\\n" + //); + // HACK: rand() causes parsing errors, to avoid backtraces inject artificial parameter + let processed_script = re_replacer( + &processed_script, + &Regex::new(r"rand\(\)").unwrap(), + &r"rand($IRULESCAN)" // this would produce a TCL syntax error + ); + let processed_script = re_replacer( + &processed_script, + &Regex::new(r"(?\}|else|then|while|for)[\n\s]*\{").unwrap(), + &r"$token {" + ); + return processed_script; +} \ No newline at end of file diff --git a/irulescan/src/main.rs b/irulescan/src/main.rs index a5219c8..9a56c7a 100644 --- a/irulescan/src/main.rs +++ b/irulescan/src/main.rs @@ -4,7 +4,6 @@ extern crate rustc_serialize; extern crate docopt; extern crate irulescan; -use std::error::Error; use std::fs; use std::io::prelude::*; use std::io; @@ -13,11 +12,13 @@ use docopt::Docopt; use irulescan::rstcl; use irulescan::CheckResult; -const USAGE: &'static str = "Usage: irulescan check [--no-warn] ( - | ) +const USAGE: &'static str = " +Usage: irulescan check [--no-warn] ( - | ) irulescan parsestr ( - | ) -# -# details: https://github.com/simonkowallik/irulescan -# version: 1.0.0"; +Additional Information: + home: https://github.com/simonkowallik/irulescan + version: 1.1.0 +"; pub fn main() { let args = Docopt::new(USAGE) @@ -38,13 +39,13 @@ pub fn main() { let path_display = path.display(); let mut file = match fs::File::open(&path) { Err(err) => panic!("ERROR: Couldn't open {}: {}", - path_display, Error::description(&err)), + path_display, format!("{}", &err)), Ok(file) => file, }; let mut file_content = String::new(); match file.read_to_string(&mut file_content) { Err(err) => panic!("ERROR: Couldn't read {}: {}", - path_display, Error::description(&err)), + path_display, format!("{}", &err)), Ok(_) => file_content, } }, @@ -53,14 +54,14 @@ pub fn main() { let mut stdin_content = String::new(); match io::stdin().read_to_string(&mut stdin_content) { Err(err) => panic!("ERROR: Couldn't read stdin: {}", - Error::description(&err)), + format!("{}", &err)), Ok(_) => stdin_content, } }, (false, true, false) => arg_script_str.to_owned(), _ => panic!("Internal error: could not load script"), }; - let script = &script_in; + let script = &irulescan::preprocess_script(&script_in); match (cmd_check, cmd_parsestr) { (true, false) => { let mut results = irulescan::scan_script(script); @@ -71,7 +72,8 @@ pub fn main() { } if results.len() > 0 { for check_result in results.iter() { - println!("{}", check_result); + // HACK: restore original rand() by removing artificial parameter + println!("{}", format!("{}",check_result).replace("rand($IRULESCAN)", "rand()")); } println!(""); }; diff --git a/irulescan/src/rstcl.rs b/irulescan/src/rstcl.rs index 41a4525..d98aaee 100644 --- a/irulescan/src/rstcl.rs +++ b/irulescan/src/rstcl.rs @@ -1,3 +1,4 @@ +#![allow(deprecated)] use std::mem::uninitialized; use std::ffi::CString;