GHSA-HC3C-63HC-2R9F: Root-Cause Fix for encrypt Buffer-Length Panic
Summary
The patch corrects a panic condition in libcrux-chacha20poly1305::encrypt caused by assuming the destination buffer length exactly matched plaintext length plus tag length. By splitting the output buffer in two stages, the implementation now accepts oversized destination buffers while still carving out the ciphertext and fixed-size tag regions safely. The added regression test directly exercises the previously crashing case, and the advisory characterization as a denial-of-service via unhandled panic is consistent with the code change.
Analysis
Vulnerability
GHSA-HC3C-63HC-2R9F describes a denial-of-service condition in libcrux-chacha20poly1305 where encrypt could panic on an oversized destination buffer. The vulnerable implementation treated the caller-provided ciphertext buffer as if it had to be exactly ptxt_len + TAG_LEN bytes long and immediately split it at ptxt_len, binding the remainder directly to tag. That makes the downstream logic sensitive to the remainder length being exactly the tag size; if the caller provides a larger buffer, the function reaches an invalid state and terminates via panic instead of handling the extra capacity safely.
The issue is availability-impacting rather than confidentiality- or integrity-impacting: a malformed but otherwise plausible API input can crash the process. The advisory and changelog both characterize this as a potential panic in libcrux_chacha20poly1305::encrypt, which is source-consistent with an uncaught exception style DoS in Rust panic semantics when not contained by the application.
// vulnerable shape from impl_hacl.rs
// ensure destination slice has just the right length
let (ctxt_cpa, tag) = ctxt.split_at_mut(ptxt_len as usize);Sources: patch PR #1386, GitHub Security Advisory, CVE Reports entry.
Patch
The patch in PR #1386 changes the buffer partitioning logic so the function no longer assumes the post-ciphertext remainder is exactly the authentication tag. Instead, it first splits at the plaintext length, then explicitly takes only TAG_LEN bytes from the remainder and ignores any trailing slack. This is the correct normalization for an API that should tolerate oversized output buffers.
let (ctxt_cpa, rest) = ctxt.split_at_mut(ptxt_len as usize);
// The ciphertext buffer may be longer than ptxt_len + TAG_LEN.
let (tag, _rest) = rest.split_at_mut(TAG_LEN);This change directly addresses the faulty assumption in the original code: the destination buffer may be larger than the minimum required size, and only the first ptxt_len bytes plus the next TAG_LEN bytes are semantically relevant. The patch also adds a regression test that encrypts into a 30-byte buffer for a 13-byte message, then decrypts from the first 29 bytes, demonstrating that oversized buffers are now accepted without panic and still produce valid output.
The changelog entry was updated to document the fix as a potential panic in libcrux_chacha20poly1305::encrypt. Relevant source: https://github.com/cryspen/libcrux/pull/1386.
Review
Pros
- The code change is minimal and directly targets the failing assumption rather than layering on panic suppression.
- The new two-stage split is semantically correct for AEAD output layout: ciphertext region first, fixed-size tag second, trailing capacity ignored.
- The regression test covers the exact bug class: a destination buffer longer than
ptxt.len() + TAG_LEN. - The decrypt assertion on the produced ciphertext confirms the patch preserves functional correctness, not just panic avoidance.
- The advisory description and patch behavior align cleanly, which increases confidence that the fix maps to the reported issue.
Cons
- The provided snippets do not show whether there is also an explicit lower-bound check for undersized destination buffers in this function; if absent elsewhere, that remains a separate correctness concern.
- The test coverage shown exercises one oversized-buffer case but does not demonstrate boundary cases such as exactly sized, minimally oversized, or severely oversized buffers.
- The patch is narrowly scoped to
encrypt; the review material does not establish whether similar buffer-shape assumptions exist in adjacent code paths.
Verdict
Root-cause.
The vulnerability stems from incorrect output-buffer slicing semantics, specifically treating the entire post-ciphertext remainder as the tag region. The patch fixes that underlying logic by carving out only TAG_LEN bytes for the tag and tolerating extra capacity. This is not merely a guard against one manifestation of the panic; it corrects the API's handling of oversized destination buffers at the point where the invalid assumption was encoded. Based on the available diff and tests in PR #1386 and the issue statement in the advisory, the patch should be considered a root-cause fix for GHSA-HC3C-63HC-2R9F.
Recommended Labs
Try this vulnerability pattern yourself with hands-on labs.
- Panic DoS.go
Closest match to the advisory theme: an application-level Denial of Service caused by panic/uncaught-exception behavior. This lab is especially relevant for learning defensive patterns around input validation, guard checks, and converting crash-prone error paths into safe handling.
- DoS.api
Good hands-on defensive lab for DoS mitigation at API boundaries. It reinforces the same core lesson as this patch review: validate sizes/limits early, reject malformed or oversized inputs safely, and avoid termination-causing failure modes.
- DoS GraphQL.ts
Useful for practicing defense-in-depth against request-triggered DoS. While not crypto-specific, it maps well to the reported issue by emphasizing boundary checks, safe error handling, and resilience against attacker-controlled input that could otherwise crash or overwhelm the service.