bunpen: expose the bare / tmpfs at /unbacked, to allow for debugging ramdisk usage
This commit is contained in:
@@ -24,7 +24,6 @@ install:
|
|||||||
|
|
||||||
test:
|
test:
|
||||||
hare test
|
hare test
|
||||||
# N.B.: integration_test relies on bashisms, not just `sh`.
|
PATH=$(PWD):$(PATH) ./integration_test
|
||||||
PATH=$(PWD):$(PATH) bash ./integration_test
|
|
||||||
|
|
||||||
.PHONY: all install test
|
.PHONY: all install test
|
||||||
|
@@ -1,8 +1,12 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# vim: set shiftwidth=2 :
|
# vim: set shiftwidth=2 :
|
||||||
|
# XXX: `bash` shebang because this relies on bashisms, not just `sh`!
|
||||||
|
|
||||||
|
set -o pipefail
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
|
SELF=$(realpath $0)
|
||||||
|
|
||||||
# we can't rely on /usr/bin/env existing in the nix build environment
|
# we can't rely on /usr/bin/env existing in the nix build environment
|
||||||
env=$(which env)
|
env=$(which env)
|
||||||
test=$(which test)
|
test=$(which test)
|
||||||
@@ -19,7 +23,7 @@ captureExitStatus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test_01_invoke_01_trivial() {
|
test_01_invoke_01_trivial() {
|
||||||
bunpen --bunpen-path / "$env" true
|
bunpen --bunpen-debug=4 --bunpen-path / "$env" true
|
||||||
}
|
}
|
||||||
|
|
||||||
test_01_invoke_02_by_path() {
|
test_01_invoke_02_by_path() {
|
||||||
@@ -202,33 +206,108 @@ test_09_reap_children() {
|
|||||||
ps x | grep -E 'Zs +[0-9]+:[0-9]+ \[true\] <defunct>' && return 1 || return 0
|
ps x | grep -E 'Zs +[0-9]+:[0-9]+ \[true\] <defunct>' && return 1 || return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test_10_tmpfs_root_is_introspectable() {
|
||||||
|
echo from_parent > test_file0
|
||||||
|
bunpen --bunpen-path /nix/store --bunpen-path $PWD bash -c "test -f $PWD/test_file0 && echo 'from_sandbox' > /test_file1 && sleep 2" &
|
||||||
|
local sbox_pid=$!
|
||||||
|
sleep 0.5
|
||||||
|
|
||||||
rc=0
|
# /proc/$pid/root is the rootfs as seen from inside the sandbox.
|
||||||
succeeded=()
|
# within the sandbox, `/unbacked` holds the actual rootfs, without anyting mounted over it;
|
||||||
failed=()
|
# i.e., the portion of the fstree which is backed only by the tmpfs-root
|
||||||
for f in $(declare -F); do
|
test ! -e /proc/$sbox_pid/root/unbacked/$PWD/test_file0
|
||||||
if [[ "$f" =~ ^test_* ]]; then
|
test /proc/$sbox_pid/root/$PWD/test_file0
|
||||||
mkdir "$f"
|
local test_file1=$(cat /proc/$sbox_pid/root/unbacked/test_file1)
|
||||||
|
|
||||||
|
# diagnostics, should the test fail:
|
||||||
|
echo -n "/proc/$sbox_pid: " && ls /proc/$sbox_pid
|
||||||
|
echo -n "/proc/$sbox_pid/root: " && ls /proc/$sbox_pid/root
|
||||||
|
echo -n "/proc/$sbox_pid/root/unbacked: " && ls /proc/$sbox_pid/root/unbacked
|
||||||
|
echo -n "/proc/$sbox_pid/root/unbacked/test_file1: " && echo "$test_file1"
|
||||||
|
echo -n "/proc/$sbox_pid/root/unbacked/$PWD: " && ls -l /proc/$sbox_pid/root/unbacked/$PWD
|
||||||
|
|
||||||
|
test "$test_file1" = from_sandbox
|
||||||
|
}
|
||||||
|
|
||||||
|
runTests() {
|
||||||
|
local testsToRun=("$@")
|
||||||
|
rc=0
|
||||||
|
succeeded=()
|
||||||
|
failed=()
|
||||||
|
basedir=$PWD
|
||||||
|
for f in "${testsToRun[@]}"; do
|
||||||
|
mkdir "$basedir/$f"
|
||||||
echo -n "$f: ..."
|
echo -n "$f: ..."
|
||||||
if (cd "$f"; "$f"); then
|
cd "$basedir/$f"
|
||||||
|
if $SELF "$f" > "$basedir/$f/stdall" 2>&1; then
|
||||||
echo " SUCCESS"
|
echo " SUCCESS"
|
||||||
succeeded+=("$f")
|
succeeded+=("$f")
|
||||||
else
|
else
|
||||||
rc=1
|
rc=1
|
||||||
echo " FAIL"
|
echo " FAIL"
|
||||||
|
cat "$basedir/$f/stdall"
|
||||||
failed+=("$f")
|
failed+=("$f")
|
||||||
fi
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "${#failed[@]}" != 0 ]]; then
|
||||||
|
echo
|
||||||
|
echo "FAILED TESTS:"
|
||||||
|
fi
|
||||||
|
|
||||||
|
for t in "${failed[@]}"; do
|
||||||
|
echo "- $t"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "${#succeeded[@]} tests succeeded"
|
||||||
|
test -n "${#succeeded[@]}" || return 1
|
||||||
|
return "$rc"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
self_test_succeed() {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
self_test_fail() {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
self_test_pipe_fail() {
|
||||||
|
true | false | true
|
||||||
|
}
|
||||||
|
self_test_step_fail() {
|
||||||
|
true
|
||||||
|
false
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
selfTest() {
|
||||||
|
# bash is dumb; make sure that if a test would fail, the toplevel test runner would also fail
|
||||||
|
if "$SELF" self_test_succeed \
|
||||||
|
&& ! "$SELF" self_test_fail \
|
||||||
|
&& ! "$SELF" self_test_pipe_fail \
|
||||||
|
&& ! "$SELF" self_test_step_fail \
|
||||||
|
&& ! "$SELF" self_test_succeed self_test_fail self_test_succeed \
|
||||||
|
; then
|
||||||
|
echo "self-test: success"
|
||||||
|
else
|
||||||
|
echo "self-test: FAIL"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
allTests=()
|
||||||
|
for f in $(declare -F); do
|
||||||
|
if [[ "$f" =~ ^test_* ]]; then
|
||||||
|
allTests+=("$f")
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ -n "${#failed[@]}" ]]; then
|
if [ $# = 1 ]; then
|
||||||
echo
|
# run the test inline
|
||||||
echo "FAILED TESTS:"
|
"$@"
|
||||||
|
elif [ $# = 0 ]; then
|
||||||
|
selfTest
|
||||||
|
runTests "${allTests[@]}"
|
||||||
|
else
|
||||||
|
runTests "$@"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for t in "${failed[@]}"; do
|
|
||||||
echo "- $t"
|
|
||||||
done
|
|
||||||
|
|
||||||
test -n "${#succeeded[@]}"
|
|
||||||
exit "$rc"
|
|
||||||
|
@@ -34,6 +34,10 @@
|
|||||||
|
|
||||||
doCheck = true;
|
doCheck = true;
|
||||||
|
|
||||||
|
postPatch = ''
|
||||||
|
patchShebangs --build integration_test
|
||||||
|
'';
|
||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
description = "userspace sandbox helper";
|
description = "userspace sandbox helper";
|
||||||
longDescription = ''
|
longDescription = ''
|
||||||
|
@@ -41,6 +41,23 @@ fn isolate_paths(what: *restrict::resources) void = {
|
|||||||
// errors::ext::check("[namespace] mount -t tmpfs tmpfs new", rt::ext::mount("tmpfs", "new", "tmpfs", rt::ext::mount_flag::NODEV | rt::ext::mount_flag::NOSUID, null));
|
// errors::ext::check("[namespace] mount -t tmpfs tmpfs new", rt::ext::mount("tmpfs", "new", "tmpfs", rt::ext::mount_flag::NODEV | rt::ext::mount_flag::NOSUID, null));
|
||||||
// errors::ext::check("[namespace] mount -o rbind new new", rt::ext::mount("new", "new", "", rt::ext::mount_flag::BIND | rt::ext::mount_flag::REC, null));
|
// errors::ext::check("[namespace] mount -o rbind new new", rt::ext::mount("new", "new", "", rt::ext::mount_flag::BIND | rt::ext::mount_flag::REC, null));
|
||||||
|
|
||||||
|
// bind the new `/` tmpfs also to `/unbacked`; this is entirely optional, to
|
||||||
|
// aid with troubleshooting:
|
||||||
|
// `/unbacked` is a shallow bind of `/`, so provides a view into which files
|
||||||
|
// are backed only by RAM.
|
||||||
|
// admins can debug unexpected RAM use by querying (from outside the sandbox):
|
||||||
|
// - `for p in /proc/*; do if [ "$p" != /proc/self -a "$p" != /proc/thread-self -a -d "$p/root/unbacked" ]; then du -h "$p/root/unbacked"; fi; done | sort -h`
|
||||||
|
errors::ext::check("[namespace] mkdir new/unbacked", rt::mkdir("new/unbacked", 0o755));
|
||||||
|
errors::ext::check("[namespace] mount new new/unbacked",
|
||||||
|
rt::ext::mount(
|
||||||
|
"new",
|
||||||
|
"new/unbacked",
|
||||||
|
"",
|
||||||
|
rt::ext::mount_flag::BIND,
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
// try to mount a new /proc.
|
// try to mount a new /proc.
|
||||||
// - this is "safe" because we're not doing anything
|
// - this is "safe" because we're not doing anything
|
||||||
// the sandboxed program can't do. IOW, if this is unsafe, then the downstream
|
// the sandboxed program can't do. IOW, if this is unsafe, then the downstream
|
||||||
|
Reference in New Issue
Block a user