fix(nvhttp): use-after-free in clientpairingsecret crashes pair flow#1481
Open
xdzleo wants to merge 1 commit into
Open
fix(nvhttp): use-after-free in clientpairingsecret crashes pair flow#1481xdzleo wants to merge 1 commit into
xdzleo wants to merge 1 commit into
Conversation
The success branch of clientpairingsecret() erased the session from map_id_sess (an std::unordered_map<std::string, pair_session_t>) before calling add_authorized_client() and the unconditional remove_session(sess) below. Because map_id_sess stores values by value, that early erase destroys the pair_session_t and leaves `sess` and its inner `client` reference dangling. add_authorized_client() then runs save_state() and load_state(), which do non-trivial heap activity (parse and rewrite sunshine_state.json, rebuild client_root.named_devices, recreate the cert chain). That activity reliably reuses the freed pair_session_t slot, so remove_session(sess) -> map_id_sess.erase(sess.client.uniqueID) reads sess.client.uniqueID out of corrupted memory and crashes the process with a 0xc0000005 access violation at a deterministic offset (because the heap allocator reuses the slot the same way every run). Repro: pair any client that finishes the full pair flow (e.g. iOS Moonlight on Darwin/25.5.0). The state file gets written with the new named_device entry, then Sunshine.exe segfaults right after sending the response, the client sees the connection drop and reports "pairing failed". Fix: remove the eager erase. remove_session(sess) at the end of clientpairingsecret() handles cleanup for both the success and the hash-mismatch branches and `sess` stays valid until that point.
Owner
|
Thanks but I'm not accepting commits from Claude. You can do a review yourself and commit with your own email address. For example, those comments are pure noises for future maintenance because the problematic code is already gone. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
clientpairingsecret()insrc/nvhttp.cpphad a use-after-free in its success path that crashes Apollo with a deterministic 0xc0000005 access violation right after a successful pair handshake. The state file gets written with the newnamed_devicesentry, thenSunshine.exefaults, and the client sees the connection drop and reports "pairing failed" even though the host actually paired.This appears to be the root cause of #1436 and likely contributes to other reports of clients pairing repeatedly without ever succeeding (especially on iOS Moonlight, where I hit it 100% reproducibly with
Darwin/25.5.0over LAN against both v0.4.6 and current masterdd99a822).Root cause
map_id_sessisstd::unordered_map<std::string, pair_session_t>— values, not pointers. The success branch did:add_authorized_client()runssave_state()(writessunshine_state.json) andload_state()(reparses it, rebuildsclient_root.named_devices, recreates the cert chain). That heap activity reliably reuses the freedpair_session_tslot, so theremove_session(sess)below dereferences a dangling reference. The crash is at the same offset every run because the allocator reuses the slot the same way.The hash-mismatch branch never had this problem — it only calls
remove_session(sess)once.Fix
Drop the eager erase.
remove_session(sess)at the end already handles both branches, andsessis still valid at that point because nothing in between mutatesmap_id_sess.Net: +9 / -3 lines including the explanatory comment.
Test plan
dd99a822with iOS Moonlight (Darwin/25.5.0) on LAN — Application Error 0xc0000005 at fixed offset right after the/pair?phrase=clientpairingsecretrequest,sunshine_state.jsonalready updated with the new device.remove_session(sess)is hit once, no leak).-Wall -Wextra(gcc 16, ucrt64).Notes for reviewers
While auditing this path I noticed two adjacent issues that I'm happy to send as separate PRs if you're interested:
map_id_sessandclient_rootare mutated from multiple threads (HTTPS pair handler, HTTP pair handler, web-UIpin()thread) with no lock. The same UAF class is reachable concurrently — two simultaneous pair attempts can race the destructor.nvhttp::pin()usesstd::begin(map_id_sess)->secondto pick a session, which doesn't correlate the user-typed PIN with the specific pending device. With two concurrent pair attempts the wrong device can be paired with the legitimate user's PIN.Both are pre-existing but worth fixing while pair-flow ownership is in mind. Happy to file follow-ups.