Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -6030,6 +6030,10 @@ EXTRA_DIST += \
test/TEST-11-ISSUE-3166/test.sh \
test/TEST-12-ISSUE-3171/Makefile \
test/TEST-12-ISSUE-3171/test.sh \
test/TEST-13-NSPAWN-SMOKE/Makefile \
test/TEST-13-NSPAWN-SMOKE/has-overflow.c \
test/TEST-13-NSPAWN-SMOKE/create-busybox-container \
test/TEST-13-NSPAWN-SMOKE/test.sh \
test/test-functions

EXTRA_DIST += \
Expand Down
7 changes: 7 additions & 0 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,13 @@ REQUIREMENTS:
under all circumstances. In fact, systemd-hostnamed will warn
if nss-myhostname is not installed.

Additional packages are necessary to run some tests:
- busybox (used by test/TEST-13-NSPAWN-SMOKE)
- nc (used by test/TEST-12-ISSUE-3171)
- python3-pyparsing
- python3-evdev (used by hwdb parsing tests)
- strace (used by test/test-functions)

USERS AND GROUPS:
Default udev rules use the following standard system group
names, which need to be resolvable by getgrnam() at any time,
Expand Down
11 changes: 11 additions & 0 deletions test/TEST-13-NSPAWN-SMOKE/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
all: has-overflow
@make -s --no-print-directory -C ../.. all
@basedir=../.. TEST_BASE_DIR=../ ./test.sh --all
setup: has-overflow
@make --no-print-directory -C ../.. all
@basedir=../.. TEST_BASE_DIR=../ ./test.sh --setup
clean:
@basedir=../.. TEST_BASE_DIR=../ ./test.sh --clean
@rm -f has-overflow
run:
@basedir=../.. TEST_BASE_DIR=../ ./test.sh --run
53 changes: 53 additions & 0 deletions test/TEST-13-NSPAWN-SMOKE/create-busybox-container
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/bin/bash

set -e
set -u
set -o pipefail

root="${1:?Usage $0 container-root}"
mkdir -p "$root"
mkdir "$root/bin"
cp $(type -P busybox) "$root/bin"

mkdir -p "$root/usr/lib"
touch "$root/usr/lib/os-release"

ln -s busybox "$root/bin/sh"
ln -s busybox "$root/bin/cat"
ln -s busybox "$root/bin/tr"
ln -s busybox "$root/bin/ps"
ln -s busybox "$root/bin/ip"

mkdir -p "$root/sbin"
cat <<'EOF' >"$root/sbin/init"
#!/bin/sh

printf "ps aufx:\n"
ps aufx

printf "/proc/1/cmdline:\n"
printf "%s\n\n" "$(tr '\0' ' ' </proc/1/cmdline)"

printf "/proc/1/environ:\n"
printf "%s\n\n" "$(tr '\0' '\n' </proc/1/environ)"

printf "/proc/1/mountinfo:\n"
cat /proc/self/mountinfo
printf "\n"

printf "/proc/1/cgroup:\n"
printf "%s\n\n" "$(cat /proc/1/cgroup)"

printf "/proc/1/uid_map:\n"
printf "%s\n\n" "$(cat /proc/1/uid_map)"

printf "/proc/1/setgroups:\n"
printf "%s\n\n" "$(cat /proc/1/setgroups)"

printf "/proc/1/gid_map:\n"
printf "%s\n\n" "$(cat /proc/1/gid_map)"

printf "ip link:\n"
ip link
EOF
chmod +x "$root/sbin/init"
143 changes: 143 additions & 0 deletions test/TEST-13-NSPAWN-SMOKE/has-overflow.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#define _GNU_SOURCE
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mount.h>
#include <sched.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <signal.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <grp.h>

#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0)

struct child_args {
int pipe_fd[2]; /* Pipe used to synchronize parent and child */
};

static void usage(char *pname) {
fprintf(stderr, "Options can be:\n");
fprintf(stderr, "\t-M uid_map Specify UID map for user namespace\n");
fprintf(stderr, "\t-G gid_map Specify GID map for user namespace\n");

exit(EXIT_FAILURE);
}

