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