From ef53d72ff873ea7d900aedd00a57c2509573e8ad Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 23 Apr 2024 22:06:29 +0200 Subject: [PATCH] clients: use fine-grained capabilities for privileged protocols --- src/acceptor.rs | 21 +++++++----- src/client.rs | 32 +++++++++++++------ src/globals.rs | 26 ++++++++------- src/ifs/ext_foreign_toplevel_list_v1.rs | 6 ++-- src/ifs/ext_idle_notifier_v1.rs | 6 ++-- src/ifs/ext_session_lock_manager_v1.rs | 6 ++-- src/ifs/ipc/zwlr_data_control_manager_v1.rs | 6 ++-- src/ifs/jay_compositor.rs | 6 ++-- src/ifs/wl_registry.rs | 2 +- .../zwp_virtual_keyboard_manager_v1.rs | 6 ++-- src/ifs/zwlr_layer_shell_v1.rs | 6 ++-- src/ifs/zwlr_screencopy_manager_v1.rs | 6 ++-- src/xwayland.rs | 4 +-- 13 files changed, 78 insertions(+), 55 deletions(-) diff --git a/src/acceptor.rs b/src/acceptor.rs index f4ae3b21..e85871e6 100644 --- a/src/acceptor.rs +++ b/src/acceptor.rs @@ -1,6 +1,7 @@ use { crate::{ async_engine::SpawnedFuture, + client::ClientCaps, state::State, utils::{errorfmt::ErrorFmt, oserror::OsError, xrd::xrd}, }, @@ -145,12 +146,16 @@ impl Acceptor { } let acc = Rc::new(Acceptor { socket }); let futures = vec![ - state - .eng - .spawn(accept(acc.socket.secure.clone(), state.clone(), true)), - state - .eng - .spawn(accept(acc.socket.insecure.clone(), state.clone(), false)), + state.eng.spawn(accept( + acc.socket.secure.clone(), + state.clone(), + ClientCaps::all(), + )), + state.eng.spawn(accept( + acc.socket.insecure.clone(), + state.clone(), + ClientCaps::none(), + )), ]; state.acceptor.set(Some(acc.clone())); Ok((acc, futures)) @@ -166,7 +171,7 @@ impl Acceptor { } } -async fn accept(fd: Rc, state: Rc, secure: bool) { +async fn accept(fd: Rc, state: Rc, caps: ClientCaps) { loop { let fd = match state.ring.accept(&fd, c::SOCK_CLOEXEC).await { Ok(fd) => fd, @@ -176,7 +181,7 @@ async fn accept(fd: Rc, state: Rc, secure: bool) { } }; let id = state.clients.id(); - if let Err(e) = state.clients.spawn(id, &state, fd, secure) { + if let Err(e) = state.clients.spawn(id, &state, fd, caps) { log::error!("Could not spawn a client: {}", ErrorFmt(e)); break; } diff --git a/src/client.rs b/src/client.rs index e0f2da25..26aafa66 100644 --- a/src/client.rs +++ b/src/client.rs @@ -43,6 +43,18 @@ mod error; mod objects; mod tasks; +bitflags! { + ClientCaps: u32; + CAP_DATA_CONTROL_MANAGER = 1 << 0, + CAP_VIRTUAL_KEYBOARD_MANAGER = 1 << 1, + CAP_FOREIGN_TOPLEVEL_LIST = 1 << 2, + CAP_IDLE_NOTIFIER = 1 << 3, + CAP_SESSION_LOCK_MANAGER = 1 << 4, + CAP_JAY_COMPOSITOR = 1 << 5, + CAP_LAYER_SHELL = 1 << 6, + CAP_SCREENCOPY_MANAGER = 1 << 7, +} + #[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)] pub struct ClientId(u64); @@ -101,7 +113,7 @@ impl Clients { id: ClientId, global: &Rc, socket: Rc, - secure: bool, + caps: ClientCaps, ) -> Result<(), ClientError> { let (uid, pid) = { let mut cred = c::ucred { @@ -120,7 +132,7 @@ impl Clients { } } }; - self.spawn2(id, global, socket, uid, pid, secure, false)?; + self.spawn2(id, global, socket, uid, pid, caps, false)?; Ok(()) } @@ -131,7 +143,7 @@ impl Clients { socket: Rc, uid: c::uid_t, pid: c::pid_t, - secure: bool, + caps: ClientCaps, is_xwayland: bool, ) -> Result, ClientError> { let data = Rc::new(Client { @@ -145,7 +157,7 @@ impl Clients { shutdown: Default::default(), tracker: Default::default(), is_xwayland, - secure, + caps, last_enter_serial: Cell::new(0), pid_info: get_pid_info(uid, pid), serials: Default::default(), @@ -165,13 +177,13 @@ impl Clients { data: data.clone(), }; log::info!( - "Client {} connected, pid: {}, uid: {}, fd: {}, secure: {}, comm: {:?}", + "Client {} connected, pid: {}, uid: {}, fd: {}, comm: {:?}, caps: {:?}", id, pid, uid, client.data.socket.raw(), - secure, data.pid_info.comm, + caps, ); self.clients.borrow_mut().insert(client.data.id, client); Ok(data) @@ -193,13 +205,15 @@ impl Clients { } } - pub fn broadcast(&self, secure: bool, xwayland_only: bool, mut f: B) + pub fn broadcast(&self, required_caps: ClientCaps, xwayland_only: bool, mut f: B) where B: FnMut(&Rc), { let clients = self.clients.borrow(); for client in clients.values() { - if (!secure || client.data.secure) && (!xwayland_only || client.data.is_xwayland) { + if client.data.caps.contains(required_caps) + && (!xwayland_only || client.data.is_xwayland) + { f(&client.data); } } @@ -258,7 +272,7 @@ pub struct Client { shutdown: AsyncEvent, pub tracker: Tracker, pub is_xwayland: bool, - pub secure: bool, + pub caps: ClientCaps, pub last_enter_serial: Cell, pub pid_info: PidInfo, pub serials: RefCell>, diff --git a/src/globals.rs b/src/globals.rs index 6e3aa850..7c344ab8 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -1,7 +1,7 @@ use { crate::{ backend::Backend, - client::Client, + client::{Client, ClientCaps}, ifs::{ ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1Global, ext_idle_notifier_v1::ExtIdleNotifierV1Global, @@ -113,8 +113,8 @@ pub trait Global: GlobalBase { fn singleton(&self) -> bool; fn version(&self) -> u32; fn break_loops(&self) {} - fn secure(&self) -> bool { - false + fn required_caps(&self) -> ClientCaps { + ClientCaps::none() } fn xwayland_only(&self) -> bool { false @@ -215,7 +215,7 @@ impl Globals { fn insert(&self, state: &State, global: Rc) { self.insert_no_broadcast_(&global); - self.broadcast(state, global.secure(), global.xwayland_only(), |r| { + self.broadcast(state, global.required_caps(), global.xwayland_only(), |r| { r.send_global(&global) }); } @@ -223,11 +223,13 @@ impl Globals { pub fn get( &self, name: GlobalName, - allow_secure: bool, + client_caps: ClientCaps, allow_xwayland_only: bool, ) -> Result, GlobalsError> { let global = self.take(name, false)?; - if (global.secure() && !allow_secure) || (global.xwayland_only() && !allow_xwayland_only) { + if client_caps.not_contains(global.required_caps()) + || (global.xwayland_only() && !allow_xwayland_only) + { return Err(GlobalsError::GlobalDoesNotExist(name)); } Ok(global) @@ -236,7 +238,7 @@ impl Globals { pub fn remove(&self, state: &State, global: &T) -> Result<(), GlobalsError> { let _global = self.take(global.name(), true)?; global.remove(self); - self.broadcast(state, global.secure(), global.xwayland_only(), |r| { + self.broadcast(state, global.required_caps(), global.xwayland_only(), |r| { r.send_global_remove(global.name()) }); Ok(()) @@ -247,14 +249,16 @@ impl Globals { } pub fn notify_all(&self, registry: &Rc) { - let secure = registry.client.secure; + let caps = registry.client.caps; let xwayland = registry.client.is_xwayland; let globals = self.registry.lock(); macro_rules! emit { ($singleton:expr) => { for global in globals.values() { if global.singleton() == $singleton { - if (secure || !global.secure()) && (xwayland || !global.xwayland_only()) { + if caps.contains(global.required_caps()) + && (xwayland || !global.xwayland_only()) + { registry.send_global(global); } } @@ -268,11 +272,11 @@ impl Globals { fn broadcast)>( &self, state: &State, - secure: bool, + required_caps: ClientCaps, xwayland_only: bool, f: F, ) { - state.clients.broadcast(secure, xwayland_only, |c| { + state.clients.broadcast(required_caps, xwayland_only, |c| { let registries = c.lock_registries(); for registry in registries.values() { f(registry); diff --git a/src/ifs/ext_foreign_toplevel_list_v1.rs b/src/ifs/ext_foreign_toplevel_list_v1.rs index 8e21c486..352ebb9d 100644 --- a/src/ifs/ext_foreign_toplevel_list_v1.rs +++ b/src/ifs/ext_foreign_toplevel_list_v1.rs @@ -1,6 +1,6 @@ use { crate::{ - client::{Client, ClientError}, + client::{Client, ClientCaps, ClientError, CAP_FOREIGN_TOPLEVEL_LIST}, globals::{Global, GlobalName}, ifs::{ ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, @@ -145,8 +145,8 @@ impl Global for ExtForeignToplevelListV1Global { 1 } - fn secure(&self) -> bool { - true + fn required_caps(&self) -> ClientCaps { + CAP_FOREIGN_TOPLEVEL_LIST } } diff --git a/src/ifs/ext_idle_notifier_v1.rs b/src/ifs/ext_idle_notifier_v1.rs index 6cbade69..ed29440e 100644 --- a/src/ifs/ext_idle_notifier_v1.rs +++ b/src/ifs/ext_idle_notifier_v1.rs @@ -1,6 +1,6 @@ use { crate::{ - client::{Client, ClientError}, + client::{Client, ClientCaps, ClientError, CAP_IDLE_NOTIFIER}, globals::{Global, GlobalName}, ifs::ext_idle_notification_v1::ExtIdleNotificationV1, leaks::Tracker, @@ -117,8 +117,8 @@ impl Global for ExtIdleNotifierV1Global { 1 } - fn secure(&self) -> bool { - true + fn required_caps(&self) -> ClientCaps { + CAP_IDLE_NOTIFIER } } diff --git a/src/ifs/ext_session_lock_manager_v1.rs b/src/ifs/ext_session_lock_manager_v1.rs index 85b14a8c..b2981ddf 100644 --- a/src/ifs/ext_session_lock_manager_v1.rs +++ b/src/ifs/ext_session_lock_manager_v1.rs @@ -1,6 +1,6 @@ use { crate::{ - client::{Client, ClientError}, + client::{Client, ClientCaps, ClientError, CAP_SESSION_LOCK_MANAGER}, globals::{Global, GlobalName}, ifs::ext_session_lock_v1::ExtSessionLockV1, leaks::Tracker, @@ -98,8 +98,8 @@ impl Global for ExtSessionLockManagerV1Global { 1 } - fn secure(&self) -> bool { - true + fn required_caps(&self) -> ClientCaps { + CAP_SESSION_LOCK_MANAGER } } diff --git a/src/ifs/ipc/zwlr_data_control_manager_v1.rs b/src/ifs/ipc/zwlr_data_control_manager_v1.rs index 87aa1f5f..ad5ce34f 100644 --- a/src/ifs/ipc/zwlr_data_control_manager_v1.rs +++ b/src/ifs/ipc/zwlr_data_control_manager_v1.rs @@ -1,6 +1,6 @@ use { crate::{ - client::{Client, ClientError}, + client::{Client, ClientCaps, ClientError, CAP_DATA_CONTROL_MANAGER}, globals::{Global, GlobalName}, ifs::ipc::{ zwlr_data_control_device_v1::{ZwlrDataControlDeviceV1, PRIMARY_SELECTION_SINCE}, @@ -111,8 +111,8 @@ impl Global for ZwlrDataControlManagerV1Global { 2 } - fn secure(&self) -> bool { - true + fn required_caps(&self) -> ClientCaps { + CAP_DATA_CONTROL_MANAGER } } diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs index a28d0b05..d98580de 100644 --- a/src/ifs/jay_compositor.rs +++ b/src/ifs/jay_compositor.rs @@ -1,7 +1,7 @@ use { crate::{ cli::CliLogLevel, - client::{Client, ClientError}, + client::{Client, ClientCaps, ClientError, CAP_JAY_COMPOSITOR}, globals::{Global, GlobalName}, ifs::{ jay_idle::JayIdle, @@ -68,8 +68,8 @@ impl Global for JayCompositorGlobal { 1 } - fn secure(&self) -> bool { - true + fn required_caps(&self) -> ClientCaps { + CAP_JAY_COMPOSITOR } } diff --git a/src/ifs/wl_registry.rs b/src/ifs/wl_registry.rs index 7c91713c..b1457c41 100644 --- a/src/ifs/wl_registry.rs +++ b/src/ifs/wl_registry.rs @@ -48,7 +48,7 @@ impl WlRegistryRequestHandler for WlRegistry { fn bind(&self, bind: Bind, _slf: &Rc) -> Result<(), Self::Error> { let name = GlobalName::from_raw(bind.name); let globals = &self.client.state.globals; - let global = globals.get(name, self.client.secure, self.client.is_xwayland)?; + let global = globals.get(name, self.client.caps, self.client.is_xwayland)?; if global.interface().name() != bind.interface { return Err(WlRegistryError::InvalidInterface(InterfaceError { name: global.name(), diff --git a/src/ifs/wl_seat/zwp_virtual_keyboard_manager_v1.rs b/src/ifs/wl_seat/zwp_virtual_keyboard_manager_v1.rs index 717666ca..54027dbf 100644 --- a/src/ifs/wl_seat/zwp_virtual_keyboard_manager_v1.rs +++ b/src/ifs/wl_seat/zwp_virtual_keyboard_manager_v1.rs @@ -1,6 +1,6 @@ use { crate::{ - client::{Client, ClientError}, + client::{Client, ClientCaps, ClientError, CAP_VIRTUAL_KEYBOARD_MANAGER}, globals::{Global, GlobalName}, ifs::wl_seat::zwp_virtual_keyboard_v1::ZwpVirtualKeyboardV1, leaks::Tracker, @@ -61,8 +61,8 @@ impl Global for ZwpVirtualKeyboardManagerV1Global { 1 } - fn secure(&self) -> bool { - true + fn required_caps(&self) -> ClientCaps { + CAP_VIRTUAL_KEYBOARD_MANAGER } } diff --git a/src/ifs/zwlr_layer_shell_v1.rs b/src/ifs/zwlr_layer_shell_v1.rs index 7e8ca7c2..7391cea0 100644 --- a/src/ifs/zwlr_layer_shell_v1.rs +++ b/src/ifs/zwlr_layer_shell_v1.rs @@ -1,6 +1,6 @@ use { crate::{ - client::{Client, ClientError}, + client::{Client, ClientCaps, ClientError, CAP_LAYER_SHELL}, globals::{Global, GlobalName}, ifs::wl_surface::zwlr_layer_surface_v1::{ZwlrLayerSurfaceV1, ZwlrLayerSurfaceV1Error}, leaks::Tracker, @@ -110,8 +110,8 @@ impl Global for ZwlrLayerShellV1Global { 4 } - fn secure(&self) -> bool { - true + fn required_caps(&self) -> ClientCaps { + CAP_LAYER_SHELL } } diff --git a/src/ifs/zwlr_screencopy_manager_v1.rs b/src/ifs/zwlr_screencopy_manager_v1.rs index 866c3691..5df2a52f 100644 --- a/src/ifs/zwlr_screencopy_manager_v1.rs +++ b/src/ifs/zwlr_screencopy_manager_v1.rs @@ -1,6 +1,6 @@ use { crate::{ - client::{Client, ClientError}, + client::{Client, ClientCaps, ClientError, CAP_SCREENCOPY_MANAGER}, globals::{Global, GlobalName}, ifs::zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1, leaks::Tracker, @@ -59,8 +59,8 @@ impl Global for ZwlrScreencopyManagerV1Global { 3 } - fn secure(&self) -> bool { - true + fn required_caps(&self) -> ClientCaps { + CAP_SCREENCOPY_MANAGER } } diff --git a/src/xwayland.rs b/src/xwayland.rs index 79e5a398..c734a380 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -3,7 +3,7 @@ mod xwm; use { crate::{ - client::ClientError, + client::{ClientCaps, ClientError}, compositor::DISPLAY, forker::{ForkerError, ForkerProxy}, ifs::{ @@ -171,7 +171,7 @@ async fn run( Rc::new(client1), uapi::getuid(), pid, - true, + ClientCaps::all(), true, ); let client = match client {