Skip to content

Commit 97dad4e

Browse files
committed
test(stack-encrypt): pin the wipe on drop and the Debug of a planned value
The mutants gate on #1595 found six surviving mutants: the two wipe functions and the Drop of a marked query, the Zeroize and Debug of a planned value, and the Debug of a field handle. Each is now pinned by a unit test: a dropped query wipes its value once (both bounds of a range), a value taken out of it is not wiped a second time, zeroizing a planned value empties its frame, and Debug names the wrapper and the field but shows no value. Refs #1486.
1 parent 80ce513 commit 97dad4e

2 files changed

Lines changed: 112 additions & 0 deletions

File tree

‎packages/stack-encrypt/src/dynamic/planned.rs‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,3 +676,53 @@ impl<'p, R: TargetResolver + ?Sized> DynamicQuery<'p, R> {
676676
.term(keyset, self.value, &self.scopes, self.resolver)
677677
}
678678
}
679+
680+
#[cfg(test)]
681+
mod tests {
682+
use super::*;
683+
use crate::dynamic::test_frame::{frame_of, Tree};
684+
use vitaminc_protected::AsProtectedRef as _;
685+
686+
fn s(value: &str) -> Tree {
687+
Tree::String(value.into())
688+
}
689+
690+
fn the_plan() -> Plan {
691+
let spec = Tree::Object(vec![
692+
("context".into(), Tree::Array(vec![s("users"), s("age")])),
693+
("outputs".into(), Tree::Array(vec![s("c")])),
694+
("type".into(), s("uint32")),
695+
]);
696+
let value = Tree::Object(vec![("age".into(), spec)]);
697+
record::plan(frame_of(&value).as_protected_ref().as_deref()).expect("parses")
698+
}
699+
700+
fn a_row() -> ValueFrame {
701+
let value = Tree::Object(vec![("age".into(), Tree::UInt32(34))]);
702+
ValueFrame::from_bytes(frame_of(&value).as_protected_ref().as_deref()).expect("a frame")
703+
}
704+
705+
/// Zeroizing a planned value wipes the value it holds: the frame is
706+
/// replaced by the empty one, whose dropped buffer wipes the bytes.
707+
#[test]
708+
fn zeroizing_a_planned_value_empties_its_frame() {
709+
let plan = the_plan();
710+
let mut planned = Planned::new(&plan, a_row());
711+
assert_ne!(planned.value, ValueFrame::empty());
712+
planned.zeroize();
713+
assert_eq!(planned.value, ValueFrame::empty());
714+
}
715+
716+
/// `Debug` names the wrapper and the handle's field, and shows no value.
717+
#[test]
718+
fn debug_names_the_wrapper_and_the_field_but_no_value() {
719+
let plan = the_plan();
720+
let shown = format!("{:?}", Planned::new(&plan, a_row()));
721+
assert!(shown.starts_with("Planned"), "{shown}");
722+
assert!(!shown.contains("34"), "{shown}");
723+
let handle = plan.field("age").expect("the field");
724+
let shown = format!("{handle:?}");
725+
assert!(shown.starts_with("FieldHandle"), "{shown}");
726+
assert!(shown.contains("age"), "{shown}");
727+
}
728+
}

‎packages/stack-encrypt/src/marker.rs‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,3 +383,65 @@ where
383383
.map(move |terms| T::Via::query(verifier, terms, token))
384384
}
385385
}
386+
387+
#[cfg(test)]
388+
mod tests {
389+
use super::*;
390+
use std::sync::atomic::{AtomicUsize, Ordering};
391+
use std::sync::Arc;
392+
393+
/// A query value that counts how many times it was wiped.
394+
struct Spy(Arc<AtomicUsize>);
395+
396+
impl Zeroize for Spy {
397+
fn zeroize(&mut self) {
398+
let _ = self.0.fetch_add(1, Ordering::SeqCst);
399+
}
400+
}
401+
402+
fn query<In>(value: In, wipe: fn(In)) -> MarkedQuery<(), (), In> {
403+
MarkedQuery {
404+
label: Label::parse("users/age").expect("a label"),
405+
value: Some(value),
406+
wipe,
407+
scopes: DeclaredContext::default(),
408+
marker: PhantomData,
409+
}
410+
}
411+
412+
/// A query dropped before it is derived wipes the value it copied.
413+
#[test]
414+
fn a_dropped_query_wipes_its_value() {
415+
let wipes = Arc::new(AtomicUsize::new(0));
416+
let query = query(Spy(Arc::clone(&wipes)), wipe_one::<Spy>);
417+
assert_eq!(wipes.load(Ordering::SeqCst), 0, "held, not wiped");
418+
drop(query);
419+
assert_eq!(wipes.load(Ordering::SeqCst), 1, "wiped once on drop");
420+
}
421+
422+
/// A range query wipes both of its bounds.
423+
#[test]
424+
fn a_dropped_range_query_wipes_both_bounds() {
425+
let lo = Arc::new(AtomicUsize::new(0));
426+
let hi = Arc::new(AtomicUsize::new(0));
427+
let query = query(
428+
(Spy(Arc::clone(&lo)), Spy(Arc::clone(&hi))),
429+
wipe_pair::<Spy>,
430+
);
431+
drop(query);
432+
assert_eq!(lo.load(Ordering::SeqCst), 1);
433+
assert_eq!(hi.load(Ordering::SeqCst), 1);
434+
}
435+
436+
/// A query whose value was taken, as deriving it does, wipes nothing
437+
/// twice: the taken value is the derivation's to wipe.
438+
#[test]
439+
fn a_taken_value_is_not_wiped_by_the_drop() {
440+
let wipes = Arc::new(AtomicUsize::new(0));
441+
let mut query = query(Spy(Arc::clone(&wipes)), wipe_one::<Spy>);
442+
let taken = query.value.take().expect("the value");
443+
drop(query);
444+
assert_eq!(wipes.load(Ordering::SeqCst), 0, "nothing left to wipe");
445+
drop(taken);
446+
}
447+
}

0 commit comments

Comments
 (0)