1use std::collections::VecDeque;
7use std::io::{IoSlice, IoSliceMut};
8use std::os::fd::{AsRawFd, OwnedFd, RawFd};
9use std::os::unix::net::{UnixListener, UnixStream};
10use std::path::Path;
11use std::sync::atomic::{AtomicBool, Ordering};
12use std::sync::{Arc, Mutex};
13use std::time::{Duration, Instant};
14
15use anyhow::{Context, Result};
16use nix::sys::socket::{ControlMessage, ControlMessageOwned, MsgFlags, recvmsg, sendmsg};
17
18fn setup_signal_handler(shutdown: Arc<AtomicBool>) -> Result<()> {
20 for sig in [signal_hook::consts::SIGTERM, signal_hook::consts::SIGINT] {
21 signal_hook::flag::register(sig, Arc::clone(&shutdown))?;
22 }
23 Ok(())
24}
25
26use crate::config::Config;
27
28const MAX_CONNECTIONS: usize = 128;
29
30mod firewall;
31
32use firewall::{FirewallState, is_blocked_global, rate_allow};
33
34pub fn run_compositor(config: &Config, name: &str) -> Result<()> {
41 let xdg_runtime = std::env::var("XDG_RUNTIME_DIR")
42 .or_else(|_| {
43 let uid = nix::unistd::getuid().as_raw();
44 Ok::<_, std::env::VarError>(format!("/run/user/{uid}"))
45 })
46 .context("XDG_RUNTIME_DIR not set")?;
47
48 let wayland_display = std::env::var("WAYLAND_DISPLAY").unwrap_or_else(|_| "wayland-0".into());
49 let host_socket = Path::new(&xdg_runtime).join(&wayland_display);
50
51 if !host_socket.exists() {
52 anyhow::bail!(
53 "Host Wayland socket not found at {} (WAYLAND_DISPLAY={})",
54 host_socket.display(),
55 wayland_display
56 );
57 }
58
59 let socket_path = Path::new(&xdg_runtime)
60 .join("podbox")
61 .join(format!("{name}-wayland.sock"));
62
63 let _ = std::fs::remove_file(&socket_path);
64 std::fs::create_dir_all(socket_path.parent().context("socket path has no parent")?)?;
65
66 let shutdown = Arc::new(AtomicBool::new(false));
67 setup_signal_handler(Arc::clone(&shutdown))?;
68
69 let listener = UnixListener::bind(&socket_path).with_context(|| {
70 format!(
71 "Failed to bind Wayland proxy socket at {}",
72 socket_path.display()
73 )
74 })?;
75 listener.set_nonblocking(true)?;
79
80 let blocked = config.wayland.blocked_interfaces.clone();
81
82 let mut connections = 0;
83 loop {
84 if shutdown.load(Ordering::Relaxed) || connections >= MAX_CONNECTIONS {
85 break;
86 }
87
88 let stream = match listener.accept() {
89 Ok((s, _)) => s,
90 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
91 std::thread::sleep(Duration::from_millis(200));
92 continue;
93 }
94 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
95 Err(e) => {
96 tracing::error!("compositor: accept failed: {e}");
97 break;
98 }
99 };
100 connections += 1;
101
102 stream.set_nonblocking(false)?;
103
104 let host_conn = match UnixStream::connect(&host_socket) {
105 Ok(s) => s,
106 Err(e) => {
107 tracing::error!("compositor: failed to connect to host Wayland socket: {e}");
108 continue;
109 }
110 };
111
112 let state = Arc::new(Mutex::new(FirewallState::new(blocked.clone())));
113 let done = Arc::new(AtomicBool::new(false));
114
115 let client_clone = stream.try_clone()?;
116 let host_clone = host_conn.try_clone()?;
117 let state_c2h = Arc::clone(&state);
118 let done_c2h = Arc::clone(&done);
119
120 std::thread::spawn(move || {
121 if let Err(e) = bridge_loop(stream, host_clone, state_c2h, &done_c2h, true) {
122 tracing::error!("compositor: client→host bridge error: {e}");
123 }
124 done_c2h.store(true, Ordering::Relaxed);
125 });
126
127 let state_h2c = state;
128 let done_h2c = done;
129
130 std::thread::spawn(move || {
131 if let Err(e) = bridge_loop(host_conn, client_clone, state_h2c, &done_h2c, false) {
132 tracing::error!("compositor: host→client bridge error: {e}");
133 }
134 done_h2c.store(true, Ordering::Relaxed);
135 });
136 }
137
138 Ok(())
139}
140
141fn bridge_loop(
155 in_socket: UnixStream,
156 out_socket: UnixStream,
157 state: Arc<Mutex<FirewallState>>,
158 done: &AtomicBool,
159 is_client_to_host: bool,
160) -> Result<()> {
161 let mut read_buf = [0u8; 16384];
162 let mut cmsg_buffer = vec![0u8; 4096];
163 let mut bytes_cache = Vec::with_capacity(32768);
164 let mut fd_batches: VecDeque<(usize, Vec<OwnedFd>)> = VecDeque::new();
167 let mut base_offset: usize = 0;
169
170 let mut bucket: f64 = 10_000.0;
173 let mut last_refill = Instant::now();
174
175 loop {
176 if done.load(Ordering::Relaxed) {
177 break;
178 }
179
180 let msg_bytes = {
181 let mut iov = [IoSliceMut::new(&mut read_buf)];
182 let msg = match recvmsg::<()>(
183 in_socket.as_raw_fd(),
184 &mut iov,
185 Some(&mut cmsg_buffer),
186 MsgFlags::empty(),
187 ) {
188 Ok(m) => m,
189 Err(e) if e == nix::errno::Errno::EINTR => continue,
190 Err(e) => {
191 done.store(true, Ordering::Relaxed);
192 let _ = in_socket.shutdown(std::net::Shutdown::Both);
193 let _ = out_socket.shutdown(std::net::Shutdown::Both);
194 return Err(e.into());
195 }
196 };
197
198 let bytes = msg.bytes;
199 if bytes == 0 {
200 break;
201 }
202
203 let mut read_fds: Vec<OwnedFd> = Vec::new();
204 if let Ok(cmsgs) = msg.cmsgs() {
205 for cmsg in cmsgs {
206 if let ControlMessageOwned::ScmRights(fds) = cmsg {
207 for fd in fds {
208 read_fds.push(crate::process::adopt_scm_fd(fd));
209 }
210 }
211 }
212 }
213
214 if !read_fds.is_empty() {
217 fd_batches.push_back((base_offset + bytes_cache.len() + bytes, read_fds));
218 }
219
220 bytes
221 };
222
223 bytes_cache.extend_from_slice(&read_buf[..msg_bytes]);
224
225 let mut consumed = 0;
227 while consumed + 8 <= bytes_cache.len() {
228 let header = &bytes_cache[consumed..consumed + 8];
229 let size_and_opcode = u32::from_ne_bytes(header[4..8].try_into().unwrap());
230 let msg_size = (size_and_opcode >> 16) as usize;
231 let opcode = (size_and_opcode & 0xFFFF) as u16;
232
233 if msg_size < 8 {
234 done.store(true, Ordering::Relaxed);
235 let _ = in_socket.shutdown(std::net::Shutdown::Both);
236 let _ = out_socket.shutdown(std::net::Shutdown::Both);
237 anyhow::bail!("Invalid Wayland message size: {msg_size}");
238 }
239
240 if consumed + msg_size > bytes_cache.len() {
241 break;
242 }
243
244 let message_bytes = &bytes_cache[consumed..consumed + msg_size];
245 let should_drop =
246 !is_client_to_host && is_blocked_global(message_bytes, opcode, &state);
247
248 let message_end_abs = base_offset + consumed + msg_size;
249 let mut msg_fds = take_fd_batches(&mut fd_batches, message_end_abs);
250
251 if should_drop {
252 drop(msg_fds);
255 } else if is_client_to_host || rate_allow(&mut bucket, &mut last_refill) {
256 forward_message(&out_socket, message_bytes, &mut msg_fds)?;
257 } else {
258 drop(fd_batches);
259 done.store(true, Ordering::Relaxed);
260 let _ = in_socket.shutdown(std::net::Shutdown::Both);
261 let _ = out_socket.shutdown(std::net::Shutdown::Both);
262 tracing::warn!("compositor: rate-limited, closing connection");
263 return Ok(());
264 }
265
266 consumed += msg_size;
267 }
268
269 bytes_cache.drain(..consumed);
270 base_offset += consumed;
271 }
272
273 done.store(true, Ordering::Relaxed);
275 let _ = in_socket.shutdown(std::net::Shutdown::Both);
276 let _ = out_socket.shutdown(std::net::Shutdown::Both);
277 Ok(())
278}
279
280fn take_fd_batches<T>(fd_batches: &mut VecDeque<(usize, Vec<T>)>, message_end: usize) -> Vec<T> {
285 let mut out = Vec::new();
286 while let Some((read_end, _)) = fd_batches.front() {
287 if *read_end > message_end {
288 break;
289 }
290 let (_, fds) = fd_batches.pop_front().expect("front checked");
291 out.extend(fds);
292 }
293 out
294}
295
296fn forward_message(
299 out_socket: &UnixStream,
300 message_bytes: &[u8],
301 pending_fds: &mut Vec<OwnedFd>,
302) -> Result<()> {
303 let iov = [IoSlice::new(message_bytes)];
304
305 if pending_fds.is_empty() {
306 sendmsg::<()>(out_socket.as_raw_fd(), &iov, &[], MsgFlags::empty(), None)?;
307 } else {
308 let raw_fds: Vec<RawFd> = pending_fds.iter().map(|f| f.as_raw_fd()).collect();
309 let cmsg = ControlMessage::ScmRights(&raw_fds);
310 sendmsg::<()>(
311 out_socket.as_raw_fd(),
312 &iov,
313 &[cmsg],
314 MsgFlags::empty(),
315 None,
316 )?;
317 pending_fds.clear();
318 }
319
320 Ok(())
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326
327 fn make_global(object_id: u32, name: u32, interface: &str, version: u32) -> Vec<u8> {
328 let raw = interface.as_bytes();
329 let str_len = raw.len().checked_add(1).unwrap();
330 let padded_len = str_len.next_multiple_of(4);
331 let msg_size = u32::try_from(8 + 4 + 4 + padded_len + 4).unwrap();
332
333 let mut buf = Vec::with_capacity(msg_size as usize);
334 buf.extend_from_slice(&object_id.to_ne_bytes());
335 buf.extend_from_slice(&(msg_size << 16).to_ne_bytes());
336 buf.extend_from_slice(&name.to_ne_bytes());
337 buf.extend_from_slice(&u32::try_from(str_len).unwrap().to_ne_bytes());
338 buf.extend_from_slice(raw);
339 buf.push(0);
340 while buf.len() < (8 + 4 + 4 + padded_len) {
341 buf.push(0);
342 }
343 buf.extend_from_slice(&version.to_ne_bytes());
344 buf
345 }
346
347 fn make_message(object_id: u32, size: u32, opcode: u16) -> Vec<u8> {
348 let mut buf = Vec::with_capacity(size as usize);
349 buf.extend_from_slice(&object_id.to_ne_bytes());
350 buf.extend_from_slice(&((size << 16) | u32::from(opcode)).to_ne_bytes());
351 while buf.len() < size as usize {
352 buf.push(0);
353 }
354 buf
355 }
356
357 fn blocked_state() -> Mutex<FirewallState> {
358 Mutex::new(FirewallState::new(vec![
359 "zwlr_screencopy_manager_v1".into(),
360 "ext_foreign_toplevel_list_v1".into(),
361 ]))
362 }
363
364 fn empty_state() -> Mutex<FirewallState> {
365 Mutex::new(FirewallState::new(vec![]))
366 }
367
368 #[test]
369 fn blocks_screencopy_interface() {
370 let data = make_global(2, 42, "zwlr_screencopy_manager_v1", 1);
371 assert!(is_blocked_global(&data, 0, &blocked_state()));
372 }
373
374 #[test]
375 fn blocks_foreign_toplevel() {
376 let data = make_global(2, 43, "ext_foreign_toplevel_list_v1", 1);
377 assert!(is_blocked_global(&data, 0, &blocked_state()));
378 }
379
380 #[test]
381 fn allows_safe_interface() {
382 let data = make_global(2, 44, "wl_compositor", 6);
383 assert!(!is_blocked_global(&data, 0, &blocked_state()));
384 }
385
386 #[test]
387 fn allows_wl_shm() {
388 let data = make_global(2, 1, "wl_shm", 1);
389 assert!(!is_blocked_global(&data, 0, &blocked_state()));
390 }
391
392 #[test]
393 fn blocks_nothing_when_empty_blocklist() {
394 let data = make_global(2, 42, "zwlr_screencopy_manager_v1", 1);
395 assert!(!is_blocked_global(&data, 0, &empty_state()));
396 }
397
398 #[test]
399 fn ignores_non_registry_opcode() {
400 let data = make_message(2, 16, 1);
401 assert!(!is_blocked_global(&data, 1, &blocked_state()));
402 }
403
404 #[test]
405 fn ignores_short_payload() {
406 let data = make_message(2, 12, 0);
407 assert!(!is_blocked_global(&data, 0, &blocked_state()));
408 }
409
410 #[test]
411 fn ignores_empty_interface_string() {
412 let mut data = make_message(2, 16, 0);
413 data[12..16].copy_from_slice(&0u32.to_ne_bytes());
414 assert!(!is_blocked_global(&data, 0, &blocked_state()));
415 }
416
417 #[test]
418 fn allows_partial_name_prefix_match() {
419 let data = make_global(2, 42, "zwlr_screencopy", 1);
420 assert!(!is_blocked_global(&data, 0, &blocked_state()));
421 }
422
423 #[test]
424 fn allows_similar_but_not_blocked() {
425 let data = make_global(2, 99, "zwlr_layer_shell_v1", 1);
426 assert!(!is_blocked_global(&data, 0, &blocked_state()));
427 }
428
429 #[test]
430 fn rate_allow_accepts_first_message() {
431 let mut bucket = 10_000.0;
432 let mut last = Instant::now();
433 assert!(rate_allow(&mut bucket, &mut last));
434 }
435
436 #[test]
437 fn rate_allow_drains_bucket() {
438 let mut bucket = 2.0;
439 let mut last = Instant::now();
440 assert!(rate_allow(&mut bucket, &mut last));
441 assert!(rate_allow(&mut bucket, &mut last));
442 assert!(!rate_allow(&mut bucket, &mut last));
443 }
444
445 #[test]
446 fn rate_allow_refills_over_time() {
447 let mut bucket = 0.0;
448 let mut last = Instant::now();
449 assert!(!rate_allow(&mut bucket, &mut last));
450 std::thread::sleep(std::time::Duration::from_millis(1));
452 let mut bucket = 0.0;
453 let mut last_refill = Instant::now();
454 std::thread::sleep(std::time::Duration::from_millis(2));
455 assert!(rate_allow(&mut bucket, &mut last_refill));
456 }
457
458 fn batches(items: &[(usize, &[u32])]) -> VecDeque<(usize, Vec<u32>)> {
463 items
464 .iter()
465 .map(|&(end, fds)| (end, fds.to_vec()))
466 .collect()
467 }
468
469 #[test]
470 fn fd_batch_attaches_to_completing_message() {
471 let mut q = batches(&[(24, &[7, 8])]);
473 let fds = take_fd_batches(&mut q, 24);
474 assert_eq!(fds, vec![7u32, 8]);
475 assert!(q.is_empty());
476 }
477
478 #[test]
479 fn fd_batch_waits_for_its_own_message() {
480 let mut q = batches(&[(24, &[9])]);
484 assert!(take_fd_batches(&mut q, 12).is_empty());
485 assert_eq!(take_fd_batches(&mut q, 24), vec![9u32]);
486 }
487
488 #[test]
489 fn split_message_gets_fds_from_tail_read() {
490 let mut q = batches(&[(40, &[5])]);
494 assert!(take_fd_batches(&mut q, 20).is_empty());
495 assert_eq!(take_fd_batches(&mut q, 40), vec![5u32]);
496 }
497
498 #[test]
499 fn blocked_message_closes_only_its_own_fds() {
500 let mut q = batches(&[(28, &[3]), (60, &[4])]);
503 let dropped = take_fd_batches(&mut q, 28);
504 assert_eq!(dropped, vec![3u32]); assert_eq!(take_fd_batches(&mut q, 60), vec![4u32]); }
507}