Problem/Motivation
A usage-limited signed URL is redeemed in SignedUrl::consumeUse(). It takes the lock file_gate_redemption:<jti>, reads the redemption counter, and writes the counter back plus one.
GrantInventory::revokeJti() writes the same counter, set to the maximum, without taking that lock. If a revoke lands between a redemption's read and its write, the redemption overwrites the kill mark with a small number. The revoke returned success and removed the grant from the inventory, and the grant still has uses left.
The window is one request wide, so this is unlikely in practice. It matters because revoke is the incident-response path, and it is exactly when a link is being used that someone revokes it.
Steps to reproduce
Found by reading 1.x. Not reproduced. A kernel test can hold the redemption lock, run a revoke, release the lock and let the redemption finish, then assert the grant is refused.
Proposed resolution
- Write the kill mark under the same lock the redemption uses, in single and bulk revoke.
- If the lock cannot be taken in time, fail the revoke loudly. A revoke must not report success without the mark in place.
- Remove the kill-mark expiry collection on uninstall along with the other key-value collections.
Remaining tasks
- Kernel tests: interleaved redemption and revoke leaves the grant refused; lock timeout makes the revoke fail; bulk revoke takes the lock per grant.
API changes
None.
Comments
Comment #2
jmcerdaFixed in 1.10.1. revokeJti takes the same file_gate_redemption: lock as consumeUse. If the lock cannot be taken, HTTP answers 503 with Retry-After and the MCP tool returns its fixed refusal.