use std::{
fmt,
sync::atomic::{AtomicU64, Ordering},
};
use crate::metadata::typesystem::CilFlavor;
static SYMBOLIC_ID_COUNTER: AtomicU64 = AtomicU64::new(1);
fn next_symbolic_id() -> u64 {
SYMBOLIC_ID_COUNTER.fetch_add(1, Ordering::Relaxed)
}
#[cfg(test)]
pub fn reset_symbolic_id_counter() {
SYMBOLIC_ID_COUNTER.store(1, Ordering::Relaxed);
}
#[derive(Clone, Debug)]
pub struct SymbolicValue {
pub id: u64,
pub cil_flavor: CilFlavor,
pub source: TaintSource,
pub name: Option<String>,
pub dependencies: Vec<u64>,
}
impl SymbolicValue {
#[must_use]
pub fn new(cil_flavor: CilFlavor, source: TaintSource) -> Self {
SymbolicValue {
id: next_symbolic_id(),
cil_flavor,
source,
name: None,
dependencies: Vec::new(),
}
}
#[must_use]
pub fn parameter(index: u16, cil_flavor: CilFlavor) -> Self {
SymbolicValue {
id: next_symbolic_id(),
cil_flavor,
source: TaintSource::Parameter(index),
name: Some(format!("arg{index}")),
dependencies: Vec::new(),
}
}
#[must_use]
pub fn local(index: u16, cil_flavor: CilFlavor) -> Self {
SymbolicValue {
id: next_symbolic_id(),
cil_flavor,
source: TaintSource::Local(index),
name: Some(format!("loc{index}")),
dependencies: Vec::new(),
}
}
#[must_use]
pub fn field(field_token: u32, cil_flavor: CilFlavor) -> Self {
SymbolicValue {
id: next_symbolic_id(),
cil_flavor,
source: TaintSource::Field(field_token),
name: Some(format!("field_{field_token:08X}")),
dependencies: Vec::new(),
}
}
#[must_use]
pub fn return_value(method_token: u32, cil_flavor: CilFlavor) -> Self {
SymbolicValue {
id: next_symbolic_id(),
cil_flavor,
source: TaintSource::MethodReturn(method_token),
name: Some(format!("ret_{method_token:08X}")),
dependencies: Vec::new(),
}
}
#[must_use]
pub fn derived(cil_flavor: CilFlavor, source: TaintSource) -> Self {
SymbolicValue {
id: next_symbolic_id(),
cil_flavor,
source,
name: None,
dependencies: Vec::new(),
}
}
#[must_use]
pub fn derived_from(cil_flavor: CilFlavor, dependencies: Vec<u64>) -> Self {
SymbolicValue {
id: next_symbolic_id(),
cil_flavor,
source: TaintSource::Computation,
name: None,
dependencies,
}
}
#[must_use]
pub fn user_input(cil_flavor: CilFlavor) -> Self {
SymbolicValue {
id: next_symbolic_id(),
cil_flavor,
source: TaintSource::UserInput,
name: Some("user_input".to_string()),
dependencies: Vec::new(),
}
}
#[must_use]
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[must_use]
pub fn is_tainted(&self) -> bool {
self.source.is_tainted()
}
#[must_use]
pub fn is_parameter(&self) -> bool {
matches!(self.source, TaintSource::Parameter(_))
}
#[must_use]
pub fn is_local(&self) -> bool {
matches!(self.source, TaintSource::Local(_))
}
#[must_use]
pub fn is_computed(&self) -> bool {
matches!(self.source, TaintSource::Computation)
}
#[must_use]
pub fn parameter_index(&self) -> Option<u16> {
match self.source {
TaintSource::Parameter(idx) => Some(idx),
_ => None,
}
}
#[must_use]
pub fn local_index(&self) -> Option<u16> {
match self.source {
TaintSource::Local(idx) => Some(idx),
_ => None,
}
}
}
impl fmt::Display for SymbolicValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ref name) = self.name {
write!(f, "{}#{}", name, self.id)
} else {
write!(f, "sym#{}", self.id)
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum TaintSource {
Unknown,
Parameter(u16),
Local(u16),
Field(u32),
StaticField(u32),
ArrayElement,
MethodReturn(u32),
UserInput,
ExternalData,
Computation,
Constant,
Exception,
}
impl TaintSource {
#[must_use]
pub fn is_tainted(&self) -> bool {
match self {
TaintSource::Unknown | TaintSource::Constant => false,
TaintSource::Parameter(_)
| TaintSource::Local(_)
| TaintSource::Field(_)
| TaintSource::StaticField(_)
| TaintSource::ArrayElement
| TaintSource::MethodReturn(_)
| TaintSource::UserInput
| TaintSource::ExternalData
| TaintSource::Exception
| TaintSource::Computation => true,
}
}
#[must_use]
pub fn description(&self) -> &'static str {
match self {
TaintSource::Unknown => "unknown",
TaintSource::Parameter(_) => "parameter",
TaintSource::Local(_) => "local variable",
TaintSource::Field(_) => "instance field",
TaintSource::StaticField(_) => "static field",
TaintSource::ArrayElement => "array element",
TaintSource::MethodReturn(_) => "method return",
TaintSource::UserInput => "user input",
TaintSource::ExternalData => "external data",
TaintSource::Computation => "computed",
TaintSource::Constant => "constant",
TaintSource::Exception => "exception",
}
}
}
impl fmt::Display for TaintSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TaintSource::Unknown => write!(f, "unknown"),
TaintSource::Parameter(i) => write!(f, "param[{i}]"),
TaintSource::Local(i) => write!(f, "local[{i}]"),
TaintSource::Field(t) => write!(f, "field[{t:08X}]"),
TaintSource::StaticField(t) => write!(f, "static[{t:08X}]"),
TaintSource::ArrayElement => write!(f, "array[]"),
TaintSource::MethodReturn(t) => write!(f, "call[{t:08X}]"),
TaintSource::UserInput => write!(f, "user_input"),
TaintSource::ExternalData => write!(f, "external"),
TaintSource::Computation => write!(f, "computed"),
TaintSource::Constant => write!(f, "const"),
TaintSource::Exception => write!(f, "exception"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_symbolic_value_creation() {
reset_symbolic_id_counter();
let sym1 = SymbolicValue::new(CilFlavor::I4, TaintSource::Unknown);
let sym2 = SymbolicValue::new(CilFlavor::I8, TaintSource::UserInput);
assert_eq!(sym1.id, 1);
assert_eq!(sym2.id, 2);
assert_eq!(sym1.cil_flavor, CilFlavor::I4);
assert_eq!(sym2.cil_flavor, CilFlavor::I8);
}
#[test]
fn test_symbolic_parameter() {
reset_symbolic_id_counter();
let param = SymbolicValue::parameter(0, CilFlavor::I4);
assert!(param.is_parameter());
assert!(!param.is_local());
assert_eq!(param.parameter_index(), Some(0));
assert!(matches!(param.source, TaintSource::Parameter(0)));
}
#[test]
fn test_symbolic_local() {
reset_symbolic_id_counter();
let local = SymbolicValue::local(2, CilFlavor::Object);
assert!(local.is_local());
assert!(!local.is_parameter());
assert_eq!(local.local_index(), Some(2));
}
#[test]
fn test_symbolic_derived() {
reset_symbolic_id_counter();
let a = SymbolicValue::parameter(0, CilFlavor::I4);
let b = SymbolicValue::parameter(1, CilFlavor::I4);
let derived = SymbolicValue::derived_from(CilFlavor::I4, vec![a.id, b.id]);
assert!(derived.is_computed());
assert_eq!(derived.dependencies.len(), 2);
}
#[test]
fn test_taint_sources() {
assert!(!TaintSource::Unknown.is_tainted());
assert!(!TaintSource::Constant.is_tainted());
assert!(TaintSource::UserInput.is_tainted());
assert!(TaintSource::ExternalData.is_tainted());
assert!(TaintSource::Parameter(0).is_tainted());
assert!(TaintSource::Field(0x04000001).is_tainted());
assert!(TaintSource::MethodReturn(0x06000001).is_tainted());
}
#[test]
fn test_symbolic_value_is_tainted() {
let user_input = SymbolicValue::user_input(CilFlavor::Object);
assert!(user_input.is_tainted());
let param = SymbolicValue::parameter(0, CilFlavor::I4);
assert!(param.is_tainted());
let unknown = SymbolicValue::new(CilFlavor::I4, TaintSource::Unknown);
assert!(!unknown.is_tainted());
}
#[test]
fn test_symbolic_value_with_name() {
let sym = SymbolicValue::new(CilFlavor::I4, TaintSource::Unknown).with_name("myValue");
assert_eq!(sym.name, Some("myValue".to_string()));
}
#[test]
fn test_symbolic_value_display() {
reset_symbolic_id_counter();
let named = SymbolicValue::parameter(0, CilFlavor::I4);
assert!(format!("{}", named).starts_with("arg0#"));
reset_symbolic_id_counter();
let unnamed = SymbolicValue::new(CilFlavor::I4, TaintSource::Computation);
assert!(format!("{}", unnamed).starts_with("sym#"));
}
#[test]
fn test_taint_source_display() {
assert_eq!(format!("{}", TaintSource::Parameter(0)), "param[0]");
assert_eq!(format!("{}", TaintSource::Local(5)), "local[5]");
assert_eq!(
format!("{}", TaintSource::Field(0x04000001)),
"field[04000001]"
);
assert_eq!(format!("{}", TaintSource::UserInput), "user_input");
}
#[test]
fn test_taint_source_description() {
assert_eq!(TaintSource::Unknown.description(), "unknown");
assert_eq!(TaintSource::Parameter(0).description(), "parameter");
assert_eq!(TaintSource::UserInput.description(), "user input");
assert_eq!(TaintSource::Computation.description(), "computed");
}
#[test]
fn test_symbolic_field() {
let field = SymbolicValue::field(0x04000001, CilFlavor::I4);
assert!(matches!(field.source, TaintSource::Field(0x04000001)));
assert!(field.is_tainted());
}
#[test]
fn test_symbolic_return_value() {
let ret = SymbolicValue::return_value(0x06000001, CilFlavor::Object);
assert!(matches!(ret.source, TaintSource::MethodReturn(0x06000001)));
assert!(ret.is_tainted());
}
}