From 269e62514bc32ae3a88cf7309d8ec47b3842bbd2 Mon Sep 17 00:00:00 2001 From: Klemens Nanni Date: Thu, 4 Jan 2024 16:44:18 +0100 Subject: [PATCH 1/3] Use pledge(2) on OpenBSD to restrict system calls Limit the attack surface of spotifyd, an internet facing network daemon with read/write filesystem access and options to execute arbitrary commands. Most importantly, prevent fork(2)/execve(2) unless `onevent` is used. While the set of runtime promises retains full read/write filesystem as well as network access (for now), it does exclude a number of groups of unused system calls -- the manual[0] for details. OpenBSD's official package has been shipping this patch for a month by now without any regressions or reports of breakage. 0: https://man.openbsd.org/pledge.2 --- Cargo.lock | 10 ++++++++++ Cargo.toml | 3 +++ src/main.rs | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 9f955905..8353c5bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2291,6 +2291,15 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "pledge" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252599417b7d9a43b7fdc63dd790b0848666a8910b2ebe1a25118309c3c981e5" +dependencies = [ + "libc", +] + [[package]] name = "polling" version = "2.8.0" @@ -2963,6 +2972,7 @@ dependencies = [ "librespot-discovery", "librespot-playback", "log", + "pledge", "rspotify", "serde", "sha-1 0.10.1", diff --git a/Cargo.toml b/Cargo.toml index 184d9677..0dfddb28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,9 @@ syslog = "6" [target."cfg(target_os = \"macos\")".dependencies] whoami = "1" +[target."cfg(target_os = \"openbsd\")".dependencies] +pledge = "0.4.2" + [dev-dependencies] env_logger = "0.10" diff --git a/src/main.rs b/src/main.rs index 708f02fb..4acb3807 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,8 @@ use log::{info, trace, LevelFilter}; use std::fs; use structopt::StructOpt; use tokio::runtime::Runtime; +#[cfg(target_os = "openbsd")] +use pledge::pledge; #[cfg(feature = "alsa_backend")] mod alsa_mixer; @@ -85,6 +87,11 @@ fn setup_logger(log_target: LogTarget, verbose: bool) -> eyre::Result<()> { } fn main() -> eyre::Result<()> { + // Start with superset of all potentially required promises. + // Drop later after CLI arguments and configuration files were parsed. + #[cfg(target_os = "openbsd")] + pledge("stdio rpath wpath cpath inet mcast flock chown unix dns proc exec audio", None).unwrap(); + color_eyre::install().wrap_err("Couldn't initialize error reporting")?; let mut cli_config: CliConfig = CliConfig::from_args(); @@ -166,6 +173,32 @@ fn main() -> eyre::Result<()> { } } + #[cfg(target_os = "openbsd")] + { + // At this point: + // * --username-cmd, --password-cmd were handled + // > no "proc exec" + // * --pid, daemon(3) were handled + // > no "cpath flock chown" for PID file + // > no "proc" for double-fork(2) + // + // Required runtime promises: + // stdout/err, syslog(3) "stdio" + // ${TMPDIR}/.tmp*, cache "[rwc]path" + // Spotify API/Connect "inet dns" + // D-Bus, MPRIS "unix" + // Zeroconf Discovery "mcast" + // PortAudio, sio_open(3) ("[rwc]path unix inet audio") + // > after sndio(7) cookie "audio" + + // --on-song-change-hook aka. "onevent", run via --shell aka. "shell" + if internal_config.onevent.is_some() { + pledge("stdio rpath wpath cpath inet mcast unix dns proc exec audio", None).unwrap(); + } else { + pledge("stdio rpath wpath cpath inet mcast unix dns audio", None).unwrap(); + } + } + let runtime = Runtime::new().unwrap(); runtime.block_on(async { let mut initial_state = setup::initial_state(internal_config); From 7065a5ba74eb38b369e30b5e415944ab8cad5e8f Mon Sep 17 00:00:00 2001 From: Klemens Nanni Date: Sun, 4 Feb 2024 07:55:09 +0100 Subject: [PATCH 2/3] Adapt CI codestyle rustfmt diff --- src/main.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4acb3807..cc82303b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,12 +10,12 @@ use daemonize::Daemonize; #[cfg(unix)] use log::error; use log::{info, trace, LevelFilter}; +#[cfg(target_os = "openbsd")] +use pledge::pledge; #[cfg(windows)] use std::fs; use structopt::StructOpt; use tokio::runtime::Runtime; -#[cfg(target_os = "openbsd")] -use pledge::pledge; #[cfg(feature = "alsa_backend")] mod alsa_mixer; @@ -90,7 +90,11 @@ fn main() -> eyre::Result<()> { // Start with superset of all potentially required promises. // Drop later after CLI arguments and configuration files were parsed. #[cfg(target_os = "openbsd")] - pledge("stdio rpath wpath cpath inet mcast flock chown unix dns proc exec audio", None).unwrap(); + pledge( + "stdio rpath wpath cpath inet mcast flock chown unix dns proc exec audio", + None + ) + .unwrap(); color_eyre::install().wrap_err("Couldn't initialize error reporting")?; @@ -193,7 +197,11 @@ fn main() -> eyre::Result<()> { // --on-song-change-hook aka. "onevent", run via --shell aka. "shell" if internal_config.onevent.is_some() { - pledge("stdio rpath wpath cpath inet mcast unix dns proc exec audio", None).unwrap(); + pledge( + "stdio rpath wpath cpath inet mcast unix dns proc exec audio", + None + ) + .unwrap(); } else { pledge("stdio rpath wpath cpath inet mcast unix dns audio", None).unwrap(); } From b1e82bbadff425611ec15a8c3432468b9f94f966 Mon Sep 17 00:00:00 2001 From: Klemens Nanni Date: Sun, 4 Feb 2024 08:13:38 +0100 Subject: [PATCH 3/3] fixup! Adapt CI codestyle rustfmt diff --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index cc82303b..deb36d88 100644 --- a/src/main.rs +++ b/src/main.rs @@ -92,7 +92,7 @@ fn main() -> eyre::Result<()> { #[cfg(target_os = "openbsd")] pledge( "stdio rpath wpath cpath inet mcast flock chown unix dns proc exec audio", - None + None, ) .unwrap(); @@ -199,7 +199,7 @@ fn main() -> eyre::Result<()> { if internal_config.onevent.is_some() { pledge( "stdio rpath wpath cpath inet mcast unix dns proc exec audio", - None + None, ) .unwrap(); } else {