└── README.md /README.md: -------------------------------------------------------------------------------- 1 | # The Complete Rust Security Handbook: From Safe Code to Unbreakable Systems 2 | 3 | *"Rust prevents you from shooting yourself in the foot with memory corruption, but it can't stop you from aiming the gun at your users' money." - The Security Rustacean's Creed* 4 | 5 | ## Prologue: The Three Pillars of Rust Security 6 | 7 | Rust gives you memory safety for free, but **application security** is earned through discipline. This guide will teach you to think like both a Rustacean and a security engineer, because in production systems, being "mostly secure" is like being "mostly pregnant." 8 | 9 | **The Security Trinity:** 10 | 1. **Type Safety** - Make invalid states impossible to represent 11 | 2. **Error Safety** - Turn panics into controlled failures 12 | 3. **Secret Safety** - What happens in RAM should stay in RAM (until properly zeroized) 13 | 14 | --- 15 | 16 | ## Chapter 1: The Type System - Your First Line of Defense 17 | 18 | ### Why Primitives Are Security Vulnerabilities 19 | 20 | Every `u64` in your API is a potential million-dollar bug waiting to happen: 21 | 22 | ```rust 23 | // ⚠️ Three bare u64s – compiler can’t tell them apart 24 | fn transfer(from: u64, to: u64, amount: u64) -> Result<(), Error> { 25 | // What happens when a tired developer swaps these at 2 AM? 26 | // transfer(balance, user_id, amount) ← 💥 Goodbye money 27 | } 28 | ``` 29 | 30 | In traditional languages, this compiles and runs. In blockchain contexts, it transfers user ID `12345` tokens from account `67890`. The code works perfectly—it just transfers money to the wrong place. 31 | 32 | ### The Newtype Pattern: Zero-Cost Type Safety 33 | 34 | Wrap every meaningful primitive in a semantic type: 35 | 36 | ```rust 37 | // ✅ Type-safe: cross-type swaps won’t compile 38 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 39 | struct UserId(u64); 40 | 41 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 42 | struct Balance(u64); 43 | 44 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 45 | struct TokenAmount(u64); 46 | 47 | // Cross-type swaps now fail to compile 48 | fn transfer(from: UserId, to: UserId, amount: TokenAmount) -> Result<(), Error> { 49 | // Swapping two UserId values still compiles — keep names clear 50 | } 51 | ``` 52 | 53 | **The Magic:** These wrappers vanish at compile time (zero runtime cost) while preventing cross-type parameter swaps. 54 | 55 | ### Real-World Impact: The Merkle Root Mixup 56 | 57 | ```rust 58 | // ❌ PRODUCTION INCIDENT: All roots look the same 59 | fn verify_proof(root: [u8; 32], proof: Vec<[u8; 32]>, leaf: [u8; 32]) -> bool { 60 | // merkle verification logic 61 | } 62 | 63 | // Oops - passed balance root where nullifier root expected 64 | let valid = verify_proof(balance_root, proof, nullifier_hash); // 💥 Logic bug 65 | ``` 66 | 67 | ```rust 68 | // ✅ BULLETPROOF: Each root type is distinct 69 | struct BalanceRoot([u8; 32]); 70 | struct NullifierRoot([u8; 32]); 71 | 72 | fn verify_balance_proof(root: BalanceRoot, proof: Vec<[u8; 32]>, leaf: [u8; 32]) -> bool { 73 | // verification logic 74 | } 75 | 76 | // This won't compile - the type system saves us! 77 | let valid = verify_balance_proof(nullifier_root, proof, leaf); // ❌ Compile error 78 | ``` 79 | 80 | **When to Use Newtypes (Answer: Almost Always):** 81 | - IDs, hashes, roots, keys, addresses, nonces 82 | - Business values (Price, Balance, Quantity) 83 | - Validated data (Email, PhoneNumber) 84 | - Array indices with specific meaning 85 | 86 | --- 87 | 88 | ## Chapter 2: Error Handling - When unwrap() Becomes a Weapon 89 | 90 | ### The unwrap() Time Bomb 91 | 92 | In Web3 and financial systems, panics aren't just crashes—they're **denial-of-service attacks**: 93 | 94 | ```rust 95 | // ❌ DOS VULNERABILITY 96 | fn calculate_fee(amount: u64, rate: u64) -> u64 { 97 | amount.checked_mul(rate).unwrap() / 10000 // 💥 Panic = burned gas + no tx 98 | } 99 | ``` 100 | 101 | An attacker provides values that cause overflow, the transaction panics, the user's gas is burned, and nothing happens. Repeat this attack to effectively DoS a smart contract. 102 | 103 | ### The ? Operator: Graceful Degradation 104 | 105 | ```rust 106 | // ✅ GRACEFUL FAILURE 107 | fn calculate_fee_safe(amount: u64, rate: u64) -> Result { 108 | let fee_total = amount 109 | .checked_mul(rate) 110 | .ok_or(FeeError::Overflow)?; // Returns error instead of panic 111 | 112 | Ok(fee_total / 10000) 113 | } 114 | ``` 115 | 116 | **The ? Operator Magic:** 117 | - `Ok(value)` → extracts value and continues 118 | - `Err(error)` → returns early with error 119 | - No panics, no DoS vectors, just controlled failure 120 | 121 | ### When unwrap() is Actually Safe 122 | 123 | Sometimes unwrap() is mathematically provable to be safe: 124 | 125 | ```rust 126 | // ✅ SAFE: Vector was just created with known size 127 | let numbers = vec![1, 2, 3, 4, 5]; 128 | let first = numbers.get(0).expect("vector has 5 elements"); 129 | 130 | // ✅ SAFE: We just checked the condition 131 | if !user_input.is_empty() { 132 | let first_char = user_input.chars().next().unwrap(); 133 | } 134 | ``` 135 | 136 | **Rule:** If you can't write a comment explaining why the `unwrap()` can't fail, it probably can. 137 | 138 | --- 139 | 140 | ## Chapter 3: Integer Arithmetic - Where Money Goes to Die 141 | 142 | ### The Silent Killer: Integer Overflow 143 | 144 | Rust silently wraps overflows in release mode by default, turning your financial calculations into random number generators: 145 | 146 | ```rust 147 | // ❌ SILENT MONEY CORRUPTION 148 | fn add_to_balance(current: u64, deposit: u64) -> u64 { 149 | current + deposit // If this overflows: u64::MAX + 1 = 0 150 | } 151 | 152 | // User has maximum balance, deposits 1 wei → balance becomes 0! 153 | ``` 154 | 155 | This isn't theoretical. Integer overflow has caused real financial losses in production systems. 156 | 157 | ### The Three Pillars of Safe Arithmetic 158 | 159 | #### 1. Checked Arithmetic - For Critical Money Operations 160 | 161 | ```rust 162 | // ✅ OVERFLOW DETECTION 163 | fn add_to_balance_safe(current: u64, deposit: u64) -> Result { 164 | current 165 | .checked_add(deposit) 166 | .ok_or(BalanceError::Overflow) 167 | } 168 | ``` 169 | 170 | **Use for:** Money, prices, balances, critical calculations 171 | 172 | #### 2. Saturating Arithmetic - For Counters and Limits 173 | 174 | ```rust 175 | // ✅ CLAMPING TO BOUNDS 176 | fn apply_penalty(reputation: u32, penalty: u32) -> u32 { 177 | reputation.saturating_sub(penalty) // Never goes below 0 178 | } 179 | 180 | fn increment_counter(count: u32) -> u32 { 181 | count.saturating_add(1) // Never overflows, just stays at MAX 182 | } 183 | ``` 184 | 185 | **Use for:** Counters, reputation systems, rate limiting 186 | 187 | #### 3. Wrapping Arithmetic - For Hash Functions 188 | 189 | ```rust 190 | // ✅ EXPLICIT WRAPAROUND 191 | fn hash_combine(hash: u32, value: u32) -> u32 { 192 | hash.wrapping_mul(31).wrapping_add(value) // Wraparound is expected 193 | } 194 | ``` 195 | 196 | **Use for:** Cryptographic operations where wraparound is mathematically correct 197 | 198 | ### Financial Calculation Best Practices 199 | 200 | ```rust 201 | // ❌ WRONG: Float precision and rounding issues 202 | fn calculate_fee_wrong(amount: u64, rate_percent: f64) -> u64 { 203 | (amount as f64 * rate_percent / 100.0).round() as u64 // 💥 Precision loss 204 | } 205 | 206 | // ✅ CORRECT: Integer arithmetic with explicit rounding 207 | fn calculate_fee_correct(amount: u64, rate_bps: u64) -> Result { 208 | // Multiply first, then divide (order matters!) 209 | let fee_precise = amount 210 | .checked_mul(rate_bps) 211 | .ok_or(Error::Overflow)?; 212 | 213 | // For fees: round UP (ceiling division) 214 | let fee = fee_precise / 10000; 215 | if fee_precise % 10000 > 0 { 216 | fee.checked_add(1).ok_or(Error::Overflow) 217 | } else { 218 | Ok(fee) 219 | } 220 | } 221 | 222 | fn calculate_payout(amount: u64, rate_bps: u64) -> Result { 223 | // For payouts: round DOWN (floor division) 224 | amount 225 | .checked_mul(rate_bps) 226 | .and_then(|x| x.checked_div(10000)) 227 | .ok_or(Error::Overflow) 228 | } 229 | ``` 230 | 231 | **Golden Rules:** 232 | - **Fees/charges:** Round UP (never under-collect) 233 | - **Payouts/refunds:** Round DOWN (never overpay) 234 | - **Always multiply first, then divide** 235 | - **Use basis points (bps) instead of floats for rates** 236 | 237 | ### Enable Runtime Overflow Checks 238 | 239 | ```toml 240 | [profile.release] 241 | overflow-checks = true # Panic instead of silent wraparound 242 | ``` 243 | 244 | --- 245 | 246 | ## Chapter 4: Cryptography and Secrets - The Art of Digital Locks 247 | 248 | ### Random Numbers: The Foundation of Security 249 | 250 | Cryptographic security often reduces to: "Can an attacker predict this number?" 251 | 252 | ```rust 253 | // ❌ PREDICTABLE = BROKEN 254 | use rand::{Rng, rngs::StdRng, SeedableRng}; 255 | let mut rng = StdRng::seed_from_u64(42); // Same seed = same sequence! 256 | let private_key: [u8; 32] = rng.gen(); // Predictable = stolen funds 257 | ``` 258 | 259 | ```rust 260 | // ✅ CRYPTOGRAPHICALLY SECURE 261 | use rand::rngs::OsRng; 262 | let private_key: [u8; 32] = OsRng.gen(); // Pulls from OS entropy pool 263 | ``` 264 | 265 | **Rule:** If it protects secrets or money, use `OsRng`. If it's for gameplay or testing, deterministic is fine. 266 | 267 | ### Secret Lifecycle Management 268 | 269 | Secrets don't just disappear when you think they do: 270 | 271 | ```rust 272 | // ❌ SECRET LIVES FOREVER IN MEMORY 273 | let mut password = String::from("super_secret_password"); 274 | password.clear(); // Only changes length - data remains in RAM! 275 | ``` 276 | 277 | ```rust 278 | // ✅ CRYPTOGRAPHICALLY SECURE WIPING 279 | use zeroize::{Zeroize, Zeroizing}; 280 | 281 | // Manual zeroization 282 | let mut secret = [0u8; 32]; 283 | OsRng.fill_bytes(&mut secret); 284 | // ... use secret ... 285 | secret.zeroize(); // Overwrites memory with zeros 286 | 287 | // Automatic zeroization 288 | let api_key = Zeroizing::new(load_api_key()); 289 | // Automatically zeroized when dropped 290 | ``` 291 | 292 | ### The Debug Trait Trap 293 | 294 | ```rust 295 | // ❌ SECRETS IN LOGS 296 | #[derive(Debug)] 297 | struct ApiCredentials { 298 | key: String, 299 | secret: String, 300 | } 301 | 302 | let creds = ApiCredentials { /* ... */ }; 303 | println!("Credentials: {:?}", creds); // Logged forever! 304 | ``` 305 | 306 | ```rust 307 | // ✅ REDACTED LOGGING 308 | use secrecy::{Secret, ExposeSecret}; 309 | 310 | struct ApiCredentials { 311 | key: String, 312 | secret: Secret, 313 | } 314 | 315 | let creds = ApiCredentials { 316 | key: "public_key".to_string(), 317 | secret: Secret::new("very_secret".to_string()), 318 | }; 319 | 320 | println!("Credentials: key={}, secret=[REDACTED]", creds.key); 321 | 322 | // Only expose when absolutely necessary 323 | let actual_secret = creds.secret.expose_secret(); 324 | ``` 325 | 326 | ### Safe Cryptographic Patterns 327 | 328 | ```rust 329 | // ✅ AUTHENTICATED ENCRYPTION (never use raw encryption!) 330 | use aes_gcm::{Aes256Gcm, Key, Nonce, aead::{Aead, NewAead}}; 331 | 332 | fn encrypt_secure(plaintext: &[u8], key: &[u8; 32]) -> Result, Error> { 333 | let cipher = Aes256Gcm::new(Key::from_slice(key)); 334 | let nonce: [u8; 12] = OsRng.gen(); 335 | 336 | let ciphertext = cipher 337 | .encrypt(Nonce::from_slice(&nonce), plaintext) 338 | .map_err(|_| Error::EncryptionFailed)?; 339 | 340 | // Prepend nonce for storage 341 | let mut result = nonce.to_vec(); 342 | result.extend_from_slice(&ciphertext); 343 | Ok(result) 344 | } 345 | 346 | // ✅ CONSTANT-TIME COMPARISONS (prevent timing attacks) 347 | use subtle::ConstantTimeEq; 348 | 349 | fn verify_mac(expected: &[u8], actual: &[u8]) -> bool { 350 | expected.ct_eq(actual).into() // Always takes same time 351 | } 352 | ``` 353 | 354 | --- 355 | 356 | ## Chapter 5: Injection Attacks - When Strings Become Code 357 | 358 | ### SQL Injection: The Eternal Enemy 359 | 360 | Even in Rust, string formatting creates injection vulnerabilities: 361 | 362 | ```rust 363 | // ❌ SQL INJECTION 364 | fn find_user(name: &str) -> Result { 365 | let query = format!("SELECT * FROM users WHERE name = '{}'", name); 366 | // name = "'; DROP TABLE users; --" = goodbye database 367 | database.execute(&query) 368 | } 369 | ``` 370 | 371 | ```rust 372 | // ✅ PARAMETERIZED QUERIES 373 | use sqlx::PgPool; 374 | 375 | async fn find_user_safe(pool: &PgPool, name: &str) -> Result { 376 | let user = sqlx::query_as!( 377 | User, 378 | "SELECT * FROM users WHERE name = $1", // $1 is safely escaped 379 | name 380 | ) 381 | .fetch_one(pool) 382 | .await?; 383 | 384 | Ok(user) 385 | } 386 | ``` 387 | 388 | ### Command Injection: Shell Shenanigans 389 | 390 | ```rust 391 | // ❌ COMMAND INJECTION 392 | fn search_logs(pattern: &str) -> Result { 393 | let output = Command::new("sh") 394 | .arg("-c") 395 | .arg(format!("grep {} /var/log/app.log", pattern)) // pattern = "; rm -rf /" 396 | .output()?; 397 | 398 | Ok(String::from_utf8(output.stdout)?) 399 | } 400 | ``` 401 | 402 | ```rust 403 | // ✅ SAFE: Individual arguments, no shell 404 | fn search_logs_safe(pattern: &str) -> Result { 405 | let output = Command::new("grep") 406 | .arg(pattern) // Treated as literal argument 407 | .arg("/var/log/app.log") 408 | .output()?; 409 | 410 | Ok(String::from_utf8(output.stdout)?) 411 | } 412 | ``` 413 | 414 | --- 415 | 416 | ## Chapter 6: Async Rust - Concurrency Without Tears 417 | 418 | ### The Blocking Operation Trap 419 | 420 | Async Rust is fast until you accidentally block the entire runtime: 421 | 422 | ```rust 423 | // ❌ BLOCKS ENTIRE RUNTIME 424 | async fn hash_password_wrong(password: &str) -> String { 425 | // This CPU-intensive work freezes ALL async tasks! 426 | expensive_password_hash(password) 427 | } 428 | ``` 429 | 430 | ```rust 431 | // ✅ OFFLOAD TO THREAD POOL 432 | async fn hash_password_right(password: String) -> Result { 433 | let hash = tokio::task::spawn_blocking(move || { 434 | expensive_password_hash(&password) 435 | }) 436 | .await 437 | .map_err(|_| Error::TaskFailed)?; 438 | 439 | Ok(hash) 440 | } 441 | ``` 442 | 443 | **When to use `spawn_blocking`:** 444 | - CPU-intensive work (hashing, parsing, compression) 445 | - Synchronous I/O (file operations, blocking database calls) 446 | - Any operation taking more than a few milliseconds 447 | 448 | ### The Lock-Across-Await Deadlock 449 | 450 | ```rust 451 | // ❌ DEADLOCK WAITING TO HAPPEN 452 | async fn dangerous_pattern(shared: &Mutex>) { 453 | let mut data = shared.lock().unwrap(); // Lock acquired 454 | data.push("item".to_string()); 455 | 456 | some_async_operation().await; // ⚠️ Lock held across await! 457 | 458 | data.push("another".to_string()); 459 | } // Lock released only here - other tasks blocked! 460 | ``` 461 | 462 | ```rust 463 | // ✅ SAFE: Release locks before await points 464 | async fn safe_pattern(shared: &tokio::sync::Mutex>) { 465 | { 466 | let mut data = shared.lock().await; 467 | data.push("item".to_string()); 468 | } // Lock released here 469 | 470 | some_async_operation().await; // No lock held 471 | 472 | { 473 | let mut data = shared.lock().await; 474 | data.push("another".to_string()); 475 | } // Lock released again 476 | } 477 | ``` 478 | 479 | ### Cancellation Safety: The Hidden Async Danger 480 | 481 | Every `.await` is a potential cancellation point where your future might be dropped: 482 | 483 | ```rust 484 | // ❌ NOT CANCELLATION SAFE 485 | async fn transfer_funds_unsafe(from: &Account, to: &Account, amount: u64) { 486 | from.balance -= amount; // ⚠️ What if cancelled here? 487 | network_commit().await; // Cancellation point! 488 | to.balance += amount; // Might never execute → money lost! 489 | } 490 | ``` 491 | 492 | ```rust 493 | // ✅ CANCELLATION SAFE: Atomic state updates 494 | async fn transfer_funds_safe(from: &Account, to: &Account, amount: u64) -> Result<(), Error> { 495 | // Do all async work first 496 | let transfer_id = prepare_transfer(amount).await?; 497 | 498 | // Then atomic state update (no cancellation points) 499 | tokio::task::spawn_blocking(move || { 500 | // This runs to completion 501 | from.balance -= amount; 502 | to.balance += amount; 503 | commit_transfer(transfer_id); 504 | }).await?; 505 | 506 | Ok(()) 507 | } 508 | ``` 509 | 510 | --- 511 | 512 | ## Chapter 7: Web3 and Smart Contract Security 513 | 514 | ### The Missing Signer Check (Solana Example) 515 | 516 | ```rust 517 | // ❌ AUTHORIZATION BYPASS 518 | pub fn withdraw(ctx: Context, amount: u64) -> Result<()> { 519 | let user_account = &mut ctx.accounts.user_account; 520 | // Bug: Never verified that user_account signed this transaction! 521 | 522 | user_account.balance -= amount; 523 | Ok(()) 524 | } 525 | ``` 526 | 527 | ```rust 528 | // ✅ VERIFY AUTHORIZATION 529 | pub fn withdraw_safe(ctx: Context, amount: u64) -> Result<()> { 530 | let user_account = &mut ctx.accounts.user_account; 531 | 532 | // Critical: Verify the account holder authorized this 533 | require!(user_account.is_signer, ErrorCode::MissingSigner); 534 | 535 | // Additional safety: Check balance 536 | require!( 537 | user_account.balance >= amount, 538 | ErrorCode::InsufficientFunds 539 | ); 540 | 541 | user_account.balance -= amount; 542 | Ok(()) 543 | } 544 | ``` 545 | 546 | ### Program Derived Address (PDA) Verification 547 | 548 | ```rust 549 | // ❌ TRUSTING USER INPUT 550 | pub fn update_vault(ctx: Context) -> Result<()> { 551 | let vault = &mut ctx.accounts.vault; 552 | // Bug: Attacker could pass a vault they control! 553 | vault.amount += 100; 554 | Ok(()) 555 | } 556 | ``` 557 | 558 | ```rust 559 | // ✅ VERIFY PDA OWNERSHIP 560 | pub fn update_vault_safe(ctx: Context) -> Result<()> { 561 | let vault = &mut ctx.accounts.vault; 562 | 563 | // Recompute expected PDA 564 | let (expected_vault, _bump) = Pubkey::find_program_address( 565 | &[b"vault", ctx.accounts.user.key().as_ref()], 566 | ctx.program_id, 567 | ); 568 | 569 | require!(vault.key() == expected_vault, ErrorCode::InvalidVault); 570 | 571 | vault.amount += 100; 572 | Ok(()) 573 | } 574 | ``` 575 | 576 | ### Determinism in Smart Contracts 577 | 578 | ```rust 579 | // ❌ NON-DETERMINISTIC (causes consensus failures) 580 | pub fn create_auction(duration_hours: u64) -> Result<()> { 581 | let start_time = SystemTime::now() // Different on each validator! 582 | .duration_since(UNIX_EPOCH) 583 | .unwrap() 584 | .as_secs(); 585 | Ok(()) 586 | } 587 | ``` 588 | 589 | ```rust 590 | // ✅ DETERMINISTIC 591 | pub fn create_auction_safe(ctx: Context, duration_hours: u64) -> Result<()> { 592 | let clock = Clock::get()?; // Blockchain-provided timestamp 593 | let start_time = clock.unix_timestamp as u64; // Same on all validators 594 | Ok(()) 595 | } 596 | ``` 597 | 598 | --- 599 | 600 | ## Chapter 8: The unsafe Keyword - Handle With Care 601 | 602 | ### Document Your Contracts 603 | 604 | Every `unsafe` block must explain its safety invariants: 605 | 606 | ```rust 607 | // ❌ DANGEROUS: No safety documentation 608 | unsafe fn write_bytes(ptr: *mut u8, bytes: &[u8]) { 609 | std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr, bytes.len()); 610 | } 611 | ``` 612 | 613 | ```rust 614 | // ✅ SAFE: Clear safety contract 615 | /// Copy bytes to the given pointer. 616 | /// 617 | /// # Safety 618 | /// 619 | /// - `ptr` must be non-null and properly aligned 620 | /// - `ptr` must point to writable memory for at least `bytes.len()` bytes 621 | /// - The memory region must not overlap with `bytes` 622 | /// - No other threads may access the memory region during this call 623 | unsafe fn write_bytes_safe(ptr: *mut u8, bytes: &[u8]) { 624 | debug_assert!(!ptr.is_null(), "ptr must not be null"); 625 | debug_assert!(ptr.is_aligned(), "ptr must be aligned"); 626 | 627 | // SAFETY: Caller guarantees all safety requirements above 628 | std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr, bytes.len()); 629 | } 630 | ``` 631 | 632 | ### FFI Safety 633 | 634 | ```rust 635 | extern "C" { 636 | fn c_hash_function(input: *const u8, len: usize, output: *mut u8); 637 | } 638 | 639 | // ✅ SAFE FFI WRAPPER 640 | /// Compute hash using C library. 641 | pub fn hash_bytes(data: &[u8]) -> [u8; 32] { 642 | let mut output = [0u8; 32]; 643 | 644 | // SAFETY: 645 | // - data.as_ptr() is valid for data.len() bytes 646 | // - output.as_mut_ptr() is valid for 32 bytes 647 | // - C function documented to write exactly 32 bytes 648 | unsafe { 649 | c_hash_function(data.as_ptr(), data.len(), output.as_mut_ptr()); 650 | } 651 | 652 | output 653 | } 654 | ``` 655 | 656 | --- 657 | 658 | ## Chapter 9: Development and Deployment Security 659 | 660 | ### Dependency Security 661 | 662 | ```bash 663 | # ✅ Security audit pipeline 664 | cargo audit # Check for vulnerabilities 665 | cargo build --release --locked # Use exact dependency versions 666 | cargo clippy -- -D warnings # Lint for security issues 667 | ``` 668 | 669 | ### Security-Focused Compiler Settings 670 | 671 | ```toml 672 | [profile.release] 673 | overflow-checks = true # Catch integer overflows 674 | debug-assertions = true # Keep debug_assert! in release 675 | strip = true # Remove debug symbols 676 | lto = true # Link-time optimization 677 | codegen-units = 1 # Better optimization 678 | 679 | # Security-focused clippy config 680 | [alias] 681 | secure-check = [ 682 | "clippy", "--all-targets", "--all-features", "--", 683 | "-D", "clippy::unwrap_used", 684 | "-D", "clippy::expect_used", 685 | "-D", "clippy::indexing_slicing", 686 | "-D", "clippy::panic", 687 | ] 688 | ``` 689 | 690 | ### Property-Based Testing 691 | 692 | ```rust 693 | use proptest::prelude::*; 694 | 695 | // Test security properties, not just happy paths 696 | proptest! { 697 | #[test] 698 | fn transfer_never_creates_money( 699 | initial_from in 0u64..=1_000_000, 700 | initial_to in 0u64..=1_000_000, 701 | amount in 0u64..=1_000_000 702 | ) { 703 | let mut from = Account { balance: initial_from }; 704 | let mut to = Account { balance: initial_to }; 705 | let total_before = initial_from + initial_to; 706 | 707 | let _ = transfer(&mut from, &mut to, amount); 708 | 709 | let total_after = from.balance + to.balance; 710 | prop_assert_eq!(total_before, total_after, "Money created or destroyed!"); 711 | } 712 | } 713 | ``` 714 | 715 | --- 716 | 717 | ## Chapter 10: The Security Mindset 718 | 719 | ### Threat Modeling Questions 720 | 721 | For every function, ask: 722 | 1. **What if the inputs are malicious?** 723 | 2. **What if this is called a million times per second?** 724 | 3. **What if multiple threads call this simultaneously?** 725 | 4. **What's the worst an attacker could do with this function?** 726 | 5. **What assumptions might not hold in production?** 727 | 728 | ### Defense in Depth 729 | 730 | ```rust 731 | // ✅ LAYERED SECURITY 732 | pub fn process_payment( 733 | user: &User, 734 | amount: TokenAmount, 735 | signature: &Signature, 736 | ) -> Result<(), PaymentError> { 737 | // Layer 1: Authentication 738 | verify_signature(&user.public_key, signature)?; 739 | 740 | // Layer 2: Authorization 741 | user.check_payment_permissions()?; 742 | 743 | // Layer 3: Input validation 744 | if amount.0 == 0 { 745 | return Err(PaymentError::ZeroAmount); 746 | } 747 | 748 | // Layer 4: Business rules 749 | if amount.0 > user.balance.0 { 750 | return Err(PaymentError::InsufficientFunds); 751 | } 752 | 753 | // Layer 5: Rate limiting 754 | user.check_rate_limit()?; 755 | 756 | // Layer 6: Overflow protection 757 | let new_balance = user.balance.0 758 | .checked_sub(amount.0) 759 | .ok_or(PaymentError::ArithmeticError)?; 760 | 761 | // Finally: Execute 762 | user.balance = Balance(new_balance); 763 | user.record_payment(amount); 764 | 765 | Ok(()) 766 | } 767 | ``` 768 | 769 | --- 770 | 771 | ## The Ultimate Security Checklist 772 | 773 | Before deploying Rust code to production: 774 | 775 | ### Type Safety ✅ 776 | - [ ] All primitives wrapped in semantic newtypes 777 | - [ ] No parameter-swapping possible in APIs 778 | - [ ] Business concepts encoded in types 779 | 780 | ### Error Handling ✅ 781 | - [ ] No `unwrap()` or `panic!()` in production paths 782 | - [ ] All `Result` types properly handled with `?` 783 | - [ ] Clear error types for different failure modes 784 | 785 | ### Arithmetic Safety ✅ 786 | - [ ] All money/balance operations use `checked_*` 787 | - [ ] Overflow checks enabled in release mode 788 | - [ ] Proper rounding direction for fees vs payouts 789 | 790 | ### Cryptography ✅ 791 | - [ ] `OsRng` for all security-critical randomness 792 | - [ ] Secrets wrapped in `Zeroizing` or `secrecy` 793 | - [ ] No secrets in `Debug` output or logs 794 | - [ ] Constant-time comparisons for MACs/hashes 795 | 796 | ### Injection Prevention ✅ 797 | - [ ] All SQL queries parameterized 798 | - [ ] No shell injection via `format!()` + Command 799 | - [ ] Input validation and sanitization 800 | 801 | ### Async Safety ✅ 802 | - [ ] No blocking operations in async contexts 803 | - [ ] No locks held across `.await` points 804 | - [ ] Cancellation-safe state updates 805 | 806 | ### Smart Contract Security ✅ 807 | - [ ] All signers verified before state changes 808 | - [ ] PDAs recomputed and validated 809 | - [ ] Deterministic behavior (no `SystemTime::now()`) 810 | 811 | ### unsafe Code ✅ 812 | - [ ] All `unsafe` blocks documented with safety contracts 813 | - [ ] Runtime assertions for debug builds 814 | - [ ] FFI boundaries validated 815 | 816 | ### Development Security ✅ 817 | - [ ] `cargo audit` passes 818 | - [ ] Builds use `--locked` flag 819 | - [ ] Security-focused clippy lints enabled 820 | - [ ] Property-based tests for invariants 821 | 822 | ### Deployment Security ✅ 823 | - [ ] Overflow checks enabled in release 824 | - [ ] Debug symbols stripped 825 | - [ ] Dependencies pinned and audited 826 | 827 | --- 828 | 829 | ## Final Wisdom: The Three Laws of Secure Rust 830 | 831 | 1. **Make invalid states unrepresentable** - Use the type system to prevent bugs at compile time 832 | 2. **Fail explicitly and gracefully** - Turn potential panics into controlled `Result` types 833 | 3. **Trust but verify** - Validate all boundaries, especially between safe and unsafe code 834 | 835 | ## One-Liner for Your Laptop Sticker 836 | 837 | *"Memory safety for free, application security for a price—but that price is just good habits."* 838 | 839 | --- 840 | 841 | Remember: Rust gives you a head start on security, but it's not a silver bullet. The most secure code is code that's never written, and the second most secure code is code that's written by developers who think like attackers. 842 | 843 | Now go forth and build systems that are not just fast and memory-safe, but actually secure. Your users' money depends on it. 🦀🔒💰 844 | 845 | ## Contributing & Contact 846 | 847 | - GitHub: 848 | - Email: yevhsec1@gmail.com 849 | --------------------------------------------------------------------------------