From d8e9d71f87db912d73345fec2f9fcb61b25231cf Mon Sep 17 00:00:00 2001 From: Ophestra Date: Fri, 28 Feb 2025 15:56:15 +0900 Subject: [PATCH] test/sandbox: check mount outcome Do this at the beginning of the test for early failure. Signed-off-by: Ophestra --- test/configuration.nix | 15 ++++++++ test/sandbox/default.nix | 12 ++++++ test/sandbox/mount.nix | 79 ++++++++++++++++++++++++++++++++++++++++ test/test.py | 32 +++++++++------- 4 files changed, 124 insertions(+), 14 deletions(-) create mode 100644 test/sandbox/default.nix create mode 100644 test/sandbox/mount.nix diff --git a/test/configuration.nix b/test/configuration.nix index 0ac867a..9e8afcb 100644 --- a/test/configuration.nix +++ b/test/configuration.nix @@ -102,6 +102,21 @@ home-manager = _: _: { home.stateVersion = "23.05"; }; apps = [ + { + name = "check-sandbox"; + verbose = true; + share = pkgs.foot; + packages = [ ]; + command = "${pkgs.callPackage ./sandbox { + inherit (config.environment.fortify.package) version; + }}"; + extraPaths = [ + { + src = "/proc/mounts"; + dst = "/.fortify/host-mounts"; + } + ]; + } { name = "ne-foot"; verbose = true; diff --git a/test/sandbox/default.nix b/test/sandbox/default.nix new file mode 100644 index 0000000..fa49844 --- /dev/null +++ b/test/sandbox/default.nix @@ -0,0 +1,12 @@ +{ + writeShellScript, + callPackage, + + version, +}: +writeShellScript "check-sandbox" '' + set -e + ${callPackage ./mount.nix { inherit version; }}/bin/test + + touch /tmp/sandbox-ok +'' diff --git a/test/sandbox/mount.nix b/test/sandbox/mount.nix new file mode 100644 index 0000000..3040791 --- /dev/null +++ b/test/sandbox/mount.nix @@ -0,0 +1,79 @@ +{ + writeText, + buildGoModule, + + version, +}: +let + wantMounts = + let + ent = fsname: dir: type: opts: freq: passno: { + inherit + fsname + dir + type + opts + freq + passno + ; + }; + in + [ + (ent "tmpfs" "/" "tmpfs" "rw,nosuid,nodev,relatime,uid=1000001,gid=1000001" 0 0) + (ent "proc" "/proc" "proc" "rw,nosuid,nodev,noexec,relatime" 0 0) + (ent "tmpfs" "/.fortify" "tmpfs" "rw,nosuid,nodev,relatime,size=4k,mode=755,uid=1000001,gid=1000001" 0 0) + (ent "tmpfs" "/dev" "tmpfs" "rw,nosuid,nodev,relatime,mode=755,uid=1000001,gid=1000001" 0 0) + (ent "devtmpfs" "/dev/null" "devtmpfs" "host_passthrough" 0 0) + (ent "devtmpfs" "/dev/zero" "devtmpfs" "host_passthrough" 0 0) + (ent "devtmpfs" "/dev/full" "devtmpfs" "host_passthrough" 0 0) + (ent "devtmpfs" "/dev/random" "devtmpfs" "host_passthrough" 0 0) + (ent "devtmpfs" "/dev/urandom" "devtmpfs" "host_passthrough" 0 0) + (ent "devtmpfs" "/dev/tty" "devtmpfs" "host_passthrough" 0 0) + (ent "devpts" "/dev/pts" "devpts" "rw,nosuid,noexec,relatime,mode=620,ptmxmode=666" 0 0) + (ent "mqueue" "/dev/mqueue" "mqueue" "rw,relatime" 0 0) + (ent "/dev/disk/by-label/nixos" "/bin" "ext4" "ro,nosuid,nodev,relatime" 0 0) + (ent "/dev/disk/by-label/nixos" "/usr/bin" "ext4" "ro,nosuid,nodev,relatime" 0 0) + (ent "overlay" "/nix/store" "overlay" "ro,nosuid,nodev,relatime,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on" 0 0) + (ent "overlay" "/run/current-system" "overlay" "ro,nosuid,nodev,relatime,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on" 0 0) + (ent "sysfs" "/sys/block" "sysfs" "ro,nosuid,nodev,noexec,relatime" 0 0) + (ent "sysfs" "/sys/bus" "sysfs" "ro,nosuid,nodev,noexec,relatime" 0 0) + (ent "sysfs" "/sys/class" "sysfs" "ro,nosuid,nodev,noexec,relatime" 0 0) + (ent "sysfs" "/sys/dev" "sysfs" "ro,nosuid,nodev,noexec,relatime" 0 0) + (ent "sysfs" "/sys/devices" "sysfs" "ro,nosuid,nodev,noexec,relatime" 0 0) + (ent "overlay" "/run/opengl-driver" "overlay" "ro,nosuid,nodev,relatime,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on" 0 0) + (ent "devtmpfs" "/dev/dri" "devtmpfs" "host_passthrough" 0 0) + (ent "proc" "/.fortify/host-mounts" "proc" "ro,nosuid,nodev,noexec,relatime" 0 0) + (ent "/dev/disk/by-label/nixos" "/.fortify/etc" "ext4" "ro,nosuid,nodev,relatime" 0 0) + (ent "tmpfs" "/run/user" "tmpfs" "rw,nosuid,nodev,relatime,size=1024k,mode=755,uid=1000001,gid=1000001" 0 0) + (ent "tmpfs" "/run/user/65534" "tmpfs" "rw,nosuid,nodev,relatime,size=8192k,mode=755,uid=1000001,gid=1000001" 0 0) + (ent "/dev/disk/by-label/nixos" "/tmp" "ext4" "rw,nosuid,nodev,relatime" 0 0) + (ent "/dev/disk/by-label/nixos" "/var/lib/fortify/u0/a1" "ext4" "rw,nosuid,nodev,relatime" 0 0) + (ent "tmpfs" "/etc/passwd" "tmpfs" "ro,nosuid,nodev,relatime,uid=1000001,gid=1000001" 0 0) + (ent "tmpfs" "/etc/group" "tmpfs" "ro,nosuid,nodev,relatime,uid=1000001,gid=1000001" 0 0) + (ent "/dev/disk/by-label/nixos" "/run/user/65534/wayland-0" "ext4" "ro,nosuid,nodev,relatime" 0 0) + (ent "tmpfs" "/run/user/65534/pulse/native" "tmpfs" "ro,nosuid,nodev,relatime,size=98784k,nr_inodes=24696,mode=700,uid=1000,gid=100" 0 0) + (ent "/dev/disk/by-label/nixos" "/run/user/65534/bus" "ext4" "ro,nosuid,nodev,relatime" 0 0) + (ent "tmpfs" "/var/run/nscd" "tmpfs" "rw,nosuid,nodev,relatime,size=8k,mode=755,uid=1000001,gid=1000001" 0 0) + (ent "overlay" "/.fortify/sbin/fortify" "overlay" "ro,nosuid,nodev,relatime,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on" 0 0) + ]; + + mainFile = writeText "main.go" '' + package main + + import "git.gensokyo.uk/security/fortify/test/sandbox" + + func main() { sandbox.MustAssertMounts("", "/.fortify/host-mounts", "${writeText "want-mounts.json" (builtins.toJSON wantMounts)}") } + ''; +in +buildGoModule { + pname = "check-mounts"; + inherit version; + + src = ../.; + vendorHash = null; + + preBuild = '' + go mod init git.gensokyo.uk/security/fortify/test >& /dev/null + cp ${mainFile} main.go + ''; +} diff --git a/test/test.py b/test/test.py index 12a7e48..5f211d2 100644 --- a/test/test.py +++ b/test/test.py @@ -104,6 +104,10 @@ if denyOutput != "fsu: uid 1001 is not in the fsurc file\n": if denyOutputVerbose != "fsu: uid 1001 is not in the fsurc file\nfortify: *cannot obtain uid from fsu: permission denied\n": raise Exception(f"unexpected deny verbose output:\n{denyOutputVerbose}") +# Check sandbox state: +swaymsg("exec check-sandbox") +machine.wait_for_file("/tmp/fortify.1000/tmpdir/1/sandbox-ok") + # Start fortify permissive defaults outside Wayland session: print(machine.succeed("sudo -u alice -i fortify -v run -a 0 touch /tmp/success-bare")) machine.wait_for_file("/tmp/fortify.1000/tmpdir/0/success-bare") @@ -144,24 +148,24 @@ machine.succeed("pkill -9 mako") # Start app (foot) with Wayland enablement: swaymsg("exec ne-foot") -wait_for_window("u0_a1@machine") +wait_for_window("u0_a2@machine") machine.send_chars("clear; wayland-info && touch /tmp/success-client\n") -machine.wait_for_file("/tmp/fortify.1000/tmpdir/1/success-client") +machine.wait_for_file("/tmp/fortify.1000/tmpdir/2/success-client") collect_state_ui("foot_wayland") check_state("ne-foot", 1) # Verify acl on XDG_RUNTIME_DIR: -print(machine.succeed("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000001")) +print(machine.succeed("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000002")) machine.send_chars("exit\n") machine.wait_until_fails("pgrep foot") # Verify acl cleanup on XDG_RUNTIME_DIR: -machine.wait_until_fails("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000001") +machine.wait_until_fails("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000002") # Start app (foot) with Wayland enablement from a terminal: swaymsg( "exec foot $SHELL -c '(ne-foot) & sleep 1 && fortify show $(fortify ps --short) && touch /tmp/ps-show-ok && cat'") -wait_for_window("u0_a1@machine") +wait_for_window("u0_a2@machine") machine.send_chars("clear; wayland-info && touch /tmp/success-client-term\n") -machine.wait_for_file("/tmp/fortify.1000/tmpdir/1/success-client-term") +machine.wait_for_file("/tmp/fortify.1000/tmpdir/2/success-client-term") machine.wait_for_file("/tmp/ps-show-ok") collect_state_ui("foot_wayland_term") check_state("ne-foot", 1) @@ -172,9 +176,9 @@ machine.wait_until_fails("pgrep foot") # Test PulseAudio (fortify does not support PipeWire yet): swaymsg("exec pa-foot") -wait_for_window("u0_a2@machine") +wait_for_window("u0_a3@machine") machine.send_chars("clear; pactl info && touch /tmp/success-pulse\n") -machine.wait_for_file("/tmp/fortify.1000/tmpdir/2/success-pulse") +machine.wait_for_file("/tmp/fortify.1000/tmpdir/3/success-pulse") collect_state_ui("pulse_wayland") check_state("pa-foot", 9) machine.send_chars("exit\n") @@ -182,9 +186,9 @@ machine.wait_until_fails("pgrep foot") # Test XWayland (foot does not support X): swaymsg("exec x11-alacritty") -wait_for_window("u0_a3@machine") +wait_for_window("u0_a4@machine") machine.send_chars("clear; glinfo && touch /tmp/success-client-x11\n") -machine.wait_for_file("/tmp/fortify.1000/tmpdir/3/success-client-x11") +machine.wait_for_file("/tmp/fortify.1000/tmpdir/4/success-client-x11") collect_state_ui("alacritty_x11") check_state("x11-alacritty", 2) machine.send_chars("exit\n") @@ -192,17 +196,17 @@ machine.wait_until_fails("pgrep alacritty") # Start app (foot) with direct Wayland access: swaymsg("exec da-foot") -wait_for_window("u0_a4@machine") +wait_for_window("u0_a5@machine") machine.send_chars("clear; wayland-info && touch /tmp/success-direct\n") -machine.wait_for_file("/tmp/fortify.1000/tmpdir/4/success-direct") +machine.wait_for_file("/tmp/fortify.1000/tmpdir/5/success-direct") collect_state_ui("foot_direct") check_state("da-foot", 1) # Verify acl on XDG_RUNTIME_DIR: -print(machine.succeed("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000004")) +print(machine.succeed("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000005")) machine.send_chars("exit\n") machine.wait_until_fails("pgrep foot") # Verify acl cleanup on XDG_RUNTIME_DIR: -machine.wait_until_fails("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000004") +machine.wait_until_fails("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000005") # Test syscall filter: print(machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 strace-failure"))