static void update_map(char *mapping, char *map_file) {
int fd, j;
size_t map_len;

map_len = strlen(mapping);

fd = open(map_file, O_RDWR);
if (fd == -1) {
fprintf(stderr, "ERROR: open %s: %s\n", map_file, strerror(errno));
exit(EXIT_FAILURE);
}

if (write(fd, mapping, map_len) != map_len) {
fprintf(stderr, "ERROR: write %s: %s\n", map_file, strerror(errno));
exit(EXIT_FAILURE);
}

close(fd);
}

static void proc_setgroups_write(pid_t child_pid, char *str) {
char setgroups_path[PATH_MAX];
int fd;

snprintf(setgroups_path, PATH_MAX, "/proc/%ld/setgroups", (long) child_pid);

fd = open(setgroups_path, O_RDWR);
if (fd == -1) {
if (errno != ENOENT)
fprintf(stderr, "ERROR: open %s: %s\n", setgroups_path, strerror(errno));
return;
}

if (write(fd, str, strlen(str)) == -1)
fprintf(stderr, "ERROR: write %s: %s\n", setgroups_path, strerror(errno));

close(fd);
}

static int child_func(void *arg) {
struct child_args *args = (struct child_args *) arg;
char ch;

close(args->pipe_fd[1]);
if (read(args->pipe_fd[0], &ch, 1) != 0) {
fprintf(stderr, "Failure in child: read from pipe returned != 0\n");
exit(EXIT_FAILURE);
}

mount("tmpfs", "/tmp", "tmpfs", MS_MGC_VAL, "mode=777,uid=0,gid=0");
if (mkdir("/tmp/hey", 0777) < 0)
exit(EXIT_FAILURE);

exit(EXIT_SUCCESS);
}

#define STACK_SIZE (1024 * 1024)

static char child_stack[STACK_SIZE];

int main(int argc, char *argv[]) {
int flags, opt;
pid_t child_pid;
struct child_args args;
char *uid_map, *gid_map;
const int MAP_BUF_SIZE = 100;
char map_buf[MAP_BUF_SIZE];
char map_path[PATH_MAX];
int status;

flags = 0;
gid_map = NULL;
uid_map = NULL;
while ((opt = getopt(argc, argv, "+M:G:")) != -1) {
switch (opt) {
case 'M':
uid_map = optarg;
break;
case 'G':
gid_map = optarg;
break;
default:
usage(argv[0]);
}
}

if (!uid_map || !gid_map)
usage(argv[0]);

flags |= CLONE_NEWNS;
flags |= CLONE_NEWUSER;

if (pipe(args.pipe_fd) == -1)
errExit("pipe");

child_pid = clone(child_func, child_stack + STACK_SIZE, flags | SIGCHLD, &args);
if (child_pid == -1)
errExit("clone");

snprintf(map_path, PATH_MAX, "/proc/%ld/uid_map", (long) child_pid);
update_map(uid_map, map_path);

proc_setgroups_write(child_pid, "allow");
snprintf(map_path, PATH_MAX, "/proc/%ld/gid_map", (long) child_pid);
update_map(gid_map, map_path);

close(args.pipe_fd[1]);

if (waitpid(child_pid, &status, 0) == -1)
errExit("waitpid");

exit(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS ? EXIT_FAILURE : EXIT_SUCCESS);
}
138 changes: 138 additions & 0 deletions test/TEST-13-NSPAWN-SMOKE/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#!/bin/bash
# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
# ex: ts=8 sw=4 sts=4 et filetype=sh
TEST_DESCRIPTION="systemd-nspawn smoke test"
SKIP_INITRD=yes
. $TEST_BASE_DIR/test-functions

