use std::env;
use landlock::{
Access, AccessFs, AccessNet, Ruleset, RulesetAttr, RulesetCreatedAttr, RulesetStatus, Scope,
path_beneath_rules
};
use tracing::{trace, info, error};
use crate::DashMpdError;
use crate::fetch::DashDownloader;
pub fn restrict_thread(downloader: &DashDownloader) -> Result<(), DashMpdError> {
let mut ro_dirs = Vec::new();
ro_dirs.push(String::from("/etc"));
ro_dirs.push(String::from("/run/systemd/resolv.conf"));
ro_dirs.push(String::from("/run/NetworkManager/resolv.conf"));
ro_dirs.push(String::from("/run/conman/resolv.conf"));
ro_dirs.push(String::from("/run/netconfig/resolv.conf"));
ro_dirs.push(String::from("/mnt/wsl/resolv.conf"));
ro_dirs.push(String::from("/var/lib/libvirt/dnsmasq/"));
ro_dirs.push(String::from("/etc/mdns.allow"));
ro_dirs.push(String::from("/etc/nss_mdns.conf"));
ro_dirs.push(String::from("/usr/share/ssl/openssl.cnf"));
ro_dirs.push(String::from("/usr/share/ca-certificates/"));
ro_dirs.push(String::from("/var/lib/ca-certificates/"));
ro_dirs.push(String::from("/usr/local/share/ca-certificates/"));
ro_dirs.push(String::from("/usr/share/ssl/certs/ca-bundle.crt"));
ro_dirs.push(String::from("/var/lib/acme/certs/"));
ro_dirs.push(String::from("/dev/random"));
ro_dirs.push(String::from("/dev/urandom"));
ro_dirs.push(String::from("/proc/meminfo"));
ro_dirs.push(String::from("/proc/stat"));
ro_dirs.push(String::from("/proc/cpuinfo"));
ro_dirs.push(String::from("/proc/cmdline"));
ro_dirs.push(String::from("/proc/self/cmdline"));
ro_dirs.push(String::from("/proc/self/ns/"));
ro_dirs.push(String::from("/proc/sys/kernel/version"));
ro_dirs.push(String::from("/proc/sys/vm/overcommit_memory"));
ro_dirs.push(String::from("/sys/devices/system/cpu/"));
if let Some(config_dir) = dir_spec::config_home() {
let config_str = config_dir.into_os_string();
ro_dirs.push(String::from(config_str.to_string_lossy()));
}
if let Some(data_dir) = dir_spec::data_home() {
let data_str = data_dir.into_os_string();
ro_dirs.push(String::from(data_str.to_string_lossy()));
}
let mut rw_dirs = Vec::new();
if let Some(data_dir) = dir_spec::data_home() {
let containers_str = data_dir.join(".local/share/containers").into_os_string();
rw_dirs.push(String::from(containers_str.to_string_lossy()));
}
if let Some(runtime_dir) = dir_spec::runtime() {
let containers_str = runtime_dir.join("containers").into_os_string();
rw_dirs.push(String::from(containers_str.to_string_lossy()));
}
rw_dirs.push(String::from("/run/avahi-daemon/socket"));
rw_dirs.push(String::from("/run/nscd/socket"));
rw_dirs.push(String::from("/run/.nscd_socket"));
rw_dirs.push(String::from("/run/mdnsd"));
rw_dirs.push(String::from("/var/cache"));
rw_dirs.push(String::from("/var/run"));
rw_dirs.push(String::from("/var/tmp"));
rw_dirs.push(String::from("/dev/null"));
rw_dirs.push(String::from("/dev/zero"));
rw_dirs.push(String::from("/dev/full"));
let tmpdir = env::var("TMPDIR").unwrap_or_else(|_| String::from("/tmp"));
rw_dirs.push(tmpdir);
if let Some(runtime_dir) = dir_spec::runtime() {
let runtime_str = runtime_dir.into_os_string();
let runtime_string = String::from(runtime_str.to_string_lossy());
if !rw_dirs.contains(&runtime_string) {
rw_dirs.push(runtime_string);
}
}
if let Some(output_path) = downloader.output_path.clone() {
let os_str = output_path.as_os_str().to_string_lossy();
rw_dirs.push(String::from(os_str));
}
let cwd = env::current_dir()
.map_err(|_| DashMpdError::Other(String::from("reading cwd")))?
.into_os_string();
rw_dirs.push(String::from(cwd.to_string_lossy()));
trace!("Sandbox: allowing r+w filesystem access to directories {rw_dirs:?}");
let mut rx_dirs = Vec::new();
rx_dirs.push(String::from("/usr"));
rx_dirs.push(String::from("/lib"));
if let Some(paths) = env::var_os("PATH") {
for path in env::split_paths(&paths) {
let path_str = path.into_os_string();
rx_dirs.push(String::from(path_str.to_string_lossy()));
}
}
if let Some(paths) = env::var_os("LD_LIBRARY_PATH") {
for path in env::split_paths(&paths) {
let path_str = path.into_os_string();
rx_dirs.push(String::from(path_str.to_string_lossy()));
}
}
rx_dirs.push(downloader.ffmpeg_location.clone());
rx_dirs.push(downloader.vlc_location.clone());
rx_dirs.push(downloader.mkvmerge_location.clone());
rx_dirs.push(downloader.mp4box_location.clone());
rx_dirs.push(downloader.mp4decrypt_location.clone());
rx_dirs.push(downloader.shaka_packager_location.clone());
trace!("Sandbox: allowing r+x filesystem access to directories {rx_dirs:?}");
trace!("Sandbox: allowing readonly filesystem access to {ro_dirs:?}");
let fs_ro = AccessFs::from_read(landlock::ABI::V2) & !AccessFs::Execute;
let fs_rx = AccessFs::from_read(landlock::ABI::V2) | AccessFs::Execute;
let fs_rw = AccessFs::from_all(landlock::ABI::V2) & !AccessFs::Execute;
let status = Ruleset::default()
.handle_access(AccessFs::from_all(landlock::ABI::V2))
.map_err(|_| DashMpdError::Other(String::from("restricting filesystem access")))?
.handle_access(AccessNet::BindTcp)
.map_err(|_| DashMpdError::Other(String::from("restricting network access")))?
.scope(Scope::from_all(landlock::ABI::V6))
.map_err(|_| DashMpdError::Other(String::from("restricting signal scope")))?
.create()
.map_err(|_| DashMpdError::Other(String::from("creating Landlock ruleset")))?
.add_rules(path_beneath_rules(ro_dirs, fs_ro))
.map_err(|_| DashMpdError::Other(String::from("allowing readonly access to /etc and /dev/zero")))?
.add_rules(path_beneath_rules(&["/dev/null"], AccessFs::from_all(landlock::ABI::V2)))
.map_err(|_| DashMpdError::Other(String::from("allowing access to /dev/null")))?
.add_rules(path_beneath_rules(rx_dirs, fs_rx))
.map_err(|_| DashMpdError::Other(String::from("allowing limited read-exec access to binaries")))?
.add_rules(path_beneath_rules(rw_dirs, fs_rw))
.map_err(|_| DashMpdError::Other(String::from("allowing write access to output directories")))?
.restrict_self()
.map_err(|_| DashMpdError::Other(String::from("enforcing landlock sandboxing ruleset")))?;
match status.ruleset {
RulesetStatus::FullyEnforced => info!(" ✓ Sandboxing enabled."),
RulesetStatus::PartiallyEnforced => info!("Partially sandboxed."),
RulesetStatus::NotEnforced => error!("Not sandboxed! Please update your Linux kernel."),
}
Ok(())
}