Summary
The define_aead_key_type! macro (and the cipher context structs it supports) does not implement Drop with zeroization. When a key or cipher context is dropped, its backing memory is not cleared, leaving secret material available in heap/stack memory until it is overwritten by subsequent allocations.
Affected types
Aes128GcmKey, Aes256GcmKey, Aes128GcmSivKey, Aes256GcmSivKey
ChaCha20Poly1305Key, XChaCha20Poly1305Key
Aegis256Key, Ascon128Key
- The cipher context structs themselves (e.g.
Aes256Gcm stores the expanded key schedule ek, GHASH key h, and all precomputed h_powers_rev_* arrays — none of which are zeroized on drop)
Contrast with SecretBytes<N>
SecretBytes<N> correctly implements Drop:
impl<const N: usize> Drop for SecretBytes<N> {
fn drop(&mut self) {
ct::zeroize(&mut self.0);
}
}
But the key types generated by define_aead_key_type! do not have an equivalent Drop impl.
Impact
If an application uses one of these key types and the key is later dropped, the raw key bytes and the expanded key schedule remain in memory. An attacker who can read process memory — via a heap disclosure vulnerability, a core dump, a cold-boot attack, or a swap file — can recover the key. This is particularly relevant for long-lived servers that process many keys over their lifetime.
Suggested fix
Add Drop with ct::zeroize to the define_aead_key_type! macro:
impl Drop for $name {
fn drop(&mut self) {
crate::traits::ct::zeroize(&mut self.0);
}
}
And add equivalent Drop implementations to the cipher context structs, zeroing out ek, h, and all h_powers_rev_* fields.
Notes
ct::zeroize (volatile writes + compiler_fence(SeqCst)) is already present and correct — this is purely a missing Drop impl.
- This is a defense-in-depth finding. Direct key disclosure via this path requires a separate memory-read primitive.
- Discovered via Show HN post (2026-06-04). Happy to contribute a PR if helpful.
— nullref (security researcher)
Summary
The
define_aead_key_type!macro (and the cipher context structs it supports) does not implementDropwith zeroization. When a key or cipher context is dropped, its backing memory is not cleared, leaving secret material available in heap/stack memory until it is overwritten by subsequent allocations.Affected types
Aes128GcmKey,Aes256GcmKey,Aes128GcmSivKey,Aes256GcmSivKeyChaCha20Poly1305Key,XChaCha20Poly1305KeyAegis256Key,Ascon128KeyAes256Gcmstores the expanded key scheduleek, GHASH keyh, and all precomputedh_powers_rev_*arrays — none of which are zeroized on drop)Contrast with
SecretBytes<N>SecretBytes<N>correctly implementsDrop:But the key types generated by
define_aead_key_type!do not have an equivalentDropimpl.Impact
If an application uses one of these key types and the key is later dropped, the raw key bytes and the expanded key schedule remain in memory. An attacker who can read process memory — via a heap disclosure vulnerability, a core dump, a cold-boot attack, or a swap file — can recover the key. This is particularly relevant for long-lived servers that process many keys over their lifetime.
Suggested fix
Add
Dropwithct::zeroizeto thedefine_aead_key_type!macro:And add equivalent
Dropimplementations to the cipher context structs, zeroing outek,h, and allh_powers_rev_*fields.Notes
ct::zeroize(volatile writes +compiler_fence(SeqCst)) is already present and correct — this is purely a missingDropimpl.— nullref (security researcher)