Summary
The Rust implementation of TWData releases its underlying Vec<u8> without securely erasing its contents.
This can leave sensitive data—including decrypted wallet plaintext and derived key material—in allocator-managed memory after TWData is deleted. The equivalent C++ issue was fixed in PR #4667, but Rust TWData is an independent implementation and is not covered by that fix.
Details
Rust TWData directly owns a regular Vec<u8>:
#[derive(Clone, Debug, Default)]
pub struct TWData(Data);
Its deletion function reconstructs the owner and relies on the default Drop implementation:
pub unsafe extern "C" fn tw_data_delete(data: *mut TWData) {
let _ = TWData::from_ptr(data);
}
Dropping a regular Vec<u8> deallocates its buffer without securely erasing its contents.
One affected path is cloud backup wallet restoration:
let plaintext = try_or_else!(
session
.access(|session| session.restore_wallet(wid, ep.as_slice()))
.and_then(Result::ok),
std::ptr::null_mut
);
TWData::from(plaintext.to_vec()).into_ptr()
Although plaintext is a Zeroizing<Vec<u8>>, plaintext.to_vec() creates a separate copy. That copy is moved into TWData and is not zeroized when tw_data_delete is called.
The problem applies globally to sensitive values stored in Rust TWData. It also affects implicit destruction paths, such as TWData instances owned by TWDataVector, because zeroization is not enforced by the TWData type itself.
The independent C++ implementation already establishes the expected invariant by calling memzero before deallocation:
void TWDataDelete(TWData *_Nonnull data) {
auto* vConst = reinterpret_cast<const Data*>(data);
if (!vConst->empty()) {
auto* v = const_cast<Data*>(vConst);
memzero(v->data(), v->size());
}
delete vConst;
}
Rust TWData should enforce the same invariant through its destruction semantics, covering every direct and container-owned destruction path.
PoC
- Create a Rust
TWData containing a recognizable sensitive pattern:
let data = TWData::from(b"TW_DATA_SENSITIVE_PLAINTEXT".to_vec());
let ptr = data.into_ptr();
- Obtain the underlying buffer address and length:
let bytes = unsafe { tw_data_bytes(ptr) };
let size = unsafe { tw_data_size(ptr) };
- Call the Rust deletion function:
unsafe { tw_data_delete(ptr) };
- Observe the buffer immediately before allocator deallocation with a debugger or instrumented allocator.
The bytes passed to the allocator still contain:
TW_DATA_SENSITIVE_PLAINTEXT
No zeroization operation occurs between tw_data_delete taking ownership and Vec<u8> deallocating its buffer.
Reading the buffer after deallocation would itself be undefined behavior; allocator/debugger instrumentation should therefore inspect it at the deallocation boundary.
Impact
This is a sensitive-memory-remanence vulnerability.
Applications using Rust-backed wallet-core APIs may leave decrypted wallet plaintext, derived keys, passwords, or other sensitive TWData contents in process heap memory after deletion. The residual data may be exposed through:
- process memory dumps;
- crash reports or core dumps;
- debugger or forensic inspection;
- allocator reuse following another memory-safety vulnerability.
The issue affects every caller that stores sensitive material in the Rust TWData implementation. It is not limited to the cloud backup restore path.
Summary
The Rust implementation of
TWDatareleases its underlyingVec<u8>without securely erasing its contents.This can leave sensitive data—including decrypted wallet plaintext and derived key material—in allocator-managed memory after
TWDatais deleted. The equivalent C++ issue was fixed in PR #4667, but RustTWDatais an independent implementation and is not covered by that fix.Details
Rust
TWDatadirectly owns a regularVec<u8>:Its deletion function reconstructs the owner and relies on the default
Dropimplementation:Dropping a regular
Vec<u8>deallocates its buffer without securely erasing its contents.One affected path is cloud backup wallet restoration:
Although
plaintextis aZeroizing<Vec<u8>>,plaintext.to_vec()creates a separate copy. That copy is moved intoTWDataand is not zeroized whentw_data_deleteis called.The problem applies globally to sensitive values stored in Rust
TWData. It also affects implicit destruction paths, such asTWDatainstances owned byTWDataVector, because zeroization is not enforced by theTWDatatype itself.The independent C++ implementation already establishes the expected invariant by calling
memzerobefore deallocation:Rust
TWDatashould enforce the same invariant through its destruction semantics, covering every direct and container-owned destruction path.PoC
TWDatacontaining a recognizable sensitive pattern:The bytes passed to the allocator still contain:
No zeroization operation occurs between
tw_data_deletetaking ownership andVec<u8>deallocating its buffer.Reading the buffer after deallocation would itself be undefined behavior; allocator/debugger instrumentation should therefore inspect it at the deallocation boundary.
Impact
This is a sensitive-memory-remanence vulnerability.
Applications using Rust-backed wallet-core APIs may leave decrypted wallet plaintext, derived keys, passwords, or other sensitive
TWDatacontents in process heap memory after deletion. The residual data may be exposed through:The issue affects every caller that stores sensitive material in the Rust
TWDataimplementation. It is not limited to the cloud backup restore path.