Skip to main content

panic_attacker/attack/
executor.rs

1// SPDX-License-Identifier: PMPL-1.0-or-later
2
3//! Attack execution engine
4
5use crate::attack::strategies::*;
6use crate::signatures::SignatureEngine;
7use crate::types::*;
8use crate::xray::patterns::PatternDetector;
9use anyhow::{Context, Result};
10use std::process::{Command, Stdio};
11use std::time::Instant;
12
13pub struct AttackExecutor {
14    config: AttackConfig,
15    patterns: Vec<AttackPattern>,
16}
17
18impl AttackExecutor {
19    pub fn new(config: AttackConfig) -> Self {
20        Self {
21            config,
22            patterns: Vec::new(),
23        }
24    }
25
26    pub fn with_patterns(
27        config: AttackConfig,
28        language: Language,
29        frameworks: &[Framework],
30    ) -> Self {
31        let patterns = PatternDetector::patterns_for(language, frameworks);
32        Self { config, patterns }
33    }
34
35    pub fn execute(&self) -> Result<Vec<AttackResult>> {
36        let mut results = Vec::new();
37
38        for program in &self.config.target_programs {
39            for axis in &self.config.axes {
40                println!("Attacking {:?} on axis {:?}...", program, axis);
41
42                let result = self.execute_single_attack(program, *axis)?;
43                results.push(result);
44            }
45        }
46
47        Ok(results)
48    }
49
50    fn execute_single_attack(
51        &self,
52        program: &std::path::PathBuf,
53        axis: AttackAxis,
54    ) -> Result<AttackResult> {
55        let strategy = self.select_strategy(axis);
56        println!("  Strategy: {}", strategy.description());
57
58        // Log applicable patterns for this axis
59        let applicable: Vec<_> = self
60            .patterns
61            .iter()
62            .filter(|p| p.applicable_axes.contains(&axis))
63            .collect();
64        if !applicable.is_empty() {
65            println!("  Applicable patterns:");
66            for pat in &applicable {
67                println!("    - {}: {}", pat.name, pat.description);
68            }
69        }
70
71        let start = Instant::now();
72        let mut crashes = Vec::new();
73        let mut peak_memory = 0u64;
74
75        // Execute attack based on strategy
76        let (exit_code, success) = match strategy {
77            AttackStrategy::CpuStress => self.attack_cpu(program, &mut crashes)?,
78            AttackStrategy::MemoryExhaustion => {
79                self.attack_memory(program, &mut crashes, &mut peak_memory)?
80            }
81            AttackStrategy::DiskThrashing => self.attack_disk(program, &mut crashes)?,
82            AttackStrategy::NetworkFlood => self.attack_network(program, &mut crashes)?,
83            AttackStrategy::ConcurrencyStorm => self.attack_concurrency(program, &mut crashes)?,
84            AttackStrategy::TimeBomb => self.attack_time(program, &mut crashes)?,
85        };
86
87        let duration = start.elapsed();
88
89        // Run signature detection on any crashes
90        let signatures_detected = if !crashes.is_empty() {
91            let engine = SignatureEngine::new();
92            crashes
93                .iter()
94                .flat_map(|crash| engine.detect_from_crash(crash))
95                .collect()
96        } else {
97            Vec::new()
98        };
99
100        Ok(AttackResult {
101            program: program.clone(),
102            axis,
103            success,
104            exit_code,
105            duration,
106            peak_memory,
107            crashes,
108            signatures_detected,
109        })
110    }
111
112    fn select_strategy(&self, axis: AttackAxis) -> AttackStrategy {
113        match axis {
114            AttackAxis::Cpu => AttackStrategy::CpuStress,
115            AttackAxis::Memory => AttackStrategy::MemoryExhaustion,
116            AttackAxis::Disk => AttackStrategy::DiskThrashing,
117            AttackAxis::Network => AttackStrategy::NetworkFlood,
118            AttackAxis::Concurrency => AttackStrategy::ConcurrencyStorm,
119            AttackAxis::Time => AttackStrategy::TimeBomb,
120        }
121    }
122
123    fn attack_cpu(
124        &self,
125        program: &std::path::PathBuf,
126        crashes: &mut Vec<CrashReport>,
127    ) -> Result<(Option<i32>, bool)> {
128        // CPU stress: run program with high computational load
129        let iterations = (1000.0 * self.config.intensity.multiplier()) as u64;
130
131        let output = Command::new(program)
132            .arg("--iterations")
133            .arg(iterations.to_string())
134            .stdin(Stdio::null())
135            .stdout(Stdio::piped())
136            .stderr(Stdio::piped())
137            .output()
138            .context("Failed to execute program")?;
139
140        let success = output.status.success();
141        let exit_code = output.status.code();
142
143        if !success {
144            crashes.push(CrashReport {
145                timestamp: chrono::Utc::now().to_rfc3339(),
146                signal: Self::extract_signal(&output.stderr),
147                backtrace: Self::extract_backtrace(&output.stderr),
148                stderr: String::from_utf8_lossy(&output.stderr).to_string(),
149                stdout: String::from_utf8_lossy(&output.stdout).to_string(),
150            });
151        }
152
153        Ok((exit_code, success))
154    }
155
156    fn attack_memory(
157        &self,
158        program: &std::path::PathBuf,
159        crashes: &mut Vec<CrashReport>,
160        peak_memory: &mut u64,
161    ) -> Result<(Option<i32>, bool)> {
162        // Memory exhaustion: allocate large amounts of memory
163        let memory_mb = (1024.0 * self.config.intensity.multiplier()) as u64;
164
165        let output = Command::new(program)
166            .arg("--allocate-mb")
167            .arg(memory_mb.to_string())
168            .stdin(Stdio::null())
169            .stdout(Stdio::piped())
170            .stderr(Stdio::piped())
171            .output()
172            .context("Failed to execute program")?;
173
174        *peak_memory = memory_mb * 1024 * 1024;
175
176        let success = output.status.success();
177        let exit_code = output.status.code();
178
179        if !success {
180            crashes.push(CrashReport {
181                timestamp: chrono::Utc::now().to_rfc3339(),
182                signal: Self::extract_signal(&output.stderr),
183                backtrace: Self::extract_backtrace(&output.stderr),
184                stderr: String::from_utf8_lossy(&output.stderr).to_string(),
185                stdout: String::from_utf8_lossy(&output.stdout).to_string(),
186            });
187        }
188
189        Ok((exit_code, success))
190    }
191
192    fn attack_disk(
193        &self,
194        program: &std::path::PathBuf,
195        crashes: &mut Vec<CrashReport>,
196    ) -> Result<(Option<i32>, bool)> {
197        // Disk I/O stress
198        let file_count = (100.0 * self.config.intensity.multiplier()) as u64;
199
200        let output = Command::new(program)
201            .arg("--write-files")
202            .arg(file_count.to_string())
203            .stdin(Stdio::null())
204            .stdout(Stdio::piped())
205            .stderr(Stdio::piped())
206            .output()
207            .context("Failed to execute program")?;
208
209        let success = output.status.success();
210        let exit_code = output.status.code();
211
212        if !success {
213            crashes.push(CrashReport {
214                timestamp: chrono::Utc::now().to_rfc3339(),
215                signal: Self::extract_signal(&output.stderr),
216                backtrace: Self::extract_backtrace(&output.stderr),
217                stderr: String::from_utf8_lossy(&output.stderr).to_string(),
218                stdout: String::from_utf8_lossy(&output.stdout).to_string(),
219            });
220        }
221
222        Ok((exit_code, success))
223    }
224
225    fn attack_network(
226        &self,
227        program: &std::path::PathBuf,
228        crashes: &mut Vec<CrashReport>,
229    ) -> Result<(Option<i32>, bool)> {
230        // Network flood
231        let connections = (100.0 * self.config.intensity.multiplier()) as u64;
232
233        let output = Command::new(program)
234            .arg("--connections")
235            .arg(connections.to_string())
236            .stdin(Stdio::null())
237            .stdout(Stdio::piped())
238            .stderr(Stdio::piped())
239            .output()
240            .context("Failed to execute program")?;
241
242        let success = output.status.success();
243        let exit_code = output.status.code();
244
245        if !success {
246            crashes.push(CrashReport {
247                timestamp: chrono::Utc::now().to_rfc3339(),
248                signal: Self::extract_signal(&output.stderr),
249                backtrace: Self::extract_backtrace(&output.stderr),
250                stderr: String::from_utf8_lossy(&output.stderr).to_string(),
251                stdout: String::from_utf8_lossy(&output.stdout).to_string(),
252            });
253        }
254
255        Ok((exit_code, success))
256    }
257
258    fn attack_concurrency(
259        &self,
260        program: &std::path::PathBuf,
261        crashes: &mut Vec<CrashReport>,
262    ) -> Result<(Option<i32>, bool)> {
263        // Concurrency storm: spawn many threads/tasks
264        let threads = (50.0 * self.config.intensity.multiplier()) as u64;
265
266        let output = Command::new(program)
267            .arg("--threads")
268            .arg(threads.to_string())
269            .stdin(Stdio::null())
270            .stdout(Stdio::piped())
271            .stderr(Stdio::piped())
272            .output()
273            .context("Failed to execute program")?;
274
275        let success = output.status.success();
276        let exit_code = output.status.code();
277
278        if !success {
279            crashes.push(CrashReport {
280                timestamp: chrono::Utc::now().to_rfc3339(),
281                signal: Self::extract_signal(&output.stderr),
282                backtrace: Self::extract_backtrace(&output.stderr),
283                stderr: String::from_utf8_lossy(&output.stderr).to_string(),
284                stdout: String::from_utf8_lossy(&output.stdout).to_string(),
285            });
286        }
287
288        Ok((exit_code, success))
289    }
290
291    fn attack_time(
292        &self,
293        program: &std::path::PathBuf,
294        crashes: &mut Vec<CrashReport>,
295    ) -> Result<(Option<i32>, bool)> {
296        // Time-based attacks: run for extended duration
297        let duration_secs = (60.0 * self.config.intensity.multiplier()) as u64;
298
299        let output = Command::new("timeout")
300            .arg(duration_secs.to_string())
301            .arg(program)
302            .stdin(Stdio::null())
303            .stdout(Stdio::piped())
304            .stderr(Stdio::piped())
305            .output()
306            .context("Failed to execute program")?;
307
308        let success = output.status.success();
309        let exit_code = output.status.code();
310
311        if !success {
312            crashes.push(CrashReport {
313                timestamp: chrono::Utc::now().to_rfc3339(),
314                signal: Self::extract_signal(&output.stderr),
315                backtrace: Self::extract_backtrace(&output.stderr),
316                stderr: String::from_utf8_lossy(&output.stderr).to_string(),
317                stdout: String::from_utf8_lossy(&output.stdout).to_string(),
318            });
319        }
320
321        Ok((exit_code, success))
322    }
323
324    fn extract_signal(stderr: &[u8]) -> Option<String> {
325        let stderr_str = String::from_utf8_lossy(stderr);
326        if stderr_str.contains("SIGSEGV") {
327            Some("SIGSEGV".to_string())
328        } else if stderr_str.contains("SIGABRT") {
329            Some("SIGABRT".to_string())
330        } else if stderr_str.contains("SIGILL") {
331            Some("SIGILL".to_string())
332        } else {
333            None
334        }
335    }
336
337    fn extract_backtrace(stderr: &[u8]) -> Option<String> {
338        let stderr_str = String::from_utf8_lossy(stderr);
339        if stderr_str.contains("backtrace") || stderr_str.contains("stack backtrace") {
340            Some(stderr_str.to_string())
341        } else {
342            None
343        }
344    }
345}