600693914f
The low/medium findings from the July host+Windows security review, as implemented in the audit session's working tree: - Webhooks and library-art fetches refuse loopback/link-local/metadata targets and no longer follow redirects (SSRF pivots from the privileged host process). - The paired-client rosters (/clients, /native/clients) move off the streaming-client auth lane — one paired device could enumerate every other device's name + fingerprint; only the bearer/loopback console keeps them. - Device-name sanitizing extends to bidi/format control characters (spoofable rendering) via the shared native_pairing::is_spoofy_char; stream-marker quoting uses the same set. - The SYSTEM service resolves powershell by its full System32 path — CreateProcess checks the launching EXE's own directory first, so a planted powershell.exe beside the host binary would have run as SYSTEM. - The pf-vdisplay driver's opt-in file log moves from world-writable C:\Users\Public to WUDFHost's own temp dir. - GameStream pairing sessions are single-use (removed whatever the outcome). - Uninstall also removes the pf_mouse driver-store entry (rider from the virtual-HID-mouse work). - openapi.json regenerated (hardened-config-dir doc wording). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
66 lines
2.5 KiB
Rust
66 lines
2.5 KiB
Rust
//! Session-tagged management endpoints: stop the active session, force an IDR. Split out of the
|
|
//! `mgmt` facade (plan §W5).
|
|
|
|
use super::shared::*;
|
|
use std::sync::atomic::Ordering;
|
|
|
|
/// Stop the active session
|
|
///
|
|
/// Kicks the connected client: stops the video/audio stream threads and clears the launch
|
|
/// state. Idempotent — succeeds even when nothing is streaming.
|
|
#[utoipa::path(
|
|
delete,
|
|
path = "/session",
|
|
tag = "session",
|
|
operation_id = "stopSession",
|
|
responses(
|
|
(status = NO_CONTENT, description = "Session stopped (or none was active)"),
|
|
(status = UNAUTHORIZED, description = "Missing or invalid bearer token", body = ApiError),
|
|
)
|
|
)]
|
|
pub(crate) async fn stop_session(State(st): State<Arc<MgmtState>>) -> StatusCode {
|
|
let was_streaming = st.app.streaming.swap(false, Ordering::SeqCst);
|
|
st.app.audio_streaming.store(false, Ordering::SeqCst);
|
|
*st.app.launch.lock().unwrap_or_else(|e| e.into_inner()) = None;
|
|
*st.app.stream.lock().unwrap_or_else(|e| e.into_inner()) = None;
|
|
// Native plane: the GameStream flags above don't reach it (it runs its own loops off the shared
|
|
// session registry), so signal every live native session to tear down too.
|
|
let native = crate::session_status::count();
|
|
crate::session_status::stop_all();
|
|
tracing::info!(
|
|
was_streaming,
|
|
native_sessions = native,
|
|
"management API: session stopped"
|
|
);
|
|
StatusCode::NO_CONTENT
|
|
}
|
|
|
|
/// Force a keyframe
|
|
///
|
|
/// Asks the encoder for an IDR frame on the active video stream (what a client requests
|
|
/// after unrecoverable loss — exposed for debugging).
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/session/idr",
|
|
tag = "session",
|
|
operation_id = "requestIdr",
|
|
responses(
|
|
(status = ACCEPTED, description = "Keyframe requested"),
|
|
(status = UNAUTHORIZED, description = "Missing or invalid bearer token", body = ApiError),
|
|
(status = CONFLICT, description = "No active video stream", body = ApiError),
|
|
)
|
|
)]
|
|
pub(crate) async fn request_idr(State(st): State<Arc<MgmtState>>) -> Response {
|
|
let gs = st.app.streaming.load(Ordering::SeqCst);
|
|
let native = crate::session_status::count();
|
|
if !gs && native == 0 {
|
|
return api_error(StatusCode::CONFLICT, "no active video stream");
|
|
}
|
|
if gs {
|
|
st.app.force_idr.store(true, Ordering::SeqCst);
|
|
}
|
|
// Native sessions get the keyframe request through their registry flag (see `session_status`).
|
|
crate::session_status::force_idr_all();
|
|
StatusCode::ACCEPTED.into_response()
|
|
}
|