mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-08 13:31:03 +03:00
drm: re-authorize the _drm stream per frame and auth the producer (review 3.3, 4.1)
3.3: DRM/KMS capture is not session-scoped -- the worker grabs a CRTC's physical scanout regardless of which session owns the display -- but the peer was authorized only once at accept. Capture the peer uid and re-check it at the top of the forward loop: root is always allowed, any other peer must still be the active-session uid, fail closed otherwise. A session change now tears the stream down within one frame (~33ms) instead of leaking the incoming user's screen to the outgoing user's --server. 4.1: connect_drm accepted any producer. Reject a non-root peer (peer_uid != 0) so a process that won the socket-path race cannot feed the consumer a display list, frames and dma-buf fds while the DRM path suppresses the portal consent prompt.
This commit is contained in:
29
src/ipc.rs
29
src/ipc.rs
@@ -1566,8 +1566,17 @@ pub(crate) fn drm_ipc_path() -> String {
|
||||
/// must carry the scanout dma-buf fd as ancillary data, which the codec cannot do (see `DrmConn`).
|
||||
#[cfg(all(target_os = "linux", feature = "drm"))]
|
||||
pub(crate) async fn connect_drm(ms_timeout: u64) -> ResultType<DrmConn> {
|
||||
use std::os::fd::AsRawFd;
|
||||
let path = drm_ipc_path();
|
||||
let stream = timeout(ms_timeout, tokio::net::UnixStream::connect(&path)).await??;
|
||||
// The producer MUST be root. DRM/KMS scanout export is a root-service capability, and the DRM
|
||||
// path outranks PipeWire (an available DRM stream suppresses the portal consent prompt), so a
|
||||
// non-root peer that won a socket-path race must not be trusted to supply the display list,
|
||||
// frames and an arbitrary dma-buf fd (review 4.1). The producer direction is authorized in
|
||||
// handle_drm_conn; this closes the same gap on the consumer direction.
|
||||
if peer_uid_from_fd(stream.as_raw_fd()) != Some(0) {
|
||||
bail!("drm: _drm producer is not root; refusing to consume");
|
||||
}
|
||||
Ok(DrmConn::new(stream))
|
||||
}
|
||||
|
||||
@@ -2031,6 +2040,14 @@ async fn handle_drm_conn(stream: Connection) -> ResultType<()> {
|
||||
}
|
||||
let _conn_guard = DrmConnGuard;
|
||||
|
||||
// Capture the peer uid now so the forward loop can RE-authorize every frame. The check above runs
|
||||
// once at accept, but DRM/KMS capture is NOT session-scoped: `drm_capture_worker` grabs the
|
||||
// physical scanout of a CRTC regardless of which session currently owns the display. So a stream
|
||||
// authorized for one session must stop the moment the active session changes, or the outgoing
|
||||
// user's --server keeps receiving the incoming user's screen (and the greeter in between) until
|
||||
// the socket dies (review 3.3). `peer_uid` is the --server's fixed uid.
|
||||
let peer_uid = stream.peer_uid();
|
||||
|
||||
// Move the authorized `_drm` stream onto the bespoke SCM_RIGHTS framing (see `DrmConn`). ALL
|
||||
// further traffic — display list, `DrmStart`, frame descriptors + their ancillary fd, and the
|
||||
// cursor / CPU-fallback bodies — goes through `conn` so no `Framed` read buffer ever competes with
|
||||
@@ -2107,6 +2124,18 @@ async fn handle_drm_conn(stream: Connection) -> ResultType<()> {
|
||||
// reconnects to a fresh list anyway.
|
||||
let mut seen_gen = DRM_DISPLAY_GENERATION.load(Ordering::Acquire);
|
||||
while let Some(msg) = frame_rx.recv().await {
|
||||
// Re-authorize per frame (review 3.3): root (0) is always allowed; any other peer must still
|
||||
// be the active-session uid. On a session change the outgoing --server no longer matches, so
|
||||
// we stop within one frame (~33ms) instead of streaming the new session's screen to it. Fail
|
||||
// closed if the peer uid cannot be determined.
|
||||
match peer_uid {
|
||||
Some(0) => {}
|
||||
Some(uid) if Some(uid) == active_uid() => {}
|
||||
_ => {
|
||||
log::warn!("drm: _drm peer no longer matches the active session; closing");
|
||||
break;
|
||||
}
|
||||
}
|
||||
let gen = DRM_DISPLAY_GENERATION.load(Ordering::Acquire);
|
||||
// Keep the ledger's epoch at the live generation so a hotplug/modeset (which may recycle an
|
||||
// fb_id onto a new buffer) invalidates every cached buffer and forces a real fd on the next
|
||||
|
||||
Reference in New Issue
Block a user