blob: 470ebeb7494b30e42910bfce36a45dd627b85ed8 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
#!/bin/sh
# Copyright (C) 2026 Doan Luu Duc Tai <doan@luuduct.ai>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
set -eu
umask 077
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)
command -v bwrap >/dev/null || { echo 'Bootstrap tests require bubblewrap.' >&2; exit 1; }
STAGE=$(mktemp -d)
trap 'chmod -R u+w "$STAGE"; rm -rf -- "$STAGE"' EXIT HUP INT TERM
mkdir -p "$STAGE/capsule" "$STAGE/pack/gnu/store/00000000000000000000000000000000-tool/bin" \
"$STAGE/pack/gnu/store/11111111111111111111111111111111-profile/bin" "$STAGE/pack/etc"
cp "$ROOT/infra/guix/bootstrap.sh" "$STAGE/capsule/bootstrap"
printf '%s\n' '#!/bin/sh' 'printf "%s\n" canonical-tool' > "$STAGE/pack/gnu/store/00000000000000000000000000000000-tool/bin/tool"
chmod 755 "$STAGE/pack/gnu/store/00000000000000000000000000000000-tool/bin/tool"
ln -s /gnu/store/00000000000000000000000000000000-tool/bin/tool "$STAGE/pack/gnu/store/11111111111111111111111111111111-profile/bin/tool"
ln -s /gnu/store/11111111111111111111111111111111-profile/bin "$STAGE/pack/bin"
printf '%s\n' 'Must stay inside pack.' > "$STAGE/pack/etc/pack-sentinel"
tar -czf "$STAGE/capsule/runtime.tar.gz" -C "$STAGE/pack" .
(cd "$STAGE/capsule" && sha256sum bootstrap runtime.tar.gz > SHA256SUMS)
cp -a "$STAGE/capsule" "$STAGE/tampered"
printf '%s\n' 'tamper' >> "$STAGE/tampered/runtime.tar.gz"
cp -a "$STAGE/capsule" "$STAGE/next"
mkdir "$STAGE/pack/gnu/store/22222222222222222222222222222222-next"
printf '%s\n' 'Second closure.' > "$STAGE/pack/gnu/store/22222222222222222222222222222222-next/value"
tar -czf "$STAGE/next/runtime.tar.gz" -C "$STAGE/pack" .
(cd "$STAGE/next" && sha256sum bootstrap runtime.tar.gz > SHA256SUMS)
bwrap --unshare-user --uid 0 --gid 0 --unshare-pid --cap-add CAP_DAC_OVERRIDE \
--tmpfs / --ro-bind /usr /usr \
--symlink usr/bin /bin --symlink usr/lib /lib --symlink usr/lib /lib64 \
--ro-bind "$STAGE/capsule" /capsule --ro-bind "$STAGE/next" /next \
--ro-bind "$STAGE/tampered" /tampered --proc /proc --dev /dev --tmpfs /tmp \
--chdir / /bin/sh -eu <<'TEST'
fail() { printf '%s\n' "$1" >&2; exit 1; }
reject() { if sh "$1/bootstrap"; then fail "$2"; fi; }
TOOL=/gnu/store/00000000000000000000000000000000-tool/bin/tool
PROFILE=/gnu/store/11111111111111111111111111111111-profile
mkdir /etc
printf '%s\n' 'Host configuration.' > /etc/host-sentinel
[ ! -e /gnu ] && [ ! -e /var/guix ]
if sh /capsule/bootstrap extra; then fail 'Accepted bootstrap arguments.'; fi
reject /tampered 'Accepted altered runtime.'
[ ! -e /gnu ]
sh /capsule/bootstrap > /tmp/bootstrap-first.log &
FIRST=$!
sh /capsule/bootstrap
wait "$FIRST"
DIGEST=$(sha256sum /capsule/runtime.tar.gz)
DIGEST=${DIGEST%% *}
[ "$(cat "/gnu/.stack-runtimes/$DIGEST")" = "$DIGEST" ]
[ -z "$(find /gnu/store -mindepth 1 ! -user 0 -print -quit)" ]
[ -z "$(find /gnu/store -mindepth 1 ! -type l -perm /222 -print -quit)" ]
[ "$("$PROFILE/bin/tool")" = canonical-tool ]
[ "$(readlink "$PROFILE/bin/tool")" = "$TOOL" ]
[ "$(readlink /bin)" = usr/bin ]
[ "$(cat /etc/host-sentinel)" = 'Host configuration.' ]
[ ! -e /etc/pack-sentinel ]
INODE=$(stat -c %i "/gnu/.stack-runtimes/$DIGEST")
sh /capsule/bootstrap
[ "$(stat -c %i "/gnu/.stack-runtimes/$DIGEST")" = "$INODE" ]
rm "/gnu/.stack-runtimes/$DIGEST"
chmod -R u+w "$PROFILE"
rm -rf "$PROFILE"
sh /capsule/bootstrap
[ "$("$PROFILE/bin/tool")" = canonical-tool ]
[ -f "/gnu/.stack-runtimes/$DIGEST" ]
[ -z "$(find /gnu -maxdepth 1 -name '.stack-stage.*' -print -quit)" ]
sh /next/bootstrap
[ "$("$PROFILE/bin/tool")" = canonical-tool ]
[ "$(cat /gnu/store/22222222222222222222222222222222-next/value)" = 'Second closure.' ]
[ -f "/gnu/.stack-runtimes/$DIGEST" ]
rm "/gnu/.stack-runtimes/$DIGEST"
chmod u+w "$TOOL"
printf '%s\n' 'conflicting content' >> "$TOOL"
chmod a-w "$TOOL"
reject /capsule 'Overwrote conflicting store entry.'
[ ! -e "/gnu/.stack-runtimes/$DIGEST" ]
[ -z "$(find /gnu -maxdepth 1 -name '.stack-stage.*' -print -quit)" ]
tail -n 1 "$TOOL" | grep -Fx 'conflicting content'
chmod -R u+w /gnu
rm -rf /gnu
mkdir -p /var/guix
reject /capsule 'Accepted managed Guix installation.'
[ ! -e /gnu ]
rm -rf /var/guix
mkdir -p /gnu/store
printf '%s\n' 'Foreign store.' > /gnu/store/sentinel
reject /capsule 'Accepted foreign store.'
[ "$(cat /gnu/store/sentinel)" = 'Foreign store.' ]
rm -rf /gnu
mkdir /outside
ln -s /outside /gnu
reject /capsule 'Accepted symlink /gnu.'
[ -z "$(find /outside -mindepth 1 -print -quit)" ]
rm /gnu
mkdir -p /gnu/.stack-runtimes
chmod 755 /gnu /gnu/.stack-runtimes
ln -s /outside /gnu/store
reject /capsule 'Accepted symlink store.'
[ -z "$(find /outside -mindepth 1 -print -quit)" ]
rm /gnu/store
chmod 777 /gnu/.stack-runtimes
reject /capsule 'Accepted writable ownership metadata.'
[ ! -e /gnu/store ]
printf '%s\n' 'Canonical bootstrap, retry, idempotency, preservation and refusal checks passed.'
TEST
|