diff --git a/docs/features.md b/docs/features.md index c280eafb..964cb4d0 100644 --- a/docs/features.md +++ b/docs/features.md @@ -141,6 +141,7 @@ Jay supports the following wayland protocols: | wp_fractional_scale_manager_v1 | 1 | | | wp_linux_drm_syncobj_manager_v1 | 1 | | | wp_presentation | 1 | | +| wp_security_context_manager_v1 | 1 | | | wp_single_pixel_buffer_manager_v1 | 1 | | | wp_tearing_control_manager_v1 | 1[^no_tearing] | | | wp_viewporter | 1 | | diff --git a/release-notes.md b/release-notes.md index f4008d54..33a3405f 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,5 +1,7 @@ # Unreleased +- Add support for wp-security-manager-v1. + # 1.1.0 (2024-04-22) - Screencasts now support window capture. diff --git a/src/acceptor.rs b/src/acceptor.rs index b359d773..a73fca68 100644 --- a/src/acceptor.rs +++ b/src/acceptor.rs @@ -171,7 +171,7 @@ impl Acceptor { } } -async fn accept(fd: Rc, state: Rc, caps: ClientCaps) { +async fn accept(fd: Rc, state: Rc, effective_caps: ClientCaps) { loop { let fd = match state.ring.accept(&fd, c::SOCK_CLOEXEC).await { Ok(fd) => fd, @@ -181,7 +181,10 @@ async fn accept(fd: Rc, state: Rc, caps: ClientCaps) { } }; let id = state.clients.id(); - if let Err(e) = state.clients.spawn(id, &state, fd, caps) { + if let Err(e) = state + .clients + .spawn(id, &state, fd, effective_caps, ClientCaps::all()) + { log::error!("Could not spawn a client: {}", ErrorFmt(e)); break; } diff --git a/src/client.rs b/src/client.rs index 26aafa66..2c95c68d 100644 --- a/src/client.rs +++ b/src/client.rs @@ -113,7 +113,8 @@ impl Clients { id: ClientId, global: &Rc, socket: Rc, - caps: ClientCaps, + effective_caps: ClientCaps, + bounding_caps: ClientCaps, ) -> Result<(), ClientError> { let (uid, pid) = { let mut cred = c::ucred { @@ -132,7 +133,16 @@ impl Clients { } } }; - self.spawn2(id, global, socket, uid, pid, caps, false)?; + self.spawn2( + id, + global, + socket, + uid, + pid, + effective_caps, + bounding_caps, + false, + )?; Ok(()) } @@ -143,7 +153,8 @@ impl Clients { socket: Rc, uid: c::uid_t, pid: c::pid_t, - caps: ClientCaps, + effective_caps: ClientCaps, + bounding_caps: ClientCaps, is_xwayland: bool, ) -> Result, ClientError> { let data = Rc::new(Client { @@ -157,7 +168,8 @@ impl Clients { shutdown: Default::default(), tracker: Default::default(), is_xwayland, - caps, + effective_caps, + bounding_caps, last_enter_serial: Cell::new(0), pid_info: get_pid_info(uid, pid), serials: Default::default(), @@ -183,7 +195,7 @@ impl Clients { uid, client.data.socket.raw(), data.pid_info.comm, - caps, + effective_caps, ); self.clients.borrow_mut().insert(client.data.id, client); Ok(data) @@ -211,7 +223,7 @@ impl Clients { { let clients = self.clients.borrow(); for client in clients.values() { - if client.data.caps.contains(required_caps) + if client.data.effective_caps.contains(required_caps) && (!xwayland_only || client.data.is_xwayland) { f(&client.data); @@ -272,7 +284,8 @@ pub struct Client { shutdown: AsyncEvent, pub tracker: Tracker, pub is_xwayland: bool, - pub caps: ClientCaps, + pub effective_caps: ClientCaps, + pub bounding_caps: ClientCaps, pub last_enter_serial: Cell, pub pid_info: PidInfo, pub serials: RefCell>, diff --git a/src/compositor.rs b/src/compositor.rs index eb361305..eeebec41 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -235,6 +235,7 @@ fn start_compositor2( wait_for_sync_obj: Rc::new(WaitForSyncObj::new(&ring, &engine)), explicit_sync_enabled: Cell::new(true), keyboard_state_ids: Default::default(), + security_context_acceptors: Default::default(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); diff --git a/src/globals.rs b/src/globals.rs index 7c344ab8..df2eb32e 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -34,6 +34,7 @@ use { wp_cursor_shape_manager_v1::WpCursorShapeManagerV1Global, wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1Global, wp_presentation::WpPresentationGlobal, + wp_security_context_manager_v1::WpSecurityContextManagerV1Global, wp_single_pixel_buffer_manager_v1::WpSinglePixelBufferManagerV1Global, wp_tearing_control_manager_v1::WpTearingControlManagerV1Global, wp_viewporter::WpViewporterGlobal, @@ -184,6 +185,7 @@ impl Globals { add_singleton!(ZwpVirtualKeyboardManagerV1Global); add_singleton!(ZwpInputMethodManagerV2Global); add_singleton!(ZwpTextInputManagerV3Global); + add_singleton!(WpSecurityContextManagerV1Global); } pub fn add_backend_singletons(&self, backend: &Rc) { @@ -249,7 +251,7 @@ impl Globals { } pub fn notify_all(&self, registry: &Rc) { - let caps = registry.client.caps; + let caps = registry.client.effective_caps; let xwayland = registry.client.is_xwayland; let globals = self.registry.lock(); macro_rules! emit { diff --git a/src/ifs.rs b/src/ifs.rs index 2fab7574..ccd578da 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -46,6 +46,8 @@ pub mod wp_linux_drm_syncobj_manager_v1; pub mod wp_linux_drm_syncobj_timeline_v1; pub mod wp_presentation; pub mod wp_presentation_feedback; +pub mod wp_security_context_manager_v1; +pub mod wp_security_context_v1; pub mod wp_single_pixel_buffer_manager_v1; pub mod wp_tearing_control_manager_v1; pub mod wp_viewporter; diff --git a/src/ifs/wl_registry.rs b/src/ifs/wl_registry.rs index b1457c41..9392043e 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.caps, self.client.is_xwayland)?; + let global = globals.get(name, self.client.effective_caps, self.client.is_xwayland)?; if global.interface().name() != bind.interface { return Err(WlRegistryError::InvalidInterface(InterfaceError { name: global.name(), diff --git a/src/ifs/wp_security_context_manager_v1.rs b/src/ifs/wp_security_context_manager_v1.rs new file mode 100644 index 00000000..7ea10332 --- /dev/null +++ b/src/ifs/wp_security_context_manager_v1.rs @@ -0,0 +1,107 @@ +use { + crate::{ + client::{Client, ClientError}, + globals::{Global, GlobalName}, + ifs::wp_security_context_v1::WpSecurityContextV1, + leaks::Tracker, + object::{Object, Version}, + wire::{wp_security_context_manager_v1::*, WpSecurityContextManagerV1Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct WpSecurityContextManagerV1Global { + pub name: GlobalName, +} + +impl WpSecurityContextManagerV1Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: WpSecurityContextManagerV1Id, + client: &Rc, + version: Version, + ) -> Result<(), WpSecurityContextManagerV1Error> { + let obj = Rc::new(WpSecurityContextManagerV1 { + id, + client: client.clone(), + tracker: Default::default(), + version, + }); + track!(client, obj); + client.add_client_obj(&obj)?; + Ok(()) + } +} + +global_base!( + WpSecurityContextManagerV1Global, + WpSecurityContextManagerV1, + WpSecurityContextManagerV1Error +); + +impl Global for WpSecurityContextManagerV1Global { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 1 + } +} + +simple_add_global!(WpSecurityContextManagerV1Global); + +pub struct WpSecurityContextManagerV1 { + pub id: WpSecurityContextManagerV1Id, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, +} + +impl WpSecurityContextManagerV1RequestHandler for WpSecurityContextManagerV1 { + type Error = WpSecurityContextManagerV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + Ok(()) + } + + fn create_listener(&self, req: CreateListener, _slf: &Rc) -> Result<(), Self::Error> { + let obj = Rc::new(WpSecurityContextV1 { + id: req.id, + client: self.client.clone(), + tracker: Default::default(), + version: self.version, + listen_fd: req.listen_fd, + close_fd: req.close_fd, + sandbox_engine: Default::default(), + app_id: Default::default(), + instance_id: Default::default(), + committed: Default::default(), + }); + track!(self.client, obj); + self.client.add_client_obj(&obj)?; + Ok(()) + } +} + +object_base! { + self = WpSecurityContextManagerV1; + version = self.version; +} + +impl Object for WpSecurityContextManagerV1 {} + +simple_add_obj!(WpSecurityContextManagerV1); + +#[derive(Debug, Error)] +pub enum WpSecurityContextManagerV1Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(WpSecurityContextManagerV1Error, ClientError); diff --git a/src/ifs/wp_security_context_v1.rs b/src/ifs/wp_security_context_v1.rs new file mode 100644 index 00000000..670a70d3 --- /dev/null +++ b/src/ifs/wp_security_context_v1.rs @@ -0,0 +1,119 @@ +use { + crate::{ + client::{Client, ClientCaps, ClientError}, + leaks::Tracker, + object::{Object, Version}, + wire::{wp_security_context_v1::*, WpSecurityContextV1Id}, + }, + std::{ + cell::{Cell, RefCell}, + rc::Rc, + }, + thiserror::Error, + uapi::OwnedFd, +}; + +pub struct WpSecurityContextV1 { + pub id: WpSecurityContextV1Id, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, + pub listen_fd: Rc, + pub close_fd: Rc, + pub sandbox_engine: RefCell>, + pub app_id: RefCell>, + pub instance_id: RefCell>, + pub committed: Cell, +} + +impl WpSecurityContextV1 { + fn check_committed(&self) -> Result<(), WpSecurityContextV1Error> { + if self.committed.get() { + return Err(WpSecurityContextV1Error::Committed); + } + Ok(()) + } +} + +impl WpSecurityContextV1RequestHandler for WpSecurityContextV1 { + type Error = WpSecurityContextV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + Ok(()) + } + + fn set_sandbox_engine( + &self, + req: SetSandboxEngine<'_>, + _slf: &Rc, + ) -> Result<(), Self::Error> { + self.check_committed()?; + let val = &mut *self.sandbox_engine.borrow_mut(); + if val.is_some() { + return Err(WpSecurityContextV1Error::EnginSet); + } + *val = Some(req.name.to_string()); + Ok(()) + } + + fn set_app_id(&self, req: SetAppId<'_>, _slf: &Rc) -> Result<(), Self::Error> { + self.check_committed()?; + let val = &mut *self.app_id.borrow_mut(); + if val.is_some() { + return Err(WpSecurityContextV1Error::AppSet); + } + *val = Some(req.app_id.to_string()); + Ok(()) + } + + fn set_instance_id(&self, req: SetInstanceId<'_>, _slf: &Rc) -> Result<(), Self::Error> { + self.check_committed()?; + let val = &mut *self.instance_id.borrow_mut(); + if val.is_some() { + return Err(WpSecurityContextV1Error::InstanceSet); + } + *val = Some(req.instance_id.to_string()); + Ok(()) + } + + fn commit(&self, _req: Commit, _slf: &Rc) -> Result<(), Self::Error> { + self.check_committed()?; + self.committed.set(true); + let caps = ClientCaps::none() & self.client.bounding_caps; + self.client.state.security_context_acceptors.spawn( + &self.client.state, + self.sandbox_engine.take(), + self.app_id.take(), + self.instance_id.take(), + &self.listen_fd, + &self.close_fd, + caps, + ); + Ok(()) + } +} + +object_base! { + self = WpSecurityContextV1; + version = self.version; +} + +impl Object for WpSecurityContextV1 {} + +simple_add_obj!(WpSecurityContextV1); + +#[derive(Debug, Error)] +pub enum WpSecurityContextV1Error { + #[error(transparent)] + ClientError(Box), + #[error("The sandbox engine has already been set")] + EnginSet, + #[error("The app id has already been set")] + AppSet, + #[error("The instance id has already been set")] + InstanceSet, + #[error("The context has already been committed")] + Committed, +} +efrom!(WpSecurityContextV1Error, ClientError); diff --git a/src/main.rs b/src/main.rs index bdd39264..9047758a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -76,6 +76,7 @@ mod rect; mod renderer; mod scale; mod screenshoter; +mod security_context_acceptor; mod sighand; mod state; mod tasks; diff --git a/src/security_context_acceptor.rs b/src/security_context_acceptor.rs new file mode 100644 index 00000000..69f102af --- /dev/null +++ b/src/security_context_acceptor.rs @@ -0,0 +1,123 @@ +use { + crate::{ + async_engine::SpawnedFuture, + client::ClientCaps, + state::State, + utils::{copyhashmap::CopyHashMap, errorfmt::ErrorFmt}, + }, + std::{ + cell::Cell, + fmt::{Display, Formatter}, + rc::Rc, + }, + uapi::{c, OwnedFd}, +}; + +#[derive(Default)] +pub struct SecurityContextAcceptors { + ids: AcceptorIds, + acceptors: CopyHashMap>, +} + +linear_ids!(AcceptorIds, AcceptorId, u64); + +struct Acceptor { + id: AcceptorId, + state: Rc, + sandbox_engine: Option, + app_id: Option, + instance_id: Option, + listen_fd: Rc, + close_fd: Rc, + caps: ClientCaps, + listen_future: Cell>>, + close_future: Cell>>, +} + +impl SecurityContextAcceptors { + pub fn clear(&self) { + for (_, acceptor) in self.acceptors.lock().drain() { + acceptor.kill(); + } + } + + pub fn spawn( + &self, + state: &Rc, + sandbox_engine: Option, + app_id: Option, + instance_id: Option, + listen_fd: &Rc, + close_fd: &Rc, + caps: ClientCaps, + ) { + let acceptor = Rc::new(Acceptor { + id: self.ids.next(), + state: state.clone(), + sandbox_engine, + app_id, + instance_id, + listen_fd: listen_fd.clone(), + close_fd: close_fd.clone(), + caps, + listen_future: Cell::new(None), + close_future: Cell::new(None), + }); + log::info!("Creating security acceptor {acceptor}"); + acceptor + .listen_future + .set(Some(state.eng.spawn(acceptor.clone().accept()))); + acceptor + .close_future + .set(Some(state.eng.spawn(acceptor.clone().close()))); + self.acceptors.set(acceptor.id, acceptor); + } +} + +impl Acceptor { + fn kill(&self) { + log::info!("Destroying security acceptor {self}"); + self.listen_future.take(); + self.close_future.take(); + self.state + .security_context_acceptors + .acceptors + .remove(&self.id); + } + + async fn accept(self: Rc) { + let s = &self.state; + loop { + let fd = match s.ring.accept(&self.listen_fd, c::SOCK_CLOEXEC).await { + Ok(fd) => fd, + Err(e) => { + log::error!("Could not accept a client: {}", ErrorFmt(e)); + break; + } + }; + let id = s.clients.id(); + if let Err(e) = s.clients.spawn(id, s, fd, self.caps, self.caps) { + log::error!("Could not spawn a client: {}", ErrorFmt(e)); + break; + } + } + self.kill(); + } + + async fn close(self: Rc) { + let _ = self.state.ring.poll(&self.close_fd, 0).await; + self.kill(); + } +} + +impl Display for Acceptor { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}/{}/{}", + self.sandbox_engine.as_deref().unwrap_or(""), + self.app_id.as_deref().unwrap_or(""), + self.instance_id.as_deref().unwrap_or(""), + ) + } +} diff --git a/src/state.rs b/src/state.rs index 03a078fa..fe49b79e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -51,6 +51,7 @@ use { rect::Rect, renderer::{RenderResult, Renderer}, scale::Scale, + security_context_acceptor::SecurityContextAcceptors, theme::{Color, Theme}, tree::{ ContainerNode, ContainerSplit, Direction, DisplayNode, FloatNode, Node, NodeIds, @@ -181,6 +182,7 @@ pub struct State { pub wait_for_sync_obj: Rc, pub explicit_sync_enabled: Cell, pub keyboard_state_ids: KeyboardStateIds, + pub security_context_acceptors: SecurityContextAcceptors, } // impl Drop for State { @@ -744,6 +746,7 @@ impl State { self.render_ctx_watchers.clear(); self.workspace_watchers.clear(); self.toplevel_lists.clear(); + self.security_context_acceptors.clear(); self.slow_clients.clear(); for (_, h) in self.input_device_handlers.borrow_mut().drain() { h.async_event.clear(); diff --git a/src/xwayland.rs b/src/xwayland.rs index c734a380..7b11d972 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -172,6 +172,7 @@ async fn run( uapi::getuid(), pid, ClientCaps::all(), + ClientCaps::all(), true, ); let client = match client { diff --git a/wire/wp_security_context_manager_v1.txt b/wire/wp_security_context_manager_v1.txt new file mode 100644 index 00000000..15aabdae --- /dev/null +++ b/wire/wp_security_context_manager_v1.txt @@ -0,0 +1,9 @@ +request destroy { + +} + +request create_listener { + id: id(wp_security_context_v1), + listen_fd: fd, + close_fd: fd, +} diff --git a/wire/wp_security_context_v1.txt b/wire/wp_security_context_v1.txt new file mode 100644 index 00000000..1c538df5 --- /dev/null +++ b/wire/wp_security_context_v1.txt @@ -0,0 +1,19 @@ +request destroy { + +} + +request set_sandbox_engine { + name: str, +} + +request set_app_id { + app_id: str, +} + +request set_instance_id { + instance_id: str, +} + +request commit { + +}