1use crate::observation::{TaintFlow, TaintLabel, Value};
37use std::collections::HashMap;
38
39#[derive(Debug, Clone)]
41pub struct Source {
42 pub api: String,
44 pub label: TaintLabel,
46 pub description: String,
48}
49
50#[derive(Debug, Clone)]
52pub struct Sink {
53 pub api: String,
55 pub dangerous_args: Vec<usize>,
57 pub severity: Severity,
59 pub cwe: String,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum Severity {
66 Critical,
67 High,
68 Medium,
69 Low,
70 Info,
71}
72
73impl std::fmt::Display for Severity {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 match self {
76 Self::Critical => write!(f, "critical"),
77 Self::High => write!(f, "high"),
78 Self::Medium => write!(f, "medium"),
79 Self::Low => write!(f, "low"),
80 Self::Info => write!(f, "info"),
81 }
82 }
83}
84
85#[derive(Debug, Default)]
90pub struct TaintTracker {
91 sources: HashMap<String, Source>,
93 sinks: HashMap<String, Sink>,
95 flows: Vec<TaintFlow>,
97 next_label: u32,
99}
100
101impl TaintTracker {
102 pub fn new() -> Self {
104 Self {
105 sources: HashMap::new(),
106 sinks: HashMap::new(),
107 flows: Vec::new(),
108 next_label: 1, }
110 }
111
112 pub fn register_source(
117 &mut self,
118 api: impl Into<String>,
119 description: impl Into<String>,
120 ) -> TaintLabel {
121 let label = TaintLabel::new(self.next_label);
122 self.next_label = self.next_label.saturating_add(1);
124 if label.0 == 0 {
126 return TaintLabel::new(1);
127 }
128
129 let source = Source {
130 api: api.into(),
131 label,
132 description: description.into(),
133 };
134
135 self.sources.insert(source.api.clone(), source);
136 label
137 }
138
139 pub fn register_sink(
141 &mut self,
142 api: impl Into<String>,
143 dangerous_args: Vec<usize>,
144 severity: Severity,
145 cwe: impl Into<String>,
146 ) {
147 let sink = Sink {
148 api: api.into(),
149 dangerous_args,
150 severity,
151 cwe: cwe.into(),
152 };
153 self.sinks.insert(sink.api.clone(), sink);
154 }
155
156 pub fn is_source(&self, api: &str) -> Option<&Source> {
158 self.sources.get(api)
159 }
160
161 pub fn is_sink(&self, api: &str) -> Option<&Sink> {
163 self.sinks.get(api)
164 }
165
166 pub fn apply_source_taint(&self, api: &str, value: Value) -> Value {
171 if let Some(source) = self.is_source(api) {
172 value.with_taint(source.label)
173 } else {
174 value
175 }
176 }
177
178 pub fn check_sink(&mut self, api: &str, args: &[Value]) -> Option<TaintFlow> {
183 let sink = self.is_sink(api)?;
184
185 let dangerous_values: Vec<(usize, &Value)> = args
187 .iter()
188 .enumerate()
189 .filter(|(idx, _)| sink.dangerous_args.contains(idx))
190 .collect();
191
192 if let Some(flow) = Value::check_taint_at_sink(
193 api,
194 &dangerous_values
195 .iter()
196 .map(|(_, v)| (*v).clone())
197 .collect::<Vec<_>>(),
198 ) {
199 self.flows.push(flow.clone());
200 Some(flow)
201 } else {
202 None
203 }
204 }
205
206 pub fn flows(&self) -> &[TaintFlow] {
208 &self.flows
209 }
210
211 pub fn take_flows(&mut self) -> Vec<TaintFlow> {
213 std::mem::take(&mut self.flows)
214 }
215
216 pub fn has_flows(&self) -> bool {
218 !self.flows.is_empty()
219 }
220
221 pub fn flow_count(&self) -> usize {
223 self.flows.len()
224 }
225
226 pub fn sources(&self) -> &HashMap<String, Source> {
228 &self.sources
229 }
230
231 pub fn sinks(&self) -> &HashMap<String, Sink> {
233 &self.sinks
234 }
235}
236
237pub fn propagate_concat(values: &[Value]) -> Option<Value> {
256 if values.is_empty() {
257 return Some(Value::string(""));
258 }
259
260 let mut result = String::new();
262 let mut combined_label = TaintLabel::CLEAN;
263
264 for value in values {
265 match value {
266 Value::String(s, label) => {
267 result.push_str(s);
268 if combined_label.is_clean() && label.is_tainted() {
269 combined_label = *label;
270 }
271 }
272 _ => return None, }
274 }
275
276 Some(Value::String(result, combined_label))
277}
278
279pub fn propagate_slice(value: &Value, start: usize, end: usize) -> Option<Value> {
283 value.slice(start, end)
284}
285
286pub fn propagate_replace(value: &Value, from: &str, to: &str) -> Option<Value> {
290 value.replace(from, to)
291}
292
293pub fn propagate_json_parse(value: &Value) -> Option<Value> {
298 match value {
299 Value::String(s, label) => Some(Value::Json(s.clone(), *label)),
300 _ => None,
301 }
302}
303
304pub fn propagate_json_stringify(value: &Value) -> Option<Value> {
308 match value {
309 Value::Json(s, label) => Some(Value::String(s.clone(), *label)),
310 _ => None,
311 }
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317
318 #[test]
319 fn taint_label_basic() {
320 let clean = TaintLabel::CLEAN;
321 assert!(!clean.is_tainted());
322 assert!(clean.is_clean());
323
324 let tainted = TaintLabel::new(1);
325 assert!(tainted.is_tainted());
326 assert!(!tainted.is_clean());
327 }
328
329 #[test]
330 fn taint_label_combine() {
331 let clean = TaintLabel::CLEAN;
332 let t1 = TaintLabel::new(1);
333 let t2 = TaintLabel::new(2);
334
335 assert_eq!(clean.combine(clean), clean);
336 assert_eq!(clean.combine(t1), t1);
337 assert_eq!(t1.combine(clean), t1);
338 assert_eq!(t1.combine(t2), t1); }
340
341 #[test]
342 fn value_taint_tracking() {
343 let clean = Value::string("clean");
344 let tainted = Value::tainted_string("tainted", TaintLabel::new(1));
345
346 assert!(!clean.is_tainted());
347 assert!(tainted.is_tainted());
348
349 assert_eq!(clean.taint_label(), TaintLabel::CLEAN);
350 assert_eq!(tainted.taint_label(), TaintLabel::new(1));
351 }
352
353 #[test]
354 fn value_with_taint() {
355 let clean = Value::string("test");
356 let tainted = clean.with_taint(TaintLabel::new(5));
357
358 assert!(tainted.is_tainted());
359 assert_eq!(tainted.taint_label(), TaintLabel::new(5));
360 }
361
362 #[test]
363 fn check_taint_at_sink_detects_tainted() {
364 let args = vec![
365 Value::string("safe"),
366 Value::tainted_string("dangerous", TaintLabel::new(1)),
367 ];
368
369 let flow = Value::check_taint_at_sink("eval", &args);
370 assert!(flow.is_some());
371
372 let flow = flow.unwrap();
373 assert_eq!(flow.sink, "eval");
374 assert_eq!(flow.label, TaintLabel::new(1));
375 assert_eq!(flow.tainted_args, vec![1]);
376 }
377
378 #[test]
379 fn check_taint_at_sink_returns_none_for_clean() {
380 let args = vec![Value::string("safe1"), Value::string("safe2")];
381
382 let flow = Value::check_taint_at_sink("eval", &args);
383 assert!(flow.is_none());
384 }
385
386 #[test]
387 fn value_concat_propagates_taint() {
388 let a = Value::string("hello ");
389 let b = Value::tainted_string("world", TaintLabel::new(1));
390
391 let result = a.concat(&b).unwrap();
392
393 assert!(result.is_tainted());
394 assert_eq!(result.taint_label(), TaintLabel::new(1));
395 assert_eq!(result.as_str(), Some("hello world"));
396 }
397
398 #[test]
399 fn value_slice_preserves_taint() {
400 let s = Value::tainted_string("abcdef", TaintLabel::new(2));
401
402 let result = s.slice(1, 4).unwrap();
403
404 assert!(result.is_tainted());
405 assert_eq!(result.taint_label(), TaintLabel::new(2));
406 assert_eq!(result.as_str(), Some("bcd"));
407 }
408
409 #[test]
410 fn value_replace_preserves_taint() {
411 let s = Value::tainted_string("hello world", TaintLabel::new(3));
412
413 let result = s.replace("world", "universe").unwrap();
414
415 assert!(result.is_tainted());
416 assert_eq!(result.taint_label(), TaintLabel::new(3));
417 assert_eq!(result.as_str(), Some("hello universe"));
418 }
419
420 #[test]
421 fn value_equality_ignores_taint() {
422 let a = Value::string("test");
423 let b = Value::tainted_string("test", TaintLabel::new(1));
424
425 assert_eq!(a, b);
427
428 assert!(!a.is_tainted());
430 assert!(b.is_tainted());
431 }
432
433 #[test]
434 fn taint_tracker_registration() {
435 let mut tracker = TaintTracker::new();
436
437 let label = tracker.register_source("chrome.runtime.onMessage", "Message from extension");
438 assert_eq!(label, TaintLabel::new(1));
439
440 tracker.register_sink("eval", vec![0], Severity::Critical, "CWE-95");
441
442 assert!(tracker.is_source("chrome.runtime.onMessage").is_some());
443 assert!(tracker.is_sink("eval").is_some());
444 assert!(tracker.is_source("fetch").is_none());
445 }
446
447 #[test]
448 fn taint_tracker_detects_flow() {
449 let mut tracker = TaintTracker::new();
450
451 tracker.register_source("source.api", "Test source");
452 tracker.register_sink("sink.api", vec![0], Severity::High, "CWE-79");
453
454 let tainted = tracker.apply_source_taint("source.api", Value::string("evil"));
456 assert!(tainted.is_tainted());
457
458 let flow = tracker.check_sink("sink.api", &[tainted]);
460 assert!(flow.is_some());
461 assert_eq!(tracker.flow_count(), 1);
462 }
463
464 #[test]
465 fn propagate_concat_multiple() {
466 let values = vec![
467 Value::string("a"),
468 Value::tainted_string("b", TaintLabel::new(1)),
469 Value::string("c"),
470 ];
471
472 let result = propagate_concat(&values).unwrap();
473 assert!(result.is_tainted());
474 assert_eq!(result.as_str(), Some("abc"));
475 }
476
477 #[test]
478 fn propagate_concat_empty() {
479 let result = propagate_concat(&[]).unwrap();
480 assert_eq!(result.as_str(), Some(""));
481 assert!(!result.is_tainted());
482 }
483
484 #[test]
485 fn propagate_json_parse_stringifies() {
486 let json_str = Value::tainted_string(r#"{"key":"value"}"#, TaintLabel::new(1));
487
488 let parsed = propagate_json_parse(&json_str).unwrap();
489 assert!(matches!(parsed, Value::Json(_, _)));
490 assert!(parsed.is_tainted());
491 }
492
493 #[test]
494 fn propagate_json_stringify_preserves_taint() {
495 let json = Value::tainted_json(r#"{"key":"value"}"#, TaintLabel::new(2));
496
497 let stringified = propagate_json_stringify(&json).unwrap();
498 assert!(matches!(stringified, Value::String(_, _)));
499 assert!(stringified.is_tainted());
500 assert_eq!(stringified.taint_label(), TaintLabel::new(2));
501 }
502}