network-driver: QuerySpentTokens/deleteTokens: unvalidated response length drives an unguarded index and panics - #2171
Conversation
|
Thanks a lot for submitting this PR. The guard here is right, but it only covers the Fabric fetcher. The sibling implementation of the saterface — Executor.QuerySpentTokens in token/services/network/fabricx/qe/qe.go — violates the samepositional-length contract by construction, so the panic this PR closes stays reachable on a fabricx TMS: keys := make([]driver.PKey, 0, len(ids)) Any nil entry in ids yields len(spentFlags) < len(ids), and an all-nil slice returns nil, nil — exactly the shape that makes spent[i] panic in tokens.deleteTokens and htlc.OwnerWallet.deleteTokens. It isn't triggerable from today's two callers (both build ids[i] = &tok.Id, never nil), so this is latent rather than a live bug — but it's the same defect in the same interface, and a future caller is a more likely trigger than a misbehaving peer. |
AkramBitar
left a comment
There was a problem hiding this comment.
See my comment in the PR itself
24e7894 to
4ee4200
Compare
@AkramBitar Done |
Signed-off-by: Effi-S <effi.szt@gmail.com>
Fixes #2059
Summary
spentTokenFetcher.QuerySpentTokensunmarshals the chaincode'sareTokensSpentresponse directly into[]boolwith no check that its length matches the number of ids requested. Its sole caller,Service.deleteTokens, then indexes the result positionally against its own input slice with no bounds check — an unguarded index driven entirely by an unvalidated response length from the network layer.Where
token/services/network/fabric/tokenfetcher.go:94-132(client):token/services/tokens/tokens.go:314-349(consumer):On the chaincode side,
Translator.AreTokensSpent(token/services/network/common/rws/translator/translator.go:170) does build its result asmake([]bool, len(ids))against the ids it received — so this is exploitable only if something between the client's request and the chaincode's response can desynchronize the two ids lists (e.g. a future refactor, an alternate driver/backend implementing the same interface less carefully, or a compromised/buggy peer returning a manipulated response), but nothing in the current code path structurally prevents that mismatch from reachingdeleteTokensand panicking.Impact
Any response shorter than the caller's
tokensslice (whether from a bug in an alternate chaincode/driver implementation, a malformed/truncated response, or a future change that decouples request/response ordering) panicsdeleteTokenson the very next token-vault cleanup pass — a client-side crash driven entirely by data returned from the network layer, with no defensive check at the trust boundary between "what the chaincode returned" and "what we assumed it returned."Reproduction
Reproduced locally with a unit test (not yet committed) exercising the full, real production call path —
Service.PruneInvalidUnspentTokens→Service.deleteTokens→*network.Network.AreTokensSpent— with only thedriver.Networkboundary faked:A contrast test with a correctly-sized
spentslice proves the panic is specifically caused by the length mismatch. Happy to include both in the fix PR.Suggested fix
Validate
len(spent) == len(IDs)inQuerySpentTokensbefore returning (returning an error on mismatch instead of the raw unmarshaled slice), and/or add a defensive bounds check indeleteTokensbefore indexingspent[i].Severity
HIGH