Skip to content

Returning ZSET score instead of member in Redis-backed NonceManager causes nonce parse failures and blocks blockchain publishing #1868

@neekolas

Description

@neekolas

Severity: Informational | Likelihood: Low | Impact: Low | Type: Vulnerability

Details

The Redis nonce reservation script returns the sorted-set score (a float) as the nonce, while the Go path rigidly expects an integer string, leading to parse/type failures that stop nonce allocation and block blockchain publishing. An adjacent overflow risk exists when very large pending nonces are cast to int64 during replenishment, potentially wedging publishing in a retry loop.

In the Redis-backed NonceManager, replenishment inserts nonces into a sorted set with the score equal to the nonce (as a float) and the member equal to the nonce value. The reservation Lua script pops the minimum and returns the score as the nonce. The Go code strictly expects the first return element to be a base-10 integer string and parses it with strconv.ParseInt. Under realistic conditions (e.g., very large nonce values, or Redis/RESP variations), the score may be represented as scientific notation or returned as a numeric type instead of a plain string. This triggers a parse/type error, causing GetNonce to fail and blocking blockchain publishing calls that depend on a nonce. Additionally, replenishment casts the pending nonce to int64; if the source nonce exceeds MaxInt64, overflow to negative values can occur, resulting in negative nonces being served and an infinite 'nonce too high' retry loop, further blocking publishing. The issue impacts only the affected node and does not spread.

Exploitation

Scenario 1

A faulty or malicious RPC provider returns a very large pending nonce (e.g., ~1e16). Replenishment stores float scores; ZPOPMIN returns the score formatted in scientific notation; the Go code fails to ParseInt the nonce; GetNonce errors and blockchain publishing is blocked for the node.

Preconditions / Assumptions:

  • (a) Gateway uses the Redis-backed NonceManager
  • (b) External RPC provider returns an abnormally large pending nonce (~1e16)
  • (c) Redis formats the score as scientific notation or a non-integer string

Scenario 2

An RPC provider returns a pending nonce greater than MaxInt64. The replenishment path casts to int64 causing overflow to negative values; negative nonces are served; the publisher repeatedly receives 'nonce too high' rejections and loops, resulting in persistent inability to publish.

Preconditions / Assumptions:

  • (a) Gateway uses the Redis-backed NonceManager
  • (b) External RPC provider returns a pending nonce greater than MaxInt64
  • (c) Replenishment path casts the nonce to int64, enabling overflow to negative

Scenario 3

A Redis server/variant or protocol setting causes the Lua EVAL return for the score to be a numeric type (e.g., float) rather than a string. The Go code's strict string assertion fails, causing nonce allocation errors and blocking blockchain publishing.

Preconditions / Assumptions:

  • (a) Gateway uses the Redis-backed NonceManager
  • (b) Redis server/variant or protocol setting returns the score from EVAL as a numeric type
  • (c) Go client decodes the EVAL result's first element as numeric instead of string

Files impacted

  • pkg/blockchain/noncemanager/redis/manager.go

Lines 41-57:

-- Move each stale nonce back to available pool
for i, nonce in ipairs(staleNonces) do
    redis.call('ZADD', availableKey, nonce, nonce)
end
-- Remove from reserved set
redis.call('ZREMRANGEBYSCORE', reservedKey, '-inf', staleThreshold)
cleanupCount = #staleNonces
end
-- Then, reserve the next available nonce
local result = redis.call('ZPOPMIN', availableKey, 1)
if #result == 0 then
    return {nil, cleanupCount}
end
local nonce = result[2]
-- Add it to the reserved set with current timestamp as score for cleanup
redis.call('ZADD', reservedKey, currentTime, nonce)

Metadata

Metadata

Labels

Type

No type

Projects

Status

No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions