Merge pull request #7198 from poettering/stdin-stdout

Add StandardInput=data, StandardInput=file:... and more
This commit is contained in:
Lennart Poettering 2017-11-19 19:49:11 +01:00 committed by GitHub
commit 0133d5553a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 1242 additions and 422 deletions

37
TODO
View File

@ -32,13 +32,6 @@ Features:
* Add NetworkNamespacePath= to specify a path to a network namespace
* Add StandardInputData= and StandardInputText= for putting together data to
pass to a service through stdin
* Add StandardInputPath=, StandardOutputPath=, StandardErrorPath= to connect a
service to a specific file. Be smart, and if the specified path refers to an
AF_UNIX socket, connect() to it.
* maybe use SOURCE_DATE_EPOCH (i.e. the env var the reproducible builds folks
introduced) as the RTC epoch, instead of the mtime of NEWS.
@ -51,6 +44,9 @@ Features:
* document Environment=SYSTEMD_LOG_LEVEL=debug drop-in in debugging document
* rework ExecOutput and ExecInput enums so that EXEC_OUTPUT_NULL loses its
magic meaning and is no longer upgraded to something else if set explicitly.
* add a way to remove fds from the fdstore by name, and make logind use it
* in the long run: permit a system with /etc/machine-id linked to /dev/null, to
@ -99,9 +95,18 @@ Features:
taken if multiple dirs are configured. Maybe avoid setting the env vars in
that case?
* introduce SuccessAction= that permits shutting down the system when a service
succeeds. This is useful to replace "ExecPost=/usr/bin/systemctl poweroff" and
similar constructs, which are frequently used. This is particularly nice for
implementation of a systemd.run= kernel command line option that runs some
command and immediately shuts down.
* expose IO accounting data on the bus, show it in systemd-run --wait and log
about it in the resource log message
* rework unbase64 code to drop whitespace automatically, so that we don't have
to drop it first.
* add "systemctl purge" for flushing out configuration, state, logs, ... of a
unit when it is stopped
@ -110,12 +115,6 @@ Features:
* replace all uses of fgets() + LINE_MAX by read_line()
* set IPAddressDeny=any on all services that shouldn't do networking (possibly
combined with IPAddressAllow=localhost).
* dissect: when we discover squashfs, don't claim we had a "writable" partition
in systemd-dissect
* Add AddUser= setting to unit files, similar to DynamicUser=1 which however
creates a static, persistent user rather than a dynamic, transient user. We
can leverage code from sysusers.d for this.
@ -150,15 +149,6 @@ Features:
--as-pid2 switch, and sanely proxy sd_notify() messages dropping stuff such
as MAINPID.
* change the dependency Set* objects in Unit structures to become Hashmap*, and
then store a bit mask who created a specific dependency: the source unit via
fragment configuration, the destination unit via fragment configuration, or
the source unit via udev rules (in case of .device units), or any combination
thereof. This information can then be used to flush out old udev-created
dependencies when the udev properties change, and eventually to implement a
"systemctl refresh" operation for reloading the configuration of individual
units without reloading the whole set.
* Add ExecMonitor= setting. May be used multiple times. Forks off a process in
the service cgroup, which is supposed to monitor the service, and when it
exits the service is considered failed by its monitor.
@ -334,8 +324,6 @@ Features:
* Rework systemctl's GetAll property parsing to use the generic bus_map_all_properties() API
* implement a per-service firewall based on net_cls
* Port various tools to make use of verbs.[ch], where applicable: busctl,
coredumpctl, hostnamectl, localectl, systemd-analyze, timedatectl
@ -689,7 +677,6 @@ Features:
- document that deps in [Unit] sections ignore Alias= fields in
[Install] units of other units, unless those units are disabled
- man: clarify that time-sync.target is not only sysv compat but also useful otherwise. Same for similar targets
- document the exit codes when services fail before they are exec()ed
- document that service reload may be implemented as service reexec
- document in wiki how to map ical recurrence events to systemd timer unit calendar specifications
- add a man page containing packaging guidelines and recommending usage of things like Documentation=, PrivateTmp=, PrivateNetwork= and ReadOnlyDirectories=/etc /usr.

View File

