LizardByte / LizardByte/Sunshine
Pairing: stale pending pair session poisons all subsequent attempts until restart (emplace never refreshes map_id_sess)
Nobody has claimed this yet.
- 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:
- New attempt:
getservercert→emplacefails (key exists) → the old session object is reused, only its salt/response handle are updated. - User submits the PIN →
pin()→getservercert(sess, ...)→if (sess.last_phase != PAIR_PHASE::NONE) fail_pair("Out of order call to getservercert"). - The held
/pairconnection 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
- Client A sends
getservercertfor uniqueid X, then dies/never gets a PIN (or a PIN meant for a different attempt is submitted — anything that leaves the session withlast_phase != NONE). - Client A (same uniqueid X) starts a fresh attempt:
getservercert. - Enter the correct PIN in the web UI.
- 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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