Skip to main content

sentri_core/
attack_patterns.rs

1//! Known attack patterns and corresponding defensive invariants.
2//!
3//! This module documents historical exploits and provides protective
4//! invariants to prevent similar attacks.
5
6use std::collections::BTreeMap;
7
8/// A known attack pattern with defensive invariants.
9#[derive(Debug, Clone)]
10pub struct AttackPattern {
11    /// Unique identifier for the attack.
12    pub id: String,
13    /// Attack name (e.g., "Reentrancy", "Integer Overflow").
14    pub name: String,
15    /// Description of how the attack works.
16    pub description: String,
17    /// Year the attack was discovered/first exploited.
18    pub year: u32,
19    /// Notable incidents where this attack occurred.
20    pub incidents: Vec<String>,
21    /// Code patterns that indicate vulnerability.
22    pub vulnerable_patterns: Vec<String>,
23    /// Defensive invariants to prevent the attack.
24    pub defensive_invariants: Vec<String>,
25    /// Affected chains: "solana", "evm", "move".
26    pub affected_chains: Vec<String>,
27    /// CVSS severity score (1-10).
28    pub cvss_score: f32,
29}
30
31/// Attack pattern database.
32pub struct AttackPatternDB {
33    patterns: BTreeMap<String, AttackPattern>,
34}
35
36impl AttackPatternDB {
37    /// Create a new attack pattern database with known patterns.
38    pub fn new() -> Self {
39        let mut patterns = BTreeMap::new();
40
41        // Attack 1: Reentrancy
42        patterns.insert(
43            "reentrancy".to_string(),
44            AttackPattern {
45                id: "reentrancy".to_string(),
46                name: "Reentrancy".to_string(),
47                description:
48                    "Attacker calls back into contract during execution, modifying state before \
49                    previous execution completes"
50                        .to_string(),
51                year: 2016,
52                incidents: vec!["The DAO (2016) - $50M loss".to_string()],
53                vulnerable_patterns: vec![
54                    "transfer_funds(); /* state update after */".to_string(),
55                    "transfer(amount)".to_string(),
56                    "delegatecall".to_string(),
57                    "state update AFTER external call".to_string(),
58                    "payable(msg.sender).transfer".to_string(),
59                    "call.value()() without checking re-entry".to_string(),
60                    "state_change_after_external_call".to_string(),
61                ],
62                defensive_invariants: vec![
63                    "state_update_before_external_call".to_string(),
64                    "mutex_lock_during_transfer".to_string(),
65                    "checks_effects_interactions_order".to_string(),
66                    "balance_matches_sum_before_and_after".to_string(),
67                ],
68                affected_chains: vec!["evm".to_string()],
69                cvss_score: 9.8,
70            },
71        );
72
73        // Attack 2: Integer Overflow/Underflow
74        patterns.insert(
75            "integer_overflow".to_string(),
76            AttackPattern {
77                id: "integer_overflow".to_string(),
78                name: "Integer Overflow/Underflow".to_string(),
79                description:
80                    "Arithmetic operations exceed max/min bounds, wrapping to opposite extreme"
81                        .to_string(),
82                year: 2018,
83                incidents: vec![
84                    "BEC Token (2018) - $7.6M frozen".to_string(),
85                    "BeautyChain (2018) - batch transfer bug".to_string(),
86                ],
87                vulnerable_patterns: vec![
88                    "unchecked_addition".to_string(),
89                    "unchecked_subtraction".to_string(),
90                    "balance + amount without overflow check".to_string(),
91                ],
92                defensive_invariants: vec![
93                    "addition_with_overflow_check".to_string(),
94                    "subtraction_with_underflow_check".to_string(),
95                    "total_supply_constant".to_string(),
96                    "balance_never_negative".to_string(),
97                ],
98                affected_chains: vec!["evm".to_string(), "move".to_string()],
99                cvss_score: 8.5,
100            },
101        );
102
103        // Attack 3: Access Control Bypass
104        patterns.insert(
105            "access_control_bypass".to_string(),
106            AttackPattern {
107                id: "access_control_bypass".to_string(),
108                name: "Access Control Bypass".to_string(),
109                description:
110                    "Attacker circumvents permission checks to perform privileged operations"
111                        .to_string(),
112                year: 2017,
113                incidents: vec!["Parity Wallet (2017) - $30M frozen".to_string()],
114                vulnerable_patterns: vec![
115                    "missing_require(is_owner())".to_string(),
116                    "tx.origin != msg.sender".to_string(),
117                    "no_signature_validation".to_string(),
118                    "public_function_without_auth".to_string(),
119                ],
120                defensive_invariants: vec![
121                    "only_owner_can_transfer".to_string(),
122                    "multisig_required_for_critical_ops".to_string(),
123                    "all_privileged_ops_checked".to_string(),
124                    "authorization_before_state_change".to_string(),
125                ],
126                affected_chains: vec!["evm".to_string(), "solana".to_string(), "move".to_string()],
127                cvss_score: 9.9,
128            },
129        );
130
131        // Attack 4: Flash Loan Attack
132        patterns.insert(
133            "flash_loan".to_string(),
134            AttackPattern {
135                id: "flash_loan".to_string(),
136                name: "Flash Loan Attack".to_string(),
137                description:
138                    "Attacker borrows large amount in single transaction to manipulate price"
139                        .to_string(),
140                year: 2020,
141                incidents: vec![
142                    "bZx (2020) - $350K + $600K losses".to_string(),
143                    "Harvest Finance (2020) - $34M loss".to_string(),
144                ],
145                vulnerable_patterns: vec![
146                    "price_oracle_single_source".to_string(),
147                    "no_price_validation".to_string(),
148                    "lending_without_collateral_check".to_string(),
149                ],
150                defensive_invariants: vec![
151                    "price_from_multiple_sources".to_string(),
152                    "collateral_check_before_lending".to_string(),
153                    "price_deviation_limits".to_string(),
154                    "no_same_block_operations".to_string(),
155                ],
156                affected_chains: vec!["evm".to_string()],
157                cvss_score: 8.7,
158            },
159        );
160
161        // Attack 5: Frontrunning/MEV
162        patterns.insert(
163            "frontrunning".to_string(),
164            AttackPattern {
165                id: "frontrunning".to_string(),
166                name: "Frontrunning / MEV Extraction".to_string(),
167                description:
168                    "Attacker observes pending transaction and places own transaction first"
169                        .to_string(),
170                year: 2018,
171                incidents: vec!["General vulnerability since Ethereum inception".to_string()],
172                vulnerable_patterns: vec![
173                    "price_depends_on_order".to_string(),
174                    "state_visible_in_mempool".to_string(),
175                    "no_slippage_protection".to_string(),
176                ],
177                defensive_invariants: vec![
178                    "slippage_limits_enforced".to_string(),
179                    "atomic_swap_no_intermediate_states".to_string(),
180                    "timestamp_deadline_checks".to_string(),
181                    "sorted_by_priority_not_order".to_string(),
182                ],
183                affected_chains: vec!["evm".to_string()],
184                cvss_score: 7.5,
185            },
186        );
187
188        // Attack 6: Type Confusion
189        patterns.insert(
190            "type_confusion".to_string(),
191            AttackPattern {
192                id: "type_confusion".to_string(),
193                name: "Type Confusion / Implicit Conversion".to_string(),
194                description: "Implicit type conversions cause incorrect comparisons or operations"
195                    .to_string(),
196                year: 2019,
197                incidents: vec!["Multiplier Finance (2021) - $1M loss".to_string()],
198                vulnerable_patterns: vec![
199                    "implicit_type_conversion".to_string(),
200                    "comparison_different_types".to_string(),
201                    "address_to_uint_conversion".to_string(),
202                ],
203                defensive_invariants: vec![
204                    "no_implicit_conversions".to_string(),
205                    "explicit_type_matching_required".to_string(),
206                    "type_checked_before_comparison".to_string(),
207                ],
208                affected_chains: vec!["evm".to_string()],
209                cvss_score: 7.2,
210            },
211        );
212
213        // Attack 7: Delegatecall Misuse
214        patterns.insert(
215            "delegatecall_misuse".to_string(),
216            AttackPattern {
217                id: "delegatecall_misuse".to_string(),
218                name: "Delegatecall to Untrusted Code".to_string(),
219                description: "Contract delegatecalls to address that can be controlled by attacker"
220                    .to_string(),
221                year: 2016,
222                incidents: vec!["King of the Ether (2016) - theft of contract funds".to_string()],
223                vulnerable_patterns: vec![
224                    "delegatecall(attacker_address)".to_string(),
225                    "delegatecall_to_user_input".to_string(),
226                    "no_validation_before_delegatecall".to_string(),
227                ],
228                defensive_invariants: vec![
229                    "delegatecall_target_hardcoded".to_string(),
230                    "delegatecall_target_audited".to_string(),
231                    "no_delegatecall_to_untrusted".to_string(),
232                    "delegatecall_results_validated".to_string(),
233                ],
234                affected_chains: vec!["evm".to_string()],
235                cvss_score: 9.8,
236            },
237        );
238
239        // Attack 8: Timestamp Dependence
240        patterns.insert(
241            "timestamp_dependence".to_string(),
242            AttackPattern {
243                id: "timestamp_dependence".to_string(),
244                name: "Timestamp Dependence".to_string(),
245                description: "Miner/validator can manipulate block timestamp for advantage"
246                    .to_string(),
247                year: 2015,
248                incidents: vec!["Various lottery and randomness exploits".to_string()],
249                vulnerable_patterns: vec![
250                    "random_number = block.timestamp".to_string(),
251                    "critical_logic_depends_on_block.timestamp".to_string(),
252                    "no_time_bounds_checking".to_string(),
253                ],
254                defensive_invariants: vec![
255                    "no_randomness_from_timestamp".to_string(),
256                    "randomness_from_external_oracle".to_string(),
257                    "time_bounds_enforced".to_string(),
258                    "timestamp_within_reasonable_bounds".to_string(),
259                ],
260                affected_chains: vec!["evm".to_string()],
261                cvss_score: 6.5,
262            },
263        );
264
265        Self { patterns }
266    }
267
268    /// Get all attack patterns.
269    pub fn all_patterns(&self) -> Vec<&AttackPattern> {
270        self.patterns.values().collect()
271    }
272
273    /// Get patterns affecting a specific chain.
274    pub fn patterns_for_chain(&self, chain: &str) -> Vec<&AttackPattern> {
275        self.patterns
276            .values()
277            .filter(|p| p.affected_chains.contains(&chain.to_string()))
278            .collect()
279    }
280
281    /// Get pattern by ID.
282    pub fn get_pattern(&self, id: &str) -> Option<&AttackPattern> {
283        self.patterns.get(id)
284    }
285
286    /// Check if code might be vulnerable to a pattern.
287    pub fn check_code(&self, code: &str, attack_id: &str) -> Vec<String> {
288        let mut issues = Vec::new();
289
290        if let Some(pattern) = self.get_pattern(attack_id) {
291            for vulnerable_pattern in &pattern.vulnerable_patterns {
292                if code.contains(vulnerable_pattern) {
293                    issues.push(format!(
294                        "Found vulnerable pattern '{}' from {} attack",
295                        vulnerable_pattern, pattern.name
296                    ));
297                }
298            }
299        }
300
301        issues
302    }
303}
304
305impl Default for AttackPatternDB {
306    fn default() -> Self {
307        Self::new()
308    }
309}
310
311#[cfg(test)]
312mod tests {
313    use super::*;
314
315    #[test]
316    fn test_attack_db_creation() {
317        let db = AttackPatternDB::new();
318        assert_eq!(db.all_patterns().len(), 8);
319    }
320
321    #[test]
322    fn test_get_pattern() {
323        let db = AttackPatternDB::new();
324        let pattern = db.get_pattern("reentrancy").unwrap();
325        assert_eq!(pattern.name, "Reentrancy");
326        assert_eq!(pattern.year, 2016);
327    }
328
329    #[test]
330    fn test_patterns_for_chain() {
331        let db = AttackPatternDB::new();
332        let evm_patterns = db.patterns_for_chain("evm");
333        assert!(!evm_patterns.is_empty());
334
335        let solana_patterns = db.patterns_for_chain("solana");
336        assert!(solana_patterns
337            .iter()
338            .any(|p| p.id == "access_control_bypass"));
339    }
340
341    #[test]
342    fn test_code_vulnerability_check() {
343        let db = AttackPatternDB::new();
344        let vulnerable_code = "transfer_funds(); /* state update after */";
345        let issues = db.check_code(vulnerable_code, "reentrancy");
346        assert!(!issues.is_empty());
347    }
348
349    #[test]
350    fn test_cvss_scores() {
351        let db = AttackPatternDB::new();
352        for pattern in db.all_patterns() {
353            assert!(pattern.cvss_score > 0.0 && pattern.cvss_score <= 10.0);
354        }
355    }
356}