use std::{
alloc::{
GlobalAlloc,
Layout,
System,
},
cell::Cell,
collections::BTreeMap,
ffi::OsStr,
fmt::{
self,
Write,
},
sync::{
Mutex,
MutexGuard,
atomic::{
AtomicUsize,
Ordering,
},
},
};
use qubit_redact::{
InputOutputLimit,
LogOutputLimit,
MaskPolicy,
Redact,
RedactedMap,
RedactionPolicy,
Redactor,
Sensitivity,
argv::{
ArgvItem,
ArgvRedactor,
},
env::EnvRedactor,
};
#[cfg(feature = "uri")]
use qubit_redact::UriRedactor;
static ALLOCATION_TEST_LOCK: Mutex<()> = Mutex::new(());
thread_local! {
static TRACK_ALLOCATIONS: Cell<bool> = const { Cell::new(false) };
}
static LARGEST_ALLOCATION: AtomicUsize = AtomicUsize::new(0);
struct MeasuringAllocator;
unsafe impl GlobalAlloc for MeasuringAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
record_allocation(layout.size());
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, pointer: *mut u8, layout: Layout) {
unsafe { System.dealloc(pointer, layout) }
}
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
record_allocation(layout.size());
unsafe { System.alloc_zeroed(layout) }
}
unsafe fn realloc(
&self,
pointer: *mut u8,
layout: Layout,
new_size: usize,
) -> *mut u8 {
record_allocation(new_size);
unsafe { System.realloc(pointer, layout, new_size) }
}
}
#[global_allocator]
static GLOBAL_ALLOCATOR: MeasuringAllocator = MeasuringAllocator;
#[inline(always)]
fn record_allocation(size: usize) {
if TRACK_ALLOCATIONS.with(Cell::get) {
LARGEST_ALLOCATION.fetch_max(size, Ordering::Relaxed);
}
}
fn measure_largest_allocation<T>(operation: impl FnOnce() -> T) -> (T, usize) {
LARGEST_ALLOCATION.store(0, Ordering::Relaxed);
TRACK_ALLOCATIONS.with(|tracking| tracking.set(true));
let result = operation();
TRACK_ALLOCATIONS.with(|tracking| tracking.set(false));
(result, LARGEST_ALLOCATION.load(Ordering::Relaxed))
}
fn allocation_test_lock() -> MutexGuard<'static, ()> {
ALLOCATION_TEST_LOCK
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
struct FixedBuffer {
bytes: [u8; 512],
len: usize,
}
impl FixedBuffer {
const fn new() -> Self {
Self {
bytes: [0; 512],
len: 0,
}
}
}
impl Write for FixedBuffer {
fn write_str(&mut self, value: &str) -> fmt::Result {
let end = self.len.saturating_add(value.len());
if end > self.bytes.len() {
return Err(fmt::Error);
}
self.bytes[self.len..end].copy_from_slice(value.as_bytes());
self.len = end;
Ok(())
}
}
fn amplified_policy() -> RedactionPolicy {
let replacement = "X".repeat(1024 * 1024);
let budget = InputOutputLimit::new(4096, 128)
.expect("the diagnostic budget should be valid");
RedactionPolicy::builder()
.mask(Sensitivity::High, MaskPolicy::fixed(&replacement))
.expect("the test mask policy should be valid")
.mask(Sensitivity::Secret, MaskPolicy::fixed(&replacement))
.expect("the test mask policy should be valid")
.diagnostic_event(budget)
.build()
.expect("the amplified policy should be valid")
}
struct NestedBoundedMap<'a> {
values: &'a BTreeMap<&'a str, &'a str>,
policy: RedactionPolicy,
limit: LogOutputLimit,
}
impl Redact for NestedBoundedMap<'_> {
fn fmt_redacted(
&self,
_session: &qubit_redact::RedactionSession<'_>,
formatter: &mut fmt::Formatter<'_>,
) -> fmt::Result {
write!(
formatter,
"{}",
RedactedMap::new(self.values, self.policy.clone())
.with_output_limit(self.limit),
)
}
}
#[test]
fn test_nested_bounded_display_does_not_widen_mask_allocation_limit() {
let _guard = allocation_test_lock();
let values = BTreeMap::from([("password", "raw-secret")]);
let inner_limit = LogOutputLimit::new(2 * 1024 * 1024)
.expect("the inner output limit should be valid");
let outer_limit = LogOutputLimit::new(14)
.expect("the outer output limit should be valid");
let nested = NestedBoundedMap {
values: &values,
policy: amplified_policy(),
limit: inner_limit,
};
let view = nested.redacted().with_output_limit(outer_limit);
let mut output = FixedBuffer::new();
let (result, largest) =
measure_largest_allocation(|| write!(&mut output, "{view}"));
result.expect("the nested bounded view should fit the fixed output buffer");
assert!(
largest < 4096,
"inner bounded view widened the mask allocation limit: {largest}",
);
}
#[test]
fn test_bounded_redacted_map_avoids_amplified_mask_allocation() {
let _guard = allocation_test_lock();
let values = BTreeMap::from([("password", "raw-secret")]);
let limit = LogOutputLimit::new(128)
.expect("the test output limit should be valid");
let view =
RedactedMap::new(&values, amplified_policy()).with_output_limit(limit);
let mut output = FixedBuffer::new();
let (result, largest) =
measure_largest_allocation(|| write!(&mut output, "{view}"));
result.expect("the bounded map should fit the fixed output buffer");
assert!(
largest < 4096,
"bounded map copied an amplified mask: {largest}"
);
}
#[test]
fn test_bounded_argv_avoids_amplified_mask_allocation() {
let _guard = allocation_test_lock();
let redactor = ArgvRedactor::new(Redactor::new(amplified_policy()));
let (rendered, largest) = measure_largest_allocation(|| {
redactor
.redact_items([ArgvItem::sensitive(
OsStr::new("raw-secret"),
Sensitivity::Secret,
)])
.to_string()
});
assert!(rendered.len() <= 128, "{rendered}");
assert!(
largest < 4096,
"bounded argv copied an amplified mask: {largest}"
);
}
#[test]
fn test_bounded_environment_avoids_amplified_mask_allocation() {
let _guard = allocation_test_lock();
let redactor = EnvRedactor::new(Redactor::new(amplified_policy()));
let (rendered, largest) = measure_largest_allocation(|| {
redactor
.redact_os_pairs([(
OsStr::new("PASSWORD"),
OsStr::new("raw-secret"),
)])
.to_string()
});
assert!(rendered.len() <= 128, "{rendered}");
assert!(
largest < 4096,
"bounded environment copied an amplified mask: {largest}",
);
}
#[cfg(feature = "uri")]
#[test]
fn test_bounded_uri_avoids_amplified_mask_allocation() {
let _guard = allocation_test_lock();
let replacement = "X".repeat(1024 * 1024);
let budget = InputOutputLimit::new(4096, 128)
.expect("the diagnostic budget should be valid");
let core = RedactionPolicy::default()
.to_builder()
.mask(Sensitivity::High, MaskPolicy::fixed(&replacement))
.expect("the high mask policy should be valid")
.mask(Sensitivity::Secret, MaskPolicy::fixed(&replacement))
.expect("the secret mask policy should be valid")
.diagnostic_event(budget)
.build()
.expect("the core policy should be valid");
let uri_policy = RedactionPolicy::builder_from(&core)
.build()
.expect("the URI policy should be valid");
let redactor = UriRedactor::new(uri_policy);
let query = ["password=query-secret"; 32].join("&");
let input = format!("https://user:password@example.test/?{query}#fragment");
let (result, largest) =
measure_largest_allocation(|| redactor.redact_uri_str(&input));
assert!(result.is_truncated());
assert!(result.log_safe_text().as_ref().len() <= 128);
assert!(
largest <= 4096,
"URI redaction copied an amplified mask: {largest}",
);
}