panic_attacker/signatures/
engine.rs1use crate::signatures::rules::RuleSet;
6use crate::types::*;
7use std::collections::{HashMap, HashSet};
8
9pub struct SignatureEngine {
10 rules: RuleSet,
11}
12
13impl SignatureEngine {
14 pub fn new() -> Self {
15 Self {
16 rules: RuleSet::default(),
17 }
18 }
19
20 pub fn detect_from_crash(&self, crash: &CrashReport) -> Vec<BugSignature> {
22 let mut signatures = Vec::new();
23
24 let facts = self.extract_facts(crash);
26
27 for rule in self.rules.rules() {
29 match rule.name.as_str() {
30 "use_after_free" => {
31 signatures.extend(self.infer_use_after_free(&facts, crash));
32 }
33 "double_free" => {
34 signatures.extend(self.infer_double_free(&facts, crash));
35 }
36 "deadlock" => {
37 signatures.extend(self.infer_deadlock(&facts, crash));
38 }
39 "data_race" => {
40 signatures.extend(self.infer_data_race(&facts, crash));
41 }
42 _ => {} }
44 }
45
46 signatures.extend(self.infer_null_deref(&facts, crash));
48 signatures.extend(self.infer_buffer_overflow(&facts, crash));
49
50 signatures
51 }
52
53 fn extract_facts(&self, crash: &CrashReport) -> HashSet<Fact> {
55 let mut facts = HashSet::new();
56
57 let stderr = &crash.stderr;
58
59 if stderr.contains("malloc") || stderr.contains("alloc") {
61 facts.insert(Fact::Alloc {
62 var: "heap_var".to_string(),
63 location: 0,
64 });
65 }
66
67 if stderr.contains("free") || stderr.contains("drop") {
69 facts.insert(Fact::Free {
70 var: "heap_var".to_string(),
71 location: 1,
72 });
73 }
74
75 if stderr.contains("use") || stderr.contains("access") {
77 facts.insert(Fact::Use {
78 var: "heap_var".to_string(),
79 location: 2,
80 });
81 }
82
83 if stderr.contains("lock") || stderr.contains("mutex") {
85 facts.insert(Fact::Lock {
86 mutex: "mutex1".to_string(),
87 location: 0,
88 });
89 }
90
91 if stderr.contains("unlock") {
92 facts.insert(Fact::Unlock {
93 mutex: "mutex1".to_string(),
94 location: 1,
95 });
96 }
97
98 if stderr.contains("thread") || stderr.contains("spawn") {
100 facts.insert(Fact::ThreadSpawn {
101 id: "thread1".to_string(),
102 location: 0,
103 });
104 }
105
106 facts
107 }
108
109 fn infer_use_after_free(
116 &self,
117 facts: &HashSet<Fact>,
118 crash: &CrashReport,
119 ) -> Vec<BugSignature> {
120 let mut signatures = Vec::new();
121
122 for fact1 in facts {
124 if let Fact::Free {
125 var: var1,
126 location: free_loc,
127 } = fact1
128 {
129 for fact2 in facts {
130 if let Fact::Use {
131 var: var2,
132 location: use_loc,
133 } = fact2
134 {
135 if var1 == var2 && free_loc < use_loc {
136 signatures.push(BugSignature {
138 signature_type: SignatureType::UseAfterFree,
139 confidence: 0.85,
140 evidence: vec![
141 format!("Free at location {}", free_loc),
142 format!("Use at location {}", use_loc),
143 "Temporal ordering violation detected".to_string(),
144 ],
145 location: Some(format!("Location {}", use_loc)),
146 });
147 }
148 }
149 }
150 }
151 }
152
153 if crash.stderr.contains("use after free")
155 || crash.stderr.contains("use-after-free")
156 || (crash.stderr.contains("freed") && crash.stderr.contains("accessed"))
157 {
158 signatures.push(BugSignature {
159 signature_type: SignatureType::UseAfterFree,
160 confidence: 0.95,
161 evidence: vec!["Direct mention in error message".to_string()],
162 location: None,
163 });
164 }
165
166 signatures
167 }
168
169 fn infer_double_free(&self, facts: &HashSet<Fact>, crash: &CrashReport) -> Vec<BugSignature> {
176 let mut signatures = Vec::new();
177 let mut free_locations: HashMap<String, Vec<usize>> = HashMap::new();
178
179 for fact in facts {
181 if let Fact::Free { var, location } = fact {
182 free_locations
183 .entry(var.clone())
184 .or_default()
185 .push(*location);
186 }
187 }
188
189 for (var, locations) in free_locations {
191 if locations.len() > 1 {
192 signatures.push(BugSignature {
193 signature_type: SignatureType::DoubleFree,
194 confidence: 0.90,
195 evidence: vec![
196 format!("Variable {} freed multiple times", var),
197 format!("Locations: {:?}", locations),
198 ],
199 location: Some(format!("Locations {:?}", locations)),
200 });
201 }
202 }
203
204 if crash.stderr.contains("double free")
206 || crash.stderr.contains("double-free")
207 || crash.stderr.contains("freed twice")
208 {
209 signatures.push(BugSignature {
210 signature_type: SignatureType::DoubleFree,
211 confidence: 0.95,
212 evidence: vec!["Direct mention in error message".to_string()],
213 location: None,
214 });
215 }
216
217 signatures
218 }
219
220 fn infer_deadlock(&self, facts: &HashSet<Fact>, crash: &CrashReport) -> Vec<BugSignature> {
227 let mut signatures = Vec::new();
228
229 let mut locks: Vec<(String, usize)> = Vec::new();
231 for fact in facts {
232 if let Fact::Lock { mutex, location } = fact {
233 locks.push((mutex.clone(), *location));
234 }
235 }
236
237 if locks.len() >= 2 {
239 signatures.push(BugSignature {
240 signature_type: SignatureType::Deadlock,
241 confidence: 0.70,
242 evidence: vec![
243 format!("{} locks detected", locks.len()),
244 "Potential lock ordering issue".to_string(),
245 ],
246 location: None,
247 });
248 }
249
250 if crash.stderr.contains("deadlock")
252 || crash.stderr.contains("deadlocked")
253 || (crash.stderr.contains("waiting") && crash.stderr.contains("lock"))
254 {
255 signatures.push(BugSignature {
256 signature_type: SignatureType::Deadlock,
257 confidence: 0.90,
258 evidence: vec!["Deadlock pattern in error message".to_string()],
259 location: None,
260 });
261 }
262
263 signatures
264 }
265
266 fn infer_data_race(&self, facts: &HashSet<Fact>, crash: &CrashReport) -> Vec<BugSignature> {
273 let mut signatures = Vec::new();
274
275 let has_writes = facts.iter().any(|f| matches!(f, Fact::Write { .. }));
277 let has_reads = facts.iter().any(|f| matches!(f, Fact::Read { .. }));
278 let has_threads = facts.iter().any(|f| matches!(f, Fact::ThreadSpawn { .. }));
279
280 if has_writes && has_reads && has_threads {
281 signatures.push(BugSignature {
282 signature_type: SignatureType::DataRace,
283 confidence: 0.65,
284 evidence: vec![
285 "Concurrent reads and writes detected".to_string(),
286 "Multiple threads present".to_string(),
287 ],
288 location: None,
289 });
290 }
291
292 if crash.stderr.contains("data race")
294 || crash.stderr.contains("race condition")
295 || crash.stderr.contains("ThreadSanitizer")
296 {
297 signatures.push(BugSignature {
298 signature_type: SignatureType::DataRace,
299 confidence: 0.95,
300 evidence: vec!["Race condition detected by sanitizer".to_string()],
301 location: None,
302 });
303 }
304
305 signatures
306 }
307
308 fn infer_null_deref(&self, _facts: &HashSet<Fact>, crash: &CrashReport) -> Vec<BugSignature> {
310 let mut signatures = Vec::new();
311
312 if crash.signal == Some("SIGSEGV".to_string())
313 || crash.stderr.contains("null pointer")
314 || crash.stderr.contains("nullptr")
315 || crash.stderr.contains("nil pointer")
316 || crash.stderr.contains("address 0x0")
317 {
318 signatures.push(BugSignature {
319 signature_type: SignatureType::NullPointerDeref,
320 confidence: 0.90,
321 evidence: vec!["SIGSEGV or null pointer pattern detected".to_string()],
322 location: None,
323 });
324 }
325
326 signatures
327 }
328
329 fn infer_buffer_overflow(
331 &self,
332 _facts: &HashSet<Fact>,
333 crash: &CrashReport,
334 ) -> Vec<BugSignature> {
335 let mut signatures = Vec::new();
336
337 if crash.stderr.contains("buffer overflow")
338 || crash.stderr.contains("stack smashing")
339 || crash.stderr.contains("heap corruption")
340 || crash.stderr.contains("AddressSanitizer")
341 {
342 signatures.push(BugSignature {
343 signature_type: SignatureType::BufferOverflow,
344 confidence: 0.95,
345 evidence: vec!["Buffer overflow pattern detected".to_string()],
346 location: None,
347 });
348 }
349
350 signatures
351 }
352}
353
354impl Default for SignatureEngine {
355 fn default() -> Self {
356 Self::new()
357 }
358}