feat: use privileged sockets

This commit is contained in:
Ashley Wulber 2023-11-09 09:50:58 -05:00 committed by Ashley Wulber
parent 6d0826f319
commit fd2dc23fac
6 changed files with 186 additions and 42 deletions

View file

@ -101,6 +101,31 @@ async fn receive_ipc(state: &mut IpcState, rx: &mut OwnedReadHalf) -> Result<()>
}
}
pub fn create_privileged_socket(
sockets: &mut Vec<UnixStream>,
env_vars: &[(String, String)],
) -> Result<(Vec<(String, String)>, OwnedFd)> {
// Create a new pair of unnamed Unix sockets
let (comp_socket, client_socket) =
UnixStream::pair().wrap_err("failed to create socket pair")?;
// Push one socket to the list of sockets we were passed
sockets.push(comp_socket);
// Turn the other socket into a non-blocking fd, which we can pass to the child
// process
let client_fd = {
let std_stream = client_socket
.into_std()
.wrap_err("failed to convert client socket to std socket")?;
std_stream
.set_nonblocking(true)
.wrap_err("failed to mark client socket as non-blocking")?;
OwnedFd::from(std_stream)
};
let mut env_vars = env_vars.to_vec();
env_vars.push(("WAYLAND_SOCKET".into(), client_fd.as_raw_fd().to_string()));
Ok((env_vars, client_fd))
}
async fn send_fd(session_tx: &mut OwnedWriteHalf, stream: Vec<UnixStream>) -> Result<()> {
// Turn our list of Unix streams into non-blocking file descriptors.
let fds = stream
@ -134,12 +159,15 @@ async fn send_fd(session_tx: &mut OwnedWriteHalf, stream: Vec<UnixStream>) -> Re
tokio::time::sleep(std::time::Duration::from_micros(100)).await;
// Send our file descriptors.
let fd: &UnixStream = session_tx.as_ref();
info!("sending {} fds", fds.len());
fd.send_with_fd(
&[0],
&fds.iter().map(|fd| fd.as_raw_fd()).collect::<Vec<_>>(),
&fds.into_iter()
.map(|fd| fd.into_raw_fd())
.collect::<Vec<_>>(),
)
.wrap_err("failed to send fd")?;
info!("sent {} fds", fds.len());
Ok(())
}

View file