check_result_qemu() {
ret=1
mkdir -p $TESTDIR/root
mount ${LOOPDEV}p1 $TESTDIR/root
[[ -e $TESTDIR/root/testok ]] && ret=0
[[ -f $TESTDIR/root/failed ]] && cp -a $TESTDIR/root/failed $TESTDIR
cp -a $TESTDIR/root/var/log/journal $TESTDIR
umount $TESTDIR/root
[[ -f $TESTDIR/failed ]] && cat $TESTDIR/failed
ls -l $TESTDIR/journal/*/*.journal
test -s $TESTDIR/failed && ret=$(($ret+1))
return $ret
}

test_run() {
if run_qemu; then
check_result_qemu || return 1
else
dwarn "can't run QEMU, skipping"
fi
return 0
}

test_setup() {
create_empty_image
mkdir -p $TESTDIR/root
mount ${LOOPDEV}p1 $TESTDIR/root

# Create what will eventually be our root filesystem onto an overlay
(
LOG_LEVEL=5
eval $(udevadm info --export --query=env --name=${LOOPDEV}p2)

setup_basic_environment
dracut_install busybox chmod rmdir
dracut_install ./has-overflow

cp create-busybox-container $initdir/

# setup the testsuite service
cat >$initdir/etc/systemd/system/testsuite.service <<EOF
[Unit]
Description=Testsuite service
After=multi-user.target

[Service]
ExecStart=/test-nspawn.sh
Type=oneshot
EOF

cat >$initdir/test-nspawn.sh <<'EOF'
#!/bin/bash
set -x
set -e
set -u
set -o pipefail

export SYSTEMD_LOG_LEVEL=debug

# check cgroup-v2
is_v2_supported=no
mkdir -p /tmp/cgroup2
if mount -t cgroup2 cgroup2 /tmp/cgroup2; then
is_v2_supported=yes
umount /tmp/cgroup2
fi
rmdir /tmp/cgroup2

# check cgroup namespaces
is_cgns_supported=no
if [[ -f /proc/1/ns/cgroup ]]; then
is_cgns_supported=yes
fi

function run {
if [[ "$1" = "yes" && "$is_v2_supported" = "no" ]]; then
printf "Unified cgroup hierarchy is not supported. Skipping.\n" >&2
return 0
fi
if [[ "$2" = "yes" && "$is_cgns_supported" = "no" ]]; then
printf "Cgroup namespaces are not supported. Skipping.\n" >&2
return 0
fi

local _root="/var/lib/machines/unified-$1-cgns-$2"
/create-busybox-container "$_root"
UNIFIED_CGROUP_HIERARCHY="$1" SYSTEMD_NSPAWN_USE_CGNS="$2" systemd-nspawn --register=no -D "$_root" -b
UNIFIED_CGROUP_HIERARCHY="$1" SYSTEMD_NSPAWN_USE_CGNS="$2" systemd-nspawn --register=no -D "$_root" --private-network -b

if ! UNIFIED_CGROUP_HIERARCHY="$1" SYSTEMD_NSPAWN_USE_CGNS="$2" systemd-nspawn --register=no -D "$_root" -U -b; then
if [[ "$1" = "no" && "$2" = "yes" ]] && /has-overflow -M '0 1073283072 65536' -G '0 1073283072 65536'; then
printf "Failure expected, ignoring (see https://github.com/systemd/systemd/issues/4352)\n" >&2
else
return 1
fi
fi

if ! UNIFIED_CGROUP_HIERARCHY="$1" SYSTEMD_NSPAWN_USE_CGNS="$2" systemd-nspawn --register=no -D "$_root" --private-network -U -b; then
if [[ "$1" = "no" && "$2" = "yes" ]] && /has-overflow -M '0 1073283072 65536' -G '0 1073283072 65536'; then
printf "Failure expected, ignoring (see https://github.com/systemd/systemd/issues/4352)\n" >&2
else
return 1
fi
fi

return 0
}

run no no
run yes no
run no yes
run yes yes

touch /testok
EOF

chmod 0755 $initdir/test-nspawn.sh
setup_testsuite
) || return 1

ddebug "umount $TESTDIR/root"
umount $TESTDIR/root
}

test_cleanup() {
umount $TESTDIR/root 2>/dev/null
[[ $LOOPDEV ]] && losetup -d $LOOPDEV
return 0
}

do_test "$@"