Context
I am the author of sosecrets-rs and right now, I am writing RTSecret<T, MEC: typenum::Unsigned>
, which represents a secret that would return an Err
or panics only if the value of its internal counter (a counter that measures the number of time a secret is exposed, I name it as ec
) exceeds the runtime unsigned integer value represented by the type parameter MEC
.
Here is the current definition of RTSecret<T, MEC: typenum::Unsigned>
:
pub struct RTSecret<
#[cfg(feature = "zeroize")] T: Zeroize,
#[cfg(not(feature = "zeroize"))] T,
MEC: ChooseMinimallyRepresentableUInt + Unsigned,
>(T, Cell<<MEC as ChooseMinimallyRepresentableUInt>::Output>);
<MEC as ChooseMinimallyRepresentableUInt>::Output
basically resolves to either u8
, u16
, u32
or u64
depending on what the user of this library chooses to be MEC
, e.g. if he chooses MEC
to be typenum::U69
, then <MEC as ChooseMinimallyRepresentableUInt>::Output
will resolve to u8
, since '69' is minimally representable by u8
.
Right now, there is a try_expose_secret
which takes in a closure, it returns a Result<ReturnType, ExposeSecretError<MEC>>
like so:
#[inline(always)]
fn try_expose_secret<ReturnType, ClosureType>(
&self,
scope: ClosureType,
) -> Result<ReturnType, error::ExposeSecretError<MEC>>
where
MEC: IsGreater<Unchecked, Output = True>,
for<'brand> ClosureType: FnOnce(RTExposedSecret<'brand, &'brand T>) -> ReturnType,
{
let ec_mut = self.1.get();
let mec = MEC::cast_unsigned_to_self_type::<MEC>(__private::SealedToken {});
if ec_mut >= mec {
// NOTE HERE, I HAVE A PROBLEM HERE ###################
return Err(error::ExposeSecretError::ExposeMoreThanMaximallyAllow(
error::ExposeMoreThanMaximallyAllowError { mec, ec: ec_mut },
));
};
self.1.set(ec_mut + MEC::ONE);
Ok(scope(RTExposedSecret(&self.0, PhantomData)))
}
Note the comment '// NOTE HERE, I HAVE A PROBLEM HERE ###################' within the code snippet.
Problem
Suppose a secret has been maximally exposed but is not dropped immediately because it is still not out of scope, and if the program is a long-running process, that would mean that the secret will be held in memory for a long time. Hence, I am thinking of wrapping T
with ManuallyDrop<Cell<T>>
so that I can 1. get a &mut T
from Cell<T>
and 2. drop T
only when the exposure count is greater than the maximal exposure count.
However, there is one important problem/blocker.
if ec_mut >= mec {
// NOTE HERE, I HAVE A PROBLEM HERE ###################
return Err(error::ExposeSecretError::ExposeMoreThanMaximallyAllow(
error::ExposeMoreThanMaximallyAllowError { mec, ec: ec_mut },
));
};
Suppose a user of my library, continues to call try_expose_secret
despite getting an Err
for the first call, I would be double-freeing if I keep dropping T
through ManuallyDrop<Cell<T>>
.
Maybe one workaround will be to add one to the exposure count after it exceeds the maximal exposure count, but what if the caller sets the maximal exposure count to u*::MAX
, then there will be overflow, and that must not happen - because in release mode, it will render the Secret
type completely useless as the count is reset to zero.
Of course, I can completely ignore this 'problem' and let Secret
get dropped only when its scope ends, however long that may take.