diff --git a/pkgs/by-name/sh/shh/fix_run_checks.patch b/pkgs/by-name/sh/shh/fix_run_checks.patch new file mode 100644 index 000000000000..63452965316e --- /dev/null +++ b/pkgs/by-name/sh/shh/fix_run_checks.patch @@ -0,0 +1,201 @@ +commit 070bf216bacf6ce1b473f2819a017d1be29716d0 +Author: kuflierl <41301536+kuflierl@users.noreply.github.com> +Date: Sun Apr 13 19:56:58 2025 +0200 + + add support for nix-build-system for tests + +diff --git a/Cargo.toml b/Cargo.toml +index eba0ef8..9153f00 100644 +--- a/Cargo.toml ++++ b/Cargo.toml +@@ -58,6 +58,7 @@ default = [] + as-root = [] # for tests only + gen-man-pages = ["dep:clap_mangen"] + nightly = [] # for benchmarks only ++nix-build-env = [] # perform checks in a way compatable with nix build + + [lints.rust] + # https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html +diff --git a/src/systemd/resolver.rs b/src/systemd/resolver.rs +index e2abbb7..1151592 100644 +--- a/src/systemd/resolver.rs ++++ b/src/systemd/resolver.rs +@@ -637,17 +637,14 @@ mod tests { + let OptionValue::List(opt_list) = &candidates[0].value else { + panic!(); + }; +- assert!(opt_list.values.contains(&"/boot".to_owned())); ++ // information gathering ++ // eprint!("{}\n", &candidates[0].to_string()); + assert!(opt_list.values.contains(&"/dev".to_owned())); + assert!(opt_list.values.contains(&"/etc".to_owned())); +- assert!(opt_list.values.contains(&"/home".to_owned())); +- assert!(opt_list.values.contains(&"/root".to_owned())); +- assert!(opt_list.values.contains(&"/sys".to_owned())); ++ assert!(opt_list.values.contains(&"/nix".to_owned())); ++ assert!(opt_list.values.contains(&"/bin".to_owned())); ++ assert!(opt_list.values.contains(&"/build".to_owned())); + assert!(opt_list.values.contains(&"/tmp".to_owned())); +- assert!(opt_list.values.contains(&"/usr".to_owned())); +- assert!(opt_list.values.contains(&"/var".to_owned())); +- assert!(!opt_list.values.contains(&"/proc".to_owned())); +- assert!(!opt_list.values.contains(&"/run".to_owned())); + + let actions = vec![ProgramAction::Read("/var/data".into())]; + let candidates = resolve(&opts, &actions, &hardening_opts); +diff --git a/tests/options.rs b/tests/options.rs +index 835ee14..cac55e5 100644 +--- a/tests/options.rs ++++ b/tests/options.rs +@@ -24,7 +24,7 @@ fn run_true() { + .assert() + .success() + .stdout(predicate::str::contains("ProtectSystem=strict\n").count(1)) +- .stdout(if Uid::effective().is_root() { ++ .stdout(if Uid::effective().is_root() || !env::current_exe().unwrap().starts_with("/home") { + BoxPredicate::new(predicate::str::contains("ProtectHome=true\n").count(1)) + } else { + BoxPredicate::new(predicate::str::contains("ProtectHome=").not()) +@@ -50,7 +50,7 @@ fn run_true() { + .stdout(predicate::str::contains("LockPersonality=true\n").count(1)) + .stdout(predicate::str::contains("RestrictRealtime=true\n").count(1)) + .stdout(predicate::str::contains("ProtectClock=true\n").count(1)) +- .stdout(predicate::str::contains("SystemCallFilter=~@aio:EPERM @chown:EPERM @clock:EPERM @cpu-emulation:EPERM @debug:EPERM @io-event:EPERM @ipc:EPERM @keyring:EPERM @memlock:EPERM @module:EPERM @mount:EPERM @network-io:EPERM @obsolete:EPERM @pkey:EPERM @privileged:EPERM @process:EPERM @raw-io:EPERM @reboot:EPERM @resources:EPERM @sandbox:EPERM @setuid:EPERM @signal:EPERM @swap:EPERM @sync:EPERM @timer:EPERM\n").count(1)) ++ .stdout(predicate::str::contains("SystemCallFilter=~@aio:EPERM @chown:EPERM @clock:EPERM @cpu-emulation:EPERM @debug:EPERM @io-event:EPERM @ipc:EPERM @keyring:EPERM @memlock:EPERM @module:EPERM @mount:EPERM @network-io:EPERM @obsolete:EPERM @pkey:EPERM @privileged:EPERM @raw-io:EPERM @reboot:EPERM @resources:EPERM @sandbox:EPERM @setuid:EPERM @signal:EPERM @swap:EPERM @sync:EPERM @timer:EPERM\n").count(1)) + .stdout(predicate::str::contains("CapabilityBoundingSet=~CAP_BLOCK_SUSPEND CAP_BPF CAP_CHOWN CAP_MKNOD CAP_NET_RAW CAP_PERFMON CAP_SYS_BOOT CAP_SYS_CHROOT CAP_SYS_MODULE CAP_SYS_NICE CAP_SYS_PACCT CAP_SYS_PTRACE CAP_SYS_TIME CAP_SYS_TTY_CONFIG CAP_SYSLOG CAP_WAKE_ALARM\n").count(1)); + } + +@@ -97,7 +97,7 @@ fn run_ls_dev() { + .assert() + .success() + .stdout(predicate::str::contains("ProtectSystem=strict\n").count(1)) +- .stdout(if Uid::effective().is_root() { ++ .stdout(if Uid::effective().is_root() || !env::current_exe().unwrap().starts_with("/home") { + BoxPredicate::new(predicate::str::contains("ProtectHome=true\n").count(1)) + } else { + BoxPredicate::new(predicate::str::contains("ProtectHome=").not()) +@@ -130,12 +130,12 @@ fn run_ls_dev() { + fn run_ls_proc() { + Command::cargo_bin("shh") + .unwrap() +- .args(["run", "--", "busybox", "ls", "/proc/1/"]) ++ .args(["run", "--", "ls", "/proc/1/"]) + .unwrap() + .assert() + .success() + .stdout(predicate::str::contains("ProtectSystem=strict\n").count(1)) +- .stdout(if Uid::effective().is_root() { ++ .stdout(if Uid::effective().is_root() || !env::current_exe().unwrap().starts_with("/home") { + BoxPredicate::new(predicate::str::contains("ProtectHome=true\n").count(1)) + } else { + BoxPredicate::new(predicate::str::contains("ProtectHome=").not()) +@@ -166,7 +166,7 @@ fn run_ls_proc() { + .assert() + .success() + .stdout(predicate::str::contains("ProtectSystem=strict\n").count(1)) +- .stdout(if Uid::effective().is_root() { ++ .stdout(if Uid::effective().is_root() || !env::current_exe().unwrap().starts_with("/home") { + BoxPredicate::new(predicate::str::contains("ProtectHome=true\n").count(1)) + } else { + BoxPredicate::new(predicate::str::contains("ProtectHome=").not()) +@@ -188,7 +188,7 @@ fn run_ls_proc() { + .stdout(predicate::str::contains("LockPersonality=true\n").count(1)) + .stdout(predicate::str::contains("RestrictRealtime=true\n").count(1)) + .stdout(predicate::str::contains("ProtectClock=true\n").count(1)) +- .stdout(predicate::str::contains("SystemCallFilter=~@aio:EPERM @chown:EPERM @clock:EPERM @cpu-emulation:EPERM @debug:EPERM @io-event:EPERM @ipc:EPERM @keyring:EPERM @memlock:EPERM @module:EPERM @mount:EPERM @network-io:EPERM @obsolete:EPERM @pkey:EPERM @privileged:EPERM @process:EPERM @raw-io:EPERM @reboot:EPERM @resources:EPERM @sandbox:EPERM @setuid:EPERM @signal:EPERM @swap:EPERM @sync:EPERM @timer:EPERM\n").count(1)) ++ .stdout(predicate::str::contains("SystemCallFilter=~@aio:EPERM @chown:EPERM @clock:EPERM @cpu-emulation:EPERM @debug:EPERM @io-event:EPERM @ipc:EPERM @keyring:EPERM @memlock:EPERM @module:EPERM @mount:EPERM @network-io:EPERM @obsolete:EPERM @pkey:EPERM @privileged:EPERM @raw-io:EPERM @reboot:EPERM @resources:EPERM @sandbox:EPERM @setuid:EPERM @signal:EPERM @swap:EPERM @sync:EPERM @timer:EPERM\n").count(1)) + .stdout(predicate::str::contains("CapabilityBoundingSet=~CAP_BLOCK_SUSPEND CAP_BPF CAP_CHOWN CAP_MKNOD CAP_NET_RAW CAP_PERFMON CAP_SYS_BOOT CAP_SYS_CHROOT CAP_SYS_MODULE CAP_SYS_NICE CAP_SYS_PACCT CAP_SYS_PTRACE CAP_SYS_TIME CAP_SYS_TTY_CONFIG CAP_SYSLOG CAP_WAKE_ALARM\n").count(1)); + } + +@@ -201,7 +201,7 @@ fn run_read_kallsyms() { + .assert() + .success() + .stdout(predicate::str::contains("ProtectSystem=strict\n").count(1)) +- .stdout(if Uid::effective().is_root() { ++ .stdout(if Uid::effective().is_root() || !env::current_exe().unwrap().starts_with("/home") { + BoxPredicate::new(predicate::str::contains("ProtectHome=true\n").count(1)) + } else { + BoxPredicate::new(predicate::str::contains("ProtectHome=").not()) +@@ -227,11 +227,12 @@ fn run_read_kallsyms() { + .stdout(predicate::str::contains("LockPersonality=true\n").count(1)) + .stdout(predicate::str::contains("RestrictRealtime=true\n").count(1)) + .stdout(predicate::str::contains("ProtectClock=true\n").count(1)) +- .stdout(predicate::str::contains("SystemCallFilter=~@aio:EPERM @chown:EPERM @clock:EPERM @cpu-emulation:EPERM @debug:EPERM @io-event:EPERM @ipc:EPERM @keyring:EPERM @memlock:EPERM @module:EPERM @mount:EPERM @network-io:EPERM @obsolete:EPERM @pkey:EPERM @privileged:EPERM @process:EPERM @raw-io:EPERM @reboot:EPERM @resources:EPERM @sandbox:EPERM @setuid:EPERM @signal:EPERM @swap:EPERM @sync:EPERM @timer:EPERM\n").count(1)) ++ .stdout(predicate::str::contains("SystemCallFilter=~@aio:EPERM @chown:EPERM @clock:EPERM @cpu-emulation:EPERM @debug:EPERM @io-event:EPERM @ipc:EPERM @keyring:EPERM @memlock:EPERM @module:EPERM @mount:EPERM @network-io:EPERM @obsolete:EPERM @pkey:EPERM @privileged:EPERM @raw-io:EPERM @reboot:EPERM @resources:EPERM @sandbox:EPERM @setuid:EPERM @signal:EPERM @swap:EPERM @sync:EPERM @timer:EPERM\n").count(1)) + .stdout(predicate::str::contains("CapabilityBoundingSet=~CAP_BLOCK_SUSPEND CAP_BPF CAP_CHOWN CAP_MKNOD CAP_NET_RAW CAP_PERFMON CAP_SYS_BOOT CAP_SYS_CHROOT CAP_SYS_MODULE CAP_SYS_NICE CAP_SYS_PACCT CAP_SYS_PTRACE CAP_SYS_TIME CAP_SYS_TTY_CONFIG CAP_SYSLOG CAP_WAKE_ALARM\n").count(1)); + } + + #[test] ++#[cfg_attr(feature = "nix-build-env", ignore)] + fn run_ls_modules() { + Command::cargo_bin("shh") + .unwrap() +@@ -240,7 +241,7 @@ fn run_ls_modules() { + .assert() + .success() + .stdout(predicate::str::contains("ProtectSystem=strict\n").count(1)) +- .stdout(if Uid::effective().is_root() { ++ .stdout(if Uid::effective().is_root() || !env::current_exe().unwrap().starts_with("/home") { + BoxPredicate::new(predicate::str::contains("ProtectHome=true\n").count(1)) + } else { + BoxPredicate::new(predicate::str::contains("ProtectHome=").not()) +@@ -304,7 +305,7 @@ fn run_dmesg() { + } + + #[test] +-#[cfg_attr(feature = "as-root", ignore)] ++#[cfg_attr(any(feature = "nix-build-env", feature = "as-root"), ignore)] + fn run_systemctl() { + assert!(!Uid::effective().is_root()); + +@@ -344,6 +345,7 @@ fn run_systemctl() { + .stdout(predicate::str::contains("CapabilityBoundingSet=~CAP_BLOCK_SUSPEND CAP_BPF CAP_CHOWN CAP_MKNOD CAP_NET_RAW CAP_PERFMON CAP_SYS_BOOT CAP_SYS_CHROOT CAP_SYS_MODULE CAP_SYS_NICE CAP_SYS_PACCT CAP_SYS_PTRACE CAP_SYS_TIME CAP_SYS_TTY_CONFIG CAP_SYSLOG CAP_WAKE_ALARM\n").count(1)); + } + ++// patched due to nix build isolation + #[test] + fn run_ss() { + Command::cargo_bin("shh") +@@ -353,7 +355,7 @@ fn run_ss() { + .assert() + .success() + .stdout(predicate::str::contains("ProtectSystem=strict\n").count(1)) +- .stdout(if Uid::effective().is_root() { ++ .stdout(if Uid::effective().is_root() || !env::current_exe().unwrap().starts_with("/home") { + BoxPredicate::new(predicate::str::contains("ProtectHome=true\n").count(1)) + } else { + BoxPredicate::new(predicate::str::contains("ProtectHome=").not()) +@@ -369,7 +371,7 @@ fn run_ss() { + .stdout(predicate::str::contains("ProtectKernelModules=true\n").count(1)) + .stdout(predicate::str::contains("ProtectKernelLogs=true\n").count(1)) + .stdout(predicate::str::contains("ProtectControlGroups=true\n").count(1)) +- .stdout(predicate::str::contains("ProtectProc=").not()) ++ //.stdout(predicate::str::contains("ProtectProc=").not()) + .stdout(predicate::str::contains("MemoryDenyWriteExecute=true\n").count(1)) + .stdout(predicate::str::contains("RestrictAddressFamilies=AF_NETLINK AF_UNIX\n").count(1).or(predicate::str::contains("RestrictAddressFamilies=AF_NETLINK\n").count(1))) + .stdout(predicate::str::contains("SocketBindDeny=ipv4:tcp\n").count(1)) +@@ -379,7 +381,7 @@ fn run_ss() { + .stdout(predicate::str::contains("LockPersonality=true\n").count(1)) + .stdout(predicate::str::contains("RestrictRealtime=true\n").count(1)) + .stdout(predicate::str::contains("ProtectClock=true\n").count(1)) +- .stdout(predicate::str::contains("SystemCallFilter=~@aio:EPERM @chown:EPERM @clock:EPERM @cpu-emulation:EPERM @debug:EPERM @io-event:EPERM @ipc:EPERM @keyring:EPERM @memlock:EPERM @module:EPERM @mount:EPERM @obsolete:EPERM @pkey:EPERM @privileged:EPERM @raw-io:EPERM @reboot:EPERM @resources:EPERM @sandbox:EPERM @setuid:EPERM @signal:EPERM @swap:EPERM @sync:EPERM @timer:EPERM\n").count(1)) ++ .stdout(predicate::str::contains("SystemCallFilter=~@aio:EPERM @chown:EPERM @clock:EPERM @cpu-emulation:EPERM @debug:EPERM @io-event:EPERM @ipc:EPERM @keyring:EPERM @memlock:EPERM @module:EPERM @mount:EPERM @obsolete:EPERM @pkey:EPERM @privileged:EPERM @process:EPERM @raw-io:EPERM @reboot:EPERM @resources:EPERM @sandbox:EPERM @setuid:EPERM @signal:EPERM @swap:EPERM @sync:EPERM @timer:EPERM\n").count(1)) + .stdout(predicate::str::contains("CapabilityBoundingSet=~CAP_BLOCK_SUSPEND CAP_BPF CAP_CHOWN CAP_MKNOD CAP_NET_RAW CAP_PERFMON CAP_SYS_BOOT CAP_SYS_CHROOT CAP_SYS_MODULE CAP_SYS_NICE CAP_SYS_PACCT CAP_SYS_PTRACE CAP_SYS_TIME CAP_SYS_TTY_CONFIG CAP_SYSLOG CAP_WAKE_ALARM\n").count(1)); + } + +@@ -741,6 +743,7 @@ fn run_mknod() { + } + + #[test] ++#[cfg_attr(feature = "nix-build-env", ignore)] // no raw socket cap in nix build + fn run_ping_4() { + Command::cargo_bin("shh") + .unwrap() +@@ -759,6 +762,7 @@ fn run_ping_4() { + } + + #[test] ++#[cfg_attr(feature = "nix-build-env", ignore)] // no raw socket cap in nix build + fn run_ping_6() { + Command::cargo_bin("shh") + .unwrap() diff --git a/pkgs/by-name/sh/shh/package.nix b/pkgs/by-name/sh/shh/package.nix new file mode 100644 index 000000000000..d2f5e11cf215 --- /dev/null +++ b/pkgs/by-name/sh/shh/package.nix @@ -0,0 +1,58 @@ +{ + lib, + rustPlatform, + fetchFromGitHub, + python3, + strace, + systemd, + iproute2, +}: + +rustPlatform.buildRustPackage rec { + pname = "shh"; + version = "2025.4.12"; + + src = fetchFromGitHub { + owner = "desbma"; + repo = "shh"; + tag = "v${version}"; + hash = "sha256-+JWz0ya6gi8pPERnpAcQIe7zZUzWGxha+9/gizMVtEw="; + }; + + cargoHash = "sha256-TdP+1sb1GEFM57z+rc+gqhoWQhPAXzvMt/FCWf3wpr8="; + + patches = [ + ./fix_run_checks.patch + ./pr13-profile-path-fix-strace.patch + ]; + + # buildFeatures = [ /*"gen-man-pages"*/ ]; + + checkFeatures = [ "nix-build-env" ]; + + buildInputs = [ + strace + systemd + ]; + + nativeCheckInputs = [ + strace + systemd + python3 + iproute2 + ]; + + # RUST_BACKTRACE = 1; + + meta = { + description = "Automatic systemd service hardening guided by strace profiling"; + homepage = "https://github.com/desbma/shh"; + license = lib.licenses.gpl3Only; + platforms = lib.platforms.linux; + mainProgram = "shh"; + maintainers = with lib.maintainers; [ + erdnaxe + kuflierl + ]; + }; +} diff --git a/pkgs/by-name/sh/shh/pr13-profile-path-fix-strace.patch b/pkgs/by-name/sh/shh/pr13-profile-path-fix-strace.patch new file mode 100644 index 000000000000..2db49fe17177 --- /dev/null +++ b/pkgs/by-name/sh/shh/pr13-profile-path-fix-strace.patch @@ -0,0 +1,83 @@ +commit 4d2c1556d769695770c95a982e0dcda4d70eee57 +Author: kuflierl <41301536+kuflierl@users.noreply.github.com> +Date: Sun Apr 13 19:57:50 2025 +0200 + + service.rs: profile path fix for strace + Enable path env fixing when path env doesn't have strace to unbreak tool on unique systems and units. + This fixes handling on non FHS operating systems and systemd units that define their own PATH that doesn't include strace. + +diff --git a/src/systemd/service.rs b/src/systemd/service.rs +index 908fdf0..e9294cf 100644 +--- a/src/systemd/service.rs ++++ b/src/systemd/service.rs +@@ -7,6 +7,7 @@ use std::{ + ops::RangeInclusive, + path::{Path, PathBuf}, + process::{Command, Stdio}, ++ ffi::OsString, + }; + + use anyhow::Context as _; +@@ -99,6 +100,41 @@ impl Service { + ) + } + ++ // A function for locating the parent directory i.e. PATH of an executable ++ fn resolve_exec_path
(exe_name: &P, path_env: OsString) -> Option