#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod coverage_tests {
use super::*;
use wasmparser::Parser;
fn get_code_payload(wasm: &[u8]) -> Option<Payload<'_>> {
for payload in Parser::new(0).parse_all(wasm) {
if let Ok(p @ Payload::CodeSectionEntry(_)) = payload {
return Some(p);
}
}
None
}
fn minimal_wasm_module() -> Vec<u8> {
vec![
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, ]
}
fn potential_overflow_wasm() -> Vec<u8> {
vec![
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x04, 0x01, 0x60, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00, 0x0a, 0x0d, 0x01, 0x0b, 0x00, 0x02, 0x40, 0x41, 0x01, 0x41, 0x02, 0x6a, 0x0d, 0x00, 0x0b, 0x0b, ]
}
fn potential_buffer_overflow_wasm() -> Vec<u8> {
vec![
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x04, 0x01, 0x60, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00, 0x05, 0x03, 0x01, 0x00, 0x01, 0x0a, 0x0d, 0x01, 0x0b, 0x00, 0x41, 0x00, 0x41, 0x04, 0x6a, 0x41, 0x2a, 0x36, 0x02, 0x00, 0x0b, ]
}
#[test]
fn test_pattern_detector_new() {
let detector = PatternDetector::new();
assert_eq!(detector.patterns.len(), 5); assert!(detector.found.is_empty());
}
#[test]
fn test_pattern_detector_default() {
let detector = PatternDetector::default();
assert_eq!(detector.patterns.len(), 5);
}
#[test]
fn test_scan_minimal_module() {
let mut detector = PatternDetector::new();
for payload in Parser::new(0).parse_all(&minimal_wasm_module()) {
if let Ok(p) = payload {
let _ = detector.scan(&p);
}
}
assert!(detector.finalize().is_empty());
}
#[test]
fn test_scan_detects_potential_overflow() {
let mut detector = PatternDetector::new();
let wasm = potential_overflow_wasm();
for payload in Parser::new(0).parse_all(&wasm) {
if let Ok(p) = payload {
let result = detector.scan(&p);
assert!(result.is_ok());
}
}
let findings = detector.finalize();
assert!(!findings.is_empty());
assert!(findings
.iter()
.any(|f| f.pattern == "potential-integer-overflow"));
}
#[test]
fn test_scan_detects_buffer_overflow() {
let mut detector = PatternDetector::new();
let wasm = potential_buffer_overflow_wasm();
for payload in Parser::new(0).parse_all(&wasm) {
if let Ok(p) = payload {
let _ = detector.scan(&p);
}
}
let findings = detector.finalize();
assert!(
findings.is_empty()
|| !findings
.iter()
.any(|f| f.pattern == "potential-buffer-overflow")
);
}
#[test]
fn test_finalize_returns_clone() {
let mut detector = PatternDetector::new();
let wasm = potential_overflow_wasm();
for payload in Parser::new(0).parse_all(&wasm) {
if let Ok(p) = payload {
let _ = detector.scan(&p);
}
}
let first = detector.finalize();
let second = detector.finalize();
assert_eq!(first.len(), second.len());
}
#[test]
fn test_vulnerability_pattern_matches_sequence() {
let pattern = VulnerabilityPattern {
name: "test-pattern",
opcodes: vec![OpcodePattern::Sequence(vec![
OperatorMatcher::I32Add,
OperatorMatcher::I32Sub,
])],
severity: Severity::Medium,
};
let operators = vec![Operator::I32Add, Operator::I32Sub];
let result = pattern.matches(&operators);
assert!(result.is_some());
assert_eq!(result.unwrap(), 0);
}
#[test]
fn test_vulnerability_pattern_no_match() {
let pattern = VulnerabilityPattern {
name: "test-pattern",
opcodes: vec![OpcodePattern::Sequence(vec![
OperatorMatcher::I32Mul,
OperatorMatcher::I32DivS,
])],
severity: Severity::High,
};
let operators = vec![Operator::I32Add, Operator::I32Sub];
let result = pattern.matches(&operators);
assert!(result.is_none());
}
#[test]
fn test_opcode_pattern_sequence_exact_match() {
let pattern =
OpcodePattern::Sequence(vec![OperatorMatcher::I32Const, OperatorMatcher::I32Add]);
let operators = vec![Operator::I32Const { value: 1 }, Operator::I32Add];
let result = pattern.find_in(&operators);
assert!(result.is_some());
assert_eq!(result.unwrap(), 0);
}
#[test]
fn test_opcode_pattern_sequence_at_offset() {
let pattern =
OpcodePattern::Sequence(vec![OperatorMatcher::I32Add, OperatorMatcher::I32Sub]);
let operators = vec![Operator::Nop, Operator::I32Add, Operator::I32Sub];
let result = pattern.find_in(&operators);
assert!(result.is_some());
assert_eq!(result.unwrap(), 1);
}
#[test]
fn test_opcode_pattern_sequence_no_match() {
let pattern = OpcodePattern::Sequence(vec![OperatorMatcher::I32Mul]);
let operators = vec![Operator::I32Add, Operator::I32Sub];
let result = pattern.find_in(&operators);
assert!(result.is_none());
}
#[test]
fn test_opcode_pattern_within_distance() {
let pattern = OpcodePattern::Within {
distance: 3,
operators: vec![OperatorMatcher::I32Load, OperatorMatcher::BrIf],
};
let operators = vec![
Operator::I32Load {
memarg: wasmparser::MemArg {
align: 2,
max_align: 2,
offset: 0,
memory: 0,
},
},
Operator::Nop,
Operator::BrIf { relative_depth: 0 },
];
let result = pattern.find_in(&operators);
assert!(result.is_some());
assert_eq!(result.unwrap(), 0);
}
#[test]
fn test_opcode_pattern_within_too_far() {
let pattern = OpcodePattern::Within {
distance: 1,
operators: vec![OperatorMatcher::I32Load, OperatorMatcher::BrIf],
};
let operators = vec![
Operator::I32Load {
memarg: wasmparser::MemArg {
align: 2,
max_align: 2,
offset: 0,
memory: 0,
},
},
Operator::Nop,
Operator::Nop,
Operator::BrIf { relative_depth: 0 },
];
let result = pattern.find_in(&operators);
assert!(result.is_none());
}
#[test]
fn test_opcode_pattern_not_preceded_by_found() {
let pattern = OpcodePattern::NotPrecededBy {
target: OperatorMatcher::MemoryGrow,
guards: vec![OperatorMatcher::I32LtU],
};
let operators = vec![Operator::Nop, Operator::MemoryGrow { mem: 0 }];
let result = pattern.find_in(&operators);
assert!(result.is_some());
}
#[test]
fn test_opcode_pattern_not_preceded_by_guarded() {
let pattern = OpcodePattern::NotPrecededBy {
target: OperatorMatcher::MemoryGrow,
guards: vec![OperatorMatcher::I32LtU],
};
let operators = vec![Operator::I32LtU, Operator::MemoryGrow { mem: 0 }];
let result = pattern.find_in(&operators);
assert!(result.is_none());
}
}