Summary
Several code paths in pg-core can panic when processing malformed or truncated ciphertext, instead of returning proper Error values. This allows a denial-of-service attack where a crafted ciphertext crashes the consuming application (or browser tab in the WASM case).
Affected code paths
1. In-memory unsealer: split_at without bounds check
File: pg-core/src/client/rust/mod.rs:139-145
let (preamble_bytes, b) = b.split_at(PREAMBLE_SIZE);
let (version, header_len) = preamble_checked(preamble_bytes)?;
let (header_bytes, b) = b.split_at(header_len); // panics if b.len() < header_len
let (h_sig_len_bytes, b) = b.split_at(SIG_SIZE_SIZE); // panics if remainder too short
let h_sig_len = u32::from_be_bytes(h_sig_len_bytes.try_into()?);
let (h_sig_bytes, ct) = b.split_at(h_sig_len as usize); // panics if remainder too short
preamble_checked validates that header_len <= MAX_HEADER_SIZE, but does not check that b actually has enough bytes. A ciphertext with a valid preamble but truncated body will panic at the first split_at(header_len).
2. Stream unsealer: extract_policy missing bounds check
File: pg-core/src/client/rust/stream.rs:276-285 and pg-core/src/client/web/stream.rs:347-358
fn extract_policy(buf: &mut Vec<u8>) -> Result<Option<(Policy, Identity)>, Error> {
let pol_len = u32::from_be_bytes(buf[..POL_SIZE_SIZE].try_into()?) as usize;
let pol_bytes = &buf[POL_SIZE_SIZE..POL_SIZE_SIZE + pol_len]; // panics if pol_len > buf.len()
// ...
}
pol_len is read from the decrypted first segment (attacker-controlled) without verifying POL_SIZE_SIZE + pol_len <= buf.len().
3. Stream unsealer: debug_assert used instead of runtime check
File: pg-core/src/client/rust/stream.rs:295 and pg-core/src/client/web/stream.rs:396,435
debug_assert!(seg.len() > SIG_BYTES);
let (m, sig_bytes) = seg.split_at(seg.len() - SIG_BYTES);
The debug_assert is only active in debug builds. In release builds, if the decrypted segment is shorter than SIG_BYTES, the subtraction wraps (or panics at split_at).
Suggested fix
Replace each split_at / slice operation on untrusted data with a bounds check that returns Error::FormatViolation(...) instead of panicking. Replace debug_assert! with proper runtime checks.
Example for the in-memory unsealer:
if b.len() < header_len {
return Err(Error::FormatViolation("header truncated".into()));
}
let (header_bytes, b) = b.split_at(header_len);
Impact
Any application or browser tab using pg-core to decrypt untrusted PostGuard ciphertext can be crashed by a crafted input. This affects:
- pg-wasm (browser clients crash the tab)
- pg-cli (CLI crashes)
- Any Rust application using pg-core directly
Summary
Several code paths in
pg-corecan panic when processing malformed or truncated ciphertext, instead of returning properErrorvalues. This allows a denial-of-service attack where a crafted ciphertext crashes the consuming application (or browser tab in the WASM case).Affected code paths
1. In-memory unsealer:
split_atwithout bounds checkFile:
pg-core/src/client/rust/mod.rs:139-145preamble_checkedvalidates thatheader_len <= MAX_HEADER_SIZE, but does not check thatbactually has enough bytes. A ciphertext with a valid preamble but truncated body will panic at the firstsplit_at(header_len).2. Stream unsealer:
extract_policymissing bounds checkFile:
pg-core/src/client/rust/stream.rs:276-285andpg-core/src/client/web/stream.rs:347-358pol_lenis read from the decrypted first segment (attacker-controlled) without verifyingPOL_SIZE_SIZE + pol_len <= buf.len().3. Stream unsealer:
debug_assertused instead of runtime checkFile:
pg-core/src/client/rust/stream.rs:295andpg-core/src/client/web/stream.rs:396,435The
debug_assertis only active in debug builds. In release builds, if the decrypted segment is shorter thanSIG_BYTES, the subtraction wraps (or panics atsplit_at).Suggested fix
Replace each
split_at/ slice operation on untrusted data with a bounds check that returnsError::FormatViolation(...)instead of panicking. Replacedebug_assert!with proper runtime checks.Example for the in-memory unsealer:
Impact
Any application or browser tab using pg-core to decrypt untrusted PostGuard ciphertext can be crashed by a crafted input. This affects: