systemd-run: reject unsupported option combinations (#43003)

Boundary tests were conducted on the "run" command tool, and the
identified issues were resolved.
This commit is contained in:
Luca Boccassi
2026-07-14 11:29:11 +01:00
committed by GitHub
3 changed files with 139 additions and 27 deletions
+11 -4
View File
@@ -216,7 +216,9 @@
<listitem><para>After the service process has terminated, keep the service around until it is explicitly
stopped. This is useful to collect runtime information about the service after it finished running. Also see
<varname>RemainAfterExit=</varname> in
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>. This
option may not be combined with <option>--wait</option>, <option>--scope</option>, <option>--pty</option>,
<option>--pty-late</option>, or <option>--pipe</option>.
</para>
<xi:include href="version-info.xml" xpointer="v207"/>
@@ -529,8 +531,9 @@
completed). On exit, terse information about the unit's runtime is shown, including total runtime (as well as
CPU, memory, IO, and IP accounting data, if the corresponding cgroup accounting settings are enabled)
and the exit code and status of the main process. This output may be suppressed with <option>--quiet</option>.
This option may not be combined with <option>--no-block</option>, <option>--scope</option> or the various
path, socket, or timer options.</para>
This option may not be combined with <option>--no-block</option>,
<option>--remain-after-exit</option>, <option>--scope</option> or the various path, socket, or timer
options.</para>
<xi:include href="version-info.xml" xpointer="v232"/></listitem>
</varlistentry>
@@ -575,7 +578,7 @@
<listitem><para>By default, if the specified command fails the invoked unit will be marked failed
(though possibly still unloaded, see <option>--collect=</option>, above), and this is reported in the
logs. If this switch is specified this is suppressed and any non-success exit status/code of the
command is treated as success.</para>
command is treated as success. This option is not supported in <option>--scope</option> mode.</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
@@ -616,6 +619,10 @@
</varlistentry>
</variablelist>
<para><option>--json=</option> may not be combined with <option>--scope</option>,
<option>--pty</option>, <option>--pty-late</option>, <option>--pipe</option>,
<option>--verbose</option>, or path, socket, or timer options.</para>
<para>All command line arguments after the first non-option argument become part of the command line of
the launched process.</para>
</refsect1>
+88 -23
View File
@@ -697,9 +697,9 @@ static int parse_argv(int argc, char *argv[]) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Scope execution is not supported on remote systems.");
if (arg_scope && (arg_remain_after_exit || arg_service_type))
if (arg_scope && (arg_remain_after_exit || arg_service_type || arg_ignore_failure))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--remain-after-exit and --service-type= are not supported in --scope mode.");
"--remain-after-exit, --service-type= and --ignore-failure are not supported in --scope mode.");
if (arg_scope && arg_no_block)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
@@ -717,6 +717,10 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_no_block)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--pty/--pty-late/--pipe is not compatible with --no-block.");
if (arg_remain_after_exit)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--pty/--pty-late/--pipe is not compatible with --remain-after-exit.");
}
if (arg_stdio == ARG_STDIO_PTY && arg_pty_late && streq_ptr(arg_service_type, "oneshot"))
@@ -727,6 +731,24 @@ static int parse_argv(int argc, char *argv[]) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Path, socket or timer options are not supported in --scope mode.");
if (sd_json_format_enabled(arg_json_format_flags)) {
if (arg_scope)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--json= is not supported in --scope mode.");
if (arg_stdio != ARG_STDIO_NONE)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--json= is not compatible with --pty/--pty-late/--pipe.");
if (arg_verbose)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--json= is not compatible with --verbose.");
if (with_trigger)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--json= is not compatible with path, socket or timer operations.");
}
if (arg_timer_property && !arg_with_timer)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--timer-property= has no effect without any other timer options.");
@@ -743,6 +765,10 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_scope)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--wait may not be combined with --scope.");
if (arg_remain_after_exit)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--wait may not be combined with --remain-after-exit.");
}
if (arg_scope && arg_root_directory)
@@ -2517,6 +2543,17 @@ static int start_transient_service(sd_bus *bus) {
return EXIT_SUCCESS;
}
static int log_scope_group_setup_errno(int r, gid_t gid, const char *message) {
assert(r < 0);
assert(message);
if (!ERRNO_IS_PRIVILEGE(r) || gid != getgid())
return log_error_errno(r, "%s: %m", message);
log_debug_errno(r, "%s, ignoring: %m", message);
return 0;
}
static int start_transient_scope(sd_bus *bus) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
@@ -2623,26 +2660,22 @@ static int start_transient_scope(sd_bus *bus) {
return log_error_errno(errno, "Failed to set nice level: %m");
}
gid_t gid = GID_INVALID;
if (arg_exec_group) {
gid_t gid;
r = get_group_creds(arg_exec_group, /* flags= */ 0, /* ret_name= */ NULL, &gid);
if (r < 0)
return log_error_errno(r, "Failed to resolve group '%s': %s",
arg_exec_group, STRERROR_GROUP(r));
if (setresgid(gid, gid, gid) < 0)
return log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid);
}
uid_t uid = UID_INVALID;
if (arg_exec_user) {
_cleanup_free_ char *user = NULL, *home = NULL, *shell = NULL;
uid_t uid;
gid_t gid;
gid_t user_gid;
r = get_user_creds(arg_exec_user,
USER_CREDS_CLEAN|USER_CREDS_SUPPRESS_PLACEHOLDER|USER_CREDS_PREFER_NSS,
&user, &uid, &gid, &home, &shell);
&user, &uid, &user_gid, &home, &shell);
if (r < 0)
return log_error_errno(r, "Failed to resolve user '%s': %s",
arg_exec_user, STRERROR_USER(r));
@@ -2669,14 +2702,33 @@ static int start_transient_scope(sd_bus *bus) {
if (r < 0)
return log_oom();
if (!arg_exec_group &&
setresgid(gid, gid, gid) < 0)
return log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid);
if (!gid_is_valid(gid))
gid = user_gid;
if (setresuid(uid, uid, uid) < 0)
return log_error_errno(errno, "Failed to change UID to " UID_FMT ": %m", uid);
r = initgroups_wrapper(arg_exec_user, gid);
if (r < 0) {
r = log_scope_group_setup_errno(
r,
gid,
strjoina("Failed to initialize supplementary groups for user '", arg_exec_user, "'"));
if (r < 0)
return r;
}
} else if (gid_is_valid(gid)) {
r = maybe_setgroups(/* size= */ 0, /* list= */ NULL);
if (r < 0) {
r = log_scope_group_setup_errno(r, gid, "Failed to drop supplementary groups");
if (r < 0)
return r;
}
}
if (gid_is_valid(gid) && setresgid(gid, gid, gid) < 0)
return log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid);
if (uid_is_valid(uid) && setresuid(uid, uid, uid) < 0)
return log_error_errno(errno, "Failed to change UID to " UID_FMT ": %m", uid);
if (arg_working_directory && chdir(arg_working_directory) < 0)
return log_error_errno(errno, "Failed to change directory to '%s': %m", arg_working_directory);
@@ -2808,12 +2860,16 @@ static int start_transient_trigger(sd_bus *bus, const char *suffix) {
assert(bus);
assert(suffix);
r = bus_wait_for_jobs_new(bus, &w);
if (r < 0)
return log_error_errno(r, "Could not watch jobs: %m");
if (!arg_no_block) {
r = bus_wait_for_jobs_new(bus, &w);
if (r < 0)
return log_error_errno(r, "Could not watch jobs: %m");
}
if (arg_unit) {
switch (unit_name_to_type(arg_unit)) {
UnitType t = unit_name_to_type(arg_unit);
switch (t) {
case UNIT_SERVICE:
service = strdup(arg_unit);
@@ -2825,7 +2881,14 @@ static int start_transient_trigger(sd_bus *bus, const char *suffix) {
return log_error_errno(r, "Failed to change unit suffix: %m");
break;
case UNIT_PATH:
case UNIT_SOCKET:
case UNIT_TIMER:
if (!streq(suffix + 1, unit_type_to_string(t)))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Specified unit '%s' does not match requested %s trigger.",
arg_unit, suffix + 1);
trigger = strdup(arg_unit);
if (!trigger)
return log_oom();
@@ -2875,10 +2938,12 @@ static int start_transient_trigger(sd_bus *bus, const char *suffix) {
if (r < 0)
return bus_log_parse_error(r);
r = bus_wait_for_jobs_one(w, object, arg_quiet ? 0 : BUS_WAIT_JOBS_LOG_ERROR,
arg_runtime_scope == RUNTIME_SCOPE_USER ? STRV_MAKE_CONST("--user") : NULL);
if (r < 0)
return r;
if (w) {
r = bus_wait_for_jobs_one(w, object, arg_quiet ? 0 : BUS_WAIT_JOBS_LOG_ERROR,
arg_runtime_scope == RUNTIME_SCOPE_USER ? STRV_MAKE_CONST("--user") : NULL);
if (r < 0)
return r;
}
if (!arg_quiet) {
log_info("Running %s as unit: %s", suffix + 1, trigger);
+40
View File
@@ -72,6 +72,8 @@ systemctl cat "$UNIT"
grep -q "^Type=oneshot" "/run/systemd/transient/$UNIT.service"
systemctl stop "$UNIT"
(! systemctl cat "$UNIT")
(! systemd-run --wait --remain-after-exit true)
(! systemd-run --pipe --remain-after-exit true)
: "Transient service (user daemon)"
systemd-run --wait --pipe --user --machine=testuser@ \
@@ -111,10 +113,16 @@ systemd-run --scope --slice-inherit --slice=foo \
bash -xec '[[ "$(</proc/self/cgroup)" =~ /system\.slice/system-foo\.slice/run-.+\.scope$ ]]'
# We should inherit caller's environment
systemd-run --scope bash -xec '[[ "$PARENT_FOO" == bar ]]'
systemd-run --scope --uid=testuser \
bash -xec '[[ " $(id -G) " != *" 0 "* ]]'
systemd-run --scope --gid=testuser \
bash -xec '[[ " $(id -G) " != *" 0 "* ]]'
systemd-run --scope \
--property=RuntimeMaxSec=10 \
--property=RuntimeMaxSec=infinity \
true
(! systemd-run --scope --ignore-failure false)
(! systemd-run --scope --json=short true)
: "Transient scope (user daemon)"
# FIXME: https://github.com/systemd/systemd/issues/27883
@@ -188,6 +196,17 @@ grep -q "^PathChanged=/root/bar$" "/run/systemd/transient/$UNIT.path"
grep -qE "^ExecStart=.*true.*$" "/run/systemd/transient/$UNIT.service"
systemctl stop "$UNIT.path" "$UNIT.service" || :
UNIT="path-explicit-$RANDOM.path"
SERVICE="${UNIT%.path}.service"
systemd-run --remain-after-exit \
--unit="$UNIT" \
--path-property=PathExists=/tmp \
true
systemctl cat "$UNIT" "$SERVICE"
grep -q "^PathExists=/tmp$" "/run/systemd/transient/$UNIT"
grep -qE "^ExecStart=.*true.*$" "/run/systemd/transient/$SERVICE"
systemctl stop "$UNIT" "$SERVICE" || :
: "Transient socket unit"
UNIT="socket-0-$RANDOM"
systemd-run --remain-after-exit \
@@ -205,6 +224,24 @@ grep -q "^SocketMode=0644$" "/run/systemd/transient/$UNIT.socket"
grep -qE "^ExecStart=.*true.*$" "/run/systemd/transient/$UNIT.service"
systemctl stop "$UNIT.socket" "$UNIT.service" || :
UNIT="socket-explicit-$RANDOM.socket"
SERVICE="${UNIT%.socket}.service"
systemd-run --remain-after-exit \
--unit="$UNIT" \
--socket-property=ListenFIFO=/tmp/socket-explicit.fifo \
true
systemctl cat "$UNIT" "$SERVICE"
grep -q "^ListenFIFO=/tmp/socket-explicit.fifo$" "/run/systemd/transient/$UNIT"
grep -qE "^ExecStart=.*true.*$" "/run/systemd/transient/$SERVICE"
systemctl stop "$UNIT" "$SERVICE" || :
UNIT="socket-no-block-$RANDOM"
systemd-run --no-block --collect \
--unit="$UNIT" \
--socket-property=ListenStream=/proc/systemd-run-repro/socket \
true
systemctl stop "$UNIT.socket" "$UNIT.service" || :
: "Job mode"
systemd-run --job-mode=help
(! systemd-run --job-mode=foo --scope true)
@@ -222,6 +259,9 @@ systemd-run --wait --pty true
systemd-run --wait --machine=.host --pty true
systemd-run --json=short true | jq . >/dev/null
systemd-run --json=pretty true | jq . >/dev/null
(! systemd-run --wait --pipe --json=short true)
(! systemd-run --json=short --on-active=30s true)
(! systemd-run --wait --verbose --json=short true)
(! SHELL=/bin/false systemd-run --quiet --shell)
(! systemd-run)