diff --git a/test/shell.d/windows-vm-compose-test.sh b/test/shell.d/windows-vm-compose-test.sh index 77c298a8..0495e1e0 100644 --- a/test/shell.d/windows-vm-compose-test.sh +++ b/test/shell.d/windows-vm-compose-test.sh @@ -183,6 +183,29 @@ resolve_caller [[ $(stat -Lc '%d:%i' "$EXPECTED_STORAGE") == $(stat -Lc '%d:%i' "$external_storage") ]] || fail "symlink target not pinned" pass "legitimate caller-owned symlinks remain in place" +# Reproduce the original post-validation race at the last possible moment: +# replace the familiar shared path with / only after the final guard returns, +# inside the mocked Docker Compose invocation. Compose must still consume the +# protected anchor bound to the inode that was validated earlier. +raced_shared="$HOME/Windows.before-race" +shared_id_before_race=$(stat -Lc '%d:%i' "$external_shared2") +race_ran=0 +dc() { + [[ $1 == up && ${2:-} == -d ]] || return 1 + mv -T -- "$HOME/Windows" "$raced_shared" + ln -s / "$HOME/Windows" + race_ran=1 + [[ $(get_mount_source /shared) == "$EXPECTED_SHARED" ]] || return 1 + [[ $(stat -Lc '%d:%i' "$EXPECTED_SHARED") == "$shared_id_before_race" ]] || return 1 +} +__priv_up || fail "post-validation home-path swap changed the Docker mount source" +(( race_ran == 1 )) || fail "post-validation race hook did not run" +[[ -L $HOME/Windows && $(readlink "$HOME/Windows") == / ]] || fail "race did not replace the familiar shared path" +rm "$HOME/Windows" +mv -T -- "$raced_shared" "$HOME/Windows" +unset -f dc +pass "a post-validation path swap cannot redirect Docker away from the pinned shared inode" + # Same-inode sources fail before mounting and close both descriptors. reset_case same="$TMPDIR/same-source" diff --git a/test/shell.d/windows-vm-mount-boundary-test.sh b/test/shell.d/windows-vm-mount-boundary-test.sh index 509143b2..a49e5652 100644 --- a/test/shell.d/windows-vm-mount-boundary-test.sh +++ b/test/shell.d/windows-vm-mount-boundary-test.sh @@ -119,6 +119,26 @@ if setpriv --reuid=1001 --regid=1001 --clear-groups cat "$EXPECTED_SHARED/shared fi pass "cross-filesystem symlink sources bind by identity and migrated 0700 leaves deny another account" +# Existing production boundary components are never repaired in place when +# their ownership or write permissions are unsafe. Both the preparation path +# and the final pre-Docker guard must fail closed without disturbing the binds. +chmod 0731 "$MOUNT_ROOT" +with_vm_lock prepare_caller_mounts 2>/dev/null && fail "root repaired a group-writable mount boundary instead of rejecting it" +mounts_ready 2>/dev/null && fail "final guard accepted a group-writable mount boundary" +[[ $(command stat -Lc '%a' "$MOUNT_ROOT") == 731 ]] || fail "rejection unexpectedly changed the writable boundary" +chmod 0711 "$MOUNT_ROOT" + +chown 1000:1000 "$USERS_DIR" +with_vm_lock prepare_caller_mounts 2>/dev/null && fail "root repaired a caller-owned mount boundary instead of rejecting it" +mounts_ready 2>/dev/null && fail "final guard accepted a caller-owned mount boundary" +[[ $(command stat -Lc '%u' "$USERS_DIR") == 1000 ]] || fail "rejection unexpectedly changed the boundary owner" +chown root:root "$USERS_DIR" + +[[ $(mount_layer_count "$EXPECTED_STORAGE") == 1 && + $(mount_layer_count "$EXPECTED_SHARED") == 1 ]] || fail "boundary rejection changed the verified mount pair" +mounts_ready || fail "restored production boundaries were rejected" +pass "root rejects wrong-owned and group-writable production mount boundaries without mutation" + 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"