Skip to content

Pairing: stale pending pair session poisons all subsequent attempts until restart (emplace never refreshes map_id_sess) #5464

Description

@fisherjoey

Sunshine version: 2025.924.154138 (Fedora/Nobara 43 package)
OS: Linux (Nobara 43), NVIDIA, KMS capture
Client: moonlight-web-stream (Rust moonlight-common), but the bug is client-agnostic

Summary

nvhttp.cpp stores pending pair sessions in map_id_sess keyed by client uniqueid, inserted with map_id_sess.emplace(...). emplace never overwrites an existing entry, and entries are not reliably cleaned up when a pairing attempt is abandoned (client disconnect/timeout) or fails. Once a stale entry exists with last_phase != NONE, every later getservercert from the same uniqueid silently reuses the poisoned session:

  1. New attempt: getservercertemplace fails (key exists) → the old session object is reused, only its salt/response handle are updated.
  2. User submits the PIN → pin()getservercert(sess, ...)if (sess.last_phase != PAIR_PHASE::NONE) fail_pair("Out of order call to getservercert").
  3. The held /pair connection is closed without a usable response; the client reports a generic pairing failure.

From that point, all pairing attempts for that client ID fail the same way until Sunshine is restarted. Nothing in the web UI or logs (at info level) explains why, so users retry in a loop.

A second aggravating detail: pin() completes std::begin(map_id_sess)->second — the first session in the map — so with more than one pending session the PIN can be applied to the wrong one, which both fails the intended attempt ("pin was incorrect" on the client) and advances/poisons another session.

Reproduction

  1. Client A sends getservercert for uniqueid X, then dies/never gets a PIN (or a PIN meant for a different attempt is submitted — anything that leaves the session with last_phase != NONE).
  2. Client A (same uniqueid X) starts a fresh attempt: getservercert.
  3. Enter the correct PIN in the web UI.
  4. Pairing fails; server-side debug shows Out of order call to getservercert. Repeat 2–3 forever; only a Sunshine restart recovers.

Observed live over several attempts (timestamps, socat captures of the /pair exchanges, and the client-side IncompleteMessage errors available on request).

Suggested fix

In the getservercert phrase handler, replace-or-reset the session for that uniqueid instead of emplace (e.g. insert_or_assign with a fresh pair_session_t, after completing/aborting any held response on the old one). Keying the PIN submission to the salt (or rejecting pin() when multiple sessions are pending) would also fix the wrong-session case.

Minor related observation

While capturing the exchanges, /unpair requests were answered with two back-to-back HTTP responses on one connection (HTTP/1.1 200 OK with body <root status_code="404"/>, immediately followed by HTTP/1.1 404 NOT FOUND with the same body), which strict HTTP clients treat as a framing error. Happy to file separately if preferred.

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