Conversation
execGetKeys() left result->numkeys at 0 until after its loop, but getKeysPrepareResult() copies result->numkeys entries when it grows off keysbuf, so the first MAX_KEYS_BUFFER entries were discarded and replaced with uninitialised heap. COMMAND GETKEYS EXEC with more than 256 conditions returned argv[0] for those entries, and crashed in the reply builder once the heap was no longer zeroed. Publish the count as each entry is written. Signed-off-by: madolson <madelyneolson@gmail.com>
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.
COMMAND GETKEYS EXECwith more than 256 conditions returns the wrong keys and can crash the server.execGetKeys()leavesresult->numkeysat 0 until after its loop, butgetKeysPrepareResult()copies exactlyresult->numkeysentries when it grows off the 256-entry static buffer, so the first 256keyReferenceentries are discarded and replaced with uninitialised heap. On a freshly started server that heap reads as zero,posis 0, and the reply is 256 copies ofEXEC; after any real workloadposis arbitrary and the reply builder dereferences it. This publishes the count as each entry is written.Details
Problem
execGetKeys()appends one key per condition atsrc/multi.c:246-249, growing the result withgetKeysPrepareResult(result, numkeys + 1), and only publishes the total afterwards.getKeysPrepareResult()atsrc/db.c:2368-2376branches on whether it is still using the caller's static buffer. When it moves offkeysbufit allocates and copiesresult->numkeysentries:result->numkeysis still 0 at that point, so nothing is copied andkeys[0..255]are whatever the fresh block holds.MAX_KEYS_BUFFERis 256 (src/server.h:2535) andinitGetKeysResult()setsresult->sizeto it (src/server.h:2556).Every other incremental caller keeps the field in step, for example
getKeysUsingKeySpecs()atsrc/db.c:2474, which is why nothing else trips this.Introduced by #4019, which added the condition arguments and this getkeys proc.
Reachability
Wrong keys, on a server started moments earlier:
Crash, after any churn (40 rounds of a Lua loop setting and deleting 200 keys is enough):
COMMANDis not privilege-gated by default, and cluster-aware clients and proxies callCOMMAND GETKEYSto route commands they do not parse themselves.Plain
MULTI/EXECdoes not reach it. With the same dirtied heap and 300 conditions, standalone replies*-1and cluster-enabled replies-CROSSSLOT, neither crashing. A user limited to~k*getsNOPERMwith the denied key at condition 0, at condition 260, and at 256 conditions, so there is no ACL bypass atpos0; whether a non-zeroposcan fail open is heap dependent and I could not construct it.Alternative considered
Making
getKeysPrepareResult()copyresult->sizeentries, or the requested count, instead ofresult->numkeyswould protect any future caller written this way. It loses because copyingresult->numkeysis the contract every other caller already honours, so changing the helper changes the contract for all of them to accommodate the one that broke it.Testing
The added test fails without the
src/multi.cchange:This was generated by AI but verified, with love, by a human.