Skip to content

lore-server: refuse metadata key types on the generic MutableStore path - #232

Closed
nollbit wants to merge 1 commit into
EpicGames:mainfrom
goalsgame:fix/mutable-store-metadata-keys
Closed

nollbit wants to merge 1 commit into
EpicGames:mainfrom
goalsgame:fix/mutable-store-metadata-keys

Conversation

@nollbit

@nollbit nollbit commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

RepositoryMetadata and BranchMetadata have dedicated write APIs — RepositoryMetadataSet and BranchMetadataSet — that validate read-only fields before writing. The generic mutable-store write accepts any KeyType, including those two, and writes the underlying key directly, so that validation is skipped for a caller who addresses the store instead of the API.

The path is reachable from gRPC v1, QUIC v4 and the legacy QUIC storage protocol.

This refuses both key types there, directing callers to the dedicated APIs.

Scope

Present in v0.8.6, v0.9.0, v0.10.0 and current main.

Test

rejects_repository_and_branch_metadata_key_types.

Build, cargo test -p lore-server, clippy and cargo +nightly fmt --check clean.

The judgement call, which is yours

Refusing outright is the conservative reading and it is what we run in production, but it is a behaviour change on a public path, and if any legitimate caller writes those key types through the generic store, this breaks it. I looked and did not find one, but you know the codebase far better than I do.

The gentler variant is to route those writes through the same validation the dedicated APIs apply, rather than refusing them. Happy to rework it that way, or to drop this entirely if the generic path is load-bearing for something I have missed.

Provenance

Reported through HackerOne first, together with a separate Connect ordering issue. Epic's security team closed it as Informative — the report was source analysis with no runtime PoC. Opening it here as an ordinary hardening change on that basis.