@ -484,145 +484,116 @@
<varlistentry>
<term><varname>StandardInput=</varname></term>
<listitem><para>Controls where file descriptor 0 (STDIN) of
the executed processes is connected to. Takes one of
<option>null</option>,
<option>tty</option>,
<option>tty-force</option>,
<option>tty-fail</option>,
<option>socket</option> or
<option>fd</option>.</para>
<listitem><para>Controls where file descriptor 0 (STDIN) of the executed processes is connected to. Takes one
of <option>null</option>, <option>tty</option>, <option>tty-force</option>, <option>tty-fail</option>,
<option>data</option>, <option>file:<replaceable>path</replaceable></option>, <option>socket</option> or
<option>fd:<replaceable>name</replaceable></option>.</para>
<para>If <option>null</option> is selected, standard input
will be connected to <filename>/dev/null</filename>, i.e. all
read attempts by the process will result in immediate
EOF.</para>
<para>If <option>null</option> is selected, standard input will be connected to <filename>/dev/null</filename>,
i.e. all read attempts by the process will result in immediate EOF.</para>
<para>If <option>tty</option> is selected, standard input is
connected to a TTY (as configured by
<varname>TTYPath=</varname>, see below) and the executed
process becomes the controlling process of the terminal. If
the terminal is already being controlled by another process,
the executed process waits until the current controlling
process releases the terminal.</para>
<para>If <option>tty</option> is selected, standard input is connected to a TTY (as configured by
<varname>TTYPath=</varname>, see below) and the executed process becomes the controlling process of the
terminal. If the terminal is already being controlled by another process, the executed process waits until the
current controlling process releases the terminal.</para>
<para><option>tty-force</option> is similar to
<option>tty</option>, but the executed process is forcefully
and immediately made the controlling process of the terminal,
potentially removing previous controlling processes from the
terminal.</para>
<para><option>tty-force</option> is similar to <option>tty</option>, but the executed process is forcefully and
immediately made the controlling process of the terminal, potentially removing previous controlling processes
from the terminal.</para>
<para><option>tty-fail</option> is similar to
<option>tty</option> but if the terminal already has a
controlling process start-up of the executed process
fails.</para>
<para><option>tty-fail</option> is similar to <option>tty</option>, but if the terminal already has a
controlling process start-up of the executed process fails.</para>
<para>The <option>socket</option> option is only valid in
socket-activated services, and only when the socket
configuration file (see
<citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for details) specifies a single socket only. If this option is
set, standard input will be connected to the socket the
service was activated from, which is primarily useful for
compatibility with daemons designed for use with the
traditional
<citerefentry project='freebsd'><refentrytitle>inetd</refentrytitle><manvolnum>8</manvolnum></citerefentry>
<para>The <option>data</option> option may be used to configure arbitrary textual or binary data to pass via
standard input to the executed process. The data to pass is configured via
<varname>StandardInputText=</varname>/<varname>StandardInputData=</varname> (see below). Note that the actual
file descriptor type passed (memory file, regular file, UNIX pipe, …) might depend on the kernel and available
privileges. In any case, the file descriptor is read-only, and when read returns the specified data
followed by EOF.</para>
<para>The <option>file:<replaceable>path</replaceable></option> option may be used to connect a specific file
system object to standard input. An absolute path following the <literal>:</literal> character is expected,
which may refer to a regular file, a FIFO or special file. If an <constant>AF_UNIX</constant> socket in the
file system is specified, a stream socket is connected to it. The latter is useful for connecting standard
input of processes to arbitrary system services.</para>
<para>The <option>socket</option> option is valid in socket-activated services only, and requires the relevant
socket unit file (see
<citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry> for details)
to have <varname>Accept=yes</varname> set, or to specify a single socket only. If this option is set, standard
input will be connected to the socket the service was activated from, which is primarily useful for
compatibility with daemons designed for use with the traditional <citerefentry
project='freebsd'><refentrytitle>inetd</refentrytitle><manvolnum>8</manvolnum></citerefentry> socket activation
daemon.</para>
<para>The <option>fd</option> option connects
the input stream to a single file descriptor provided by a socket unit.
A custom named file descriptor can be specified as part of this option,
after a <literal>:</literal> (e.g. <literal>fd:<replaceable>foobar</replaceable></literal>).
If no name is specified, <literal>stdin</literal> is assumed
(i.e. <literal>fd</literal> is equivalent to <literal>fd:stdin</literal>).
At least one socket unit defining such name must be explicitly provided via the
<varname>Sockets=</varname> option, and file descriptor name may differ
from the name of its containing socket unit.
If multiple matches are found, the first one will be used.
See <varname>FileDescriptorName=</varname> in
<citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for more details about named descriptors and ordering.</para>
<para>The <option>fd:<replaceable>name</replaceable></option> option connects standard input to a specific,
named file descriptor provided by a socket unit. The name may be specified as part of this option, following a
<literal>:</literal> character (e.g. <literal>fd:foobar</literal>). If no name is specified, the name
<literal>stdin</literal> is implied (i.e. <literal>fd</literal> is equivalent to <literal>fd:stdin</literal>).
At least one socket unit defining the specified name must be provided via the <varname>Sockets=</varname>
option, and the file descriptor name may differ from the name of its containing socket unit. If multiple
matches are found, the first one will be used. See <varname>FileDescriptorName=</varname> in
<citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry> for more
details about named file descriptors and their ordering.</para>
<para>This setting defaults to
<option>null</option>.</para></listitem>
<para>This setting defaults to <option>null</option>.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>StandardOutput=</varname></term>
<listitem><para>Controls where file descriptor 1 (STDOUT) of
the executed processes is connected to. Takes one of
<option>inherit</option>,
<option>null</option>,
<option>tty</option>,
<option>journal</option>,
<option>syslog</option>,
<option>kmsg</option>,
<option>journal+console</option>,
<option>syslog+console</option>,
<option>kmsg+console</option>,
<option>socket</option> or
<option>fd</option>.</para>
<listitem><para>Controls where file descriptor 1 (STDOUT) of the executed processes is connected to. Takes one
of <option>inherit</option>, <option>null</option>, <option>tty</option>, <option>journal</option>,
<option>syslog</option>, <option>kmsg</option>, <option>journal+console</option>,
<option>syslog+console</option>, <option>kmsg+console</option>,
<option>file:<replaceable>path</replaceable></option>, <option>socket</option> or
<option>fd:<replaceable>name</replaceable></option>.</para>
<para><option>inherit</option> duplicates the file descriptor
of standard input for standard output.</para>
<para><option>inherit</option> duplicates the file descriptor of standard input for standard output.</para>
<para><option>null</option> connects standard output to
<filename>/dev/null</filename>, i.e. everything written to it
will be lost.</para>
<para><option>null</option> connects standard output to <filename>/dev/null</filename>, i.e. everything written
to it will be lost.</para>
<para><option>tty</option> connects standard output to a tty
(as configured via <varname>TTYPath=</varname>, see below). If
the TTY is used for output only, the executed process will not
become the controlling process of the terminal, and will not
fail or wait for other processes to release the
terminal.</para>
<para><option>tty</option> connects standard output to a tty (as configured via <varname>TTYPath=</varname>,
see below). If the TTY is used for output only, the executed process will not become the controlling process of
the terminal, and will not fail or wait for other processes to release the terminal.</para>
<para><option>journal</option> connects standard output with
the journal which is accessible via
<citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
Note that everything that is written to syslog or kmsg (see
below) is implicitly stored in the journal as well, the
specific two options listed below are hence supersets of this
one.</para>
<para><option>journal</option> connects standard output with the journal which is accessible via
<citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>. Note that
everything that is written to syslog or kmsg (see below) is implicitly stored in the journal as well, the
specific two options listed below are hence supersets of this one.</para>
<para><option>syslog</option> connects standard output to the
<citerefentry project='man-pages'><refentrytitle>syslog</refentrytitle><manvolnum>3</manvolnum></citerefentry>
system syslog service, in addition to the journal. Note that
the journal daemon is usually configured to forward everything
it receives to syslog anyway, in which case this option is no
different from <option>journal</option>.</para>
<para><option>syslog</option> connects standard output to the <citerefentry
project='man-pages'><refentrytitle>syslog</refentrytitle><manvolnum>3</manvolnum></citerefentry> system syslog
service, in addition to the journal. Note that the journal daemon is usually configured to forward everything
it receives to syslog anyway, in which case this option is no different from <option>journal</option>.</para>
<para><option>kmsg</option> connects standard output with the
kernel log buffer which is accessible via
<para><option>kmsg</option> connects standard output with the kernel log buffer which is accessible via
<citerefentry project='man-pages'><refentrytitle>dmesg</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
in addition to the journal. The journal daemon might be
configured to send all logs to kmsg anyway, in which case this
option is no different from <option>journal</option>.</para>
in addition to the journal. The journal daemon might be configured to send all logs to kmsg anyway, in which
case this option is no different from <option>journal</option>.</para>
<para><option>journal+console</option>,
<option>syslog+console</option> and
<option>kmsg+console</option> work in a similar way as the
three options above but copy the output to the system console
as well.</para>
<para><option>journal+console</option>, <option>syslog+console</option> and <option>kmsg+console</option> work
in a similar way as the three options above but copy the output to the system console as well.</para>
<para><option>socket</option> connects standard output to a
socket acquired via socket activation. The semantics are
similar to the same option of
<varname>StandardInput=</varname>.</para>
<para>The <option>file:<replaceable>path</replaceable></option> option may be used to connect a specific file
system object to standard output. The semantics are similar to the same option of
<varname>StandardInputText=</varname>, see above. If standard input and output are directed to the same file
path, it is opened only once, for reading as well as writing and duplicated. This is particular useful when the
specified path refers to an <constant>AF_UNIX</constant> socket in the file system, as in that case only a
single stream connection is created for both input and output.</para>
<para>The <option>fd</option> option connects
the output stream to a single file descriptor provided by a socket unit.
A custom named file descriptor can be specified as part of this option,
after a <literal>:</literal> (e.g. <literal>fd:<replaceable>foobar</replaceable></literal>).
If no name is specified, <literal>stdout</literal> is assumed
(i.e. <literal>fd</literal> is equivalent to <literal>fd:stdout</literal>).
At least one socket unit defining such name must be explicitly provided via the
<varname>Sockets=</varname> option, and file descriptor name may differ
from the name of its containing socket unit.
If multiple matches are found, the first one will be used.
See <varname>FileDescriptorName=</varname> in
<citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for more details about named descriptors and ordering.</para>
<para><option>socket</option> connects standard output to a socket acquired via socket activation. The
semantics are similar to the same option of <varname>StandardInput=</varname>, see above.</para>
<para>The <option>fd:<replaceable>name</replaceable></option> option connects standard output to a specific,
named file descriptor provided by a socket unit. A name may be specified as part of this option, following a
<literal>:</literal> character (e.g. <literal>fd:foobar</literal>). If no name is
specified, the name <literal>stdout</literal> is implied (i.e. <literal>fd</literal> is equivalent to
<literal>fd:stdout</literal>). At least one socket unit defining the specified name must be provided via the
<varname>Sockets=</varname> option, and the file descriptor name may differ from the name of its containing socket
unit. If multiple matches are found, the first one will be used. See <varname>FileDescriptorName=</varname>
in <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry> for more
details about named descriptors and their ordering.</para>
<para>If the standard output (or error output, see below) of a unit is connected to the journal, syslog or the
kernel log buffer, the unit will implicitly gain a dependency of type <varname>After=</varname> on
@ -632,32 +603,66 @@
"hello" &gt; /dev/stderr</command> for writing text to stderr will not work. To mitigate this use the construct
<command>echo "hello" >&amp;2</command> instead, which is mostly equivalent and avoids this pitfall.</para>
<para>This setting defaults to the value set with
<varname>DefaultStandardOutput=</varname> in
<citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
which defaults to <option>journal</option>. Note that setting
this parameter might result in additional dependencies to be
added to the unit (see above).</para>
<para>This setting defaults to the value set with <varname>DefaultStandardOutput=</varname> in
<citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>, which
defaults to <option>journal</option>. Note that setting this parameter might result in additional dependencies
to be added to the unit (see above).</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>StandardError=</varname></term>
<listitem><para>Controls where file descriptor 2 (STDERR) of
the executed processes is connected to. The available options
are identical to those of <varname>StandardOutput=</varname>,
with some exceptions: if set to <option>inherit</option> the
file descriptor used for standard output is duplicated for
standard error, while <option>fd</option> operates on the error
stream and will look by default for a descriptor named
<listitem><para>Controls where file descriptor 2 (STDERR) of the executed processes is connected to. The
available options are identical to those of <varname>StandardOutput=</varname>, with some exceptions: if set to
<option>inherit</option> the file descriptor used for standard output is duplicated for standard error, while
<option>fd:<replaceable>name</replaceable></option> will use a default file descriptor name of
<literal>stderr</literal>.</para>
<para>This setting defaults to the value set with
<varname>DefaultStandardError=</varname> in
<citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
which defaults to <option>inherit</option>. Note that setting
this parameter might result in additional dependencies to be
added to the unit (see above).</para></listitem>
<para>This setting defaults to the value set with <varname>DefaultStandardError=</varname> in
<citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>, which
defaults to <option>inherit</option>. Note that setting this parameter might result in additional dependencies
to be added to the unit (see above).</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>StandardInputText=</varname></term>
<term><varname>StandardInputData=</varname></term>
<listitem><para>Configures arbitrary textual or binary data to pass via file descriptor 0 (STDIN) to the
executed processes. These settings have no effect unless <varname>StandardInput=</varname> is set to
<option>data</option>. Use this option to embed process input data directly in the unit file.</para>
<para><varname>StandardInputText=</varname> accepts arbitrary textual data. C-style escapes for special
characters as well as the usual <literal>%</literal>-specifiers are resolved. Each time this setting is used
the the specified text is appended to the per-unit data buffer, followed by a newline character (thus every use
appends a new line to the end of the buffer). Note that leading and trailing whitespace of lines configured
with this option is removed. If an empty line is specified the buffer is cleared (hence, in order to insert an
empty line, add an additional <literal>\n</literal> to the end or beginning of a line).</para>
<para><varname>StandardInputData=</varname> accepts arbitrary binary data, encoded in <ulink
url="https://tools.ietf.org/html/rfc2045#section-6.8">Base64</ulink>. No escape sequences or specifiers are
resolved. Any whitespace in the encoded version is ignored during decoding.</para>
<para>Note that <varname>StandardInputText=</varname> and <varname>StandardInputData=</varname> operate on the
same data buffer, and may be mixed in order to configure both binary and textual data for the same input
stream. The textual or binary data is joined strictly in the order the settings appear in the unit
file. Assigning an empty string to either will reset the data buffer.</para>
<para>Please keep in mind that in order to maintain readability long unit file settings may be split into
multiple lines, by suffixing each line (except for the last) with a <literal>\</literal> character (see
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
details). This is particularly useful for large data configured with these two options. Example:</para>
<programlisting>
StandardInput=data
StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy4KSWNrIGtpZWtl \
LCBzdGF1bmUsIHd1bmRyZSBtaXIsCnVmZiBlZW1hbCBqZWh0IHNlIHVmZiBkaWUgVMO8ci4KTmFu \
dSwgZGVuayBpY2ssIGljayBkZW5rIG5hbnUhCkpldHogaXNzZSB1ZmYsIGVyc2NodCB3YXIgc2Ug \
enUhCkljayBqZWhlIHJhdXMgdW5kIGJsaWNrZSDigJQKdW5kIHdlciBzdGVodCBkcmF1w59lbj8g \
SWNrZSEK
</programlisting>
</listitem>
</varlistentry>
<varlistentry>

View File

