Summary
The token chaincode's invoke entry point enforces a request-size ceiling before doing any per-element work, but the read-only query functions — queryStates, queryTokens, areTokensSpent — have no analogous guard: each JSON array element the (unauthenticated) caller supplies translates 1:1 into a stub.GetState (or equivalent) call, with no cap on array length or payload size.
Where
token/services/network/fabric/tcc/tcc.go:281-375
The invoke path checks request size via CheckRawRequestSize/CheckRequestLimits (driver.DefaultResourceLimits().MaxRequestBytes) before processing. queryStates/queryTokens/areTokensSpent skip this check entirely.
Impact
Any client able to reach the chaincode's query path (which, unlike invoke, does not go through the size-limited transaction-submission flow) can submit a single request with an arbitrarily large key/id array, driving an unbounded number of ledger reads inside one chaincode invocation — a straightforward resource-exhaustion / peer-slowdown vector requiring no elevated privileges.
Reproduction
Reproduced locally with a unit test (not yet committed) that builds a 10,000-key payload already larger than MaxRequestBytes (the limit that would reject an equivalently-sized invoke request) and shows queryStates processes it in full:
payload, _ := json.Marshal(keys) // 10,000 keys
require.Greater(t, len(payload), driver.DefaultResourceLimits().MaxRequestBytes)
resp := cc.Invoke(fakestub)
require.Equal(t, int32(200), resp.Status)
require.Equal(t, n, fakestub.GetStateCallCount()) // one GetState call per key, no cap
Happy to include this regression test in the fix PR.
Suggested fix
Apply the same size/count limits enforced on invoke (CheckRawRequestSize/CheckRequestLimits or an equivalent per-element cap) to queryStates, queryTokens, and areTokensSpent before processing their input arrays.
Severity
HIGH — unauthenticated resource-exhaustion vector against the chaincode/peer.
Summary
The token chaincode's
invokeentry point enforces a request-size ceiling before doing any per-element work, but the read-only query functions —queryStates,queryTokens,areTokensSpent— have no analogous guard: each JSON array element the (unauthenticated) caller supplies translates 1:1 into astub.GetState(or equivalent) call, with no cap on array length or payload size.Where
token/services/network/fabric/tcc/tcc.go:281-375The
invokepath checks request size viaCheckRawRequestSize/CheckRequestLimits(driver.DefaultResourceLimits().MaxRequestBytes) before processing.queryStates/queryTokens/areTokensSpentskip this check entirely.Impact
Any client able to reach the chaincode's query path (which, unlike
invoke, does not go through the size-limited transaction-submission flow) can submit a single request with an arbitrarily large key/id array, driving an unbounded number of ledger reads inside one chaincode invocation — a straightforward resource-exhaustion / peer-slowdown vector requiring no elevated privileges.Reproduction
Reproduced locally with a unit test (not yet committed) that builds a 10,000-key payload already larger than
MaxRequestBytes(the limit that would reject an equivalently-sizedinvokerequest) and showsqueryStatesprocesses it in full:Happy to include this regression test in the fix PR.
Suggested fix
Apply the same size/count limits enforced on
invoke(CheckRawRequestSize/CheckRequestLimitsor an equivalent per-element cap) toqueryStates,queryTokens, andareTokensSpentbefore processing their input arrays.Severity
HIGH — unauthenticated resource-exhaustion vector against the chaincode/peer.