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

jmcerda created an issue. See original summary.

jmcerda’s picture

Status: Active » Fixed

Fixed 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.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.