@ -1279,7 +1279,7 @@ int cg_split_spec(const char *spec, char **controller, char **path) {
assert(spec);
if (*spec == '/') {
if (!path_is_safe(spec))
if (!path_is_normalized(spec))
return -EINVAL;
if (path) {
@ -1332,7 +1332,7 @@ int cg_split_spec(const char *spec, char **controller, char **path) {
return -ENOMEM;
}
if (!path_is_safe(u) ||
if (!path_is_normalized(u) ||
!path_is_absolute(u)) {
free(t);
free(u);

View File

@ -27,8 +27,10 @@
#include "dirent-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "macro.h"
#include "memfd-util.h"
#include "missing.h"
#include "parse-util.h"
#include "path-util.h"
@ -378,3 +380,202 @@ int fd_get_path(int fd, char **ret) {
return r;
}
int move_fd(int from, int to, int cloexec) {
int r;
/* Move fd 'from' to 'to', make sure FD_CLOEXEC remains equal if requested, and release the old fd. If
* 'cloexec' is passed as -1, the original FD_CLOEXEC is inherited for the new fd. If it is 0, it is turned
* off, if it is > 0 it is turned on. */
if (from < 0)
return -EBADF;
if (to < 0)
return -EBADF;
if (from == to) {
if (cloexec >= 0) {
r = fd_cloexec(to, cloexec);
if (r < 0)
return r;
}
return to;
}
if (cloexec < 0) {
int fl;
fl = fcntl(from, F_GETFD, 0);
if (fl < 0)
return -errno;
cloexec = !!(fl & FD_CLOEXEC);
}
r = dup3(from, to, cloexec ? O_CLOEXEC : 0);
if (r < 0)
return -errno;
assert(r == to);
safe_close(from);
return to;
}
int acquire_data_fd(const void *data, size_t size, unsigned flags) {
char procfs_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
_cleanup_close_pair_ int pipefds[2] = { -1, -1 };
char pattern[] = "/dev/shm/data-fd-XXXXXX";
_cleanup_close_ int fd = -1;
int isz = 0, r;
ssize_t n;
off_t f;
assert(data || size == 0);
/* Acquire a read-only file descriptor that when read from returns the specified data. This is much more
* complex than I wish it was. But here's why:
*
* a) First we try to use memfds. They are the best option, as we can seal them nicely to make them
* read-only. Unfortunately they require kernel 3.17, and at the time of writing we still support 3.14.
*
* b) Then, we try classic pipes. They are the second best options, as we can close the writing side, retaining
* a nicely read-only fd in the reading side. However, they are by default quite small, and unprivileged
* clients can only bump their size to a system-wide limit, which might be quite low.
*
* c) Then, we try an O_TMPFILE file in /dev/shm (that dir is the only suitable one known to exist from
* earliest boot on). To make it read-only we open the fd a second time with O_RDONLY via
* /proc/self/<fd>. Unfortunately O_TMPFILE is not available on older kernels on tmpfs.
*
* d) Finally, we try creating a regular file in /dev/shm, which we then delete.
*
* It sucks a bit that depending on the situation we return very different objects here, but that's Linux I
* figure. */
if (size == 0 && ((flags & ACQUIRE_NO_DEV_NULL) == 0)) {
/* As a special case, return /dev/null if we have been called for an empty data block */
r = open("/dev/null", O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (r < 0)
return -errno;
return r;
}
if ((flags & ACQUIRE_NO_MEMFD) == 0) {
fd = memfd_new("data-fd");
if (fd < 0)
goto try_pipe;
n = write(fd, data, size);
if (n < 0)
return -errno;
if ((size_t) n != size)
return -EIO;
f = lseek(fd, 0, SEEK_SET);
if (f != 0)
return -errno;
r = memfd_set_sealed(fd);
if (r < 0)
return r;
r = fd;
fd = -1;
return r;
}
try_pipe:
if ((flags & ACQUIRE_NO_PIPE) == 0) {
if (pipe2(pipefds, O_CLOEXEC|O_NONBLOCK) < 0)
return -errno;
isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0);
if (isz < 0)
return -errno;
if ((size_t) isz < size) {
isz = (int) size;
if (isz < 0 || (size_t) isz != size)
return -E2BIG;
/* Try to bump the pipe size */
(void) fcntl(pipefds[1], F_SETPIPE_SZ, isz);
/* See if that worked */
isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0);
if (isz < 0)
return -errno;
if ((size_t) isz < size)
goto try_dev_shm;
}
n = write(pipefds[1], data, size);
if (n < 0)
return -errno;
if ((size_t) n != size)
return -EIO;
(void) fd_nonblock(pipefds[0], false);
r = pipefds[0];
pipefds[0] = -1;
return r;
}
try_dev_shm:
if ((flags & ACQUIRE_NO_TMPFILE) == 0) {
fd = open("/dev/shm", O_RDWR|O_TMPFILE|O_CLOEXEC, 0500);
if (fd < 0)
goto try_dev_shm_without_o_tmpfile;
n = write(fd, data, size);
if (n < 0)
return -errno;
if ((size_t) n != size)
return -EIO;
/* Let's reopen the thing, in order to get an O_RDONLY fd for the original O_RDWR one */
xsprintf(procfs_path, "/proc/self/fd/%i", fd);
r = open(procfs_path, O_RDONLY|O_CLOEXEC);
if (r < 0)
return -errno;
return r;
}
try_dev_shm_without_o_tmpfile:
if ((flags & ACQUIRE_NO_REGULAR) == 0) {
fd = mkostemp_safe(pattern);
if (fd < 0)
return fd;
n = write(fd, data, size);
if (n < 0) {
r = -errno;
goto unlink_and_return;
}
if ((size_t) n != size) {
r = -EIO;
goto unlink_and_return;
}
/* Let's reopen the thing, in order to get an O_RDONLY fd for the original O_RDWR one */
r = open(pattern, O_RDONLY|O_CLOEXEC);
if (r < 0)
r = -errno;
unlink_and_return:
(void) unlink(pattern);
return r;
}
return -EOPNOTSUPP;
}

View File

@ -76,6 +76,18 @@ bool fdname_is_valid(const char *s);
int fd_get_path(int fd, char **ret);
int move_fd(int from, int to, int cloexec);
enum {
ACQUIRE_NO_DEV_NULL = 1 << 0,
ACQUIRE_NO_MEMFD = 1 << 1,
ACQUIRE_NO_PIPE = 1 << 2,
ACQUIRE_NO_TMPFILE = 1 << 3,
ACQUIRE_NO_REGULAR = 1 << 4,
};
int acquire_data_fd(const void *data, size_t size, unsigned flags);
/* Hint: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5 */
#define ERRNO_IS_DISCONNECT(r) \
IN_SET(r, ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE, ENETUNREACH)

View File

@ -509,7 +509,7 @@ static int getenv_tmp_dir(const char **ret_path) {
r = -ENOTDIR;
goto next;
}
if (!path_is_safe(e)) {
if (!path_is_normalized(e)) {
r = -EPERM;
goto next;
}
@ -707,7 +707,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
if (errno == ENOENT &&
(flags & CHASE_NONEXISTENT) &&
(isempty(todo) || path_is_safe(todo))) {
(isempty(todo) || path_is_normalized(todo))) {
/* If CHASE_NONEXISTENT is set, and the path does not exist, then that's OK, return
* what we got so far. But don't allow this if the remaining path contains "../ or "./"

View File

@ -97,7 +97,10 @@ int unhexmem(const char *p, size_t l, void **mem, size_t *len) {
assert(mem);
assert(len);
assert(p);
assert(p || l == 0);
if (l == (size_t) -1)
l = strlen(p);
if (l % 2 != 0)
return -EINVAL;
@ -161,6 +164,8 @@ char *base32hexmem(const void *p, size_t l, bool padding) {
const uint8_t *x;
size_t len;
assert(p || l == 0);
if (padding)
/* five input bytes makes eight output bytes, padding is added so we must round up */
len = 8 * (l + 4) / 5;
@ -270,7 +275,12 @@ int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *_l
size_t len;
unsigned pad = 0;
assert(p);
assert(p || l == 0);
assert(mem);
assert(_len);
if (l == (size_t) -1)
l = strlen(p);
/* padding ensures any base32hex input has input divisible by 8 */
if (padding && l % 8 != 0)
@ -520,6 +530,9 @@ ssize_t base64mem(const void *p, size_t l, char **out) {
char *r, *z;
const uint8_t *x;
assert(p || l == 0);
assert(out);
/* three input bytes makes four output bytes, padding is added so we must round up */
z = r = malloc(4 * (l + 2) / 3 + 1);
if (!r)
@ -555,10 +568,11 @@ ssize_t base64mem(const void *p, size_t l, char **out) {
return z - r;
}
static int base64_append_width(char **prefix, int plen,
const char *sep, int indent,
const void *p, size_t l,
int width) {
static int base64_append_width(
char **prefix, int plen,
const char *sep, int indent,
const void *p, size_t l,
int width) {
_cleanup_free_ char *x = NULL;
char *t, *s;
@ -597,17 +611,18 @@ static int base64_append_width(char **prefix, int plen,
return 0;
}
int base64_append(char **prefix, int plen,
const void *p, size_t l,
int indent, int width) {
int base64_append(
char **prefix, int plen,
const void *p, size_t l,
int indent, int width) {
if (plen > width / 2 || plen + indent > width)
/* leave indent on the left, keep last column free */
return base64_append_width(prefix, plen, "\n", indent, p, l, width - indent - 1);
else
/* leave plen on the left, keep last column free */
return base64_append_width(prefix, plen, NULL, plen, p, l, width - plen - 1);
};
}
int unbase64mem(const char *p, size_t l, void **mem, size_t *_len) {
_cleanup_free_ uint8_t *r = NULL;
@ -616,7 +631,12 @@ int unbase64mem(const char *p, size_t l, void **mem, size_t *_len) {
const char *x;
size_t len;
assert(p);
assert(p || l == 0);
assert(mem);
assert(_len);
if (l == (size_t) -1)
l = strlen(p);
/* padding ensures any base63 input has input divisible by 4 */
if (l % 4 != 0)
@ -660,6 +680,7 @@ int unbase64mem(const char *p, size_t l, void **mem, size_t *_len) {
}
switch (l % 4) {
case 3:
a = unbase64char(x[0]);
if (a < 0)
@ -717,7 +738,10 @@ void hexdump(FILE *f, const void *p, size_t s) {
const uint8_t *b = p;
unsigned n = 0;
assert(s == 0 || b);
assert(b || s == 0);
if (!f)
f = stdout;
while (s > 0) {
size_t i;

View File

@ -722,7 +722,7 @@ bool filename_is_valid(const char *p) {
return true;
}
bool path_is_safe(const char *p) {
bool path_is_normalized(const char *p) {
if (isempty(p))
return false;
@ -736,7 +736,6 @@ bool path_is_safe(const char *p) {
if (strlen(p)+1 > PATH_MAX)
return false;
/* The following two checks are not really dangerous, but hey, they still are confusing */
if (startswith(p, "./") || endswith(p, "/.") || strstr(p, "/./"))
return false;

View File

@ -132,7 +132,7 @@ int parse_path_argument_and_warn(const char *path, bool suppress_root, char **ar
char* dirname_malloc(const char *path);
bool filename_is_valid(const char *p) _pure_;
bool path_is_safe(const char *p) _pure_;
bool path_is_normalized(const char *p) _pure_;
char *file_in_same_dir(const char *path, const char *filename);

View File

@ -271,17 +271,21 @@ static const char * const rlmap_initrd[] = {
};
const char* runlevel_to_target(const char *word) {
const char * const *rlmap_ptr;
size_t i;
const char * const *rlmap_ptr = in_initrd() ? rlmap_initrd
: rlmap;
if (!word)
return NULL;
if (in_initrd() && (word = startswith(word, "rd.")) == NULL)
return NULL;
if (in_initrd()) {
word = startswith(word, "rd.");
if (!word)
return NULL;
}
for (i = 0; rlmap_ptr[i] != NULL; i += 2)
rlmap_ptr = in_initrd() ? rlmap_initrd : rlmap;
for (i = 0; rlmap_ptr[i]; i += 2)
if (streq(word, rlmap_ptr[i]))
return rlmap_ptr[i+1];

View File

@ -383,7 +383,7 @@ int unit_name_path_escape(const char *f, char **ret) {
if (STR_IN_SET(p, "/", ""))
s = strdup("-");
else {
if (!path_is_safe(p))
if (!path_is_normalized(p))
return -EINVAL;
/* Truncate trailing slashes */
@ -433,7 +433,7 @@ int unit_name_path_unescape(const char *f, char **ret) {
if (!s)
return -ENOMEM;
if (!path_is_safe(s)) {
if (!path_is_normalized(s)) {
free(s);
return -EINVAL;
}

View File

@ -606,7 +606,7 @@ bool valid_home(const char *p) {
if (!path_is_absolute(p))
return false;
if (!path_is_safe(p))
if (!path_is_normalized(p))
return false;
/* Colons are used as field separators, and hence not OK */

View File

@ -35,6 +35,7 @@
#include "execute.h"
#include "fd-util.h"
#include "fileio.h"
#include "hexdecoct.h"
#include "io-util.h"
#include "ioprio.h"
#include "journal-util.h"
@ -684,7 +685,7 @@ static int property_get_syslog_facility(
return sd_bus_message_append(reply, "i", LOG_FAC(c->syslog_priority));
}
static int property_get_input_fdname(
static int property_get_stdio_fdname(
sd_bus *bus,
const char *path,
const char *interface,
@ -694,19 +695,26 @@ static int property_get_input_fdname(
sd_bus_error *error) {
ExecContext *c = userdata;
const char *name;
int fileno;
assert(bus);
assert(c);
assert(property);
assert(reply);
name = exec_context_fdname(c, STDIN_FILENO);
if (streq(property, "StandardInputFileDescriptorName"))
fileno = STDIN_FILENO;
else if (streq(property, "StandardOutputFileDescriptorName"))
fileno = STDOUT_FILENO;
else {
assert(streq(property, "StandardErrorFileDescriptorName"));
fileno = STDERR_FILENO;
}
return sd_bus_message_append(reply, "s", name);
return sd_bus_message_append(reply, "s", exec_context_fdname(c, fileno));
}
static int property_get_output_fdname(
static int property_get_input_data(
sd_bus *bus,
const char *path,
const char *interface,
@ -716,19 +724,13 @@ static int property_get_output_fdname(
sd_bus_error *error) {
ExecContext *c = userdata;
const char *name = NULL;
assert(bus);
assert(c);
assert(property);
assert(reply);
if (c->std_output == EXEC_OUTPUT_NAMED_FD && streq(property, "StandardOutputFileDescriptorName"))
name = exec_context_fdname(c, STDOUT_FILENO);
else if (c->std_error == EXEC_OUTPUT_NAMED_FD && streq(property, "StandardErrorFileDescriptorName"))
name = exec_context_fdname(c, STDERR_FILENO);
return sd_bus_message_append(reply, "s", name);
return sd_bus_message_append_array(reply, 'y', c->stdin_data, c->stdin_data_size);
}
static int property_get_bind_paths(
@ -858,11 +860,12 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("CPUSchedulingResetOnFork", "b", bus_property_get_bool, offsetof(ExecContext, cpu_sched_reset_on_fork), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("NonBlocking", "b", bus_property_get_bool, offsetof(ExecContext, non_blocking), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("StandardInput", "s", property_get_exec_input, offsetof(ExecContext, std_input), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("StandardInputFileDescriptorName", "s", property_get_input_fdname, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("StandardInputFileDescriptorName", "s", property_get_stdio_fdname, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("StandardInputData", "ay", property_get_input_data, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("StandardOutput", "s", bus_property_get_exec_output, offsetof(ExecContext, std_output), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("StandardOutputFileDescriptorName", "s", property_get_output_fdname, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("StandardOutputFileDescriptorName", "s", property_get_stdio_fdname, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("StandardError", "s", bus_property_get_exec_output, offsetof(ExecContext, std_error), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("StandardErrorFileDescriptorName", "s", property_get_output_fdname, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("StandardErrorFileDescriptorName", "s", property_get_stdio_fdname, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("TTYPath", "s", NULL, offsetof(ExecContext, tty_path), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("TTYReset", "b", bus_property_get_bool, offsetof(ExecContext, tty_reset), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("TTYVHangup", "b", bus_property_get_bool, offsetof(ExecContext, tty_vhangup), SD_BUS_VTABLE_PROPERTY_CONST),
@ -1814,28 +1817,125 @@ int bus_exec_context_set_transient_property(
if (r < 0)
return r;
if (!fdname_is_valid(s))
if (isempty(s))
s = NULL;
else if (!fdname_is_valid(s))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid file descriptor name");
if (mode != UNIT_CHECK) {
if (streq(name, "StandardInputFileDescriptorName")) {
r = free_and_strdup(c->stdio_fdname + STDIN_FILENO, s);
if (r < 0)
return r;
c->std_input = EXEC_INPUT_NAMED_FD;
r = free_and_strdup(&c->stdio_fdname[STDIN_FILENO], s);
if (r < 0)
return r;
unit_write_drop_in_private_format(u, mode, name, "StandardInput=fd:%s", s);
unit_write_drop_in_private_format(u, mode, name, "StandardInput=fd:%s", exec_context_fdname(c, STDIN_FILENO));
} else if (streq(name, "StandardOutputFileDescriptorName")) {
c->std_output = EXEC_OUTPUT_NAMED_FD;
r = free_and_strdup(&c->stdio_fdname[STDOUT_FILENO], s);
r = free_and_strdup(c->stdio_fdname + STDOUT_FILENO, s);
if (r < 0)
return r;
unit_write_drop_in_private_format(u, mode, name, "StandardOutput=fd:%s", s);
} else if (streq(name, "StandardErrorFileDescriptorName")) {
c->std_error = EXEC_OUTPUT_NAMED_FD;
c->std_output = EXEC_OUTPUT_NAMED_FD;
unit_write_drop_in_private_format(u, mode, name, "StandardOutput=fd:%s", exec_context_fdname(c, STDOUT_FILENO));
} else {
assert(streq(name, "StandardErrorFileDescriptorName"));
r = free_and_strdup(&c->stdio_fdname[STDERR_FILENO], s);
if (r < 0)
return r;
unit_write_drop_in_private_format(u, mode, name, "StandardError=fd:%s", s);
c->std_error = EXEC_OUTPUT_NAMED_FD;
unit_write_drop_in_private_format(u, mode, name, "StandardError=fd:%s", exec_context_fdname(c, STDERR_FILENO));
}
}
return 1;
} else if (STR_IN_SET(name, "StandardInputFile", "StandardOutputFile", "StandardErrorFile")) {
const char *s;
r = sd_bus_message_read(message, "s", &s);
if (r < 0)
return r;
if (!path_is_absolute(s))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Path %s is not absolute", s);
if (!path_is_normalized(s))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Path %s is not normalized", s);
if (mode != UNIT_CHECK) {
if (streq(name, "StandardInputFile")) {
r = free_and_strdup(&c->stdio_file[STDIN_FILENO], s);
if (r < 0)
return r;
c->std_input = EXEC_INPUT_FILE;
unit_write_drop_in_private_format(u, mode, name, "StandardInput=file:%s", s);
} else if (streq(name, "StandardOutputFile")) {
r = free_and_strdup(&c->stdio_file[STDOUT_FILENO], s);
if (r < 0)
return r;
c->std_output = EXEC_OUTPUT_FILE;
unit_write_drop_in_private_format(u, mode, name, "StandardOutput=file:%s", s);
} else {
assert(streq(name, "StandardErrorFile"));
r = free_and_strdup(&c->stdio_file[STDERR_FILENO], s);
if (r < 0)
return r;
c->std_error = EXEC_OUTPUT_FILE;
unit_write_drop_in_private_format(u, mode, name, "StandardError=file:%s", s);
}
}
return 1;
} else if (streq(name, "StandardInputData")) {
const void *p;
size_t sz;
r = sd_bus_message_read_array(message, 'y', &p, &sz);
if (r < 0)
return r;
if (mode != UNIT_CHECK) {
_cleanup_free_ char *encoded = NULL;
if (sz == 0) {
c->stdin_data = mfree(c->stdin_data);
c->stdin_data_size = 0;
unit_write_drop_in_private(u, mode, name, "StandardInputData=");
} else {
void *q;
ssize_t n;
if (c->stdin_data_size + sz < c->stdin_data_size || /* check for overflow */
c->stdin_data_size + sz > EXEC_STDIN_DATA_MAX)
return -E2BIG;
n = base64mem(p, sz, &encoded);
if (n < 0)
return (int) n;
q = realloc(c->stdin_data, c->stdin_data_size + sz);
if (!q)
return -ENOMEM;
memcpy((uint8_t*) q + c->stdin_data_size, p, sz);
c->stdin_data = q;
c->stdin_data_size += sz;
unit_write_drop_in_private_format(u, mode, name, "StandardInputData=%s", encoded);
}
}
@ -2363,7 +2463,7 @@ int bus_exec_context_set_transient_property(
return r;
STRV_FOREACH(p, l) {
if (!path_is_safe(*p) || path_is_absolute(*p))
if (!path_is_normalized(*p) || path_is_absolute(*p))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "%s= path is not valid: %s", name, *p);
}

View File

@ -276,7 +276,7 @@ static bool exec_context_needs_term(const ExecContext *c) {
}
static int open_null_as(int flags, int nfd) {
int fd, r;
int fd;
assert(nfd >= 0);
@ -284,13 +284,7 @@ static int open_null_as(int flags, int nfd) {
if (fd < 0)
return -errno;
if (fd != nfd) {
r = dup2(fd, nfd) < 0 ? -errno : nfd;
safe_close(fd);
} else
r = nfd;
return r;
return move_fd(fd, nfd, false);
}
static int connect_journal_socket(int fd, uid_t uid, gid_t gid) {
@ -382,34 +376,78 @@ static int connect_logger_as(
is_kmsg_output(output),
is_terminal_output(output));
if (fd == nfd)
return nfd;
r = dup2(fd, nfd) < 0 ? -errno : nfd;
safe_close(fd);
return r;
return move_fd(fd, nfd, false);
}
static int open_terminal_as(const char *path, mode_t mode, int nfd) {
int fd, r;
static int open_terminal_as(const char *path, int flags, int nfd) {
int fd;
assert(path);
assert(nfd >= 0);
fd = open_terminal(path, mode | O_NOCTTY);
fd = open_terminal(path, flags | O_NOCTTY);
if (fd < 0)
return fd;
if (fd != nfd) {
r = dup2(fd, nfd) < 0 ? -errno : nfd;
safe_close(fd);
} else
r = nfd;
return r;
return move_fd(fd, nfd, false);
}
static int fixup_input(ExecInput std_input, int socket_fd, bool apply_tty_stdin) {
static int acquire_path(const char *path, int flags, mode_t mode) {
union sockaddr_union sa = {
.sa.sa_family = AF_UNIX,
};
int fd, r;
assert(path);
if (IN_SET(flags & O_ACCMODE, O_WRONLY, O_RDWR))
flags |= O_CREAT;
fd = open(path, flags|O_NOCTTY, mode);
if (fd >= 0)
return fd;
if (errno != ENXIO) /* ENXIO is returned when we try to open() an AF_UNIX file system socket on Linux */
return -errno;
if (strlen(path) > sizeof(sa.un.sun_path)) /* Too long, can't be a UNIX socket */
return -ENXIO;
/* So, it appears the specified path could be an AF_UNIX socket. Let's see if we can connect to it. */
fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0)
return -errno;
strncpy(sa.un.sun_path, path, sizeof(sa.un.sun_path));
if (connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) {
safe_close(fd);
return errno == EINVAL ? -ENXIO : -errno; /* Propagate initial error if we get EINVAL, i.e. we have
* indication that his wasn't an AF_UNIX socket after all */
}
if ((flags & O_ACCMODE) == O_RDONLY)
r = shutdown(fd, SHUT_WR);
else if ((flags & O_ACCMODE) == O_WRONLY)
r = shutdown(fd, SHUT_RD);
else
return fd;
if (r < 0) {
safe_close(fd);
return -errno;
}
return fd;
}
static int fixup_input(
const ExecContext *context,
int socket_fd,
bool apply_tty_stdin) {
ExecInput std_input;
assert(context);
std_input = context->std_input;
if (is_terminal_input(std_input) && !apply_tty_stdin)
return EXEC_INPUT_NULL;
@ -417,6 +455,9 @@ static int fixup_input(ExecInput std_input, int socket_fd, bool apply_tty_stdin)
if (std_input == EXEC_INPUT_SOCKET && socket_fd < 0)
return EXEC_INPUT_NULL;
if (std_input == EXEC_INPUT_DATA && context->stdin_data_size == 0)
return EXEC_INPUT_NULL;
return std_input;
}
@ -444,13 +485,15 @@ static int setup_input(
return -errno;
/* Try to make this the controlling tty, if it is a tty, and reset it */
(void) ioctl(STDIN_FILENO, TIOCSCTTY, context->std_input == EXEC_INPUT_TTY_FORCE);
(void) reset_terminal_fd(STDIN_FILENO, true);
if (isatty(STDIN_FILENO)) {
(void) ioctl(STDIN_FILENO, TIOCSCTTY, context->std_input == EXEC_INPUT_TTY_FORCE);
(void) reset_terminal_fd(STDIN_FILENO, true);
}
return STDIN_FILENO;
}
i = fixup_input(context->std_input, socket_fd, params->flags & EXEC_APPLY_TTY_STDIN);
i = fixup_input(context, socket_fd, params->flags & EXEC_APPLY_TTY_STDIN);
switch (i) {
@ -460,7 +503,7 @@ static int setup_input(
case EXEC_INPUT_TTY:
case EXEC_INPUT_TTY_FORCE:
case EXEC_INPUT_TTY_FAIL: {
int fd, r;
int fd;
fd = acquire_terminal(exec_context_tty_path(context),
i == EXEC_INPUT_TTY_FAIL,
@ -470,22 +513,46 @@ static int setup_input(
if (fd < 0)
return fd;
if (fd != STDIN_FILENO) {
r = dup2(fd, STDIN_FILENO) < 0 ? -errno : STDIN_FILENO;
safe_close(fd);
} else
r = STDIN_FILENO;
return r;
return move_fd(fd, STDIN_FILENO, false);
}
case EXEC_INPUT_SOCKET:
assert(socket_fd >= 0);
return dup2(socket_fd, STDIN_FILENO) < 0 ? -errno : STDIN_FILENO;
case EXEC_INPUT_NAMED_FD:
assert(named_iofds[STDIN_FILENO] >= 0);
(void) fd_nonblock(named_iofds[STDIN_FILENO], false);
return dup2(named_iofds[STDIN_FILENO], STDIN_FILENO) < 0 ? -errno : STDIN_FILENO;
case EXEC_INPUT_DATA: {
int fd;
fd = acquire_data_fd(context->stdin_data, context->stdin_data_size, 0);
if (fd < 0)
return fd;
return move_fd(fd, STDIN_FILENO, false);
}
case EXEC_INPUT_FILE: {
bool rw;
int fd;
assert(context->stdio_file[STDIN_FILENO]);
rw = (context->std_output == EXEC_OUTPUT_FILE && streq_ptr(context->stdio_file[STDIN_FILENO], context->stdio_file[STDOUT_FILENO])) ||
(context->std_error == EXEC_OUTPUT_FILE && streq_ptr(context->stdio_file[STDIN_FILENO], context->stdio_file[STDERR_FILENO]));
fd = acquire_path(context->stdio_file[STDIN_FILENO], rw ? O_RDWR : O_RDONLY, 0666 & ~context->umask);
if (fd < 0)
return fd;
return move_fd(fd, STDIN_FILENO, false);
}
default:
assert_not_reached("Unknown input type");
}
@ -530,7 +597,7 @@ static int setup_output(
return STDERR_FILENO;
}
i = fixup_input(context->std_input, socket_fd, params->flags & EXEC_APPLY_TTY_STDIN);
i = fixup_input(context, socket_fd, params->flags & EXEC_APPLY_TTY_STDIN);
o = fixup_output(context->std_output, socket_fd);
if (fileno == STDERR_FILENO) {
@ -559,8 +626,8 @@ static int setup_output(
if (i == EXEC_INPUT_NULL && is_terminal_input(context->std_input))
return open_terminal_as(exec_context_tty_path(context), O_WRONLY, fileno);
/* If the input is connected to anything that's not a /dev/null, inherit that... */
if (i != EXEC_INPUT_NULL)
/* If the input is connected to anything that's not a /dev/null or a data fd, inherit that... */
if (!IN_SET(i, EXEC_INPUT_NULL, EXEC_INPUT_DATA))
return dup2(STDIN_FILENO, fileno) < 0 ? -errno : fileno;
/* If we are not started from PID 1 we just inherit STDOUT from our parent process. */
@ -613,12 +680,34 @@ static int setup_output(
case EXEC_OUTPUT_SOCKET:
assert(socket_fd >= 0);
return dup2(socket_fd, fileno) < 0 ? -errno : fileno;
case EXEC_OUTPUT_NAMED_FD:
assert(named_iofds[fileno] >= 0);
(void) fd_nonblock(named_iofds[fileno], false);
return dup2(named_iofds[fileno], fileno) < 0 ? -errno : fileno;
case EXEC_OUTPUT_FILE: {
bool rw;
int fd;
assert(context->stdio_file[fileno]);
rw = context->std_input == EXEC_INPUT_FILE &&
streq_ptr(context->stdio_file[fileno], context->stdio_file[STDIN_FILENO]);
if (rw)
return dup2(STDIN_FILENO, fileno) < 0 ? -errno : fileno;
fd = acquire_path(context->stdio_file[fileno], O_WRONLY, 0666 & ~context->umask);
if (fd < 0)
return fd;
return move_fd(fd, fileno, false);
}
default:
assert_not_reached("Unknown error type");
}
@ -3501,8 +3590,10 @@ void exec_context_done(ExecContext *c) {
for (l = 0; l < ELEMENTSOF(c->rlimit); l++)
c->rlimit[l] = mfree(c->rlimit[l]);
for (l = 0; l < 3; l++)
for (l = 0; l < 3; l++) {
c->stdio_fdname[l] = mfree(c->stdio_fdname[l]);
c->stdio_file[l] = mfree(c->stdio_file[l]);
}
c->working_directory = mfree(c->working_directory);
c->root_directory = mfree(c->root_directory);
@ -3540,6 +3631,9 @@ void exec_context_done(ExecContext *c) {
c->log_level_max = -1;
exec_context_free_log_extra_fields(c);
c->stdin_data = mfree(c->stdin_data);
c->stdin_data_size = 0;
}
int exec_context_destroy_runtime_directory(ExecContext *c, const char *runtime_prefix) {
@ -3614,18 +3708,25 @@ const char* exec_context_fdname(const ExecContext *c, int fd_index) {
assert(c);
switch (fd_index) {
case STDIN_FILENO:
if (c->std_input != EXEC_INPUT_NAMED_FD)
return NULL;
return c->stdio_fdname[STDIN_FILENO] ?: "stdin";
case STDOUT_FILENO:
if (c->std_output != EXEC_OUTPUT_NAMED_FD)
return NULL;
return c->stdio_fdname[STDOUT_FILENO] ?: "stdout";
case STDERR_FILENO:
if (c->std_error != EXEC_OUTPUT_NAMED_FD)
return NULL;
return c->stdio_fdname[STDERR_FILENO] ?: "stderr";
default:
return NULL;
}
@ -3935,6 +4036,20 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
prefix, exec_output_to_string(c->std_output),
prefix, exec_output_to_string(c->std_error));
if (c->std_input == EXEC_INPUT_NAMED_FD)
fprintf(f, "%sStandardInputFileDescriptorName: %s\n", prefix, c->stdio_fdname[STDIN_FILENO]);
if (c->std_output == EXEC_OUTPUT_NAMED_FD)
fprintf(f, "%sStandardOutputFileDescriptorName: %s\n", prefix, c->stdio_fdname[STDOUT_FILENO]);
if (c->std_error == EXEC_OUTPUT_NAMED_FD)
fprintf(f, "%sStandardErrorFileDescriptorName: %s\n", prefix, c->stdio_fdname[STDERR_FILENO]);
if (c->std_input == EXEC_INPUT_FILE)
fprintf(f, "%sStandardInputFile: %s\n", prefix, c->stdio_file[STDIN_FILENO]);
if (c->std_output == EXEC_OUTPUT_FILE)
fprintf(f, "%sStandardOutputFile: %s\n", prefix, c->stdio_file[STDOUT_FILENO]);
if (c->std_error == EXEC_OUTPUT_FILE)
fprintf(f, "%sStandardErrorFile: %s\n", prefix, c->stdio_file[STDERR_FILENO]);
if (c->tty_path)
fprintf(f,
"%sTTYPath: %s\n"
@ -4631,6 +4746,8 @@ static const char* const exec_input_table[_EXEC_INPUT_MAX] = {
[EXEC_INPUT_TTY_FAIL] = "tty-fail",
[EXEC_INPUT_SOCKET] = "socket",
[EXEC_INPUT_NAMED_FD] = "fd",
[EXEC_INPUT_DATA] = "data",
[EXEC_INPUT_FILE] = "file",
};
DEFINE_STRING_TABLE_LOOKUP(exec_input, ExecInput);
@ -4647,6 +4764,7 @@ static const char* const exec_output_table[_EXEC_OUTPUT_MAX] = {
[EXEC_OUTPUT_JOURNAL_AND_CONSOLE] = "journal+console",
[EXEC_OUTPUT_SOCKET] = "socket",
[EXEC_OUTPUT_NAMED_FD] = "fd",
[EXEC_OUTPUT_FILE] = "file",
};
DEFINE_STRING_TABLE_LOOKUP(exec_output, ExecOutput);

View File

@ -38,6 +38,8 @@ typedef struct ExecParameters ExecParameters;
#include "namespace.h"
#include "nsflags.h"
#define EXEC_STDIN_DATA_MAX (64U*1024U*1024U)
typedef enum ExecUtmpMode {
EXEC_UTMP_INIT,
EXEC_UTMP_LOGIN,
@ -53,6 +55,8 @@ typedef enum ExecInput {
EXEC_INPUT_TTY_FAIL,
EXEC_INPUT_SOCKET,
EXEC_INPUT_NAMED_FD,
EXEC_INPUT_DATA,
EXEC_INPUT_FILE,
_EXEC_INPUT_MAX,
_EXEC_INPUT_INVALID = -1
} ExecInput;
@ -69,6 +73,7 @@ typedef enum ExecOutput {
EXEC_OUTPUT_JOURNAL_AND_CONSOLE,
EXEC_OUTPUT_SOCKET,
EXEC_OUTPUT_NAMED_FD,
EXEC_OUTPUT_FILE,
_EXEC_OUTPUT_MAX,
_EXEC_OUTPUT_INVALID = -1
} ExecOutput;
@ -163,6 +168,10 @@ struct ExecContext {
ExecOutput std_output;
ExecOutput std_error;
char *stdio_fdname[3];
char *stdio_file[3];
void *stdin_data;
size_t stdin_data_size;
nsec_t timer_slack_nsec;

View File

@ -41,6 +41,8 @@ $1.RemoveIPC, config_parse_bool, 0,
$1.StandardInput, config_parse_exec_input, 0, offsetof($1, exec_context)
$1.StandardOutput, config_parse_exec_output, 0, offsetof($1, exec_context)
$1.StandardError, config_parse_exec_output, 0, offsetof($1, exec_context)
$1.StandardInputText, config_parse_exec_input_text, 0, offsetof($1, exec_context)
$1.StandardInputData, config_parse_exec_input_data, 0, offsetof($1, exec_context)
$1.TTYPath, config_parse_unit_path_printf, 0, offsetof($1, exec_context.tty_path)
$1.TTYReset, config_parse_bool, 0, offsetof($1, exec_context.tty_reset)
$1.TTYVHangup, config_parse_bool, 0, offsetof($1, exec_context.tty_vhangup)

View File

@ -46,6 +46,7 @@
#include "escape.h"
#include "fd-util.h"
#include "fs-util.h"
#include "hexdecoct.h"
#include "io-util.h"
#include "ioprio.h"
#include "journal-util.h"
@ -832,21 +833,22 @@ int config_parse_socket_bindtodevice(
return 0;
}
DEFINE_CONFIG_PARSE_ENUM(config_parse_input, exec_input, ExecInput, "Failed to parse input literal specifier");
DEFINE_CONFIG_PARSE_ENUM(config_parse_output, exec_output, ExecOutput, "Failed to parse output literal specifier");
int config_parse_exec_input(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
int config_parse_exec_input(const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
ExecContext *c = data;
const char *name;
Unit *u = userdata;
const char *n;
ExecInput ei;
int r;
assert(data);
@ -854,41 +856,194 @@ int config_parse_exec_input(const char *unit,
assert(line);
assert(rvalue);
name = startswith(rvalue, "fd:");
if (name) {
/* Strip prefix and validate fd name */
if (!fdname_is_valid(name)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid file descriptor name, ignoring: %s", name);
n = startswith(rvalue, "fd:");
if (n) {
_cleanup_free_ char *resolved = NULL;
r = unit_full_printf(u, n, &resolved);
if (r < 0)
return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s: %m", n);
if (isempty(resolved))
resolved = mfree(resolved);
else if (!fdname_is_valid(resolved)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid file descriptor name: %s", resolved);
return -EINVAL;
}
free_and_replace(c->stdio_fdname[STDIN_FILENO], resolved);
ei = EXEC_INPUT_NAMED_FD;
} else if ((n = startswith(rvalue, "file:"))) {
_cleanup_free_ char *resolved = NULL;
r = unit_full_printf(u, n, &resolved);
if (r < 0)
return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s: %m", n);
if (!path_is_absolute(resolved)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "file: requires an absolute path name: %s", resolved);
return -EINVAL;
}
if (!path_is_normalized(resolved)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "file: requires a normalized path name: %s", resolved);
return -EINVAL;
}
free_and_replace(c->stdio_file[STDIN_FILENO], resolved);
ei = EXEC_INPUT_FILE;
} else {
ei = exec_input_from_string(rvalue);
if (ei < 0) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse input specifier, ignoring: %s", rvalue);
return 0;
}
c->std_input = EXEC_INPUT_NAMED_FD;
r = free_and_strdup(&c->stdio_fdname[STDIN_FILENO], name);
if (r < 0)
log_oom();
return r;
} else {
ExecInput ei = exec_input_from_string(rvalue);
if (ei == _EXEC_INPUT_INVALID)
log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse input specifier, ignoring: %s", rvalue);
else
c->std_input = ei;
return 0;
}
c->std_input = ei;
return 0;
}
int config_parse_exec_output(const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
int config_parse_exec_input_text(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
_cleanup_free_ char *unescaped = NULL, *resolved = NULL;
ExecContext *c = data;
Unit *u = userdata;
size_t sz;
void *p;
int r;
assert(data);
assert(filename);
assert(line);
assert(rvalue);
if (isempty(rvalue)) {
/* Reset if the empty string is assigned */
c->stdin_data = mfree(c->stdin_data);
c->stdin_data_size = 0;
return 0;
}
r = cunescape(rvalue, 0, &unescaped);
if (r < 0)
return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to decode C escaped text: %s", rvalue);
r = unit_full_printf(u, unescaped, &resolved);
if (r < 0)
return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers: %s", unescaped);
sz = strlen(resolved);
if (c->stdin_data_size + sz + 1 < c->stdin_data_size || /* check for overflow */
c->stdin_data_size + sz + 1 > EXEC_STDIN_DATA_MAX) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Standard input data too large (%zu), maximum of %zu permitted, ignoring.", c->stdin_data_size + sz, (size_t) EXEC_STDIN_DATA_MAX);
return -E2BIG;
}
p = realloc(c->stdin_data, c->stdin_data_size + sz + 1);
if (!p)
return log_oom();
*((char*) mempcpy((char*) p + c->stdin_data_size, resolved, sz)) = '\n';
c->stdin_data = p;
c->stdin_data_size += sz + 1;
return 0;
}
int config_parse_exec_input_data(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
_cleanup_free_ char *cleaned = NULL;
_cleanup_free_ void *p = NULL;
ExecContext *c = data;
size_t sz;
void *q;
int r;
assert(data);
assert(filename);
assert(line);
assert(rvalue);
if (isempty(rvalue)) {
/* Reset if the empty string is assigned */
c->stdin_data = mfree(c->stdin_data);
c->stdin_data_size = 0;
return 0;
}
/* Be tolerant to whitespace. Remove it all before decoding */
cleaned = strdup(rvalue);
if (!cleaned)
return log_oom();
delete_chars(cleaned, WHITESPACE);
r = unbase64mem(cleaned, (size_t) -1, &p, &sz);
if (r < 0)
return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to decode base64 data, ignoring: %s", cleaned);
assert(sz > 0);
if (c->stdin_data_size + sz < c->stdin_data_size || /* check for overflow */
c->stdin_data_size + sz > EXEC_STDIN_DATA_MAX) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Standard input data too large (%zu), maximum of %zu permitted, ignoring.", c->stdin_data_size + sz, (size_t) EXEC_STDIN_DATA_MAX);
return -E2BIG;
}
q = realloc(c->stdin_data, c->stdin_data_size + sz);
if (!q)
return log_oom();
memcpy((uint8_t*) q + c->stdin_data_size, p, sz);
c->stdin_data = q;
c->stdin_data_size += sz;
return 0;
}
int config_parse_exec_output(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
_cleanup_free_ char *resolved = NULL;
const char *n;
ExecContext *c = data;
Unit *u = userdata;
ExecOutput eo;
const char *name;
int r;
assert(data);
@ -897,38 +1052,67 @@ int config_parse_exec_output(const char *unit,
assert(lvalue);
assert(rvalue);
name = startswith(rvalue, "fd:");
if (name) {
/* Strip prefix and validate fd name */
if (!fdname_is_valid(name)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid file descriptor name, ignoring: %s", name);
return 0;
n = startswith(rvalue, "fd:");
if (n) {
r = unit_full_printf(u, n, &resolved);
if (r < 0)
return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s: %m", n);
if (isempty(resolved))
resolved = mfree(resolved);
else if (!fdname_is_valid(resolved)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid file descriptor name: %s", resolved);
return -EINVAL;
}
eo = EXEC_OUTPUT_NAMED_FD;
} else if ((n = startswith(rvalue, "file:"))) {
r = unit_full_printf(u, n, &resolved);
if (r < 0)
return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s: %m", n);
if (!path_is_absolute(resolved)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "file: requires an absolute path name: %s", resolved);
return -EINVAL;
}
if (!path_is_normalized(resolved)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "file: requires a normalized path name, ignoring: %s", resolved);
return -EINVAL;
}
eo = EXEC_OUTPUT_FILE;
} else {
eo = exec_output_from_string(rvalue);
if (eo == _EXEC_OUTPUT_INVALID) {
if (eo < 0) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse output specifier, ignoring: %s", rvalue);
return 0;
}
}
if (streq(lvalue, "StandardOutput")) {
if (eo == EXEC_OUTPUT_NAMED_FD)
free_and_replace(c->stdio_fdname[STDOUT_FILENO], resolved);
else
free_and_replace(c->stdio_file[STDOUT_FILENO], resolved);
c->std_output = eo;
r = free_and_strdup(&c->stdio_fdname[STDOUT_FILENO], name);
if (r < 0)
log_oom();
return r;
} else if (streq(lvalue, "StandardError")) {
c->std_error = eo;
r = free_and_strdup(&c->stdio_fdname[STDERR_FILENO], name);
if (r < 0)
log_oom();
return r;
} else {
log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse output property, ignoring: %s", lvalue);
return 0;
assert(streq(lvalue, "StandardError"));
if (eo == EXEC_OUTPUT_NAMED_FD)
free_and_replace(c->stdio_fdname[STDERR_FILENO], resolved);
else
free_and_replace(c->stdio_file[STDERR_FILENO], resolved);
c->std_error = eo;
}
return 0;
}
int config_parse_exec_io_class(const char *unit,
@ -3867,7 +4051,7 @@ int config_parse_exec_directories(
continue;
}
if (!path_is_safe(k) || path_is_absolute(k)) {
if (!path_is_normalized(k) || path_is_absolute(k)) {
log_syntax(unit, LOG_ERR, filename, line, 0,
"%s= path is not valid, ignoring assignment: %s", lvalue, rvalue);
continue;

View File

@ -47,9 +47,9 @@ int config_parse_service_type(const char *unit, const char *filename, unsigned l
int config_parse_service_restart(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_socket_bindtodevice(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_exec_output(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_output(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_exec_input(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_input(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_exec_input_text(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_exec_input_data(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_exec_io_class(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_exec_io_priority(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_exec_cpu_sched_policy(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);

View File

@ -572,6 +572,40 @@ static int config_parse_show_status(
return 0;
}
static int config_parse_output_restricted(
const char* unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
ExecOutput t, *eo = data;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(data);
t = exec_output_from_string(rvalue);
if (t < 0) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse output type, ignoring: %s", rvalue);
return 0;
}
if (IN_SET(t, EXEC_OUTPUT_SOCKET, EXEC_OUTPUT_NAMED_FD, EXEC_OUTPUT_FILE)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Standard output types socket, fd:, file: are not supported as defaults, ignoring: %s", rvalue);
return 0;
}
*eo = t;
return 0;
}
static int config_parse_crash_chvt(
const char* unit,
const char *filename,
@ -723,8 +757,8 @@ static int parse_config_file(void) {
#endif
{ "Manager", "TimerSlackNSec", config_parse_nsec, 0, &arg_timer_slack_nsec },
{ "Manager", "DefaultTimerAccuracySec", config_parse_sec, 0, &arg_default_timer_accuracy_usec },
{ "Manager", "DefaultStandardOutput", config_parse_output, 0, &arg_default_std_output },
{ "Manager", "DefaultStandardError", config_parse_output, 0, &arg_default_std_error },
{ "Manager", "DefaultStandardOutput", config_parse_output_restricted,0, &arg_default_std_output },
{ "Manager", "DefaultStandardError", config_parse_output_restricted,0, &arg_default_std_error },
{ "Manager", "DefaultTimeoutStartSec", config_parse_sec, 0, &arg_default_timeout_start_usec },
{ "Manager", "DefaultTimeoutStopSec", config_parse_sec, 0, &arg_default_timeout_stop_usec },
{ "Manager", "DefaultRestartSec", config_parse_sec, 0, &arg_default_restart_usec },

View File

@ -592,10 +592,8 @@ static int service_add_default_dependencies(Service *s) {
static void service_fix_output(Service *s) {
assert(s);
/* If nothing has been explicitly configured, patch default
* output in. If input is socket/tty we avoid this however,
* since in that case we want output to default to the same
* place as we read input from. */
/* If nothing has been explicitly configured, patch default output in. If input is socket/tty we avoid this
* however, since in that case we want output to default to the same place as we read input from. */
if (s->exec_context.std_error == EXEC_OUTPUT_INHERIT &&
s->exec_context.std_output == EXEC_OUTPUT_INHERIT &&
@ -605,6 +603,10 @@ static void service_fix_output(Service *s) {
if (s->exec_context.std_output == EXEC_OUTPUT_INHERIT &&
s->exec_context.std_input == EXEC_INPUT_NULL)
s->exec_context.std_output = UNIT(s)->manager->default_std_output;
if (s->exec_context.std_input == EXEC_INPUT_NULL &&
s->exec_context.stdin_data_size > 0)
s->exec_context.std_input = EXEC_INPUT_DATA;
}
static int service_setup_bus_name(Service *s) {

View File

@ -4461,7 +4461,7 @@ int unit_require_mounts_for(Unit *u, const char *path, UnitDependencyMask mask)
path_kill_slashes(p);
if (!path_is_safe(p)) {
if (!path_is_normalized(p)) {
free(p);
return -EPERM;
}

View File

@ -104,28 +104,24 @@ int import_fork_tar_x(const char *path, pid_t *ret) {
pipefd[1] = safe_close(pipefd[1]);
if (dup2(pipefd[0], STDIN_FILENO) != STDIN_FILENO) {
log_error_errno(errno, "Failed to dup2() fd: %m");
r = move_fd(pipefd[0], STDIN_FILENO, false);
if (r < 0) {
log_error_errno(r, "Failed to move fd: %m");
_exit(EXIT_FAILURE);
}
if (pipefd[0] != STDIN_FILENO)
pipefd[0] = safe_close(pipefd[0]);
null_fd = open("/dev/null", O_WRONLY|O_NOCTTY);
if (null_fd < 0) {
log_error_errno(errno, "Failed to open /dev/null: %m");
_exit(EXIT_FAILURE);
}
if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) {
log_error_errno(errno, "Failed to dup2() fd: %m");
r = move_fd(null_fd, STDOUT_FILENO, false);
if (r < 0) {
log_error_errno(r, "Failed to move fd: %m");
_exit(EXIT_FAILURE);
}
if (null_fd != STDOUT_FILENO)
null_fd = safe_close(null_fd);
stdio_unset_cloexec();
if (unshare(CLONE_NEWNET) < 0)
@ -176,28 +172,24 @@ int import_fork_tar_c(const char *path, pid_t *ret) {
pipefd[0] = safe_close(pipefd[0]);
if (dup2(pipefd[1], STDOUT_FILENO) != STDOUT_FILENO) {
log_error_errno(errno, "Failed to dup2() fd: %m");
r = move_fd(pipefd[1], STDOUT_FILENO, false);
if (r < 0) {
log_error_errno(r, "Failed to move fd: %m");
_exit(EXIT_FAILURE);
}
if (pipefd[1] != STDOUT_FILENO)
pipefd[1] = safe_close(pipefd[1]);
null_fd = open("/dev/null", O_RDONLY|O_NOCTTY);
if (null_fd < 0) {
log_error_errno(errno, "Failed to open /dev/null: %m");
_exit(EXIT_FAILURE);
}
if (dup2(null_fd, STDIN_FILENO) != STDIN_FILENO) {
log_error_errno(errno, "Failed to dup2() fd: %m");
r = move_fd(null_fd, STDIN_FILENO, false);
if (r < 0) {
log_error_errno(errno, "Failed to move fd: %m");
_exit(EXIT_FAILURE);
}
if (null_fd != STDIN_FILENO)
null_fd = safe_close(null_fd);
stdio_unset_cloexec();
if (unshare(CLONE_NEWNET) < 0)

View File

@ -493,28 +493,24 @@ int pull_verify(PullJob *main_job,
gpg_pipe[1] = safe_close(gpg_pipe[1]);
if (dup2(gpg_pipe[0], STDIN_FILENO) != STDIN_FILENO) {
log_error_errno(errno, "Failed to dup2() fd: %m");
r = move_fd(gpg_pipe[0], STDIN_FILENO, false);
if (r < 0) {
log_error_errno(errno, "Failed to move fd: %m");
_exit(EXIT_FAILURE);
}
if (gpg_pipe[0] != STDIN_FILENO)
gpg_pipe[0] = safe_close(gpg_pipe[0]);
null_fd = open("/dev/null", O_WRONLY|O_NOCTTY);
if (null_fd < 0) {
log_error_errno(errno, "Failed to open /dev/null: %m");
_exit(EXIT_FAILURE);
}
if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) {
log_error_errno(errno, "Failed to dup2() fd: %m");
r = move_fd(null_fd, STDOUT_FILENO, false);
if (r < 0) {
log_error_errno(errno, "Failed to move fd: %m");
_exit(EXIT_FAILURE);
}
if (null_fd != STDOUT_FILENO)
null_fd = safe_close(null_fd);
cmd[k++] = strjoina("--homedir=", gpg_home);
/* We add the user keyring only to the command line

View File

@ -857,12 +857,12 @@ int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bu
if (r < 0)
return r;
if (!path_is_absolute(src) || !path_is_safe(src))
if (!path_is_absolute(src) || !path_is_normalized(src))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute and not contain ../.");
if (isempty(dest))
dest = src;
else if (!path_is_absolute(dest) || !path_is_safe(dest))
else if (!path_is_absolute(dest) || !path_is_normalized(dest))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute and not contain ../.");
r = bus_verify_polkit_async(

View File

@ -887,8 +887,8 @@ static int stop_mounts(
return -EINVAL;
}
if (!path_is_safe(where)) {
log_error("Path contains unsafe components: %s", where);
if (!path_is_normalized(where)) {
log_error("Path contains non-normalized components: %s", where);
return -EINVAL;
}
@ -1569,8 +1569,8 @@ int main(int argc, char* argv[]) {
goto finish;
}
if (!path_is_safe(arg_mount_what)) {
log_error("Path contains unsafe components: %s", arg_mount_what);
if (!path_is_normalized(arg_mount_what)) {
log_error("Path contains non-normalized components: %s", arg_mount_what);
r = -EINVAL;
goto finish;
}
@ -1593,8 +1593,8 @@ int main(int argc, char* argv[]) {
goto finish;
}
if (!path_is_safe(arg_mount_where)) {
log_error("Path contains unsafe components: %s", arg_mount_where);
if (!path_is_normalized(arg_mount_where)) {
log_error("Path contains non-normalized components: %s", arg_mount_where);
r = -EINVAL;
goto finish;
}

View File

@ -29,6 +29,7 @@
#include "errno-list.h"
#include "escape.h"
#include "hashmap.h"
#include "hexdecoct.h"
#include "hostname-util.h"
#include "in-addr-util.h"
#include "list.h"
@ -274,6 +275,50 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
r = sd_bus_message_append(m, "sv", "TasksMax", "t", t);
goto finish;
} else if (STR_IN_SET(field, "StandardInput", "StandardOutput", "StandardError")) {
const char *n, *appended;
n = startswith(eq, "fd:");
if (n) {
appended = strjoina(field, "FileDescriptorName");
r = sd_bus_message_append(m, "sv", appended, "s", n);
} else if ((n = startswith(eq, "file:"))) {
appended = strjoina(field, "File");
r = sd_bus_message_append(m, "sv", appended, "s", n);
} else
r = sd_bus_message_append(m, "sv", field, "s", eq);
goto finish;
} else if (streq(field, "StandardInputText")) {
_cleanup_free_ char *unescaped = NULL;
r = cunescape(eq, 0, &unescaped);
if (r < 0)
return log_error_errno(r, "Failed to unescape text '%s': %m", eq);
if (!strextend(&unescaped, "\n", NULL))
return log_oom();
/* Note that we don't expand specifiers here, but that should be OK, as this is a programmatic
* interface anyway */
r = sd_bus_message_append(m, "s", "StandardInputData");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_open_container(m, 'v', "ay");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_array(m, 'y', unescaped, strlen(unescaped));
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_close_container(m);
goto finish;
}
r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, field);
@ -324,10 +369,8 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
uint64_t u;
r = cg_weight_parse(eq, &u);
if (r < 0) {
log_error("Failed to parse %s value %s.", field, eq);
return -EINVAL;
}
if (r < 0)
return log_error_errno(r, "Failed to parse %s value %s: %m", field, eq);
r = sd_bus_message_append(m, "v", "t", u);
@ -335,10 +378,8 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
uint64_t u;
r = cg_cpu_shares_parse(eq, &u);
if (r < 0) {
log_error("Failed to parse %s value %s.", field, eq);
return -EINVAL;
}
if (r < 0)
return log_error_errno(r, "Failed to parse %s value %s: %m", field, eq);
r = sd_bus_message_append(m, "v", "t", u);
@ -346,10 +387,8 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
uint64_t u;
r = cg_weight_parse(eq, &u);
if (r < 0) {
log_error("Failed to parse %s value %s.", field, eq);
return -EINVAL;
}
if (r < 0)
return log_error_errno(r, "Failed to parse %s value %s: %m", field, eq);
r = sd_bus_message_append(m, "v", "t", u);
@ -357,17 +396,14 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
uint64_t u;
r = cg_blkio_weight_parse(eq, &u);
if (r < 0) {
log_error("Failed to parse %s value %s.", field, eq);
return -EINVAL;
}
if (r < 0)
return log_error_errno(r, "Failed to parse %s value %s: %m", field, eq);
r = sd_bus_message_append(m, "v", "t", u);
} else if (STR_IN_SET(field,
"User", "Group", "DevicePolicy", "KillMode",
"UtmpIdentifier", "UtmpMode", "PAMName", "TTYPath",
"StandardInput", "StandardOutput", "StandardError",
"Description", "Slice", "Type", "WorkingDirectory",
"RootDirectory", "SyslogIdentifier", "ProtectSystem",
"ProtectHome", "SELinuxContext", "Restart", "RootImage",
@ -375,7 +411,32 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
"KeyringMode", "CollectMode"))
r = sd_bus_message_append(m, "v", "s", eq);
else if (STR_IN_SET(field, "AppArmorProfile", "SmackProcessLabel")) {
else if (streq(field, "StandardInputData")) {
_cleanup_free_ char *cleaned = NULL;
_cleanup_free_ void *decoded = NULL;
size_t sz;
cleaned = strdup(eq);
if (!cleaned)
return log_oom();
delete_chars(cleaned, WHITESPACE);
r = unbase64mem(cleaned, (size_t) -1, &decoded, &sz);
if (r < 0)
return log_error_errno(r, "Failed to decode base64 data '%s': %m", cleaned);
r = sd_bus_message_open_container(m, 'v', "ay");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_array(m, 'y', decoded, sz);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_close_container(m);
} else if (STR_IN_SET(field, "AppArmorProfile", "SmackProcessLabel")) {
bool ignore;
const char *s;
@ -414,10 +475,8 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
} else if (streq(field, "SecureBits")) {
r = secure_bits_from_string(eq);
if (r < 0) {
log_error("Failed to parse %s value %s.", field, eq);
return -EINVAL;
}
if (r < 0)
return log_error_errno(r, "Failed to parse %s value %s: %m", field, eq);
r = sd_bus_message_append(m, "v", "i", r);
@ -433,10 +492,8 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
}
r = capability_set_from_string(p, &sum);
if (r < 0) {
log_error("Failed to parse %s value %s.", field, eq);
return -EINVAL;
}
if (r < 0)
return log_error_errno(r, "Failed to parse %s value %s: %m", field, eq);
sum = invert ? ~sum : sum;
@ -492,10 +549,8 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
bytes = CGROUP_LIMIT_MAX;
} else {
r = parse_size(bandwidth, 1000, &bytes);
if (r < 0) {
log_error("Failed to parse byte value %s.", bandwidth);
return -EINVAL;
}
if (r < 0)
return log_error_errno(r, "Failed to parse byte value %s: %m", bandwidth);
}
r = sd_bus_message_append(m, "v", "a(st)", 1, path, bytes);
@ -524,10 +579,9 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
}
r = safe_atou64(weight, &u);
if (r < 0) {
log_error("Failed to parse %s value %s.", field, weight);
return -EINVAL;
}
if (r < 0)
return log_error_errno(r, "Failed to parse %s value %s: %m", field, weight);
r = sd_bus_message_append(m, "v", "a(st)", 1, path, u);
}
@ -669,8 +723,6 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
r = sd_bus_message_append(m, "v", "i", (int32_t) n);
#if HAVE_SECCOMP
} else if (streq(field, "SystemCallFilter")) {
int whitelist;
_cleanup_strv_free_ char **l = NULL;
@ -815,7 +867,7 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
r = sd_bus_message_close_container(m);
if (r < 0)
return bus_log_create_error(r);
#endif
} else if (streq(field, "FileDescriptorStoreMax")) {
unsigned u;
@ -858,10 +910,8 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
_cleanup_free_ char *word = NULL;
r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE);
if (r < 0) {
log_error("Failed to parse Environment value %s", eq);
return -EINVAL;
}
if (r < 0)
return log_error_errno(r, "Failed to parse Environment value %s: %m", eq);
if (r == 0)
break;
@ -908,20 +958,16 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
nsec_t n;
r = parse_nsec(eq, &n);
if (r < 0) {
log_error("Failed to parse %s value %s", field, eq);
return -EINVAL;
}
if (r < 0)
return log_error_errno(r, "Failed to parse %s value %s: %m", field, eq);
r = sd_bus_message_append(m, "v", "t", n);
} else if (streq(field, "OOMScoreAdjust")) {
int oa;
r = safe_atoi(eq, &oa);
if (r < 0) {
log_error("Failed to parse %s value %s", field, eq);
return -EINVAL;
}
if (r < 0)
return log_error_errno(r, "Failed to parse %s value %s: %m", field, eq);
if (!oom_score_adjust_is_valid(oa)) {
log_error("OOM score adjust value out of range");
@ -946,10 +992,8 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
size_t offset;
r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES);
if (r < 0) {
log_error("Failed to parse %s value %s", field, eq);
return -EINVAL;
}
if (r < 0)
return log_error_errno(r, "Failed to parse %s value %s: %m", field, eq);
if (r == 0)
break;
@ -994,10 +1038,8 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
_cleanup_free_ char *word = NULL;
r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES);
if (r < 0) {
log_error("Failed to parse %s value %s", field, eq);
return -EINVAL;
}
if (r < 0)
return log_error_errno(r, "Failed to parse %s value %s: %m", field, eq);
if (r == 0)
break;

View File

@ -56,6 +56,7 @@
#include "fs-util.h"
#include "glob-util.h"
#include "hostname-util.h"
#include "hexdecoct.h"
#include "initreq.h"
#include "install.h"
#include "io-util.h"
@ -4978,6 +4979,24 @@ static int print_property(const char *name, sd_bus_message *m, const char *conte
return bus_log_parse_error(r);
return 0;
} else if (contents[1] == SD_BUS_TYPE_BYTE && streq(name, "StandardInputData")) {
_cleanup_free_ char *h = NULL;
const void *p;
size_t sz;
ssize_t n;
r = sd_bus_message_read_array(m, 'y', &p, &sz);
if (r < 0)
return bus_log_parse_error(r);
n = base64mem(p, sz, &h);
if (n < 0)
return log_oom();
print_prop(name, "%s", h);
return 0;
}
break;

View File

@ -479,6 +479,14 @@ static void test_exec_specifier(Manager *m) {
test(m, "exec-specifier.service", 0, CLD_EXITED);
}
static void test_exec_stdin_data(Manager *m) {
test(m, "exec-stdin-data.service", 0, CLD_EXITED);
}
static void test_exec_stdio_file(Manager *m) {
test(m, "exec-stdio-file.service", 0, CLD_EXITED);
}
static int run_tests(UnitFileScope scope, const test_function_t *tests) {
const test_function_t *test = NULL;
Manager *m = NULL;
@ -535,6 +543,8 @@ int main(int argc, char *argv[]) {
test_exec_spec_interpolation,
test_exec_read_only_path_suceed,
test_exec_unset_environment,
test_exec_stdin_data,
test_exec_stdio_file,
NULL,
};
static const test_function_t system_tests[] = {

View File

@ -25,6 +25,9 @@
#include "fd-util.h"
#include "fileio.h"
#include "macro.h"
#include "random-util.h"
#include "string-util.h"
#include "util.h"
static void test_close_many(void) {
int fds[3];
@ -104,11 +107,60 @@ static void test_open_serialization_fd(void) {
write(fd, "test\n", 5);
}
static void test_acquire_data_fd_one(unsigned flags) {
char wbuffer[196*1024 - 7];
char rbuffer[sizeof(wbuffer)];
int fd;
fd = acquire_data_fd("foo", 3, flags);
assert_se(fd >= 0);
zero(rbuffer);
assert_se(read(fd, rbuffer, sizeof(rbuffer)) == 3);
assert_se(streq(rbuffer, "foo"));
fd = safe_close(fd);
fd = acquire_data_fd("", 0, flags);
assert_se(fd >= 0);
zero(rbuffer);
assert_se(read(fd, rbuffer, sizeof(rbuffer)) == 0);
assert_se(streq(rbuffer, ""));
fd = safe_close(fd);
random_bytes(wbuffer, sizeof(wbuffer));
fd = acquire_data_fd(wbuffer, sizeof(wbuffer), flags);
assert_se(fd >= 0);
zero(rbuffer);
assert_se(read(fd, rbuffer, sizeof(rbuffer)) == sizeof(rbuffer));
assert_se(memcmp(rbuffer, wbuffer, sizeof(rbuffer)) == 0);
fd = safe_close(fd);
}
static void test_acquire_data_fd(void) {
test_acquire_data_fd_one(0);
test_acquire_data_fd_one(ACQUIRE_NO_DEV_NULL);
test_acquire_data_fd_one(ACQUIRE_NO_MEMFD);
test_acquire_data_fd_one(ACQUIRE_NO_DEV_NULL|ACQUIRE_NO_MEMFD);
test_acquire_data_fd_one(ACQUIRE_NO_PIPE);
test_acquire_data_fd_one(ACQUIRE_NO_DEV_NULL|ACQUIRE_NO_PIPE);
test_acquire_data_fd_one(ACQUIRE_NO_MEMFD|ACQUIRE_NO_PIPE);
test_acquire_data_fd_one(ACQUIRE_NO_DEV_NULL|ACQUIRE_NO_MEMFD|ACQUIRE_NO_PIPE);
test_acquire_data_fd_one(ACQUIRE_NO_DEV_NULL|ACQUIRE_NO_MEMFD|ACQUIRE_NO_PIPE|ACQUIRE_NO_TMPFILE);
}
int main(int argc, char *argv[]) {
test_close_many();
test_close_nointr();
test_same_fd();
test_open_serialization_fd();
test_acquire_data_fd();
return 0;
}

View File

@ -111,6 +111,8 @@ test_data_files = '''
test-execute/exec-runtimedirectory.service
test-execute/exec-spec-interpolation.service
test-execute/exec-specifier.service
test-execute/exec-stdin-data.service
test-execute/exec-stdio-file.service
test-execute/exec-supplementarygroups-multiple-groups-default-group-user.service
test-execute/exec-supplementarygroups-multiple-groups-withgid.service
test-execute/exec-supplementarygroups-multiple-groups-withuid.service

View File

@ -0,0 +1,19 @@
[Unit]
Description=Test for StandardInputText= and StandardInputData=
[Service]
ExecStart=/bin/sh -x -c 'd=$$(mktemp -d -p /tmp); echo -e "this is a test\nand this is more\nsomething encoded!\nsomething in multiple lines\nand some more\nand a more bas64 data\nsomething with strange\nembedded\tcharacters\nand something with a exec-stdin-data.service specifier" > $d/text ; cmp $d/text ; rm -rf $d'
Type=oneshot
StandardInput=data
StandardInputText=this is a test
StandardInputText=and this is more
StandardInputData=c29tZXRoaW5nIGVuY29kZWQhCg==
StandardInputText=something \
in multiple lines
StandardInputText=\
and some more
StandardInputData=YW5kIGEgbW9y \
ZSBiYXM2NCBk\
YXRhCg==
StandardInputText=something with strange\nembedded\tcharacters
StandardInputText=and something with a %n specifier

View File

@ -0,0 +1,7 @@
[Unit]
Description=Test for StandardInput=file:
[Service]
ExecStart=/usr/bin/cmp /usr/bin/cmp
Type=oneshot
StandardInput=file:/usr/bin/cmp