Skip to main content

panic_attacker/signatures/
engine.rs

1// SPDX-License-Identifier: PMPL-1.0-or-later
2
3//! Signature detection engine using logic programming concepts
4
5use 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    /// Detect bug signatures from a crash report
21    pub fn detect_from_crash(&self, crash: &CrashReport) -> Vec<BugSignature> {
22        let mut signatures = Vec::new();
23
24        // Extract facts from crash report
25        let facts = self.extract_facts(crash);
26
27        // Apply defined rules by dispatching on rule name
28        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                _ => {} // Unknown rules ignored for forward compatibility
43            }
44        }
45
46        // These have no corresponding rules yet — always run
47        signatures.extend(self.infer_null_deref(&facts, crash));
48        signatures.extend(self.infer_buffer_overflow(&facts, crash));
49
50        signatures
51    }
52
53    /// Extract Datalog-style facts from crash report
54    fn extract_facts(&self, crash: &CrashReport) -> HashSet<Fact> {
55        let mut facts = HashSet::new();
56
57        let stderr = &crash.stderr;
58
59        // Parse allocation patterns
60        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        // Parse free patterns
68        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        // Parse use patterns
76        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        // Parse locking patterns
84        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        // Parse thread patterns
99        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    /// Infer use-after-free bugs
110    ///
111    /// Rule: UseAfterFree(var, use_loc, free_loc) :-
112    ///       Free(var, free_loc),
113    ///       Use(var, use_loc),
114    ///       Ordering(free_loc, use_loc)
115    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        // Find all free and use pairs
123        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                            // Pattern matched!
137                            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        // Also check for common patterns in stderr
154        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    /// Infer double-free bugs
170    ///
171    /// Rule: DoubleFree(var, loc1, loc2) :-
172    ///       Free(var, loc1),
173    ///       Free(var, loc2),
174    ///       loc1 != loc2
175    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        // Collect all free operations per variable
180        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        // Check for multiple frees of same variable
190        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        // Pattern matching in stderr
205        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    /// Infer deadlock bugs
221    ///
222    /// Rule: Deadlock(m1, m2) :-
223    ///       Lock(m1, loc1), Lock(m2, loc2),
224    ///       Lock(m2, loc3), Lock(m1, loc4),
225    ///       Ordering(loc1, loc2), Ordering(loc3, loc4)
226    fn infer_deadlock(&self, facts: &HashSet<Fact>, crash: &CrashReport) -> Vec<BugSignature> {
227        let mut signatures = Vec::new();
228
229        // Check for lock ordering violations (simplified)
230        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        // Look for potential circular dependencies
238        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        // Pattern matching
251        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    /// Infer data race bugs
267    ///
268    /// Rule: DataRace(var, loc1, loc2) :-
269    ///       Write(var, loc1), Read(var, loc2),
270    ///       Concurrent(loc1, loc2),
271    ///       ¬Synchronized(loc1, loc2)
272    fn infer_data_race(&self, facts: &HashSet<Fact>, crash: &CrashReport) -> Vec<BugSignature> {
273        let mut signatures = Vec::new();
274
275        // Check for concurrent accesses
276        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        // Pattern matching
293        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    /// Infer null pointer dereference
309    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    /// Infer buffer overflow
330    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}