Repository navigation
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A JSON value nested deeply enough makes the vendored parser recurse until the reading thread runs
out of stack. The resulting
StackOverflowErroris anError, not anException, so it escapesthe
catchblocks in the response classes and in the calling application, and kills the thread.Because the value is stored in Vault, every later read of that path fails the same way.
This PR fixes it in two layers:
a new internal
SafeJson.parse(), which rejects JSON nested deeper than 500 levels. The checkis a loop, so it works on any thread stack size. A response nested too deeply is ignored, like
any other malformed response.
upstream's parser rejects JSON nested deeper than 1000 levels with a
ParseException. Thislimit protects applications that call
Json.parse()directly, but the parser still recursesup to that depth, which is too deep for small thread stacks (512 KB or less). The driver's own
check covers that case.
Vault 1.20 and later reject values nested deeper than 298 levels, so the 500 limit does not affect
valid data. Older Vault versions accept deeper values, so the fix matters most for them.
Changes
Update vendored JSON library (
9c314d5)io.github.jopenlibs.vault.jsonis replaced with the latest upstream minimal-json, repackaged.New public types:
JsonHandler,Location, and a public streamingJsonParserrules
Logical.delete(),unDelete()anddestroy()useJson.array(versions)instead ofJsonObject.add(String, int[]). The request body is the same JSON array ([1,2]instead of[1, 2])MountPayloaduses privateaddIfNotNull()helpers instead ofJsonObject.addIfNotNull()MountResponseuses private getters returningnullfor missing members, instead of theone-argument
JsonObject.getString(),getInt()andgetBoolean()Add nesting depth check (
adac3f2)io.github.jopenlibs.vault.internal, not exported bymodule-info.java:SafeJson.parse(String)scans the text once, counting[/{against]/}and skippingstring contents (escaped quotes included). It throws
JsonNestingExceptionaboveMAX_NESTING_DEPTH(500), and otherwise callsJson.parse(). Other syntax errors are left tothe parser
JsonNestingExceptionis an unchecked exceptionSafeJsonTests(12 tests):mix of them
AuthResponseignores a deeply nested bodyLogicalResponseon a thread with a 256 KB stack no longerkills the thread. Parsing the same payload with plain
Json.parse()on that thread raisesStackOverflowError, so this test covers the bugUse the check everywhere (
d75a1fd)Json.parse()calls inLogicalResponse,AuthResponse,WrapResponse,LookupResponse,SealResponse,HealthResponseandVaultImpluseSafeJson.parse()AuthResponse,WrapResponse,LookupResponseandSealResponsecatchParseException | JsonNestingException.LogicalResponseandHealthResponsealready catchException; inVaultImplthe exception becomes aVaultExceptionthrough the existingcatch (RuntimeException)Breaking changes
The vendored package is public API (
LogicalResponse.getDataObject(),UnwrapResponse.getData(),Auth.wrap(),Wrapping.wrap(),MountPayload,LogicalUtilities). Upstream minimal-json doesnot have some methods that the driver had added to its old copy, so code calling them no longer
compiles, and code compiled against 6.2.3 fails at runtime with
NoSuchMethodError:JsonObject.getString(String),getInt(String),getLong(String),getFloat(String),getDouble(String)andgetBoolean(String), which returnednullfor missing members. Thetwo-argument versions with a default value are still available
JsonObject.addIfNotNull(String, Object)andJsonObject.add(String, int[])Json.value(int[])Also,
Json.array()now returnsJsonArrayinstead ofJsonValue. Source code still compiles,but code compiled against 6.2.3 fails at runtime with
NoSuchMethodError.These need a changelog entry, and either a major release or restoring the methods.
Testing
SafeJsonTestsThanks
Special thanks to Echo to help to find this problem