Skip to content

Reject SETRANGE offsets that overflow max record size#1947

Open
hexonal wants to merge 4 commits into
microsoft:mainfrom
hexonal:fix-setrange-offset-overflow
Open

Reject SETRANGE offsets that overflow max record size#1947
hexonal wants to merge 4 commits into
microsoft:mainfrom
hexonal:fix-setrange-offset-overflow

Conversation

@hexonal

@hexonal hexonal commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Problem

SETRANGE with a very large but syntactically valid offset (e.g. SETRANGE key 600000000 value) kills the client connection instead of returning a RESP error.

NetworkSetRange in libs/server/Resp/BasicCommands.cs only rejects negative offsets before dispatching to the storage layer. When offset + value.Length exceeds the maximum record size (BitmapManager.MaxBitmapPayloadBytes, 512 MiB — the same limit enforced for SETBIT/BITFIELD), RecordSizeInfo.CalculateSizes in the Tsavorite storage layer throws instead of returning a value the RESP layer can turn into a clean error. That exception propagates up as an unhandled server-side fault and the connection is dropped (RedisConnectionException: SocketClosed), rather than the client simply seeing an ERR reply.

Reproduce with SETRANGE against a fresh key and a large offset — the connection is closed instead of getting an error response.

Fix

Add a check in NetworkSetRange that rejects offset + value.Length > BitmapManager.MaxBitmapPayloadBytes before building the storage input, returning a clean RESP error instead of letting the storage layer throw. The error text matches real Redis's distinct message for this failure class ("ERR string exceeds maximum allowed size (proto-max-bulk-len)", added as a new CmdStrings constant) — separate from the existing "ERR offset is out of range" used for negative offsets, matching Redis's own t_string.c behavior and the precedent already set in this codebase by SETBIT's dedicated out-of-range message.

Testing

  • Built Garnet.server.csproj (net8.0 + net10.0 target frameworks) — clean, no warnings/errors.
  • Ran the updated SetRangeTest in test/standalone/Garnet.test/RespTests.cs against net10.0 — passes. (net8.0 runtime is not installed in this environment, so that TFM could not be executed directly; both TFMs share identical source, and the net8.0 build itself succeeded.)
  • Reverted the BasicCommands.cs fix only (kept the test change) and reran the same test to confirm it reproduces the original failure: RedisConnectionException: SocketClosed due to the connection being dropped. Restored the fix afterward.
  • Ran dotnet format Garnet.slnx --no-restore --verify-no-changes on the changed files — no formatting issues.

Not covered by this PR (noted for reviewers): the added test exercises an offset far past the boundary and the new-key path only; it does not pin the exact 512 MiB boundary or an existing-key growth-past-limit case.

hexonal added 2 commits July 20, 2026 04:58
SETRANGE only validated offset < 0, so an offset large enough that
offset + value length exceeds the 512MB max record size (e.g.
SETRANGE key 600000000 x) reached the storage layer, which throws a
raw TsavoriteException. That exception doesn't derive from
GarnetException, so it skips the dedicated error-response handler in
RespServerSession and falls into the generic catch-all, which logs
and disposes the network sender without ever sending a RESP error -
silently killing the client connection instead of returning a clean
error.

Reject the oversized offset up front in NetworkSetRange, following
the same pattern already used by SETBIT/BITFIELD, which check their
offsets against BitmapManager.MaxBitmapPayloadBytes before reaching
storage.
Real Redis distinguishes "offset is out of range" (negative offset)
from "string exceeds maximum allowed size (proto-max-bulk-len)"
(offset+length beyond the max string size). Match that wording
instead of reusing the generic offset-out-of-range message for both
cases.
Copilot AI review requested due to automatic review settings July 20, 2026 09:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the SETRANGE RESP handler to prevent large-but-valid offsets from triggering an unhandled storage-layer exception (and dropping the client connection), instead returning a clean Redis-compatible ERR response.

Changes:

  • Added a pre-dispatch size check in NetworkSetRange to reject offset + value.Length beyond the max record size, returning a RESP error rather than allowing a Tsavorite exception to propagate.
  • Introduced a new CmdStrings error constant matching Redis’s message for the max-size failure class.
  • Extended SetRangeTest to validate the error reply and confirm the connection remains usable after the rejected command.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
test/standalone/Garnet.test/RespTests.cs Adds a regression test ensuring oversized SETRANGE returns a server error (not a dropped connection) and the connection remains usable.
libs/server/Resp/CmdStrings.cs Adds a dedicated RESP error string for “string exceeds maximum allowed size (proto-max-bulk-len)”.
libs/server/Resp/BasicCommands.cs Adds early validation in NetworkSetRange to block oversized results before hitting the storage layer.

Comment thread libs/server/Resp/BasicCommands.cs
@vazois
vazois self-requested a review July 21, 2026 18:22
@vazois vazois self-assigned this Jul 21, 2026
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