1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
|
//! Scans a process memory for secret leaks.
//!
//! These tests attempt to detect secrets leaking into the stack and
//! heap without being zeroed after use. To that end, we do the
//! following:
//!
//! 1. We break free(3) by using a custom allocator that leaks all
//! heap allocations, so that memory will not be reused and we
//! can robustly detect secret leaking into the heap because
//! there is no risk of them being overwritten.
//!
//! 2. We do an operation involving secrets. We use a fixed secret
//! with a simple pattern (currently repeated '@'s) that is easy
//! to find in memory later.
//!
//! 3. After the operation, we scan the processes memory (only
//! readable and writable regions) for the secret.
//!
//! # Example of a test failure
//!
//! This shows the secret leaking into the stack:
//!
//! ```
//! test_ed25519: running test
//! [stack]: 139264 bytes
//! 7ffed1a8ee10 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffed1a8ee20 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffed1a90080 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffed1a90090 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffed1a900a0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffed1a900e0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffed1a90110 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffed1a90120 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffed1a90130 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffed1a90170 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! test_ed25519: secret leaked
//! test leak_tests::test_ed25519 ... FAILED
//! ```
//!
//! # Debugging secret leaks
//!
//! To find what code leaks the secret, we use rr, the lightweight
//! recording & deterministic debugging tool. Deterministic is key
//! here, we can see where the secret leaks to, and then replay the
//! execution and set a watchpoint on the address, and know that it
//! will leak to the exact same address again:
//!
//! ```sh
//! $ rr record target/debug/examples/secret-leak-detector test_ed25519
//! [stack]: 139264 bytes
//! 7ffc9119dab0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffc9119dac0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffc9119ece0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffc9119ecf0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffc9119ed00 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffc9119ed40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffc9119ed70 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffc9119ed80 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffc9119ed90 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffc9119edd0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! test_ed25519: secret leaked
//! $ rr replay -d rust-gdb
//! [...]
//! (rr) watch *0x7ffc9119dab0 if *0x7ffc9119dab0 == 0x40404040
//! Hardware watchpoint 1: *0x7ffc9119dab0
//! (rr) c
//! Continuing.
//! test_ed25519: running test
//!
//! Hardware watchpoint 1: *0x7ffc9119dab0
//!
//! Old value = 0
//! New value = 1077952576
//! 0x000055a463e40969 in sha2::sha512::x86::sha512_update_x_avx (x=0x7ffc9119eba0, k64=...)
//! at src/sha512/x86.rs:260
//! 260 let mut t2 = $SRL64(t0, 1);
//! (rr) c
//! Continuing.
//!
//! Hardware watchpoint 1: *0x7ffc9119dab0
//!
//! Old value = -997709592
//! New value = 1077952576
//! 0x000055a463e3bde0 in sha2::sha512::x86::load_data_avx (x=0x7ffc9119e200, ms=0x7ffc9119e180,
//! data=0x7ffc911a1658) at src/sha512/x86.rs:89
//! 89 unrolled_iterations!(0, 1, 2, 3, 4, 5, 6, 7);
//! (rr) c
//! Continuing.
//! [stack]: 139264 bytes
//! 7ffc9119dab0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffc9119dac0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffc9119ece0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffc9119ecf0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffc9119ed00 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffc9119ed40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffc9119ed70 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffc9119ed80 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffc9119ed90 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! 7ffc9119edd0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!!
//! test_ed25519: secret leaked
//!
//! Program received signal SIGKILL, Killed.
//! 0x0000000070000002 in syscall_traced ()
//! ```
#![allow(dead_code)]
use std::{
env,
io::{self, Write},
path::{Path, PathBuf},
process::Command,
sync::OnceLock,
};
use anyhow::Result;
/// Locates the detector program.
///
/// The detector program is built as an example. This has the
/// advantage that it is built using the same feature flags as the
/// test is invoked, and it is built with as little overhead as
/// possible. The downside is that it may not have been built: if
/// cargo test --test secret-leak-detector is invoked to only run this
/// integration test, then the examples are not implicitly built
/// before running this test.
fn locate_detector() -> Option<&'static Path> {
static NOFREE: OnceLock<Option<PathBuf>> = OnceLock::new();
Some(NOFREE.get_or_init(|| -> Option<PathBuf> {
let mut p = PathBuf::from(env::var_os("OUT_DIR")?);
loop {
let q = p.join("examples").join("secret-leak-detector");
if q.exists() {
break Some(q);
}
if let Some(parent) = p.parent() {
p = parent.to_path_buf();
} else {
break None;
}
}
}).as_ref()?.as_path())
}
/// Emits a message to stderr that is not captured by the test
/// framework, and returns success.
fn skip() -> Result<()> {
// Write directly to stderr. This way, we can emit the message
// even though the test output is captured.
writeln!(&mut io::stderr(),
"Detector not built, skipping test: \
run cargo build -p sequoia-openpgp \
--example secret-leak-detector first")?;
Ok(())
}
macro_rules! make_test {
($name: ident) => {
#[cfg_attr(target_os = "linux", test)]
fn $name() -> Result<()> {
if let Some(d) = locate_detector() {
let result = Command::new(d)
.arg(stringify!($name))
.output()?;
if result.status.success() {
Ok(())
} else {
io::stderr().write_all(&result.stderr)?;
Err(anyhow::anyhow!("leak detected"))
}
} else {
skip()
}
}
}
}
mod leak_tests {
use super::*;
/// Tests that we actually detect leaks.
#[cfg_attr(target_os = "linux", test)]
fn leak_basecase() -> Result<()> {
if let Some(d) = locate_detector() {
let result = Command::new(d)
.arg("leak_basecase")
.output()?;
if ! result.status.success() {
Ok(())
} else {
Err(anyhow::anyhow!("base case failed: no leak detected"))
}
} else {
skip()
}
}
// The tests.
make_test!(clean_basecase);
make_test!(test_memzero);
make_test!(test_libc_memset);
make_test!(test_protected);
make_test!(test_protected_mpi);
make_test!(test_session_key);
make_test!(test_encrypted);
make_test!(test_password);
make_test!(test_ed25519);
#[cfg(not(feature = "crypto-rust"))]
make_test!(test_aes_256_encryption);
#[cfg(not(feature = "crypto-rust"))]
make_test!(test_aes_256_decryption);
}
|