use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use net_backend_protocol::auth::{LoginRequest, TokenPair};
use net_backend_protocol::{AccessToken, Password, RefreshToken, Secret, UnixMillis};
const MARKER: &str = "zz-wipe-marker-7d41c0";
static ARMED: AtomicBool = AtomicBool::new(false);
static SEEN: AtomicUsize = AtomicUsize::new(0);
struct Checker;
unsafe impl GlobalAlloc for Checker {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
unsafe { System.alloc_zeroed(layout) }
}
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
unsafe { System.alloc_zeroed(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
let size = layout.size();
if ARMED.load(Ordering::SeqCst) && (MARKER.len()..=4096).contains(&size) {
let block = unsafe { std::slice::from_raw_parts(ptr, size) };
if block.windows(MARKER.len()).any(|w| w == MARKER.as_bytes()) {
SEEN.fetch_add(1, Ordering::SeqCst);
}
}
unsafe { System.dealloc(ptr, layout) }
}
}
#[global_allocator]
static ALLOCATOR: Checker = Checker;
fn marked() -> String {
let mut text = String::with_capacity(64);
text.push_str(MARKER);
text.push_str("-tail");
text
}
fn freed_with_marker(work: impl FnOnce()) -> usize {
SEEN.store(0, Ordering::SeqCst);
ARMED.store(true, Ordering::SeqCst);
work();
ARMED.store(false, Ordering::SeqCst);
SEEN.load(Ordering::SeqCst)
}
#[test]
fn every_secret_type_wipes_its_allocation_and_each_clone() {
assert_eq!(freed_with_marker(|| drop(marked())), 1, "the check sees an unwiped block");
let seen = freed_with_marker(|| {
let access = AccessToken::new(marked());
let copy = access.clone();
drop(access);
drop(copy);
drop(RefreshToken::new(marked()));
drop(Password::from(marked()));
drop(Secret::new(marked()));
let pair = TokenPair::new(AccessToken::new(marked()), UnixMillis(1), RefreshToken::new(marked()), UnixMillis(2));
drop(pair.clone());
drop(pair);
drop(LoginRequest::new("player@example.com", marked()));
});
assert_eq!(seen, 0, "a secret left its text in freed memory");
let json = format!("\"{MARKER}\"");
let seen = freed_with_marker(|| {
let token: AccessToken = serde_json::from_str(&json).unwrap_or_else(|e| panic!("{e}"));
assert_eq!(token.expose(), MARKER);
drop(token);
});
assert_eq!(seen, 0, "a decoded secret left its text in freed memory");
}