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"