Skip to content

pg-core: panics on malformed ciphertext input instead of returning errors #138

@dobby-coder

Description

@dobby-coder

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingsecuritySecurity-related issue (vulnerability, hardening, or risk)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions