diff --git a/src/core/cgroup.c b/src/core/cgroup.c index 15dfc56fb7..535f457fa8 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -698,16 +698,14 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) { prefix, bpf_cgroup_attach_type_to_string(p->attach_type), p->bpffs_path); if (c->socket_bind_allow) { - fprintf(f, "%sSocketBindAllow:", prefix); - LIST_FOREACH(socket_bind_items, bi, c->socket_bind_allow) - cgroup_context_dump_socket_bind_item(bi, f); + fprintf(f, "%sSocketBindAllow: ", prefix); + cgroup_context_dump_socket_bind_items(c->socket_bind_allow, f); fputc('\n', f); } if (c->socket_bind_deny) { - fprintf(f, "%sSocketBindDeny:", prefix); - LIST_FOREACH(socket_bind_items, bi, c->socket_bind_deny) - cgroup_context_dump_socket_bind_item(bi, f); + fprintf(f, "%sSocketBindDeny: ", prefix); + cgroup_context_dump_socket_bind_items(c->socket_bind_deny, f); fputc('\n', f); } @@ -734,16 +732,29 @@ void cgroup_context_dump_socket_bind_item(const CGroupSocketBindItem *item, FILE } if (item->nr_ports == 0) - fprintf(f, " %s%s%s%sany", family, colon1, protocol, colon2); + fprintf(f, "%s%s%s%sany", family, colon1, protocol, colon2); else if (item->nr_ports == 1) - fprintf(f, " %s%s%s%s%" PRIu16, family, colon1, protocol, colon2, item->port_min); + fprintf(f, "%s%s%s%s%" PRIu16, family, colon1, protocol, colon2, item->port_min); else { uint16_t port_max = item->port_min + item->nr_ports - 1; - fprintf(f, " %s%s%s%s%" PRIu16 "-%" PRIu16, family, colon1, protocol, colon2, + fprintf(f, "%s%s%s%s%" PRIu16 "-%" PRIu16, family, colon1, protocol, colon2, item->port_min, port_max); } } +void cgroup_context_dump_socket_bind_items(const CGroupSocketBindItem *items, FILE *f) { + bool first = true; + + LIST_FOREACH(socket_bind_items, bi, items) { + if (first) + first = false; + else + fputc(' ', f); + + cgroup_context_dump_socket_bind_item(bi, f); + } +} + int cgroup_context_add_device_allow(CGroupContext *c, const char *dev, CGroupDevicePermissions p) { _cleanup_free_ CGroupDeviceAllow *a = NULL; _cleanup_free_ char *d = NULL; diff --git a/src/core/cgroup.h b/src/core/cgroup.h index 9816966dbb..d7cc842835 100644 --- a/src/core/cgroup.h +++ b/src/core/cgroup.h @@ -273,6 +273,7 @@ void cgroup_context_init(CGroupContext *c); void cgroup_context_done(CGroupContext *c); void cgroup_context_dump(Unit *u, FILE* f, const char *prefix); void cgroup_context_dump_socket_bind_item(const CGroupSocketBindItem *item, FILE *f); +void cgroup_context_dump_socket_bind_items(const CGroupSocketBindItem *items, FILE *f); void cgroup_context_free_device_allow(CGroupContext *c, CGroupDeviceAllow *a); void cgroup_context_free_io_device_weight(CGroupContext *c, CGroupIODeviceWeight *w); diff --git a/src/core/execute-serialize.c b/src/core/execute-serialize.c index 63ed6a6afc..18033fe9c3 100644 --- a/src/core/execute-serialize.c +++ b/src/core/execute-serialize.c @@ -2968,9 +2968,9 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { return r; c->ioprio_set = true; } else if ((val = startswith(l, "exec-context-cpu-scheduling-policy="))) { - r = sched_policy_from_string(val); - if (r < 0) - return r; + c->cpu_sched_policy = sched_policy_from_string(val); + if (c->cpu_sched_policy < 0) + return -EINVAL; c->cpu_sched_set = true; } else if ((val = startswith(l, "exec-context-cpu-scheduling-priority="))) { r = safe_atoi(val, &c->cpu_sched_priority); diff --git a/src/test/test-socket-bind.c b/src/test/test-socket-bind.c index 9372f208ae..84a897801a 100644 --- a/src/test/test-socket-bind.c +++ b/src/test/test-socket-bind.c @@ -49,9 +49,8 @@ static int test_socket_bind( return log_unit_error_errno(u, r, "Failed to parse SocketBindAllow: %m"); } - fprintf(stderr, "SocketBindAllow:"); - LIST_FOREACH(socket_bind_items, bi, cc->socket_bind_allow) - cgroup_context_dump_socket_bind_item(bi, stderr); + fprintf(stderr, "SocketBindAllow: "); + cgroup_context_dump_socket_bind_items(cc->socket_bind_allow, stderr); fputc('\n', stderr); STRV_FOREACH(rule, deny_rules) { @@ -62,9 +61,8 @@ static int test_socket_bind( return log_unit_error_errno(u, r, "Failed to parse SocketBindDeny: %m"); } - fprintf(stderr, "SocketBindDeny:"); - LIST_FOREACH(socket_bind_items, bi, cc->socket_bind_deny) - cgroup_context_dump_socket_bind_item(bi, stderr); + fprintf(stderr, "SocketBindDeny: "); + cgroup_context_dump_socket_bind_items(cc->socket_bind_deny, stderr); fputc('\n', stderr); exec_start = strjoin("-timeout --preserve-status -sSIGTERM 1s ", netcat_path, " -l ", port, " -vv"); diff --git a/test/units/testsuite-07.exec-context.sh b/test/units/testsuite-07.exec-context.sh index c6a0f21828..d6a55cafe4 100755 --- a/test/units/testsuite-07.exec-context.sh +++ b/test/units/testsuite-07.exec-context.sh @@ -74,6 +74,109 @@ if ! systemd-detect-virt -cq; then bash -xec "test ! -r /dev/kmsg" systemd-run --wait --pipe -p ProtectKernelLogs=no -p User=testuser \ bash -xec "test -r /dev/kmsg" + + # Check if we correctly serialize, deserialize, and set directives that + # have more complex internal handling + # + # Funny detail: this originally used the underlying rootfs device, but that, + # for some reason, caused "divide error" in kernel, followed by a kernel panic + TEMPFILE="$(mktemp)" + LODEV="$(losetup --show -f "$TEMPFILE")" + ROOT_DEV_MAJ_MIN="$(lsblk -nro MAJ:MIN "$LODEV")" + EXPECTED_IO_MAX="$ROOT_DEV_MAJ_MIN rbps=1000 wbps=1000000000000 riops=2000000000 wiops=4000" + EXPECTED_IO_LATENCY="$ROOT_DEV_MAJ_MIN target=69000" + SERVICE_NAME="test-io-directives-$RANDOM.service" + CGROUP_PATH="/sys/fs/cgroup/system.slice/$SERVICE_NAME" + + # IO*= + ARGUMENTS=( + # Throw in a couple of invalid entries just to test things out + -p IOReadBandwidthMax="/foo/bar 1M" + -p IOReadBandwidthMax="/foo/baz 1M" + -p IOReadBandwidthMax="$LODEV 1M" + -p IOReadBandwidthMax="$LODEV 1K" + -p IOWriteBandwidthMax="$LODEV 1G" + -p IOWriteBandwidthMax="$LODEV 1T" + -p IOReadIOPSMax="$LODEV 2G" + -p IOWriteIOPSMax="$LODEV 4K" + -p IODeviceLatencyTargetSec="$LODEV 666ms" + -p IODeviceLatencyTargetSec="/foo/bar 69ms" + -p IODeviceLatencyTargetSec="$LODEV 69ms" + -p IOReadBandwidthMax="/foo/bar 1M" + -p IOReadBandwidthMax="/foo/baz 1M" + # TODO: IODeviceWeight= doesn't work on loop devices and virtual disks + -p IODeviceWeight="$LODEV 999" + -p IODeviceWeight="/foo/bar 999" + ) + + systemd-run --wait --pipe --unit "$SERVICE_NAME" "${ARGUMENTS[@]}" \ + bash -xec "diff <(echo $EXPECTED_IO_MAX) $CGROUP_PATH/io.max; diff <(echo $EXPECTED_IO_LATENCY) $CGROUP_PATH/io.latency" + + # CPUScheduling= + ARGUMENTS=( + -p CPUSchedulingPolicy=rr # ID: 2 + -p CPUSchedulingPolicy=fifo # ID: 1 + -p CPUSchedulingPriority=5 # Actual prio: 94 (99 - prio) + -p CPUSchedulingPriority=10 # Actual prio: 89 (99 - prio) + ) + + systemd-run --wait --pipe --unit "$SERVICE_NAME" "${ARGUMENTS[@]}" \ + bash -xec 'grep -E "^policy\s*:\s*1$" /proc/self/sched; grep -E "^prio\s*:\s*89$" /proc/self/sched' + + # Device*= + ARGUMENTS=( + -p DevicePolicy=closed + -p DevicePolicy=strict + -p DeviceAllow="char-mem rm" # Allow read & mknod for /dev/{null,zero,...} + -p DeviceAllow="/dev/loop0 rw" + -p DeviceAllow="/dev/loop0 w" # Allow write for /dev/loop0 + # Everything else should be disallowed per the strict policy + ) + + systemd-run --wait --pipe --unit "$SERVICE_NAME" "${ARGUMENTS[@]}" \ + bash -xec 'test -r /dev/null; test ! -w /dev/null; test ! -r /dev/loop0; test -w /dev/loop0; test ! -r /dev/tty; test ! -w /dev/tty' + + # SocketBind*= + ARGUMENTS=( + -p SocketBindAllow= + -p SocketBindAllow=1234 + -p SocketBindAllow=ipv4:udp:any + -p SocketBindAllow=ipv6:6666 + # Everything but the last assignment is superfluous, but it still excercises + # the parsing machinery + -p SocketBindDeny= + -p SocketBindDeny=1111 + -p SocketBindDeny=ipv4:1111 + -p SocketBindDeny=ipv4:any + -p SocketBindDeny=ipv4:tcp:any + -p SocketBindDeny=ipv4:udp:10000-11000 + -p SocketBindDeny=ipv6:1111 + -p SocketBindDeny=any + ) + + # We should fail with EPERM when trying to bind to a socket not on the allow list + # (nc exits with 2 in that case) + systemd-run --wait -p SuccessExitStatus="1 2" --pipe "${ARGUMENTS[@]}" \ + bash -xec 'timeout 1s nc -l 127.0.0.1 9999; exit 42' + systemd-run --wait -p SuccessExitStatus="1 2" --pipe "${ARGUMENTS[@]}" \ + bash -xec 'timeout 1s nc -l ::1 9999; exit 42' + systemd-run --wait -p SuccessExitStatus="1 2" --pipe "${ARGUMENTS[@]}" \ + bash -xec 'timeout 1s nc -6 -u -l ::1 9999; exit 42' + systemd-run --wait -p SuccessExitStatus="1 2" --pipe "${ARGUMENTS[@]}" \ + bash -xec 'timeout 1s nc -4 -l 127.0.0.1 6666; exit 42' + # Consequently, we should succeed when binding to a socket on the allow list + # and keep listening on it until we're killed by `timeout` (EC 124) + systemd-run --wait --pipe -p SuccessExitStatus=124 "${ARGUMENTS[@]}" \ + bash -xec 'timeout 1s nc -4 -l 127.0.0.1 1234; exit 1' + systemd-run --wait --pipe -p SuccessExitStatus=124 "${ARGUMENTS[@]}" \ + bash -xec 'timeout 1s nc -4 -u -l 127.0.0.1 5678; exit 1' + systemd-run --wait --pipe -p SuccessExitStatus=124 "${ARGUMENTS[@]}" \ + bash -xec 'timeout 1s nc -6 -l ::1 1234; exit 1' + systemd-run --wait --pipe -p SuccessExitStatus=124 "${ARGUMENTS[@]}" \ + bash -xec 'timeout 1s nc -6 -l ::1 6666; exit 1' + + losetup -d "$LODEV" + rm -f "$TEMPFILE" fi systemd-run --wait --pipe -p BindPaths="/etc /home:/mnt:norbind -/foo/bar/baz:/usr:rbind" \