From bce0f512e080df078a2c012c2ade7a44e54faa51 Mon Sep 17 00:00:00 2001 From: Atos Lins Date: Thu, 17 Sep 2026 17:44:26 -0300 Subject: [PATCH 1/2] Clear setgid when hardening Windows VM directories A numeric chmod keeps setuid/setgid on a directory, so a setgid ~/Windows stayed 2700 and failed the exact 700 privacy check, aborting the launch. dockur marks an initially empty /shared setgid (chmod 2777) on first boot, so this hits fresh installs the next time the bind anchors are recreated. --- bin/omarchy-windows-vm | 6 ++++-- test/shell.d/windows-vm-compose-test.sh | 2 ++ test/shell.d/windows-vm-mount-boundary-test.sh | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/bin/omarchy-windows-vm b/bin/omarchy-windows-vm index 6c8204da..c1fbbf9e 100755 --- a/bin/omarchy-windows-vm +++ b/bin/omarchy-windows-vm @@ -580,7 +580,9 @@ prepare_caller_mounts() { # Privacy is an explicit preflight step for both already-pinned sources, not # a side effect halfway through the two-mount transaction. Old umask-022 # installs are hardened together before either Docker-facing anchor changes. - chmod 0700 -- "/proc/$BASHPID/fd/$storage_fd" "/proc/$BASHPID/fd/$shared_fd" || { + # The mode is symbolic because a numeric chmod keeps setuid/setgid on a + # directory, and dockur marks an initially empty /shared setgid (2777). + chmod u=rwx,go=,a-s -- "/proc/$BASHPID/fd/$storage_fd" "/proc/$BASHPID/fd/$shared_fd" || { exec {storage_fd}<&- exec {shared_fd}<&- return 1 @@ -1015,7 +1017,7 @@ prepare_user_mount_sources() { echo "omarchy-windows-vm: storage and shared must be different directories" >&2 return 1 } - chmod 0700 -- "$storage" "$shared" + chmod u=rwx,go=,a-s -- "$storage" "$shared" } storage_space_path() { diff --git a/test/shell.d/windows-vm-compose-test.sh b/test/shell.d/windows-vm-compose-test.sh index 189536f4..620bc55b 100644 --- a/test/shell.d/windows-vm-compose-test.sh +++ b/test/shell.d/windows-vm-compose-test.sh @@ -112,6 +112,8 @@ pass "pkexec target is only the canonical packaged regular file, never a PATH sy reset_case external_shared="$TMPDIR/external-shared" mkdir -m 0755 -p "$HOME/.windows" "$external_shared" "$HOME/.config/windows" +# dockur leaves an initially empty share setgid, which a numeric chmod keeps. +chmod 2777 "$external_shared" ln -s "$external_shared" "$HOME/Windows" touch "$HOME/.windows/existing-disk" "$external_shared/existing-shared-file" LEGACY_COMPOSE_FILE="$HOME/.config/windows/docker-compose.yml" diff --git a/test/shell.d/windows-vm-mount-boundary-test.sh b/test/shell.d/windows-vm-mount-boundary-test.sh index 13623d81..531e2ced 100644 --- a/test/shell.d/windows-vm-mount-boundary-test.sh +++ b/test/shell.d/windows-vm-mount-boundary-test.sh @@ -143,6 +143,23 @@ chown root:root "$USERS_DIR" mounts_ready || fail "restored production boundaries were rejected" pass "root rejects wrong-owned and group-writable production mount boundaries without mutation" +# dockur marks an empty /shared setgid (chmod 2777) on first boot, and a numeric +# chmod keeps setuid/setgid on directories. Hardening has to clear the special +# bits behind live anchors and again when the binds are recreated after reboot. +chmod 2777 /home/shared-target +chmod 6755 /home/storage-target +with_vm_lock prepare_caller_mounts || fail "root rejected setgid sources behind live anchors" +mounts_ready || fail "final guard rejected re-hardened setgid sources" +umount "$EXPECTED_SHARED" +umount "$EXPECTED_STORAGE" +chmod 2777 /home/shared-target +chmod 6755 /home/storage-target +with_vm_lock prepare_caller_mounts || fail "root could not rebind setgid sources" +[[ $(command stat -Lc '%u:%a' "$EXPECTED_STORAGE") == 1000:700 && + $(command stat -Lc '%u:%a' "$EXPECTED_SHARED") == 1000:700 ]] || fail "setgid sources were not hardened to 0700" +mounts_ready || fail "final guard rejected rebound setgid sources" +pass "hardening clears the setuid/setgid bits a numeric chmod keeps on directories" + expected_space=$(command df -P -- /home/storage-target | awk 'NR==2 {print int($4/1024/1024)}') actual_space=$(available_storage_gb) [[ $actual_space == "$expected_space" ]] || fail "disk-space helper did not measure the storage target filesystem" From 45fd98f34408e02a0c07da185aeb40e5100246bc Mon Sep 17 00:00:00 2001 From: Atos Lins Date: Fri, 18 Sep 2026 07:49:06 -0300 Subject: [PATCH 2/2] Report why Windows VM directory hardening failed The mode check returned 1 without any output, so a launch from the app menu died after the polkit prompt with nothing to diagnose. Name each source directory and the mode it was left with. --- bin/omarchy-windows-vm | 1 + test/shell.d/windows-vm-mount-boundary-test.sh | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/bin/omarchy-windows-vm b/bin/omarchy-windows-vm index c1fbbf9e..96a8d575 100755 --- a/bin/omarchy-windows-vm +++ b/bin/omarchy-windows-vm @@ -592,6 +592,7 @@ prepare_caller_mounts() { if [[ $storage_mode != 700 || $shared_mode != 700 ]]; then exec {storage_fd}<&- exec {shared_fd}<&- + echo "omarchy-windows-vm: could not make the VM data directories private: $LEGACY_STORAGE is mode ${storage_mode:-unknown}, $LEGACY_SHARED is mode ${shared_mode:-unknown} (expected 700)" >&2 return 1 fi diff --git a/test/shell.d/windows-vm-mount-boundary-test.sh b/test/shell.d/windows-vm-mount-boundary-test.sh index 531e2ced..591dec41 100644 --- a/test/shell.d/windows-vm-mount-boundary-test.sh +++ b/test/shell.d/windows-vm-mount-boundary-test.sh @@ -160,6 +160,16 @@ with_vm_lock prepare_caller_mounts || fail "root could not rebind setgid sources mounts_ready || fail "final guard rejected rebound setgid sources" pass "hardening clears the setuid/setgid bits a numeric chmod keeps on directories" +# When hardening still leaves a source non-private, the rejection names the +# directory and its mode instead of failing silently. +chmod() { command chmod "$@" && command chmod g+s /home/shared-target; } +hardening_error=$(with_vm_lock prepare_caller_mounts 2>&1) && fail "root accepted a source that stayed setgid after hardening" +unset -f chmod +[[ $hardening_error == *"/home/alice/Windows is mode 2700"* ]] || fail "setgid rejection did not name the directory and mode: $hardening_error" +chmod g-s /home/shared-target +mounts_ready || fail "verified anchors were disturbed by the rejected hardening" +pass "a source that cannot be made private is rejected with its path and mode" + expected_space=$(command df -P -- /home/storage-target | awk 'NR==2 {print int($4/1024/1024)}') actual_space=$(available_storage_gb) [[ $actual_space == "$expected_space" ]] || fail "disk-space helper did not measure the storage target filesystem"