@ -9,17 +9,19 @@ mod service;
mod systemd;
use std::{
os::fd::AsRawFd,
os::fd::{AsRawFd, IntoRawFd},
sync::{Arc, Mutex},
};
use async_signals::Signals;
use color_eyre::{eyre::WrapErr, Result};
use comp::create_privileged_socket;
use cosmic_notifications_util::{DAEMON_NOTIFICATIONS_FD, PANEL_NOTIFICATIONS_FD};
use futures_util::StreamExt;
use launch_pad::{process::Process, ProcessManager};
use service::SessionRequest;
use tokio::{
net::UnixStream,
sync::{
mpsc::{self, Receiver, Sender},
oneshot,
@ -99,7 +101,7 @@ async fn start(
))
.await;
let token = CancellationToken::new();
let (_, socket_rx) = mpsc::unbounded_channel();
let (socket_tx, socket_rx) = mpsc::unbounded_channel();
let (env_tx, env_rx) = oneshot::channel();
let compositor_handle = comp::run_compositor(
&process_manager,
@ -160,6 +162,7 @@ async fn start(
panel_key.clone(),
panel_env_vars.clone(),
panel_notifications_fd.as_raw_fd(),
socket_tx.clone(),
))
.await
.expect("failed to start notifications daemon"),
@ -180,6 +183,7 @@ async fn start(
notif_key,
daemon_env_vars,
daemon_notifications_fd.as_raw_fd(),
socket_tx.clone(),
))
.await
.expect("failed to start panel"),
@ -187,19 +191,40 @@ async fn start(
drop(guard);
let span = info_span!(parent: None, "cosmic-app-library");
start_component("cosmic-app-library", span, &process_manager, &env_vars).await;
start_component(
"cosmic-app-library",
span,
&process_manager,
&env_vars,
&socket_tx,
)
.await;
let span = info_span!(parent: None, "cosmic-launcher");
start_component("cosmic-launcher", span, &process_manager, &env_vars).await;
start_component(
"cosmic-launcher",
span,
&process_manager,
&env_vars,
&socket_tx,
)
.await;
let span = info_span!(parent: None, "cosmic-workspaces");
start_component("cosmic-workspaces", span, &process_manager, &env_vars).await;
start_component(
"cosmic-workspaces",
span,
&process_manager,
&env_vars,
&socket_tx,
)
.await;
let span = info_span!(parent: None, "cosmic-osd");
start_component("cosmic-osd", span, &process_manager, &env_vars).await;
start_component("cosmic-osd", span, &process_manager, &env_vars, &socket_tx).await;
let span = info_span!(parent: None, "cosmic-bg");
start_component("cosmic-bg", span, &process_manager, &env_vars).await;
start_component("cosmic-bg", span, &process_manager, &env_vars, &socket_tx).await;
let span = info_span!(parent: None, "xdg-desktop-portal-cosmic");
start_component(
@ -207,6 +232,7 @@ async fn start(
span,
&process_manager,
&env_vars,
&socket_tx,
)
.await;
@ -258,9 +284,18 @@ async fn start_component(
span: tracing::Span,
process_manager: &ProcessManager,
env_vars: &[(String, String)],
socket_tx: &mpsc::UnboundedSender<Vec<UnixStream>>,
) {
let mut sockets = Vec::with_capacity(1);
let (env_vars, fd) = create_privileged_socket(&mut sockets, env_vars).unwrap();
let fd = fd.into_raw_fd();
if let Err(why) = socket_tx.send(sockets) {
error!(?why, "Failed to send the privileged socket");
}
let socket_tx_clone = socket_tx.clone();
let stdout_span = span.clone();
let stderr_span = span.clone();
let cmd_clone = cmd.to_string();
process_manager
.start(
Process::new()
@ -279,7 +314,37 @@ async fn start_component(
warn!("{}", line);
}
.instrument(stderr_span)
}),
})
.with_on_exit(move |mut pman, key, err_code, will_restart| {
if let Some(err) = err_code {
error!("{cmd_clone} exited with error {}", err.to_string());
}
let socket_tx_clone = socket_tx_clone.clone();
async move {
if !will_restart {
return;
}
let mut sockets = Vec::with_capacity(1);
let env_vars = Vec::with_capacity(1);
let (env_vars, new_fd) =
create_privileged_socket(&mut sockets, &env_vars).unwrap();
let new_fd = new_fd.into_raw_fd();
if let Err(why) = socket_tx_clone.send(sockets) {
error!(?why, "Failed to send the privileged socket");
}
if let Err(why) = pman.update_process_env(&key, env_vars).await {
error!(?why, "Failed to update environment variables");
}
if let Err(why) = pman.update_process_fds(&key, move || vec![new_fd]).await
{
error!(?why, "Failed to update fds");
}
}
})
.with_fds(move || vec![fd]),
)
.await
.expect(&format!("failed to start {}", cmd));

View file

