Skip to content

server: blob fetch is decided from the first live reference, so bytes shared across owners can answer a member 404 or a wrong 403 #462

Description

@justin13888

Summary

AssetIndex::find_reference(address) returns the first live reference to a content address (BTreeMap order in index/memory.rs, ORDER BY asset_id COLLATE "C" LIMIT 1 in index/postgres.rs). GET /v1/blob/{hash} decides access from that one reference's owner_id and album_id. When the same ciphertext bytes are referenced by assets of unrelated owners, the caller is judged against whichever row sorts first, not against the album they are entitled through.

Pre-existed S-C39 for owners (a wrong 404); S-C51 (#405, PR #458) widens who is affected and adds a wrong-403 case.

Precisely what happens

Bytes X referenced by Alice's asset A (album AL) and Mallory's asset M (album ML), with M < A by asset id, so find_reference(X) returns M's row:

  • Alice fetching X: owner_id == Mallory → membership of (ML, Alice) → Never → 404 although Alice owns A.
  • Bob, a current member of AL: (ML, Bob) → Never → 404 although he is entitled.
  • Carol, a current member of AL who was once on ML's roster and removed: (ML, Carol) → Revoked → 403 error.blob.access_revoked, telling a fully entitled client to re-sync and degrade.

A former member who kept the ciphertext can create this deliberately by re-uploading the same bytes under their own album (asset ids are client-chosen UUIDv7 and sort by time, so a newer asset sorts after — but a pre-existing asset, or one with a hand-picked id, sorts first). Not a confidentiality issue (nobody is served bytes they may not have), but a plausible denial on the real album's serving.

Related, same shape

serve::resolve asks pending_for_address(owner = caller, hash) when no reference exists. A writer member's upload session is filed under the album owner (routes/upload.rs, owner_id: owner), so:

  • a writer's second device asking for its own in-flight address gets 404 instead of 409 error.blob.pending_upload;
  • the album owner sees 409 for a member's in-flight upload.

Proposed fix

Make the index return every live reference for an address (find_references(address) -> Vec<BlobReference>, live rows first, tombstoned as the fallback set) and fold the authority over them in serve::resolve: any Granted → serve from that reference (its state/hold decide the 410s); else any Revoked → Forbidden; else NotFound. Extend index/conformance.rs with a shared-address-across-owners case for both adapters. For the pending answer, key pending_for_address on the uploader and on the albums the caller may write to, or carry upload_user_id alongside owner_id in the lookup.

Where

  • capsule-server/src/index/{mod,memory,postgres,conformance}.rs
  • capsule-server/src/serve/{mod,authority}.rs
  • capsule-server/tests/blob.rs

Refs #405. Found while landing S-C51 (PR #458); recorded there under "Failure modes — shared bytes across unrelated owners".

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions