diff --git a/TODO b/TODO index d0d1430845..fc66b6a578 100644 --- a/TODO +++ b/TODO @@ -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. diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index af86379bfe..864347d370 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -484,145 +484,116 @@ StandardInput= - Controls where file descriptor 0 (STDIN) of - the executed processes is connected to. Takes one of - , - , - , - , - or - . + Controls where file descriptor 0 (STDIN) of the executed processes is connected to. Takes one + of , , , , + , , or + . - If is selected, standard input - will be connected to /dev/null, i.e. all - read attempts by the process will result in immediate - EOF. + If is selected, standard input will be connected to /dev/null, + i.e. all read attempts by the process will result in immediate EOF. - If is selected, standard input is - connected to a TTY (as configured by - TTYPath=, 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. + If is selected, standard input is connected to a TTY (as configured by + TTYPath=, 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. - is similar to - , but the executed process is forcefully - and immediately made the controlling process of the terminal, - potentially removing previous controlling processes from the - terminal. + is similar to , but the executed process is forcefully and + immediately made the controlling process of the terminal, potentially removing previous controlling processes + from the terminal. - is similar to - but if the terminal already has a - controlling process start-up of the executed process - fails. + is similar to , but if the terminal already has a + controlling process start-up of the executed process fails. - The option is only valid in - socket-activated services, and only when the socket - configuration file (see - systemd.socket5 - 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 - inetd8 + The 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 + StandardInputText=/StandardInputData= (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. + + The option may be used to connect a specific file + system object to standard input. An absolute path following the : character is expected, + which may refer to a regular file, a FIFO or special file. If an AF_UNIX 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. + + The option is valid in socket-activated services only, and requires the relevant + socket unit file (see + systemd.socket5 for details) + to have Accept=yes 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 inetd8 socket activation daemon. - The 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 : (e.g. fd:foobar). - If no name is specified, stdin is assumed - (i.e. fd is equivalent to fd:stdin). - At least one socket unit defining such name must be explicitly provided via the - Sockets= 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 FileDescriptorName= in - systemd.socket5 - for more details about named descriptors and ordering. + The 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 + : character (e.g. fd:foobar). If no name is specified, the name + stdin is implied (i.e. fd is equivalent to fd:stdin). + At least one socket unit defining the specified name must be provided via the Sockets= + 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 FileDescriptorName= in + systemd.socket5 for more + details about named file descriptors and their ordering. - This setting defaults to - . + This setting defaults to . StandardOutput= - Controls where file descriptor 1 (STDOUT) of - the executed processes is connected to. Takes one of - , - , - , - , - , - , - , - , - , - or - . + Controls where file descriptor 1 (STDOUT) of the executed processes is connected to. Takes one + of , , , , + , , , + , , + , or + . - duplicates the file descriptor - of standard input for standard output. + duplicates the file descriptor of standard input for standard output. - connects standard output to - /dev/null, i.e. everything written to it - will be lost. + connects standard output to /dev/null, i.e. everything written + to it will be lost. - connects standard output to a tty - (as configured via TTYPath=, 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. + connects standard output to a tty (as configured via TTYPath=, + 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. - connects standard output with - the journal which is accessible via - journalctl1. - 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. + connects standard output with the journal which is accessible via + journalctl1. 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. - connects standard output to the - syslog3 - 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 . + connects standard output to the syslog3 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 . - connects standard output with the - kernel log buffer which is accessible via + connects standard output with the kernel log buffer which is accessible via dmesg1, - 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 . + 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 . - , - and - work in a similar way as the - three options above but copy the output to the system console - as well. + , and work + in a similar way as the three options above but copy the output to the system console as well. - connects standard output to a - socket acquired via socket activation. The semantics are - similar to the same option of - StandardInput=. + The option may be used to connect a specific file + system object to standard output. The semantics are similar to the same option of + StandardInputText=, 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 AF_UNIX socket in the file system, as in that case only a + single stream connection is created for both input and output. - The 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 : (e.g. fd:foobar). - If no name is specified, stdout is assumed - (i.e. fd is equivalent to fd:stdout). - At least one socket unit defining such name must be explicitly provided via the - Sockets= 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 FileDescriptorName= in - systemd.socket5 - for more details about named descriptors and ordering. + connects standard output to a socket acquired via socket activation. The + semantics are similar to the same option of StandardInput=, see above. + + The 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 + : character (e.g. fd:foobar). If no name is + specified, the name stdout is implied (i.e. fd is equivalent to + fd:stdout). At least one socket unit defining the specified name must be provided via the + Sockets= 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 FileDescriptorName= + in systemd.socket5 for more + details about named descriptors and their ordering. 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 After= on @@ -632,32 +603,66 @@ "hello" > /dev/stderr for writing text to stderr will not work. To mitigate this use the construct echo "hello" >&2 instead, which is mostly equivalent and avoids this pitfall. - This setting defaults to the value set with - DefaultStandardOutput= in - systemd-system.conf5, - which defaults to . Note that setting - this parameter might result in additional dependencies to be - added to the unit (see above). + This setting defaults to the value set with DefaultStandardOutput= in + systemd-system.conf5, which + defaults to . Note that setting this parameter might result in additional dependencies + to be added to the unit (see above). StandardError= - Controls where file descriptor 2 (STDERR) of - the executed processes is connected to. The available options - are identical to those of StandardOutput=, - with some exceptions: if set to the - file descriptor used for standard output is duplicated for - standard error, while operates on the error - stream and will look by default for a descriptor named + Controls where file descriptor 2 (STDERR) of the executed processes is connected to. The + available options are identical to those of StandardOutput=, with some exceptions: if set to + the file descriptor used for standard output is duplicated for standard error, while + will use a default file descriptor name of stderr. - This setting defaults to the value set with - DefaultStandardError= in - systemd-system.conf5, - which defaults to . Note that setting - this parameter might result in additional dependencies to be - added to the unit (see above). + This setting defaults to the value set with DefaultStandardError= in + systemd-system.conf5, which + defaults to . Note that setting this parameter might result in additional dependencies + to be added to the unit (see above). + + + + StandardInputText= + StandardInputData= + + Configures arbitrary textual or binary data to pass via file descriptor 0 (STDIN) to the + executed processes. These settings have no effect unless StandardInput= is set to + . Use this option to embed process input data directly in the unit file. + + StandardInputText= accepts arbitrary textual data. C-style escapes for special + characters as well as the usual %-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 \n to the end or beginning of a line). + + StandardInputData= accepts arbitrary binary data, encoded in Base64. No escape sequences or specifiers are + resolved. Any whitespace in the encoded version is ignored during decoding. + + Note that StandardInputText= and StandardInputData= 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. + + 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 \ character (see + systemd.unit5 for + details). This is particularly useful for large data configured with these two options. Example: + + … +StandardInput=data +StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy4KSWNrIGtpZWtl \ + LCBzdGF1bmUsIHd1bmRyZSBtaXIsCnVmZiBlZW1hbCBqZWh0IHNlIHVmZiBkaWUgVMO8ci4KTmFu \ + dSwgZGVuayBpY2ssIGljayBkZW5rIG5hbnUhCkpldHogaXNzZSB1ZmYsIGVyc2NodCB3YXIgc2Ug \ + enUhCkljayBqZWhlIHJhdXMgdW5kIGJsaWNrZSDigJQKdW5kIHdlciBzdGVodCBkcmF1w59lbj8g \ + SWNrZSEK +… + + diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c index 8c3d2c5ee6..cd32896b26 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -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); diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index 0a1786849e..f3863722ee 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -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/. 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; +} diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index 7893144ed9..84a0816f03 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -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) diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index 1eaf820afb..b5fd95ae8e 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -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 "./" diff --git a/src/basic/hexdecoct.c b/src/basic/hexdecoct.c index 544e61751e..3ffedcafe5 100644 --- a/src/basic/hexdecoct.c +++ b/src/basic/hexdecoct.c @@ -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; diff --git a/src/basic/path-util.c b/src/basic/path-util.c index 1d6666c2f2..883856abc2 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -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; diff --git a/src/basic/path-util.h b/src/basic/path-util.h index 5262017a82..9bd783eaf4 100644 --- a/src/basic/path-util.h +++ b/src/basic/path-util.h @@ -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); diff --git a/src/basic/proc-cmdline.c b/src/basic/proc-cmdline.c index b83a9f625b..c5d1fb1d41 100644 --- a/src/basic/proc-cmdline.c +++ b/src/basic/proc-cmdline.c @@ -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]; diff --git a/src/basic/unit-name.c b/src/basic/unit-name.c index 8ef80ca6a8..403f288b57 100644 --- a/src/basic/unit-name.c +++ b/src/basic/unit-name.c @@ -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; } diff --git a/src/basic/user-util.c b/src/basic/user-util.c index 24686591c2..ed54578a66 100644 --- a/src/basic/user-util.c +++ b/src/basic/user-util.c @@ -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 */ diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 67a6d9ef12..7b0e91cdc4 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -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); } diff --git a/src/core/execute.c b/src/core/execute.c index 80b48191bd..4d19efb719 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -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); diff --git a/src/core/execute.h b/src/core/execute.h index aad33120e1..d04dfcf6c9 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -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; diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index ffc3e20359..73b13977ed 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -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) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index c8ab68edc7..42dcaea4e1 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -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; diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index 7ee1cfb6b4..cb17bdd3c3 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -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); diff --git a/src/core/main.c b/src/core/main.c index eb51a464b3..ee9594a00f 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -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 }, diff --git a/src/core/service.c b/src/core/service.c index a68e5c9f3d..445d1becc1 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -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) { diff --git a/src/core/unit.c b/src/core/unit.c index 921971c1c0..d5e6b3891b 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -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; } diff --git a/src/import/import-common.c b/src/import/import-common.c index 1bd5a8342d..2f989a171c 100644 --- a/src/import/import-common.c +++ b/src/import/import-common.c @@ -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) diff --git a/src/import/pull-common.c b/src/import/pull-common.c index e5d47b8a13..cb2e3d9be2 100644 --- a/src/import/pull-common.c +++ b/src/import/pull-common.c @@ -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 diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index ea1568ea37..3761267b57 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -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( diff --git a/src/mount/mount-tool.c b/src/mount/mount-tool.c index ce0df21fd6..dd5f62e824 100644 --- a/src/mount/mount-tool.c +++ b/src/mount/mount-tool.c @@ -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; } diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 87e2e597e2..a607233038 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -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; diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 930fd3ac77..c046fe72d8 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -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; diff --git a/src/test/test-execute.c b/src/test/test-execute.c index 52a9e0a946..941c10a11e 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -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[] = { diff --git a/src/test/test-fd-util.c b/src/test/test-fd-util.c index 4ad8fe75ea..f45fbbe7e3 100644 --- a/src/test/test-fd-util.c +++ b/src/test/test-fd-util.c @@ -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; } diff --git a/test/meson.build b/test/meson.build index a621f5db52..40605b385f 100644 --- a/test/meson.build +++ b/test/meson.build @@ -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 diff --git a/test/test-execute/exec-stdin-data.service b/test/test-execute/exec-stdin-data.service new file mode 100644 index 0000000000..1ca536ffc5 --- /dev/null +++ b/test/test-execute/exec-stdin-data.service @@ -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 diff --git a/test/test-execute/exec-stdio-file.service b/test/test-execute/exec-stdio-file.service new file mode 100644 index 0000000000..8fd11caf8e --- /dev/null +++ b/test/test-execute/exec-stdio-file.service @@ -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