@ -2,11 +2,14 @@ use color_eyre::eyre::Context;
use color_eyre::Result;
use launch_pad::process::Process;
use launch_pad::ProcessKey;
use std::os::fd::{OwnedFd, RawFd};
use std::os::fd::{IntoRawFd, OwnedFd, RawFd};
use std::os::unix::net::UnixStream;
use std::sync::{Arc, Mutex};
use tokio::sync::mpsc;
use tracing::Instrument;
use crate::comp::create_privileged_socket;
pub fn create_socket() -> Result<(OwnedFd, OwnedFd)> {
// Create a new pair of unnamed Unix sockets
let (sock_1, sock_2) = UnixStream::pair().wrap_err("failed to create socket pair")?;
@ -28,20 +31,28 @@ pub fn notifications_process(
span: tracing::Span,
cmd: &'static str,
key: Arc<Mutex<Option<ProcessKey>>>,
env_vars: Vec<(String, String)>,
mut env_vars: Vec<(String, String)>,
fd: RawFd,
restart_span: tracing::Span,
restart_cmd: &'static str,
restart_key: Arc<Mutex<Option<ProcessKey>>>,
restart_env_vars: Vec<(String, String)>,
restart_fd: RawFd,
socket_tx: mpsc::UnboundedSender<Vec<tokio::net::UnixStream>>,
) -> Process {
env_vars.retain(|v| &v.0 != "WAYLAND_SOCKET");
let stdout_span = span.clone();
let stderr_span = span.clone();
let mut sockets = Vec::with_capacity(1);
let (env_vars, privileged_fd) = create_privileged_socket(&mut sockets, &env_vars).unwrap();
_ = socket_tx.send(sockets);
let env_clone = env_vars.clone();
let socket_tx_clone = socket_tx.clone();
let privileged_fd = privileged_fd.into_raw_fd();
Process::new()
.with_executable(cmd)
.with_fds(move || vec![fd])
.with_fds(move || vec![privileged_fd, fd])
.with_on_stdout(move |_, _, line| {
let stdout_span = stdout_span.clone();
async move {
@ -56,7 +67,7 @@ pub fn notifications_process(
}
.instrument(stderr_span)
})
.with_on_exit(move |pman, _, _, will_restart| {
.with_on_exit(move |pman, my_key, _, will_restart| {
// force restart of notifications / panel when the other exits
let new_process = notifications_process(
restart_span.clone(),
@ -69,13 +80,37 @@ pub fn notifications_process(
key.clone(),
env_clone.clone(),
fd,
socket_tx_clone.clone(),
);
let restart_key = restart_key.clone();
let socket_tx_clone = socket_tx_clone.clone();
let env_clone = env_clone.clone();
let mut pman_clone = pman.clone();
async move {
if will_restart {
let mut sockets = Vec::with_capacity(1);
let (env_vars, new_fd) =
create_privileged_socket(&mut sockets, &env_clone).unwrap();
let new_fd = new_fd.into_raw_fd();
if let Err(why) = socket_tx_clone.send(sockets) {
error!(?why, "Failed to send the privileged socket");
}
if let Err(why) = pman_clone.update_process_env(&my_key, env_vars).await {
error!(?why, "Failed to update environment variables");
}
if let Err(why) = pman_clone
.update_process_fds(&my_key, move || vec![new_fd, fd])
.await
{
error!(?why, "Failed to update fds");
}
let Some(old) = restart_key.lock().unwrap().clone() else {
error!("Couldn't stop previous invocation of {}", cmd);
return;
error!("Couldn't stop previous invocation of {}", cmd);
return;
};
_ = pman.stop_process(old).await;

View file

@ -1,19 +1,10 @@
// SPDX-License-Identifier: GPL-3.0-only
use color_eyre::eyre::{ContextCompat, Result, WrapErr};
use nix::fcntl;
use color_eyre::eyre::{Result, WrapErr};
use rustix::io::FdFlags;
use std::os::unix::prelude::*;
pub(crate) fn mark_as_not_cloexec(file: &impl AsFd) -> Result<()> {
let raw_fd = file.as_fd().as_raw_fd();
let fd_flags = fcntl::FdFlag::from_bits(
fcntl::fcntl(raw_fd, fcntl::FcntlArg::F_GETFD)
.wrap_err("failed to get GETFD value of stream")?,
)
.wrap_err("failed to get fd flags from file")?;
fcntl::fcntl(
raw_fd,
fcntl::FcntlArg::F_SETFD(fd_flags.difference(fcntl::FdFlag::FD_CLOEXEC)),
)
.wrap_err("failed to set CLOEXEC on file")?;
Ok(())
let flags = rustix::io::fcntl_getfd(file).wrap_err("failed to get GETFD value of stream")?;
rustix::io::fcntl_setfd(file, flags.difference(FdFlags::CLOEXEC))
.wrap_err("failed to unset CLOEXEC on file")
}