Merge pull request #24408 from keszybz/execstart-escape

Properly escape ExecStart= commandlines in transient units
This commit is contained in:
Lennart Poettering 2022-08-25 11:40:57 +02:00 committed by GitHub
commit 13be736d1f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 55 additions and 41 deletions

View file

@ -72,7 +72,7 @@
processes of the command are managed by the service manager similarly to normal services, and will show
up in the output of <command>systemctl list-units</command>. Execution in this case is synchronous, and
will return only when the command finishes. This mode is enabled via the <option>--scope</option> switch
(see below). </para>
(see below).</para>
<para>If a command is run with path, socket, or timer options such as <option>--on-calendar=</option> (see below),
a transient path, socket, or timer unit is created alongside the service unit for the specified command. Only the
@ -82,15 +82,16 @@
<filename>.path</filename>, <filename>.socket</filename>, or <filename>.timer</filename> unit that triggers the
specified unit.</para>
<para>By default, services created with <command>systemd-run</command> default to the <option>simple</option> type,
see the description of <varname>Type=</varname> in
<para>By default, services created with <command>systemd-run</command> default to the
<option>simple</option> type, see the description of <varname>Type=</varname> in
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
details. Note that when this type is used the service manager (and thus the <command>systemd-run</command> command)
considers service start-up successful as soon as the <function>fork()</function> for the main service process
succeeded, i.e. before the <function>execve()</function> is invoked, and thus even if the specified command cannot
be started. Consider using the <option>exec</option> service type (i.e. <option>--property=Type=exec</option>) to
ensure that <command>systemd-run</command> returns successfully only if the specified command line has been
successfully started.</para>
details. Note that when this type is used, the service manager (and thus the
<command>systemd-run</command> command) considers service start-up successful as soon as the
<function>fork()</function> for the main service process succeeded, i.e. before the
<function>execve()</function> is invoked, and thus even if the specified command cannot be started.
Consider using the <option>exec</option> service type (i.e. <option>--property=Type=exec</option>) to
ensure that <command>systemd-run</command> returns successfully only if the specified command line has
been successfully started.</para>
</refsect1>
<refsect1>
@ -411,10 +412,8 @@
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
<para>All command line arguments after the first non-option
argument become part of the command line of the launched
process. If a command is run as service unit, the first argument
needs to be an absolute program path.</para>
<para>All command line arguments after the first non-option argument become part of the command line of
the launched process.</para>
</refsect1>
<refsect1>

View file

@ -1591,7 +1591,7 @@ int bus_set_transient_exec_command(
if (!exec_chars)
return -ENOMEM;
a = unit_concat_strv(c->argv, UNIT_ESCAPE_C|UNIT_ESCAPE_SPECIFIERS);
a = unit_concat_strv(c->argv, UNIT_ESCAPE_SPECIFIERS|UNIT_ESCAPE_EXEC_SYNTAX);
if (!a)
return -ENOMEM;
@ -1601,7 +1601,8 @@ int bus_set_transient_exec_command(
_cleanup_free_ char *t = NULL;
const char *p;
p = unit_escape_setting(c->path, UNIT_ESCAPE_C|UNIT_ESCAPE_SPECIFIERS, &t);
p = unit_escape_setting(c->path,
UNIT_ESCAPE_SPECIFIERS|UNIT_ESCAPE_EXEC_SYNTAX, &t);
if (!p)
return -ENOMEM;

View file

@ -4254,51 +4254,62 @@ static const char* unit_drop_in_dir(Unit *u, UnitWriteFlags flags) {
}
char* unit_escape_setting(const char *s, UnitWriteFlags flags, char **buf) {
char *ret = NULL;
assert(!FLAGS_SET(flags, UNIT_ESCAPE_EXEC_SYNTAX | UNIT_ESCAPE_C));
_cleanup_free_ char *t = NULL;
if (!s)
return NULL;
/* Escapes the input string as requested. Returns the escaped string. If 'buf' is specified then the allocated
* return buffer pointer is also written to *buf, except if no escaping was necessary, in which case *buf is
* set to NULL, and the input pointer is returned as-is. This means the return value always contains a properly
* escaped version, but *buf when passed only contains a pointer if an allocation was necessary. If *buf is
* not specified, then the return value always needs to be freed. Callers can use this to optimize memory
* allocations. */
/* Escapes the input string as requested. Returns the escaped string. If 'buf' is specified then the
* allocated return buffer pointer is also written to *buf, except if no escaping was necessary, in
* which case *buf is set to NULL, and the input pointer is returned as-is. This means the return
* value always contains a properly escaped version, but *buf when passed only contains a pointer if
* an allocation was necessary. If *buf is not specified, then the return value always needs to be
* freed. Callers can use this to optimize memory allocations. */
if (flags & UNIT_ESCAPE_SPECIFIERS) {
ret = specifier_escape(s);
if (!ret)
t = specifier_escape(s);
if (!t)
return NULL;
s = ret;
s = t;
}
if (flags & UNIT_ESCAPE_C) {
char *a;
/* We either do c-escaping or shell-escaping, to additionally escape characters that we parse for
* ExecStart= and friend, i.e. '$' and ';' and quotes. */
a = cescape(s);
free(ret);
if (!a)
if (flags & UNIT_ESCAPE_EXEC_SYNTAX) {
char *t2 = shell_escape(s, "$;'\"");
if (!t2)
return NULL;
free_and_replace(t, t2);
ret = a;
s = t;
} else if (flags & UNIT_ESCAPE_C) {
char *t2 = cescape(s);
if (!t2)
return NULL;
free_and_replace(t, t2);
s = t;
}
if (buf) {
*buf = ret;
return ret ?: (char*) s;
*buf = TAKE_PTR(t);
return (char*) s;
}
return ret ?: strdup(s);
return TAKE_PTR(t) ?: strdup(s);
}
char* unit_concat_strv(char **l, UnitWriteFlags flags) {
_cleanup_free_ char *result = NULL;
size_t n = 0;
/* Takes a list of strings, escapes them, and concatenates them. This may be used to format command lines in a
* way suitable for ExecStart= stanzas */
/* Takes a list of strings, escapes them, and concatenates them. This may be used to format command
* lines in a way suitable for ExecStart= stanzas. */
STRV_FOREACH(i, l) {
_cleanup_free_ char *buf = NULL;

View file

@ -531,19 +531,22 @@ typedef struct UnitStatusMessageFormats {
/* Flags used when writing drop-in files or transient unit files */
typedef enum UnitWriteFlags {
/* Write a runtime unit file or drop-in (i.e. one below /run) */
UNIT_RUNTIME = 1 << 0,
UNIT_RUNTIME = 1 << 0,
/* Write a persistent drop-in (i.e. one below /etc) */
UNIT_PERSISTENT = 1 << 1,
UNIT_PERSISTENT = 1 << 1,
/* Place this item in the per-unit-type private section, instead of [Unit] */
UNIT_PRIVATE = 1 << 2,
UNIT_PRIVATE = 1 << 2,
/* Apply specifier escaping before writing */
UNIT_ESCAPE_SPECIFIERS = 1 << 3,
UNIT_ESCAPE_SPECIFIERS = 1 << 3,
/* Escape elements of ExecStart= syntax before writing */
UNIT_ESCAPE_EXEC_SYNTAX = 1 << 4,
/* Apply C escaping before writing */
UNIT_ESCAPE_C = 1 << 4,
UNIT_ESCAPE_C = 1 << 5,
} UnitWriteFlags;
/* Returns true if neither persistent, nor runtime storage is requested, i.e. this is a check invocation only */