Files
smol-machines--smolvm/tests/test_machine_image_proc_storage.sh
2026-07-13 13:13:07 +08:00

146 lines
6.0 KiB
Bash
Executable File

#!/bin/bash
#
# Image-Backed /proc and /storage Parity Tests
#
# Regression coverage for two asymmetries between bare-VM and --image machines:
#
# 1. --image froze a chunk of /proc read-only (runc's readonlyPaths), so
# docker-in-VM could not write /proc/sys/net/ipv4/ip_forward.
# 2. --image did not expose the per-VM /storage disk inside the container, so
# the docker-in-VM bind-mount pattern
# (`mount --bind /storage/docker /var/lib/docker`) broke.
#
# Both are fixed by treating a privileged (default) image machine like a bare
# VM: /proc is unrestricted and /storage is bind-mounted into the container.
# An unprivileged image machine keeps the full runc hardening.
#
# Part of the smolvm test suite. Run with:
# ./tests/test_machine_image_proc_storage.sh
#
source "$(dirname "$0")/common.sh"
init_smolvm
log_info "Pre-flight cleanup: killing orphan processes..."
kill_orphan_smolvm_processes
trap cleanup_machine EXIT
echo ""
echo "=========================================="
echo " Image-Backed /proc + /storage Parity"
echo "=========================================="
echo ""
# A privileged --image VM must have a writable /proc/sys, like a bare VM, so an
# init system / dockerd can set sysctls. Before the fix crun applied runc's
# readonlyPaths and this write failed with EROFS.
test_image_proc_sys_is_writable() {
local out
out=$(run_with_timeout 120 $SMOLVM machine run --net --image alpine -- \
sh -c 'echo 1 > /proc/sys/net/ipv4/ip_forward && cat /proc/sys/net/ipv4/ip_forward' 2>&1)
local rc=$?
[[ $rc -eq 124 ]] && { echo "FAIL: timed out booting image VM"; return 1; }
echo "$out" | grep -qx '1' || {
echo "FAIL: /proc/sys/net/ipv4/ip_forward not writable under --image"
echo " output: $out"
return 1
}
}
# Corollary: /proc/sys must not be a read-only bind in privileged mode. runc's
# readonlyPaths shows up as a `ro` mount line on the path; its absence means the
# path inherits the writable top-level /proc mount.
test_image_proc_sys_not_readonly_bind() {
local out
out=$(run_with_timeout 90 $SMOLVM machine run --net --image alpine -- \
sh -c 'mount | grep " /proc/sys " || true' 2>&1)
local rc=$?
[[ $rc -eq 124 ]] && { echo "FAIL: timed out booting image VM"; return 1; }
if echo "$out" | grep -q '\bro\b'; then
echo "FAIL: /proc/sys still mounted read-only under --image"
echo " output: $out"
return 1
fi
}
# A privileged --image VM must see the per-VM /storage ext4 disk inside the
# container, so /storage/* resolves to the disk exactly as in a bare VM.
test_image_storage_disk_visible() {
local out
out=$(run_with_timeout 90 $SMOLVM machine run --net --image alpine -- \
sh -c 'mount | grep " /storage " || true' 2>&1)
local rc=$?
[[ $rc -eq 124 ]] && { echo "FAIL: timed out booting image VM"; return 1; }
echo "$out" | grep -q ' /storage ' || {
echo "FAIL: /storage not mounted inside --image container"
echo " output: $out"
return 1
}
echo "$out" | grep -q 'ext4' || {
echo "FAIL: /storage is not the ext4 disk under --image"
echo " output: $out"
return 1
}
}
# The docker-in-VM bind-mount pattern works under --image. Binding
# /var/lib/docker onto /storage/docker must land on the ext4 disk (so dockerd's
# overlay2 nests correctly), proven by a file written through the bind appearing
# on the /storage side.
test_image_docker_bind_resolves_to_storage() {
local out
out=$(run_with_timeout 90 $SMOLVM machine run --net --image alpine -- sh -c '
set -e
mkdir -p /storage/docker /var/lib/docker
mount --bind /storage/docker /var/lib/docker
: > /var/lib/docker/probe
test -f /storage/docker/probe
# Confirm /var/lib/docker now resolves onto the ext4 storage disk.
mount | grep " /var/lib/docker " | grep -q ext4
echo BIND_OK' 2>&1)
local rc=$?
[[ $rc -eq 124 ]] && { echo "FAIL: timed out booting image VM"; return 1; }
echo "$out" | grep -q 'BIND_OK' || {
echo "FAIL: bind-mount of /var/lib/docker onto /storage did not resolve to ext4"
echo " output: $out"
return 1
}
}
# Guard the security boundary: an UNPRIVILEGED image container must NOT get the
# relaxed /proc or the /storage disk — the hardening is retained for untrusted
# code. `machine run --unprivileged` opts into the restricted profile.
test_unprivileged_image_keeps_hardening() {
# /proc/sys read-only (write must fail) and /storage absent.
local out
out=$(run_with_timeout 90 $SMOLVM machine run --net --unprivileged --image alpine -- sh -c '
if echo 1 > /proc/sys/net/ipv4/ip_forward 2>/dev/null; then echo PROC_WRITABLE; else echo PROC_RO; fi
if mount | grep -q " /storage "; then echo STORAGE_VISIBLE; else echo STORAGE_HIDDEN; fi' 2>&1)
local rc=$?
[[ $rc -eq 124 ]] && { echo "FAIL: timed out booting unprivileged image VM"; return 1; }
# If --unprivileged is unsupported by this build, skip rather than fail.
if echo "$out" | grep -qiE 'error|unexpected argument|unknown'; then
echo "SKIP: --unprivileged not supported by this build: $out"
return 0
fi
echo "$out" | grep -q 'PROC_RO' || {
echo "FAIL: unprivileged container got a writable /proc/sys (hardening lost)"
echo " output: $out"
return 1
}
echo "$out" | grep -q 'STORAGE_HIDDEN' || {
echo "FAIL: unprivileged container saw /storage (disk leak)"
echo " output: $out"
return 1
}
}
run_test "Image /proc/sys is writable (ip_forward settable)" test_image_proc_sys_is_writable || true
run_test "Image /proc/sys is not a read-only bind" test_image_proc_sys_not_readonly_bind || true
run_test "Image container sees the /storage ext4 disk" test_image_storage_disk_visible || true
run_test "Docker bind-mount onto /storage resolves to ext4" test_image_docker_bind_resolves_to_storage || true
run_test "Unprivileged image keeps /proc + /storage hardening" test_unprivileged_image_keeps_hardening || true
print_summary "Image /proc + /storage Parity Tests"