Deliberately separate from the Connect fix (#231), despite the two being filed together: they are independent, and this one carries a compatibility question the other does not. Neither should block the other.

@github-actions github-actions Bot added the area:server Server, provider integrations, telemetry label Oct 1, 2026
@nollbit
nollbit marked this pull request as ready for review October 1, 2026 15:04
RepositoryMetadata and BranchMetadata have dedicated write APIs
(RepositoryMetadataSet, BranchMetadataSet) that validate read-only
fields before writing. The generic mutable-store write accepts any
KeyType, including those two, and writes the underlying key directly --
so that validation is skipped for a caller who addresses the store
instead of the API. The path is reachable from gRPC v1, QUIC v4 and the
legacy QUIC storage protocol.

Refuse both key types there, directing callers to the dedicated APIs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Johan Mjönes <johan@playgoals.com>
@nollbit
nollbit force-pushed the fix/mutable-store-metadata-keys branch from 524b507 to 76449a6 Compare October 5, 2026 08:15
@ragnarula ragnarula added the ready-to-import Approved by Epic staff for import into Lore label Oct 5, 2026
@epic-lore-bot epic-lore-bot Bot added imported Imported into Lore for internal review and removed ready-to-import Approved by Epic staff for import into Lore labels Oct 5, 2026
@epic-lore-bot

epic-lore-bot Bot commented Oct 5, 2026

Copy link
Copy Markdown

Imported as Lore CR-799.

epic-lore-bot Bot pushed a commit that referenced this pull request Oct 7, 2026
`Connect` verifies the supplied token and populates the connection context, and only then checks whether the connection is already bound to a different repository. When that check fails the request is refused — but the context has already been mutated, so a refused `Connect` has replaced an established connection's identity with its own.

On current `main` it also replaces the cached `PartitionGrants` and `RawToken`, both of which later per-action and copy-source checks read.

The fix moves the repository check above all verification and all context mutation, so a refused `Connect` leaves the connection untouched. It is a reorder; no logic changes.

## Scope

Present in v0.8.6, v0.9.0, v0.10.0 and current `main`.

## Test

`a_rejected_reconnect_leaves_the_existing_token_alone` — establishes a connection bound to one repository with a known token, issues a `Connect` naming a different repository, and asserts both that it is refused and that the original token and repository survive. It fails before this change and passes after.

Build, `cargo test -p lore-server`, clippy and `cargo +nightly fmt --check` clean.

## Provenance

Reported through HackerOne first, since it touches authorization state. Epic's security team closed it as Informative — reasonably, since the report was source analysis with no runtime PoC, and exploiting it requires getting a request onto an established connection, which is not generally possible. Opening it here as an ordinary correctness fix on that basis.

The practical argument for it is small and cheap: an operation that reports failure should not have changed authorization state on the way out.

## Related

#232 covers the other issue from the same report — the generic `MutableStore` path writing metadata key types. The two are independent and neither should block the other; that one carries a compatibility question this one does not.

```
Imported-PR: #231
Imported-From: fdfb2db
Imported-Base: 05bc767
Imported-Merge: 9f9a1f8
Imported-Merge-Strategy: verbatim
Imported-Merged-Paths: 0
Imported-Author: Johan Mjönes (nollbit)
Signed-off-by: Johan Mjönes <johan@playgoals.com>
GH-URL: #231
```

Lore-RevId: 1520
Lore-Signature: d34dbd29270f7ed7c72a11f7be7317fa045ca68407980c786fbbd9fc89fffbff
epic-lore-bot Bot pushed a commit that referenced this pull request Oct 9, 2026
…nd MutableCompareAndSwap paths

## Motivation

Three mutable key types are written only through requests that validate the write. `RepositoryMetadataSet` and `BranchMetadataSet` check that read-only metadata fields are unchanged and that referenced blobs exist. `BranchPush` checks branch protection and that the pushed revision's fragments are present. On `main`, the generic `MutableStore` and `MutableCompareAndSwap` storage requests accept any `KeyType`, including these three, and write the key directly. A client with write access can therefore overwrite repository or branch metadata, or move a protected branch head, without any of those checks. This works over gRPC v1, legacy gRPC, QUIC v4 and legacy QUIC. A `MutableLoad` is not even needed: a CAS with a zero expected value returns the current value to retry with.

## Summary

- A shared check, `check_generic_write_key_type`, runs first in both `handle_mutable_store` and `handle_mutable_cas`. Every transport dispatches through these two functions. It refuses `RepositoryMetadata`, `BranchMetadata` and `BranchLatestPointer`.
- The refusal is a new `MessageHandleError::InvalidArgument`. gRPC answers `InvalidArgument`, which is not counted as a server error. QUIC has no code for a rejected argument, so it answers the generic `Failed`, like other request-validation errors.
- Behaviour change: the C API's `lore_storage` mutable store and CAS calls made with `remote` set, and a server running with `mutable_store.mode = "remote"`, send these same generic requests, so neither can write these key types to a server any longer. Calls on a handle's local store are unchanged. Remote mutable store mode is not in use. The release note documents the change.
- `storage_remote_test` used `BranchLatestPointer` as an arbitrary key type for its remote round trips, and now uses `Untyped`.

## Test Plan

- Added `rejects_key_types_with_a_dedicated_write_request` for both store and CAS, covering all three key types. Each unwraps the `InvalidArgument` and checks that the reason names the key type. The CAS test also checks that the stored value is unchanged. With the guard removed from `handle_mutable_cas`, the CAS test failed: the CAS returned `Ok`.
- Added transport mapping tests: gRPC `InvalidArgument` answers `Code::InvalidArgument` with the reason and is not a server error, and QUIC v4 answers `Failed`, is labelled `InvalidArgument` and is not an internal error.
- `cargo test -p lore-integration-tests --features integration_tests --test integration storage_`: 211 passed. With `storage_remote_test` temporarily set back to `BranchLatestPointer`, the remote store and CAS round trips failed against the in-process server, confirming the C API remote path reaches the guard.
- After merging `main`: `cargo test -p lore-server` (1402 passed), `cargo clippy -p lore-server -p lore-integration-tests --all-targets -- -D warnings --no-deps` (with and without `integration_tests`) clean, `cargo +nightly fmt --all`.
- Confirmed that `lore-revision` writes remote metadata only through `metadata_set` and `branch_metadata_set`, and that no URC code matches on `MessageHandleError`.
- Not run: smoke tests, and codespell, which is not installed locally.

## Provenance

Reported through HackerOne first, together with a separate `Connect` ordering issue. Epic's security team closed it as Informative — the report was source analysis with no runtime PoC. Opening it here as an ordinary hardening change on that basis.

Deliberately separate from the `Connect` fix (#231), despite the two being filed together: they are independent, and this one carries a compatibility question the other does not. Neither should block the other.

The original change refused the two metadata key types on the generic `MutableStore` path. Review extended it to `MutableCompareAndSwap` and to `BranchLatestPointer`, and changed the refusal from an authorization failure to `InvalidArgument`.

```
Imported-PR: #232
Imported-From: 76449a6
Imported-Base: 05bc767
Imported-Merge: c46a7b1
Imported-Merge-Strategy: verbatim
Imported-Merged-Paths: 0
Imported-Author: Johan Mjönes (nollbit)
Signed-off-by: Johan Mjönes <johan@playgoals.com>
GH-URL: #232
```

Co-authored-by: Peter Lockhart <peter.lockhart@epicgames.com>
Lore-RevId: 1565
Lore-Signature: 87d00b2cad61f9fc0b48921babe32026077a3d43e92ca04806d01c600269594f
@epic-lore-bot

epic-lore-bot Bot commented Oct 9, 2026

Copy link
Copy Markdown

Closed by mirrored commit 942a4a1.

@epic-lore-bot epic-lore-bot Bot closed this Oct 9, 2026
@epic-lore-bot epic-lore-bot Bot added the merged Merged into Lore codebase label Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:server Server, provider integrations, telemetry imported Imported into Lore for internal review merged Merged into Lore codebase

Development

Successfully merging this pull request may close these issues.

2 participants