Skip to content

Validate client buffer registration ownership - #111

Merged
dallison merged 1 commit into
mainfrom
fix-client-buffer-registration-ownership
Jul 16, 2026
Merged

Validate client buffer registration ownership#111
dallison merged 1 commit into
mainfrom
fix-client-buffer-registration-ownership

Conversation

@dallison

@dallison dallison commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Summary

Properly fixes the security issue cursor bot PR #83 targeted, using an approach
that is compatible with the anonymous-memfd shared-buffer model that PR #83
conflicted with (which is why it was excluded from the earlier combined PR).

A connected client could send raw RegisterClientBuffer / UnregisterClientBuffer
requests for a channel it has no publisher on. The server only validated that
the request's session matched the current server session — but that session id
is shared by every client. So any client could:

  • Inject a bogus or fd-less buffer registration for another publisher's
    channel. With the "first registration wins" dedup this could pre-empt the
    real publisher's registration, and on Android/memfd would make subscribers
    map the wrong or a missing backing fd (crashes / corrupted reads).
  • Erase another publisher's entire buffer group: UnregisterClientBuffer
    carried no ownership token and simply dropped the (session, buffer_index)
    entry.

Fix

Require that the requesting connection owns a publisher backed by the target
channel's storage before it may register or unregister client buffers:

  • ServerChannel::HasPublisherOwnedBy(ClientHandler*) checks the channel's
    users for a publisher owned by the requesting connection.
  • ChannelMultiplexer overrides it to also consult its virtual channels,
    since publishers backed by a mux's storage live on the vchans, not the mux.
  • Reject fd-backed registrations whose fd_index is missing/out of range
    instead of silently registering the buffer without its backing fd.

Ownership is derived from the server-side ClientHandler that already tracks
each connection's publishers, so it cannot be spoofed and requires no
protocol, client or Rust changes
.

Why this differs from PR #83

PR #83 added a publisher_id token to the register/unregister protocol and
stored a single owning publisher per buffer group, rejecting cross-owner
access. That single-owner model predated and conflicted with the current
anonymous-memfd shared-buffer model, where multiple publishers on a channel
(or on virtual channels of a mux) legitimately register for the same
(session, buffer_index, slot) and converge on one shared buffer via "first
registration wins". This connection-ownership approach preserves that model:
every legitimate publisher passes the check, while unrelated clients are
rejected. Shadow recovery registers buffers directly on the channel and is
unaffected.

Test plan

New server-level regression tests (//server:server_test):

  • RegisterClientBufferOwnerSucceeds
  • RegisterClientBufferForeignRejected
  • UnregisterClientBufferForeignRejected
  • RegisterClientBufferInvalidFdIndexRejected

Validated on macOS arm64 (POSIX) and, via a Linux container, on both the
default /dev/shm backend and --config=linux_memfd (the fd-backed
registration path this guards):

  • Full //... on macOS arm64 (25/25)
  • Full //... on Linux default (25/25) and memfd //... -//rust_client/... (23/23)
  • Split-buffer client_test/c_client:client_test on macOS + Linux + memfd
  • bridge_test (exercises the real registration path end to end)

A connected client could send raw RegisterClientBuffer/UnregisterClientBuffer
requests for a channel it has no publisher on.  The server only checked that
the session matched the current server session (which is shared by every
client), so any client could:

- Inject a bogus or fd-less buffer registration for another publisher's
  channel.  With "first registration wins" this could pre-empt the real
  publisher's registration, and on Android/memfd would make subscribers map
  the wrong or a missing backing fd.
- Erase another publisher's entire buffer group via UnregisterClientBuffer,
  which takes no ownership token and simply drops the (session, buffer_index)
  entry.

Require that the requesting connection owns a publisher backed by the target
channel's storage before it may register or unregister client buffers.
Ownership is derived from the server-side ClientHandler that already tracks
each connection's publishers, so it cannot be spoofed and needs no protocol,
client or Rust changes.  Crucially this still allows the legitimate
multi-publisher shared-buffer model: several publishers on a channel (or on
virtual channels of a mux) all pass the check and converge on the shared
"first registration wins" buffer group.  Multiplexer channels consult their
virtual channels, where mux-backed publishers actually live.

Also reject fd-backed registrations whose fd_index is missing/out of range
instead of silently registering the buffer without its backing fd.

Shadow recovery registers buffers directly on the channel and is unaffected.

Adds server-level regression tests for owner-succeeds, foreign register/
unregister rejection, and invalid fd index rejection.

Supersedes cursor bot PR #83, whose single-publisher_id ownership model
predated and conflicted with the anonymous-memfd shared-buffer model.
@dallison
dallison merged commit d723db7 into main Jul 16, 2026
46 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant