Validate argument count in SET-family string commands - #2050
Open
hexonal (hexonal) wants to merge 1 commit into
Open
Validate argument count in SET-family string commands#2050hexonal (hexonal) wants to merge 1 commit into
hexonal (hexonal) wants to merge 1 commit into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Adds runtime arity validation for core string commands to prevent crashes and preserve sessions.
Changes:
- Validates fixed-arity string commands.
- Safely rejects dangling
SET EX/PXoptions. - Adds malformed-command and session-survival tests.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
libs/server/Resp/BasicCommands.cs |
Adds argument validation and safe option parsing. |
test/standalone/Garnet.test/RespTests.cs |
Tests errors, connection survival, and valid commands. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+446
to
+447
| if (parseState.Count != 3) | ||
| return AbortWithWrongNumberOfArguments(nameof(RespCommand.GETRANGE)); |
Contributor
There was a problem hiding this comment.
This is correct and needs to be addressed.
SET, GETSET, SETEX/PSETEX, SETRANGE, APPEND and GETRANGE/SUBSTR only Debug.Assert(parseState.Count == N) their argument count (or consumed a missing EX/PX option value) instead of validating it at runtime. Malformed input such as 'SET k', 'SETEX k 10', 'SETRANGE k', 'APPEND k', 'GETRANGE k' or 'SET k v EX' therefore aborted the process via the assert in a Debug/CI build, and read an out-of-bounds parse-state slot in Release, instead of returning an error - dropping the connection and every command pipelined after it. Validate the argument count in each handler (and, for SET's EX/PX option, that a value token follows before consuming it), matching what the object-store handlers and NetworkSETWITHETAG already do. Well-formed commands are unchanged; the wire errors match Redis.
hexonal (hexonal)
force-pushed
the
fix-string-command-arity-validation
branch
from
August 10, 2026 01:46
8052ccf to
ff43efb
Compare
kevin-montrose
requested changes
Aug 12, 2026
kevin-montrose
left a comment
Contributor
There was a problem hiding this comment.
Copilot comment is correct, need to distinguish between two different commands using same impl.
Comment on lines
+446
to
+447
| if (parseState.Count != 3) | ||
| return AbortWithWrongNumberOfArguments(nameof(RespCommand.GETRANGE)); |
Contributor
There was a problem hiding this comment.
This is correct and needs to be addressed.
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.
Symptom
Several core string commands abort the whole session — and, in a Debug/CI build, terminate the server process — when a required argument is missing, instead of returning an error. The reply is empty and the connection is dropped, taking every pipelined command after it with it.
Debug build of
GarnetServerat 4706f3f, raw RESP with a trailingPINGso a dropped reply is visible:server log:
The same happens for, all unauthenticated and needing no prior state:
In a Release build the process does not crash — the assert is compiled out and the code instead reads the missing argument from a stale, in-allocation parse-state slot, so e.g.
SETEX k 10(no value) silently stores an empty value rather than erroring. Either way the command does not behave as it should.The hash/list/set command handlers already validate their argument counts and return
-ERR wrong number of arguments; these string handlers onlyDebug.Assert(parseState.Count == N)(or consume an option value without checking a token is present), relying on an argument-count guarantee the parser does not actually provide for these commands.Root cause
libs/server/Resp/BasicCommands.cs:NetworkSET(SET),NetworkGETSET(GETSET),NetworkSETEX(SETEX/PSETEX),NetworkSetRange(SETRANGE),NetworkAppend(APPEND) read fixed argument positions after only aDebug.AssertonparseState.Count;NetworkGetRange(GETRANGE/SUBSTR) has no count check at all. A malformed arity reachesGetArgSliceByRef/TryGetIntat an index>= Count, which asserts (Debug) or reads out of bounds (Release).NetworkSETEXNX(theSET key value [EX seconds | PX milliseconds | NX | XX | GET | KEEPTTL]option parser) consumes theEX/PXvalue withparseState.TryGetInt(tokenIdx++, ...)without first checkingtokenIdx < parseState.Count, soSET k v EX(option in final position) reads past the parse state.Only bare
SET k vreachesNetworkSET; every option-bearing form (SET k v EX 10,SET k v NX,SET k v GET,SET k v KEEPTTL, …) is parsed to a differentRespCommandand a different handler, so requiring exactly two arguments here cannot reject a valid SET.Fix
Validate the argument count at runtime, matching what the object-store handlers and the sibling
NetworkSETWITHETAGalready do:SET,GETSET,APPEND→ exactly 2 args, elseAbortWithWrongNumberOfArguments.SETEX/PSETEX,SETRANGE,GETRANGE/SUBSTR→ exactly 3 args. (GETRANGEandSUBSTRshareNetworkGetRange, so a malformedSUBSTRreports'getrange'— a pre-existing shared-handler quirk, not introduced here.)NetworkSETEXNX→ rejectEX/PXas the final token (tokenIdx >= parseState.Count→ syntax error) before consuming its value, mirroring the existing guard inNetworkSETWITHETAG.KEEPTTL(which takes no value) is unaffected, as the guard is only reached when the option isEX/PX.Well-formed commands are unchanged; the error wire text matches Redis (
wrong number of argumentsfor the arity cases,syntax errorfor a danglingEX/PX).Tests
RespTests.StringCommandsWrongArityReturnErrorAndKeepSessionAlive(raw RESP viaLightClientRequest, each malformed command followed byPING):+PONG— proving the session survived. On unpatched main the process aborts / the reply is dropped, so the request times out and the test fails.SET a 1,SETEX b 100 v,PSETEX c 5000 v,SETRANGE a 1 XY(:3),APPEND a Z(:4),GETSET a fresh($4 1XYZ),GETRANGE a 0 2($3 fre) still behave exactly as before.Full
RespTests(352) stays green.Scope
The same missing-validation pattern remains in two other string commands —
INCRBYFLOAT(INCRBYFLOAT kaborts), and the ETag extension commandsGETWITHETAG/GETIFNOTMATCHinBasicEtagCommands.cs. I left those out to keep this PR focused on the common read/write string commands, but I'm happy to follow up with them (or fold them in here) if you'd prefer to close the whole class at once.