LizardByte / LizardByte/Sunshine

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

Open
#5,464 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

ai
Dominant language
C++
Stars
41.3k
Forks
2.1k
Avg merge
23h 47m
Merged PRs (30d)
124

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in nvhttp.cpp by tracing the getservercert phrase handler, map_id_sess, and pin() through the reported stale-session and multiple-session sequences. Reproduce the abandoned-session flow and verify that a fresh pairing for the same uniqueid completes successfully, that PIN handling targets the intended session, and that no stale session remains to poison later attempts.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.