hwdb: fix screen rotation for EXO Wings 2in1 w1125 (#36283)
[systemd.io.git] / test / units / TEST-13-NSPAWN.nspawn.sh
blob076e94c7b1157bf38664f4346a61dc62aa882c22
1 #!/usr/bin/env bash
2 # SPDX-License-Identifier: LGPL-2.1-or-later
3 # shellcheck disable=SC2016
5 # Notes on coverage: when collecting coverage we need the $BUILD_DIR present
6 # and writable in the container as well. To do this in the least intrusive way,
7 # two things are going on in the background (only when built with -Db_coverage=true):
8 # 1) the systemd-nspawn@.service is copied to /etc/systemd/system/ with
9 # --bind=$BUILD_DIR appended to the ExecStart= line
10 # 2) each create_dummy_container() call also creates an .nspawn file in /run/systemd/nspawn/
11 # with the last fragment from the path used as a name
13 # The first change is quite self-contained and applies only to containers run
14 # with machinectl. The second one might cause some unexpected side-effects, namely:
15 # - nspawn config (setting) files don't support dropins, so tests that test
16 # the config files might need some tweaking (as seen below with
17 # the $COVERAGE_BUILD_DIR shenanigans) since they overwrite the .nspawn file
18 # - also a note - if /etc/systemd/nspawn/cont-name.nspawn exists, it takes
19 # precedence and /run/systemd/nspawn/cont-name.nspawn won't be read even
20 # if it exists
21 # - also a note 2 - --bind= overrides any Bind= from a config file
22 # - in some cases we don't create a test container using create_dummy_container(),
23 # so in that case an explicit call to coverage_create_nspawn_dropin() is needed
25 # However, even after jumping through all these hooks, there still might (and is)
26 # some "incorrectly" missing coverage, especially in the window between spawning
27 # the inner child process and bind-mounting the coverage $BUILD_DIR
28 set -eux
29 set -o pipefail
31 # shellcheck source=test/units/test-control.sh
32 . "$(dirname "$0")"/test-control.sh
33 # shellcheck source=test/units/util.sh
34 . "$(dirname "$0")"/util.sh
37 export SYSTEMD_LOG_LEVEL=debug
38 export SYSTEMD_LOG_TARGET=journal
40 at_exit() {
41 set +e
43 mountpoint -q /var/lib/machines && umount --recursive /var/lib/machines
44 rm -f /run/systemd/nspawn/*.nspawn
46 rm -fr /var/tmp/TEST-13-NSPAWN.*
47 rm -f /run/verity.d/test-13-nspawn-*.crt
50 trap at_exit EXIT
52 # check cgroup-v2
53 IS_CGROUPSV2_SUPPORTED=no
54 mkdir -p /tmp/cgroup2
55 if mount -t cgroup2 cgroup2 /tmp/cgroup2; then
56 IS_CGROUPSV2_SUPPORTED=yes
57 umount /tmp/cgroup2
59 rmdir /tmp/cgroup2
61 # check cgroup namespaces
62 IS_CGNS_SUPPORTED=no
63 if [[ -f /proc/1/ns/cgroup ]]; then
64 IS_CGNS_SUPPORTED=yes
67 IS_USERNS_SUPPORTED=no
68 # On some systems (e.g. CentOS 7) the default limit for user namespaces
69 # is set to 0, which causes the following unshare syscall to fail, even
70 # with enabled user namespaces support. By setting this value explicitly
71 # we can ensure the user namespaces support to be detected correctly.
72 sysctl -w user.max_user_namespaces=10000
73 if unshare -U bash -c :; then
74 IS_USERNS_SUPPORTED=yes
77 # Mount temporary directory over /var/lib/machines to not pollute the image
78 mkdir -p /var/lib/machines
79 mount --bind "$(mktemp --tmpdir=/var/tmp -d)" /var/lib/machines
81 testcase_sanity() {
82 local template root image uuid tmpdir
84 tmpdir="$(mktemp -d)"
85 template="$(mktemp -d /tmp/nspawn-template.XXX)"
86 create_dummy_container "$template"
87 # Create a simple image from the just created container template
88 image="$(mktemp /var/lib/machines/TEST-13-NSPAWN.image-XXX.img)"
89 dd if=/dev/zero of="$image" bs=1M count=256
90 mkfs.ext4 "$image"
91 mkdir -p /mnt
92 mount -o loop "$image" /mnt
93 cp -r "$template"/* /mnt/
94 umount /mnt
96 systemd-nspawn --help --no-pager
97 systemd-nspawn --version
99 # --template=
100 root="$(mktemp -u -d /var/lib/machines/TEST-13-NSPAWN.sanity.XXX)"
101 coverage_create_nspawn_dropin "$root"
102 (! systemd-nspawn --directory="$root" bash -xec 'echo hello')
103 # Initialize $root from $template (the $root directory must not exist, hence
104 # the `mktemp -u` above)
105 systemd-nspawn --directory="$root" --template="$template" bash -xec 'echo hello'
106 systemd-nspawn --directory="$root" bash -xec 'echo hello; touch /initialized'
107 test -e "$root/initialized"
108 # Check if the $root doesn't get re-initialized once it's not empty
109 systemd-nspawn --directory="$root" --template="$template" bash -xec 'echo hello'
110 test -e "$root/initialized"
112 systemd-nspawn --directory="$root" --ephemeral bash -xec 'touch /ephemeral'
113 test ! -e "$root/ephemeral"
114 (! systemd-nspawn --directory="$root" \
115 --read-only \
116 bash -xec 'touch /nope')
117 test ! -e "$root/nope"
118 systemd-nspawn --image="$image" bash -xec 'echo hello'
120 # --volatile=
121 touch "$root/usr/has-usr"
122 # volatile(=yes): rootfs is tmpfs, /usr/ from the OS tree is mounted read only
123 systemd-nspawn --directory="$root"\
124 --volatile \
125 bash -xec 'test -e /usr/has-usr; touch /usr/read-only && exit 1; touch /nope'
126 test ! -e "$root/nope"
127 test ! -e "$root/usr/read-only"
128 systemd-nspawn --directory="$root"\
129 --volatile=yes \
130 bash -xec 'test -e /usr/has-usr; touch /usr/read-only && exit 1; touch /nope'
131 test ! -e "$root/nope"
132 test ! -e "$root/usr/read-only"
133 # volatile=state: rootfs is read-only, /var/ is tmpfs
134 systemd-nspawn --directory="$root" \
135 --volatile=state \
136 bash -xec 'test -e /usr/has-usr; mountpoint /var; touch /read-only && exit 1; touch /var/nope'
137 test ! -e "$root/read-only"
138 test ! -e "$root/var/nope"
139 # volatile=overlay: tmpfs overlay is mounted over rootfs
140 systemd-nspawn --directory="$root" \
141 --volatile=overlay \
142 bash -xec 'test -e /usr/has-usr; touch /nope; touch /var/also-nope; touch /usr/nope-too'
143 test ! -e "$root/nope"
144 test ! -e "$root/var/also-nope"
145 test ! -e "$root/usr/nope-too"
147 # --volatile= with -U
148 touch "$root/usr/has-usr"
149 # volatile(=yes): rootfs is tmpfs, /usr/ from the OS tree is mounted read only
150 systemd-nspawn --directory="$root"\
151 --volatile \
152 -U \
153 bash -xec 'test -e /usr/has-usr; touch /usr/read-only && exit 1; touch /nope'
154 test ! -e "$root/nope"
155 test ! -e "$root/usr/read-only"
156 systemd-nspawn --directory="$root"\
157 --volatile=yes \
158 -U \
159 bash -xec 'test -e /usr/has-usr; touch /usr/read-only && exit 1; touch /nope'
160 test ! -e "$root/nope"
161 test ! -e "$root/usr/read-only"
162 # volatile=state: rootfs is read-only, /var/ is tmpfs
163 systemd-nspawn --directory="$root" \
164 --volatile=state \
165 -U \
166 bash -xec 'test -e /usr/has-usr; mountpoint /var; touch /read-only && exit 1; touch /var/nope'
167 test ! -e "$root/read-only"
168 test ! -e "$root/var/nope"
169 # volatile=overlay: tmpfs overlay is mounted over rootfs
170 systemd-nspawn --directory="$root" \
171 --volatile=overlay \
172 -U \
173 bash -xec 'test -e /usr/has-usr; touch /nope; touch /var/also-nope; touch /usr/nope-too'
174 test ! -e "$root/nope"
175 test ! -e "$root/var/also-nope"
176 test ! -e "$root/usr/nope-too"
178 # --machine=, --hostname=
179 systemd-nspawn --directory="$root" \
180 --machine="foo-bar.baz" \
181 bash -xec '[[ $(hostname) == foo-bar.baz ]]'
182 systemd-nspawn --directory="$root" \
183 --hostname="hello.world.tld" \
184 bash -xec '[[ $(hostname) == hello.world.tld ]]'
185 systemd-nspawn --directory="$root" \
186 --machine="foo-bar.baz" \
187 --hostname="hello.world.tld" \
188 bash -xec '[[ $(hostname) == hello.world.tld ]]'
190 # --uuid=
191 rm -f "$root/etc/machine-id"
192 uuid="deadbeef-dead-dead-beef-000000000000"
193 systemd-nspawn --directory="$root" \
194 --uuid="$uuid" \
195 bash -xec "[[ \$container_uuid == $uuid ]]"
197 # --as-pid2
198 systemd-nspawn --directory="$root" bash -xec '[[ $$ -eq 1 ]]'
199 systemd-nspawn --directory="$root" --as-pid2 bash -xec '[[ $$ -eq 2 ]]'
201 # --user=
202 # "Fake" getent passwd's bare minimum, so we don't have to pull it in
203 # with all the DSO shenanigans
204 cat >"$root/bin/getent" <<\EOF
205 #!/bin/bash
207 if [[ $# -eq 0 ]]; then
209 elif [[ $1 == passwd ]]; then
210 echo "testuser:x:1000:1000:testuser:/:/bin/sh"
211 elif [[ $1 == initgroups ]]; then
212 echo "testuser"
215 chmod +x "$root/bin/getent"
216 # The useradd is important here so the user is added to /etc/passwd. If the user is not in /etc/passwd,
217 # bash will end up loading libnss_systemd.so which breaks when libnss_systemd.so is built with sanitizers
218 # as bash isn't invoked with the necessary environment variables for that.
219 useradd --root="$root" --uid 1000 --user-group --create-home testuser
220 systemd-nspawn --directory="$root" bash -xec '[[ $USER == root ]]'
221 systemd-nspawn --directory="$root" --user=testuser bash -xec '[[ $USER == testuser ]]'
223 # --settings= + .nspawn files
224 mkdir -p /run/systemd/nspawn/
225 uuid="deadbeef-dead-dead-beef-000000000000"
226 echo -ne "[Exec]\nMachineID=deadbeef-dead-dead-beef-111111111111" >/run/systemd/nspawn/foo-bar.nspawn
227 systemd-nspawn --directory="$root" \
228 --machine=foo-bar \
229 --settings=yes \
230 bash -xec '[[ $container_uuid == deadbeef-dead-dead-beef-111111111111 ]]'
231 systemd-nspawn --directory="$root" \
232 --machine=foo-bar \
233 --uuid="$uuid" \
234 --settings=yes \
235 bash -xec "[[ \$container_uuid == $uuid ]]"
236 systemd-nspawn --directory="$root" \
237 --machine=foo-bar \
238 --uuid="$uuid" \
239 --settings=override \
240 bash -xec '[[ $container_uuid == deadbeef-dead-dead-beef-111111111111 ]]'
241 systemd-nspawn --directory="$root" \
242 --machine=foo-bar \
243 --uuid="$uuid" \
244 --settings=trusted \
245 bash -xec "[[ \$container_uuid == $uuid ]]"
247 # Mounts
248 mkdir "$tmpdir"/{1,2,3}
249 touch "$tmpdir/1/one" "$tmpdir/2/two" "$tmpdir/3/three"
250 touch "$tmpdir/foo"
251 # --bind=
252 systemd-nspawn --directory="$root" \
253 ${COVERAGE_BUILD_DIR:+--bind="$COVERAGE_BUILD_DIR"} \
254 --bind="$tmpdir:/foo" \
255 --bind="$tmpdir:/also-foo:noidmap,norbind" \
256 bash -xec 'test -e /foo/foo; touch /foo/bar; test -e /also-foo/bar'
257 test -e "$tmpdir/bar"
258 # --bind-ro=
259 systemd-nspawn --directory="$root" \
260 --bind-ro="$tmpdir:/foo" \
261 --bind-ro="$tmpdir:/bar:noidmap,norbind" \
262 bash -xec 'test -e /foo/foo; touch /foo/baz && exit 1; touch /bar && exit 1; true'
263 # --inaccessible=
264 systemd-nspawn --directory="$root" \
265 --inaccessible=/var \
266 bash -xec 'touch /var/foo && exit 1; true'
267 # --tmpfs=
268 systemd-nspawn --directory="$root" \
269 --tmpfs=/var:rw,nosuid,noexec \
270 bash -xec 'touch /var/nope'
271 test ! -e "$root/var/nope"
272 # --overlay=
273 systemd-nspawn --directory="$root" \
274 --overlay="$tmpdir/1:$tmpdir/2:$tmpdir/3:/var" \
275 bash -xec 'test -e /var/one; test -e /var/two; test -e /var/three; touch /var/foo'
276 test -e "$tmpdir/3/foo"
277 # --overlay-ro=
278 systemd-nspawn --directory="$root" \
279 --overlay-ro="$tmpdir/1:$tmpdir/2:$tmpdir/3:/var" \
280 bash -xec 'test -e /var/one; test -e /var/two; test -e /var/three; touch /var/nope && exit 1; true'
281 test ! -e "$tmpdir/3/nope"
282 rm -fr "$tmpdir"
284 # --port (sanity only)
285 systemd-nspawn --network-veth --directory="$root" --port=80 --port=90 true
286 systemd-nspawn --network-veth --directory="$root" --port=80:8080 true
287 systemd-nspawn --network-veth --directory="$root" --port=tcp:80 true
288 systemd-nspawn --network-veth --directory="$root" --port=tcp:80:8080 true
289 systemd-nspawn --network-veth --directory="$root" --port=udp:80 true
290 systemd-nspawn --network-veth --directory="$root" --port=udp:80:8080 --port=tcp:80:8080 true
291 (! systemd-nspawn --network-veth --directory="$root" --port= true)
292 (! systemd-nspawn --network-veth --directory="$root" --port=-1 true)
293 (! systemd-nspawn --network-veth --directory="$root" --port=: true)
294 (! systemd-nspawn --network-veth --directory="$root" --port=icmp:80:8080 true)
295 (! systemd-nspawn --network-veth --directory="$root" --port=tcp::8080 true)
296 (! systemd-nspawn --network-veth --directory="$root" --port=8080: true)
297 # Exercise adding/removing ports from an interface
298 systemd-nspawn --directory="$root" \
299 --network-veth \
300 --port=6667 \
301 --port=80:8080 \
302 --port=udp:53 \
303 --port=tcp:22:2222 \
304 bash -xec 'ip addr add dev host0 10.0.0.10/24; ip a; ip addr del dev host0 10.0.0.10/24'
306 # --load-credential=, --set-credential=
307 echo "foo bar" >/tmp/cred.path
308 systemd-nspawn --directory="$root" \
309 --load-credential=cred.path:/tmp/cred.path \
310 --set-credential="cred.set:hello world" \
311 bash -xec '[[ "$(</run/host/credentials/cred.path)" == "foo bar" ]]; [[ "$(</run/host/credentials/cred.set)" == "hello world" ]]'
312 # Combine with --user to ensure creds are still readable
313 systemd-nspawn --directory="$root" \
314 --user=testuser \
315 --no-new-privileges=yes \
316 --load-credential=cred.path:/tmp/cred.path \
317 --set-credential="cred.set:hello world" \
318 bash -xec '[[ "$(</run/host/credentials/cred.path)" == "foo bar" ]]; [[ "$(</run/host/credentials/cred.set)" == "hello world" ]]'
319 rm -f /tmp/cred.path
321 # Assorted tests
322 systemd-nspawn --directory="$root" --suppress-sync=yes bash -xec 'echo hello'
323 systemd-nspawn --capability=help
324 systemd-nspawn --resolv-conf=help
325 systemd-nspawn --timezone=help
327 # Handling of invalid arguments
328 opts=(
329 bind
330 bind-ro
331 bind-user
332 chdir
333 console
334 inaccessible
335 kill-signal
336 link-journal
337 load-credential
338 network-{interface,macvlan,ipvlan,veth-extra,bridge,zone}
339 no-new-privileges
340 oom-score-adjust
341 overlay
342 overlay-ro
343 personality
344 pivot-root
345 port
346 private-users
347 private-users-ownership
348 register
349 resolv-conf
350 rlimit
351 root-hash
352 root-hash-sig
353 set-credential
354 settings
355 suppress-sync
356 timezone
357 tmpfs
358 uuid
360 for opt in "${opts[@]}"; do
361 (! systemd-nspawn "--$opt")
362 [[ "$opt" == network-zone ]] && continue
363 (! systemd-nspawn "--$opt=''")
364 (! systemd-nspawn "--$opt=%\$Å¡")
365 done
366 (! systemd-nspawn --volatile="")
367 (! systemd-nspawn --volatile=-1)
368 (! systemd-nspawn --rlimit==)
371 nspawn_settings_cleanup() {
372 for dev in sd-host-only sd-shared{1,2,3} sd-macvlan{1,2} sd-ipvlan{1,2}; do
373 ip link del "$dev" || :
374 done
376 return 0
379 testcase_nspawn_settings() {
380 local root container dev private_users wlan_names
382 mkdir -p /run/systemd/nspawn
383 root="$(mktemp -d /var/lib/machines/TEST-13-NSPAWN.nspawn-settings.XXX)"
384 container="$(basename "$root")"
385 create_dummy_container "$root"
386 rm -f "/etc/systemd/nspawn/$container.nspawn"
387 mkdir -p "$root/tmp" "$root"/opt/{tmp,inaccessible,also-inaccessible}
389 # add virtual wlan interfaces
390 if modprobe mac80211_hwsim radios=2; then
391 wlan_names="wlan0 wlan1:wl-renamed1"
394 for dev in sd-host-only sd-shared{1,2,3} sd-macvlan{1,2} sd-macvlanloong sd-ipvlan{1,2} sd-ipvlanlooong; do
395 ip link add "$dev" type dummy
396 done
397 udevadm settle
398 ip link property add dev sd-shared3 altname sd-altname3 altname sd-altname-tooooooooooooo-long
399 ip link
400 trap nspawn_settings_cleanup RETURN
402 # Let's start with one huge config to test as much as we can at once
403 cat >"/run/systemd/nspawn/$container.nspawn" <<EOF
404 [Exec]
405 Boot=no
406 Ephemeral=no
407 ProcessTwo=no
408 Parameters=bash /entrypoint.sh "foo bar" 'bar baz'
409 Environment=FOO=bar
410 Environment=BAZ="hello world"
411 User=root
412 WorkingDirectory=/tmp
413 Capability=CAP_BLOCK_SUSPEND CAP_BPF CAP_CHOWN
414 DropCapability=CAP_AUDIT_CONTROL CAP_AUDIT_WRITE
415 AmbientCapability=CAP_BPF CAP_CHOWN
416 NoNewPrivileges=no
417 MachineID=f28f129b51874b1280a89421ec4b4ad4
418 PrivateUsers=no
419 NotifyReady=no
420 SystemCallFilter=@basic-io @chown
421 SystemCallFilter=~ @clock
422 LimitNOFILE=1024:2048
423 LimitRTPRIO=8:16
424 OOMScoreAdjust=32
425 CPUAffinity=0,0-5,1-5
426 Hostname=nspawn-settings
427 ResolvConf=copy-host
428 Timezone=delete
429 LinkJournal=no
430 SuppressSync=no
432 [Files]
433 ReadOnly=no
434 Volatile=no
435 TemporaryFileSystem=/tmp
436 TemporaryFileSystem=/opt/tmp
437 Inaccessible=/opt/inaccessible
438 Inaccessible=/opt/also-inaccessible
439 PrivateUsersOwnership=auto
440 Overlay=+/var::/var
441 ${COVERAGE_BUILD_DIR:+"Bind=$COVERAGE_BUILD_DIR"}
443 [Network]
444 Private=yes
445 VirtualEthernet=yes
446 VirtualEthernetExtra=my-fancy-veth1
447 VirtualEthernetExtra=fancy-veth2:my-fancy-veth2
448 Interface=sd-shared1 sd-shared2:sd-renamed2 sd-shared3:sd-altname3 ${wlan_names:-}
449 MACVLAN=sd-macvlan1 sd-macvlan2:my-macvlan2 sd-macvlanloong
450 IPVLAN=sd-ipvlan1 sd-ipvlan2:my-ipvlan2 sd-ipvlanlooong
451 Zone=sd-zone0
452 Port=80
453 Port=81:8181
454 Port=tcp:60
455 Port=udp:60:61
457 cat >"$root/entrypoint.sh" <<\EOF
458 #!/bin/bash
459 set -ex
463 [[ "$1" == "foo bar" ]]
464 [[ "$2" == "bar baz" ]]
466 [[ "$USER" == root ]]
467 [[ "$FOO" == bar ]]
468 [[ "$BAZ" == "hello world" ]]
469 [[ "$PWD" == /tmp ]]
470 [[ "$container_uuid" == f28f129b-5187-4b12-80a8-9421ec4b4ad4 ]]
471 [[ "$(ulimit -S -n)" -eq 1024 ]]
472 [[ "$(ulimit -H -n)" -eq 2048 ]]
473 [[ "$(ulimit -S -r)" -eq 8 ]]
474 [[ "$(ulimit -H -r)" -eq 16 ]]
475 [[ "$(</proc/self/oom_score_adj)" -eq 32 ]]
476 [[ "$(hostname)" == nspawn-settings ]]
477 [[ -e /etc/resolv.conf ]]
478 [[ ! -e /etc/localtime ]]
480 mountpoint /tmp
481 touch /tmp/foo
482 mountpoint /opt/tmp
483 touch /opt/tmp/foo
484 touch /opt/inaccessible/foo && exit 1
485 touch /opt/also-inaccessible/foo && exit 1
486 mountpoint /var
488 ip link
489 ip link | grep host-only && exit 1
490 ip link | grep host0@
491 ip link | grep my-fancy-veth1@
492 ip link | grep my-fancy-veth2@
493 ip link | grep sd-shared1
494 ip link | grep sd-renamed2
495 ip link | grep sd-shared3
496 ip link | grep sd-altname3
497 ip link | grep sd-altname-tooooooooooooo-long
498 ip link | grep mv-sd-macvlan1@
499 ip link | grep my-macvlan2@
500 ip link | grep iv-sd-ipvlan1@
501 ip link | grep my-ipvlan2@
503 if [[ -n "${wlan_names:-}" ]]; then
504 cat >>"$root/entrypoint.sh" <<\EOF
505 ip link | grep wlan0
506 ip link | grep wl-renamed1
510 timeout 30 systemd-nspawn --directory="$root"
512 # And now for stuff that needs to run separately
514 # Note on the condition below: since our container tree is owned by root,
515 # both "yes" and "identity" private users settings will behave the same
516 # as PrivateUsers=0:65535, which makes BindUser= fail as the UID already
517 # exists there, so skip setting it in such case
518 for private_users in "131072:65536" yes identity pick; do
519 cat >"/run/systemd/nspawn/$container.nspawn" <<EOF
520 [Exec]
521 Hostname=private-users
522 PrivateUsers=$private_users
524 [Files]
525 PrivateUsersOwnership=auto
526 BindUser=
527 $([[ "$private_users" =~ (yes|identity) ]] || echo "BindUser=testuser")
528 ${COVERAGE_BUILD_DIR:+"Bind=$COVERAGE_BUILD_DIR"}
530 cat "/run/systemd/nspawn/$container.nspawn"
531 chown -R root:root "$root"
532 systemd-nspawn --directory="$root" bash -xec '[[ "$(hostname)" == private-users ]]'
533 done
535 rm -fr "$root" "/run/systemd/nspawn/$container.nspawn"
538 bind_user_cleanup() {
539 userdel --force --remove nspawn-bind-user-1
540 userdel --force --remove nspawn-bind-user-2
543 testcase_bind_user() {
544 local root
546 root="$(mktemp -d /var/lib/machines/TEST-13-NSPAWN.bind-user.XXX)"
547 create_dummy_container "$root"
548 useradd --create-home --user-group nspawn-bind-user-1
549 useradd --create-home --user-group nspawn-bind-user-2
550 trap bind_user_cleanup RETURN
551 touch /home/nspawn-bind-user-1/foo
552 touch /home/nspawn-bind-user-2/bar
553 # Add a couple of POSIX ACLs to test the patch-uid stuff
554 mkdir -p "$root/opt"
555 setfacl -R -m 'd:u:nspawn-bind-user-1:rwX' -m 'u:nspawn-bind-user-1:rwX' "$root/opt"
556 setfacl -R -m 'd:g:nspawn-bind-user-1:rwX' -m 'g:nspawn-bind-user-1:rwX' "$root/opt"
558 systemd-nspawn --directory="$root" \
559 --private-users=pick \
560 --bind-user=nspawn-bind-user-1 \
561 bash -xec 'test -e /run/host/home/nspawn-bind-user-1/foo'
563 systemd-nspawn --directory="$root" \
564 --private-users=pick \
565 --private-users-ownership=chown \
566 --bind-user=nspawn-bind-user-1 \
567 --bind-user=nspawn-bind-user-2 \
568 bash -xec 'test -e /run/host/home/nspawn-bind-user-1/foo; test -e /run/host/home/nspawn-bind-user-2/bar'
569 chown -R root:root "$root"
571 # User/group name collision
572 echo "nspawn-bind-user-2:x:1000:1000:nspawn-bind-user-2:/home/nspawn-bind-user-2:/bin/bash" >"$root/etc/passwd"
573 (! systemd-nspawn --directory="$root" \
574 --private-users=pick \
575 --bind-user=nspawn-bind-user-1 \
576 --bind-user=nspawn-bind-user-2 \
577 true)
578 rm -f "$root/etc/passwd"
580 echo "nspawn-bind-user-2:x:1000:" >"$root/etc/group"
581 (! systemd-nspawn --directory="$root" \
582 --private-users=pick \
583 --bind-user=nspawn-bind-user-1 \
584 --bind-user=nspawn-bind-user-2 \
585 true)
586 rm -f "$root/etc/group"
588 rm -fr "$root"
591 testcase_bind_tmp_path() {
592 # https://github.com/systemd/systemd/issues/4789
593 local root
595 root="$(mktemp -d /var/lib/machines/TEST-13-NSPAWN.bind-tmp-path.XXX)"
596 create_dummy_container "$root"
597 : >/tmp/bind
598 systemd-nspawn --register=no \
599 --directory="$root" \
600 --bind=/tmp/bind \
601 bash -c 'test -e /tmp/bind'
603 rm -fr "$root" /tmp/bind
606 testcase_norbind() {
607 # https://github.com/systemd/systemd/issues/13170
608 local root
610 root="$(mktemp -d /var/lib/machines/TEST-13-NSPAWN.norbind-path.XXX)"
611 mkdir -p /tmp/binddir/subdir
612 echo -n "outer" >/tmp/binddir/subdir/file
613 mount -t tmpfs tmpfs /tmp/binddir/subdir
614 echo -n "inner" >/tmp/binddir/subdir/file
615 create_dummy_container "$root"
617 systemd-nspawn --register=no \
618 --directory="$root" \
619 --bind=/tmp/binddir:/mnt:norbind \
620 bash -c 'CONTENT=$(cat /mnt/subdir/file); if [[ $CONTENT != "outer" ]]; then echo "*** unexpected content: $CONTENT"; exit 1; fi'
622 umount /tmp/binddir/subdir
623 rm -fr "$root" /tmp/binddir/
626 rootidmap_cleanup() {
627 local dir="${1:?}"
629 mountpoint -q "$dir/bind" && umount "$dir/bind"
630 rm -fr "$dir"
633 testcase_rootidmap() {
634 local root cmd permissions
635 local owner=1000
637 root="$(mktemp -d /var/lib/machines/TEST-13-NSPAWN.rootidmap-path.XXX)"
638 # Create ext4 image, as ext4 supports idmapped-mounts.
639 mkdir -p /tmp/rootidmap/bind
640 dd if=/dev/zero of=/tmp/rootidmap/ext4.img bs=4k count=2048
641 mkfs.ext4 /tmp/rootidmap/ext4.img
642 mount /tmp/rootidmap/ext4.img /tmp/rootidmap/bind
643 trap "rootidmap_cleanup /tmp/rootidmap/" RETURN
645 touch /tmp/rootidmap/bind/file
646 chown -R "$owner:$owner" /tmp/rootidmap/bind
648 create_dummy_container "$root"
649 cmd='PERMISSIONS=$(stat -c "%u:%g" /mnt/file); if [[ $PERMISSIONS != "0:0" ]]; then echo "*** wrong permissions: $PERMISSIONS"; return 1; fi; touch /mnt/other_file'
650 if ! SYSTEMD_LOG_TARGET=console \
651 systemd-nspawn --register=no \
652 --directory="$root" \
653 --bind=/tmp/rootidmap/bind:/mnt:rootidmap \
654 bash -c "$cmd" |& tee nspawn.out; then
655 if grep -q "Failed to map ids for bind mount.*: Function not implemented" nspawn.out; then
656 echo "idmapped mounts are not supported, skipping the test..."
657 return 0
660 return 1
663 permissions=$(stat -c "%u:%g" /tmp/rootidmap/bind/other_file)
664 if [[ $permissions != "$owner:$owner" ]]; then
665 echo "*** wrong permissions: $permissions"
666 [[ "$IS_USERNS_SUPPORTED" == "yes" ]] && return 1
670 owneridmap_cleanup() {
671 local dir="${1:?}"
673 mountpoint -q "$dir/bind" && umount "$dir/bind"
674 rm -fr "$dir"
677 testcase_owneridmap() {
678 local root cmd permissions
679 local owner=1000
681 root="$(mktemp -d /var/lib/machines/TEST-13-NSPAWN.owneridmap-path.XXX)"
682 # Create ext4 image, as ext4 supports idmapped-mounts.
683 mkdir -p /tmp/owneridmap/bind
684 dd if=/dev/zero of=/tmp/owneridmap/ext4.img bs=4k count=2048
685 mkfs.ext4 /tmp/owneridmap/ext4.img
686 mount /tmp/owneridmap/ext4.img /tmp/owneridmap/bind
687 trap "owneridmap_cleanup /tmp/owneridmap/" RETURN
689 touch /tmp/owneridmap/bind/file
690 chown -R "$owner:$owner" /tmp/owneridmap/bind
692 # Allow users to read and execute / in order to execute binaries
693 chmod o+rx "$root"
695 create_dummy_container "$root"
697 # --user=
698 # "Fake" getent passwd's bare minimum, so we don't have to pull it in
699 # with all the DSO shenanigans
700 cat >"$root/bin/getent" <<\EOF
701 #!/bin/bash
703 if [[ $# -eq 0 ]]; then
705 elif [[ $1 == passwd ]]; then
706 echo "testuser:x:1010:1010:testuser:/:/bin/sh"
707 elif [[ $1 == initgroups ]]; then
708 echo "testuser"
711 chmod +x "$root/bin/getent"
713 # The useradd is important here so the user is added to /etc/passwd. If the user is not in /etc/passwd,
714 # bash will end up loading libnss_systemd.so which breaks when libnss_systemd.so is built with sanitizers
715 # as bash isn't invoked with the necessary environment variables for that.
716 useradd --root="$root" --uid 1010 --user-group --create-home testuser
718 cmd='PERMISSIONS=$(stat -c "%u:%g" /home/testuser/file); if [[ $PERMISSIONS != "1010:1010" ]]; then echo "*** wrong permissions: $PERMISSIONS"; return 1; fi; touch /home/testuser/other_file'
719 if ! SYSTEMD_LOG_TARGET=console \
720 systemd-nspawn --register=no \
721 --directory="$root" \
722 -U \
723 --user=testuser \
724 --bind=/tmp/owneridmap/bind:/home/testuser:owneridmap \
725 ${COVERAGE_BUILD_DIR:+--bind="$COVERAGE_BUILD_DIR"} \
726 /usr/bin/bash -c "$cmd" |& tee nspawn.out; then
727 if grep -q "Failed to map ids for bind mount.*: Function not implemented" nspawn.out; then
728 echo "idmapped mounts are not supported, skipping the test..."
729 return 0
732 return 1
735 permissions=$(stat -c "%u:%g" /tmp/owneridmap/bind/other_file)
736 if [[ $permissions != "$owner:$owner" ]]; then
737 echo "*** wrong permissions: $permissions"
738 [[ "$IS_USERNS_SUPPORTED" == "yes" ]] && return 1
742 testcase_notification_socket() {
743 # https://github.com/systemd/systemd/issues/4944
744 local root
745 local cmd='echo a | ncat -U -u -w 1 /run/host/notify'
747 root="$(mktemp -d /var/lib/machines/TEST-13-NSPAWN.check_notification_socket.XXX)"
748 create_dummy_container "$root"
750 systemd-nspawn --register=no --directory="$root" bash -x -c "$cmd"
751 systemd-nspawn --register=no --directory="$root" -U bash -x -c "$cmd"
753 rm -fr "$root"
756 testcase_os_release() {
757 local root entrypoint os_release_source
759 root="$(mktemp -d /var/lib/machines/TEST-13-NSPAWN.os-release.XXX)"
760 create_dummy_container "$root"
761 entrypoint="$root/entrypoint.sh"
762 cat >"$entrypoint" <<\EOF
763 #!/usr/bin/bash -ex
765 . /tmp/os-release
766 [[ -n "${ID:-}" && "$ID" != "$container_host_id" ]] && exit 1
767 [[ -n "${VERSION_ID:-}" && "$VERSION_ID" != "$container_host_version_id" ]] && exit 1
768 [[ -n "${BUILD_ID:-}" && "$BUILD_ID" != "$container_host_build_id" ]] && exit 1
769 [[ -n "${VARIANT_ID:-}" && "$VARIANT_ID" != "$container_host_variant_id" ]] && exit 1
771 cd /tmp
772 (cd /run/host && md5sum os-release) | md5sum -c
774 chmod +x "$entrypoint"
776 os_release_source="/etc/os-release"
777 if [[ ! -r "$os_release_source" ]]; then
778 os_release_source="/usr/lib/os-release"
779 elif [[ -L "$os_release_source" ]]; then
780 # Ensure that /etc always wins if available
781 cp --remove-destination -fv /usr/lib/os-release /etc/os-release
782 echo MARKER=1 >>/etc/os-release
785 systemd-nspawn --register=no \
786 --directory="$root" \
787 --bind="$os_release_source:/tmp/os-release" \
788 "${entrypoint##"$root"}"
790 if grep -q MARKER /etc/os-release; then
791 ln -svrf /usr/lib/os-release /etc/os-release
794 rm -fr "$root"
797 testcase_machinectl_bind() {
798 local service_path service_name root container_name ec
799 local cmd='for i in $(seq 1 20); do if test -f /tmp/marker && test -f /tmp/marker-varlink; then exit 0; fi; sleep .5; done; exit 1;'
801 root="$(mktemp -d /var/lib/machines/TEST-13-NSPAWN.machinectl-bind.XXX)"
802 create_dummy_container "$root"
803 container_name="$(basename "$root")"
805 service_path="$(mktemp /run/systemd/system/nspawn-machinectl-bind-XXX.service)"
806 service_name="${service_path##*/}"
807 cat >"$service_path" <<EOF
808 [Service]
809 Type=notify
810 ExecStart=systemd-nspawn --directory="$root" --notify-ready=no /usr/bin/bash -xec "$cmd"
813 systemctl daemon-reload
814 systemctl start "$service_name"
815 touch /tmp/marker
816 machinectl bind --mkdir "$container_name" /tmp/marker
817 touch /tmp/marker-varlink
818 varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.BindMount "{\"name\": \"$container_name\", \"source\": \"/tmp/marker-varlink\", \"mkdir\": true}"
820 timeout 10 bash -c "while [[ '\$(systemctl show -P SubState $service_name)' == running ]]; do sleep .2; done"
821 ec="$(systemctl show -P ExecMainStatus "$service_name")"
822 systemctl stop "$service_name"
824 rm -fr "$root" "$service_path"
826 return "$ec"
829 testcase_selinux() {
830 # Basic test coverage to avoid issues like https://github.com/systemd/systemd/issues/19976
831 if ! command -v selinuxenabled >/dev/null || ! selinuxenabled; then
832 echo >&2 "SELinux is not enabled, skipping SELinux-related tests"
833 return 0
836 local root
838 root="$(mktemp -d /var/lib/machines/TEST-13-NSPAWN.selinux.XXX)"
839 create_dummy_container "$root"
840 chcon -R -t container_t "$root"
842 systemd-nspawn --register=no \
843 --boot \
844 --directory="$root" \
845 --selinux-apifs-context=system_u:object_r:container_file_t:s0:c0,c1 \
846 --selinux-context=system_u:system_r:container_t:s0:c0,c1
848 rm -fr "$root"
851 testcase_ephemeral_config() {
852 # https://github.com/systemd/systemd/issues/13297
853 local root container_name
855 root="$(mktemp -d /var/lib/machines/TEST-13-NSPAWN.ephemeral-config.XXX)"
856 create_dummy_container "$root"
857 container_name="$(basename "$root")"
859 mkdir -p /run/systemd/nspawn/
860 rm -f "/etc/systemd/nspawn/$container_name.nspawn"
861 cat >"/run/systemd/nspawn/$container_name.nspawn" <<EOF
862 [Files]
863 ${COVERAGE_BUILD_DIR:+"Bind=$COVERAGE_BUILD_DIR"}
864 BindReadOnly=/tmp/ephemeral-config
866 touch /tmp/ephemeral-config
868 systemd-nspawn --register=no \
869 --directory="$root" \
870 --ephemeral \
871 bash -x -c "test -f /tmp/ephemeral-config"
873 systemd-nspawn --register=no \
874 --directory="$root" \
875 --ephemeral \
876 --machine=foobar \
877 bash -x -c "! test -f /tmp/ephemeral-config"
879 rm -fr "$root" "/run/systemd/nspawn/$container_name.nspawn"
882 matrix_run_one() {
883 local cgroupsv2="${1:?}"
884 local use_cgns="${2:?}"
885 local api_vfs_writable="${3:?}"
886 local root
888 if [[ "$cgroupsv2" == "yes" && "$IS_CGROUPSV2_SUPPORTED" == "no" ]]; then
889 echo >&2 "Unified cgroup hierarchy is not supported, skipping..."
890 return 0
893 if [[ "$use_cgns" == "yes" && "$IS_CGNS_SUPPORTED" == "no" ]]; then
894 echo >&2 "CGroup namespaces are not supported, skipping..."
895 return 0
898 root="$(mktemp -d "/var/lib/machines/TEST-13-NSPAWN.unified-$1-cgns-$2-api-vfs-writable-$3.XXX")"
899 create_dummy_container "$root"
901 SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$cgroupsv2" SYSTEMD_NSPAWN_USE_CGNS="$use_cgns" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$api_vfs_writable" \
902 systemd-nspawn --register=no \
903 --directory="$root" \
904 --boot
906 SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$cgroupsv2" SYSTEMD_NSPAWN_USE_CGNS="$use_cgns" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$api_vfs_writable" \
907 systemd-nspawn --register=no \
908 --directory="$root" \
909 --private-network \
910 --boot
912 if SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$cgroupsv2" SYSTEMD_NSPAWN_USE_CGNS="$use_cgns" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$api_vfs_writable" \
913 systemd-nspawn --register=no \
914 --directory="$root" \
915 --private-users=pick \
916 --boot; then
917 [[ "$IS_USERNS_SUPPORTED" == "yes" && "$api_vfs_writable" == "network" ]] && return 1
918 else
919 [[ "$IS_USERNS_SUPPORTED" == "no" && "$api_vfs_writable" == "network" ]] && return 1
922 if SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$cgroupsv2" SYSTEMD_NSPAWN_USE_CGNS="$use_cgns" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$api_vfs_writable" \
923 systemd-nspawn --register=no \
924 --directory="$root" \
925 --private-network \
926 --private-users=pick \
927 --boot; then
928 [[ "$IS_USERNS_SUPPORTED" == "yes" && "$api_vfs_writable" == "yes" ]] && return 1
929 else
930 [[ "$IS_USERNS_SUPPORTED" == "no" && "$api_vfs_writable" = "yes" ]] && return 1
933 local netns_opt="--network-namespace-path=/proc/self/ns/net"
934 local net_opt
935 local net_opts=(
936 "--network-bridge=lo"
937 "--network-interface=lo"
938 "--network-ipvlan=lo"
939 "--network-macvlan=lo"
940 "--network-veth"
941 "--network-veth-extra=lo"
942 "--network-zone=zone"
945 # --network-namespace-path and network-related options cannot be used together
946 for net_opt in "${net_opts[@]}"; do
947 echo "$netns_opt in combination with $net_opt should fail"
948 if SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$cgroupsv2" SYSTEMD_NSPAWN_USE_CGNS="$use_cgns" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$api_vfs_writable" \
949 systemd-nspawn --register=no \
950 --directory="$root" \
951 --boot \
952 "$netns_opt" \
953 "$net_opt"; then
954 echo >&2 "unexpected pass"
955 return 1
957 done
959 # allow combination of --network-namespace-path and --private-network
960 SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$cgroupsv2" SYSTEMD_NSPAWN_USE_CGNS="$use_cgns" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$api_vfs_writable" \
961 systemd-nspawn --register=no \
962 --directory="$root" \
963 --boot \
964 --private-network \
965 "$netns_opt"
967 # test --network-namespace-path works with a network namespace created by "ip netns"
968 ip netns add nspawn_test
969 netns_opt="--network-namespace-path=/run/netns/nspawn_test"
970 SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$cgroupsv2" SYSTEMD_NSPAWN_USE_CGNS="$use_cgns" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$api_vfs_writable" \
971 systemd-nspawn --register=no \
972 --directory="$root" \
973 --network-namespace-path=/run/netns/nspawn_test \
974 ip a | grep -v -E '^1: lo.*UP'
975 ip netns del nspawn_test
977 rm -fr "$root"
979 return 0
982 testcase_api_vfs() {
983 local api_vfs_writable
985 for api_vfs_writable in yes no network; do
986 matrix_run_one no no $api_vfs_writable
987 matrix_run_one yes no $api_vfs_writable
988 matrix_run_one no yes $api_vfs_writable
989 matrix_run_one yes yes $api_vfs_writable
990 done
993 testcase_check_os_release() {
994 # https://github.com/systemd/systemd/issues/29185
995 local base common_opts root
997 base="$(mktemp -d /var/lib/machines/TEST-13-NSPAWN.check_os_release_base.XXX)"
998 root="$(mktemp -d /var/lib/machines/TEST-13-NSPAWN.check_os_release.XXX)"
999 create_dummy_container "$base"
1000 cp -d "$base"/{bin,sbin,lib,lib64} "$root/"
1001 common_opts=(
1002 --boot
1003 --register=no
1004 --directory="$root"
1005 --bind-ro="$base/usr:/usr"
1008 # Might be needed to find libraries
1009 if [ -f "$base/etc/ld.so.cache" ]; then
1010 common_opts+=("--bind-ro=$base/etc/ld.so.cache:/etc/ld.so.cache")
1013 # Empty /etc/ & /usr/
1014 (! systemd-nspawn "${common_opts[@]}")
1015 (! SYSTEMD_NSPAWN_CHECK_OS_RELEASE=1 systemd-nspawn "${common_opts[@]}")
1016 (! SYSTEMD_NSPAWN_CHECK_OS_RELEASE=foo systemd-nspawn "${common_opts[@]}")
1017 SYSTEMD_NSPAWN_CHECK_OS_RELEASE=0 systemd-nspawn "${common_opts[@]}"
1019 # Empty /usr/ + a broken /etc/os-release -> /usr/os-release symlink
1020 ln -svrf "$root/etc/os-release" "$root/usr/os-release"
1021 (! systemd-nspawn "${common_opts[@]}")
1022 (! SYSTEMD_NSPAWN_CHECK_OS_RELEASE=1 systemd-nspawn "${common_opts[@]}")
1023 SYSTEMD_NSPAWN_CHECK_OS_RELEASE=0 systemd-nspawn "${common_opts[@]}"
1025 rm -fr "$root" "$base"
1028 testcase_ip_masquerade() {
1029 local root
1031 if ! command -v networkctl >/dev/null; then
1032 echo "This test requires systemd-networkd, skipping..."
1033 return 0
1036 systemctl unmask systemd-networkd.service
1037 systemctl edit --runtime --stdin systemd-networkd.service --drop-in=debug.conf <<EOF
1038 [Service]
1039 Environment=SYSTEMD_LOG_LEVEL=debug
1041 systemctl start systemd-networkd.service
1043 root="$(mktemp -d /var/lib/machines/TEST-13-NSPAWN.ip_masquerade.XXX)"
1044 create_dummy_container "$root"
1046 systemd-run --unit=nspawn-hoge.service \
1047 systemd-nspawn \
1048 --register=no \
1049 --directory="$root" \
1050 --ephemeral \
1051 --machine=hoge \
1052 --network-veth \
1053 bash -x -c "ip link set host0 up; sleep 30s"
1055 /usr/lib/systemd/systemd-networkd-wait-online -i ve-hoge --timeout 30s
1057 # Check IPMasquerade= for ve-* and friends enabled IP forwarding.
1058 [[ "$(cat /proc/sys/net/ipv4/conf/all/forwarding)" == "1" ]]
1059 [[ "$(cat /proc/sys/net/ipv4/conf/default/forwarding)" == "1" ]]
1060 [[ "$(cat /proc/sys/net/ipv6/conf/all/forwarding)" == "1" ]]
1061 [[ "$(cat /proc/sys/net/ipv6/conf/default/forwarding)" == "1" ]]
1063 systemctl stop nspawn-hoge.service || :
1064 systemctl stop systemd-networkd.service
1065 systemctl mask systemd-networkd.service
1067 rm -fr "$root"
1070 can_do_rootless_nspawn() {
1071 # Our create_dummy_ddi() uses squashfs and openssl.
1072 command -v mksquashfs &&
1073 command -v openssl &&
1075 # mountfsd must be enabled...
1076 [[ -S /run/systemd/io.systemd.MountFileSystem ]] &&
1077 # ...and have pidfd support for unprivileged operation.
1078 systemd-analyze compare-versions "$(uname -r)" ge 6.5 &&
1079 systemd-analyze compare-versions "$(pkcheck --version | awk '{print $3}')" ge 124 &&
1081 # nsresourced must be enabled...
1082 [[ -S /run/systemd/userdb/io.systemd.NamespaceResource ]] &&
1083 # ...and must support the UserNamespaceInterface.
1084 ! (SYSTEMD_LOG_TARGET=console varlinkctl call \
1085 /run/systemd/userdb/io.systemd.NamespaceResource \
1086 io.systemd.NamespaceResource.AllocateUserRange \
1087 '{"name":"test-supported","size":65536,"userNamespaceFileDescriptor":0}' \
1088 2>&1 || true) |
1089 grep -q "io.systemd.NamespaceResource.UserNamespaceInterfaceNotSupported"
1092 create_dummy_ddi() {
1093 local outdir="${1:?}"
1094 local container_name="${2:?}"
1096 cat >"$outdir"/openssl.conf <<EOF
1097 [ req ]
1098 prompt = no
1099 distinguished_name = req_distinguished_name
1101 [ req_distinguished_name ]
1102 C = DE
1103 ST = Test State
1104 L = Test Locality
1105 O = Org Name
1106 OU = Org Unit Name
1107 CN = Common Name
1108 emailAddress = test@email.com
1111 openssl req -config "$outdir"/openssl.conf -subj="/CN=waldo" \
1112 -x509 -sha256 -nodes -days 365 -newkey rsa:4096 \
1113 -keyout "${outdir}/${container_name}.key" -out "${outdir}/${container_name}.crt"
1115 mkdir -p /run/verity.d
1116 cp "${outdir}/${container_name}.crt" "/run/verity.d/test-13-nspawn-${container_name}.crt"
1118 SYSTEMD_REPART_OVERRIDE_FSTYPE=squashfs \
1119 systemd-repart --make-ddi=portable \
1120 --copy-source=/usr/share/TEST-13-NSPAWN-container-template \
1121 --certificate="${outdir}/${container_name}.crt" \
1122 --private-key="${outdir}/${container_name}.key" \
1123 "${outdir}/${container_name}.raw"
1126 testcase_unpriv() {
1127 if ! can_do_rootless_nspawn; then
1128 echo "Skipping rootless test..."
1129 return 0
1132 local tmpdir name
1133 tmpdir="$(mktemp -d /var/tmp/TEST-13-NSPAWN.unpriv.XXX)"
1134 # Note: we pick the machine name short enough to be a valid machine name,
1135 # but definitely longer than 16 chars, so that userns name mangling in the
1136 # nsresourced userns allocation logic is triggered and tested. */
1137 name="unprv-${tmpdir##*.}-somelongsuffix"
1138 trap 'rm -fr ${tmpdir@Q} || true; rm -f /run/verity.d/test-13-nspawn-${name@Q} || true' RETURN ERR
1139 create_dummy_ddi "$tmpdir" "$name"
1140 chown --recursive testuser: "$tmpdir"
1142 systemd-run \
1143 --pipe \
1144 --uid=testuser \
1145 --property=Delegate=yes \
1146 -- \
1147 systemd-nspawn --pipe --private-network --register=no --keep-unit --image="$tmpdir/$name.raw" echo hello >"$tmpdir/stdout.txt"
1148 echo hello | cmp "$tmpdir/stdout.txt" -
1150 # Make sure per-user search path logic works
1151 systemd-run --pipe --uid=testuser mkdir -p /home/testuser/.local/state/machines
1152 systemd-run --pipe --uid=testuser ln -s "$tmpdir/$name.raw" /home/testuser/.local/state/machines/"x$name.raw"
1153 systemd-run \
1154 --pipe \
1155 --uid=testuser \
1156 --property=Delegate=yes \
1157 -- \
1158 systemd-nspawn --pipe --private-network --register=no --keep-unit --machine="x$name" echo hello >"$tmpdir/stdout.txt"
1159 echo hello | cmp "$tmpdir/stdout.txt" -
1162 testcase_fuse() {
1163 if [[ "$(cat <>/dev/fuse 2>&1)" != 'cat: -: Operation not permitted' ]]; then
1164 echo "FUSE is not supported, skipping the test..."
1165 return 0
1168 # Assume that the tests are running on a kernel that is new enough for FUSE
1169 # to have user-namespace support; and so we should expect that nspawn
1170 # enables FUSE. This test does not validate that the version check
1171 # disables FUSE on old kernels.
1173 local root
1175 root="$(mktemp -d /var/lib/machines/TEST-13-NSPAWN.fuse.XXX)"
1176 create_dummy_container "$root"
1178 # To avoid adding any complex dependencies to the test, we simply check
1179 # that /dev/fuse can be opened for reading and writing (O_RDWR), but that
1180 # actually reading from it fails with EPERM. This can be done with a
1181 # simple Bash script: run `cat <>/dev/fuse` and if the EPERM error message
1182 # comes from "bash" then we know it couldn't be opened, while if it comes
1183 # from "cat" then we know that it was opened but not read. If we are able
1184 # to read from the file, then this indicates that it's not a real FUSE
1185 # device (which requires us to mount a type="fuse" filesystem with the
1186 # option string "fd=${num}" for /dev/fuse FD before reading from it will
1187 # return anything other than EPERM); if this happens then most likely
1188 # nspawn didn't create the file at all and Bash "<>" simply created a new
1189 # normal file.
1191 # "cat: -: Operation not permitted" # pass the test; opened but not read
1192 # "bash: line 1: /dev/fuse: Operation not permitted" # fail the test; could not open
1193 # "" # fail the test; reading worked
1194 [[ "$(systemd-nspawn --pipe --directory="$root" \
1195 bash -c 'cat <>/dev/fuse' 2>&1)" == 'cat: -: Operation not permitted' ]]
1197 rm -fr "$root"
1200 testcase_unpriv_fuse() {
1201 # Same as above, but for unprivileged operation.
1203 if [[ "$(cat <>/dev/fuse 2>&1)" != 'cat: -: Operation not permitted' ]]; then
1204 echo "FUSE is not supported, skipping the test..."
1205 return 0
1207 if ! can_do_rootless_nspawn; then
1208 echo "Skipping rootless test..."
1209 return 0
1212 local tmpdir name
1213 tmpdir="$(mktemp -d /var/tmp/TEST-13-NSPAWN.unpriv-fuse.XXX)"
1214 # $name must be such that len("ns-$(id -u testuser)-nspawn-${name}-65535")
1215 # <= 31, or nsresourced will reject the request for a namespace.
1216 # Therefore; len($name) <= 10 bytes.
1217 name="ufuse-${tmpdir##*.}"
1218 trap 'rm -fr ${tmpdir@Q} || true; rm -f /run/verity.d/test-13-nspawn-${name@Q} || true' RETURN ERR
1219 create_dummy_ddi "$tmpdir" "$name"
1220 chown --recursive testuser: "$tmpdir"
1222 [[ "$(systemd-run \
1223 --pipe \
1224 --uid=testuser \
1225 --property=Delegate=yes \
1226 --setenv=SYSTEMD_LOG_LEVEL \
1227 --setenv=SYSTEMD_LOG_TARGET \
1228 -- \
1229 systemd-nspawn --pipe --private-network --register=no --keep-unit --image="$tmpdir/$name.raw" \
1230 bash -c 'cat <>/dev/fuse' 2>&1)" == *'cat: -: Operation not permitted' ]]
1233 test_tun() {
1234 local expect=${1?}
1235 local exists=${2?}
1236 local command command_exists command_not_exists
1237 shift 2
1239 command_exists='[[ -c /dev/net/tun ]]; [[ "$(stat /dev/net/tun --format=%u)" == 0 ]]; [[ "$(stat /dev/net/tun --format=%g)" == 0 ]]'
1240 command_not_exists='[[ ! -e /dev/net/tun ]]'
1242 if [[ "$exists" == 0 ]]; then
1243 command="$command_not_exists"
1244 else
1245 command="$command_exists"
1248 systemd-nspawn "$@" bash -xec "$command_exists"
1250 # check if the owner of the host device is unchanged, see issue #34243.
1251 [[ "$(stat /dev/net/tun --format=%u)" == 0 ]]
1252 [[ "$(stat /dev/net/tun --format=%g)" == 0 ]]
1254 # Without DeviceAllow= for /dev/net/tun, see issue #35116.
1255 assert_rc \
1256 "$expect" \
1257 systemd-run --wait -p Environment=SYSTEMD_LOG_LEVEL=debug -p DevicePolicy=closed -p DeviceAllow="char-pts rw" \
1258 systemd-nspawn "$@" bash -xec "$command"
1260 [[ "$(stat /dev/net/tun --format=%u)" == 0 ]]
1261 [[ "$(stat /dev/net/tun --format=%g)" == 0 ]]
1264 testcase_dev_net_tun() {
1265 local root
1267 if [[ ! -c /dev/net/tun ]]; then
1268 echo "/dev/net/tun does not exist, skipping tests"
1269 return 0
1272 root="$(mktemp -d /var/lib/machines/TEST-13-NSPAWN.tun.XXX)"
1273 create_dummy_container "$root"
1275 test_tun 0 1 --ephemeral --directory="$root" --private-users=no
1276 test_tun 0 1 --ephemeral --directory="$root" --private-users=yes
1277 test_tun 0 0 --ephemeral --directory="$root" --private-users=pick
1278 test_tun 0 1 --ephemeral --directory="$root" --private-users=no --private-network
1279 test_tun 0 1 --ephemeral --directory="$root" --private-users=yes --private-network
1280 test_tun 1 0 --ephemeral --directory="$root" --private-users=pick --private-network
1282 rm -fr "$root"
1285 testcase_unpriv_dir() {
1286 if ! can_do_rootless_nspawn; then
1287 echo "Skipping rootless test..."
1288 return 0
1291 root="$(mktemp -d /var/lib/machines/TEST-13-NSPAWN.unpriv.XXX)"
1292 create_dummy_container "$root"
1294 assert_eq "$(systemd-nspawn --pipe --register=no -D "$root" --private-users=no bash -c 'echo foobar')" "foobar"
1296 # Use an image owned by some freshly acquired container user
1297 assert_eq "$(systemd-nspawn --pipe --register=no -D "$root" --private-users=pick --private-users-ownership=chown bash -c 'echo foobar')" "foobar"
1298 assert_eq "$(systemd-nspawn --pipe --register=no -D "$root" --private-users=yes --private-users-ownership=chown bash -c 'echo foobar')" "foobar"
1300 # Now move back to root owned, and try to use fs idmapping
1301 systemd-dissect --shift "$root" 0
1302 assert_eq "$(systemd-nspawn --pipe --register=no -D "$root" --private-users=no --private-users-ownership=no bash -c 'echo foobar')" "foobar"
1303 assert_eq "$(systemd-nspawn --pipe --register=no -D "$root" --private-users=pick --private-users-ownership=map bash -c 'echo foobar')" "foobar"
1305 # Use an image owned by the foreign UID range first via direct mapping, and than via the managed uid logic
1306 systemd-dissect --shift "$root" foreign
1307 assert_eq "$(systemd-nspawn --pipe --register=no -D "$root" --private-users=pick --private-users-ownership=foreign bash -c 'echo foobar')" "foobar"
1308 assert_eq "$(systemd-nspawn --pipe --register=no -D "$root" --private-users=managed --private-network bash -c 'echo foobar')" "foobar"
1310 # Test unprivileged operation
1311 chown testuser:testuser "$root/.."
1313 ls -al "/var/lib/machines"
1314 ls -al "$root"
1316 assert_eq "$(run0 --pipe -u testuser systemd-nspawn --pipe --register=no -D "$root" --private-users=managed --private-network bash -c 'echo foobar')" "foobar"
1317 assert_eq "$(run0 --pipe -u testuser systemd-nspawn --pipe --register=no -D "$root" --private-network bash -c 'echo foobar')" "foobar"
1318 chown root:root "$root/.."
1320 rm -rf "$root"
1323 run_testcases