Skip to content

Harden S2K Parser - #2283

Closed
subbudvk wants to merge 4 commits into
bcgit:mainfrom
subbudvk:subbudvk-patch-2
Closed

Harden S2K Parser #2283
subbudvk wants to merge 4 commits into
bcgit:mainfrom
subbudvk:subbudvk-patch-2

Conversation

@subbudvk

@subbudvk subbudvk commented Apr 20, 2026

Copy link
Copy Markdown

The S2K wire parser reads memorySizeExponent as a raw byte (0–255) with no bounds check. A 24-byte crafted PGP SKESK v4 packet with memExp=22 causes OOM.

This PR adds a configurable cap on memorySizeExponent at wire-parse time:

-Dorg.bouncycastle.openpgp.argon2.max_memory_exp=N

JUnit test added with the PR: one method reproduces the OOM directly and a second reads the same crafted packet through the fixed parser and asserts IOException is thrown with no heap allocation.

@dghgit dghgit self-assigned this Apr 22, 2026
@dghgit

dghgit commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Thanks for the PR. This is actually a bit weirder than it looks, BC actually has a hard coded max of 30 as it ultimately uses an int to represent the memory as bytes, which I can actually do, even in my IDE.

That said, I think the idea of being able to throttle it is a good one, but the setting needs to apply to generation as well as parsing, and we should probably keep the max at 30 as the default as I'm aware some people push values like this to the max for personal reasons, so a value of 21 is likely to see 1.85 unexpectedly break things.

I've just pushed some updates related to the issue which adds bounds checking in both core and pg which I think cover the intention of this PR as well as generation. Let me know what you think.

@subbudvk

Copy link
Copy Markdown
Author

Thanks for the PR. This is actually a bit weirder than it looks, BC actually has a hard coded max of 30 as it ultimately uses an int to represent the memory as bytes, which I can actually do, even in my IDE.

That said, I think the idea of being able to throttle it is a good one, but the setting needs to apply to generation as well as parsing, and we should probably keep the max at 30 as the default as I'm aware some people push values like this to the max for personal reasons, so a value of 21 is likely to see 1.85 unexpectedly break things.

I've just pushed some updates related to the issue which adds bounds checking in both core and pg which I think cover the intention of this PR as well as generation. Let me know what you think.

@dghgit Thanks for the quick turnaround and for extending the fix to the generation path. The OOM was confirmed practically and I chose the limit as 21. Happy to align with 30 given the int ceiling and compatibility concerns you raised. Changes looks good to me.

@dghgit

dghgit commented May 30, 2026

Copy link
Copy Markdown
Contributor

Marking as done. Now available in https://www.bouncycastle.org/betas

@dghgit dghgit closed this May 30, 2026
hubot pushed a commit that referenced this pull request May 30, 2026
@vanitasvitae

vanitasvitae commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Hey!

Thanks for tackling the potential OOM error. I have a suggestion for an improvement though:
A message may be protected with multiple encrypted session key packets, potentially using different encryption methods (passwords, keys, different parameters...).

The current implementation throws an IOException upon reading a SKESK packet with an off-limit Argon2 mem exponent parameter. As this exception is not caught in the PGPEncryptedDataList constructor, the exception causes the entire message decryption to be aborted.

My suggestion would be to catch the IOException in this try block and to log the rejected packet similar to the UnsupportedPacketVersionException. That way, other encrypted session key packets can be tried.

Otherwise, the presence of a single off-limit SKESK can still act as a DOS vector.

Edit: Is there a reason why the rejection of the parameter happens on the parsing step, not while processing the packet during decryption?
PGPPBEEncryptedData.getSymmetricAlgorithm(), .getSessionKey() seem to me to be more adequate places for the parameter check.

@vanitasvitae

Copy link
Copy Markdown
Contributor

I created a patch moving the parameter validation from the packet parsing step to the message decryption step.

hubot pushed a commit that referenced this pull request Jun 12, 2026
…hrase so the secret key decryption path is also covered, relates to github #2283 and PR #2322.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants