Merge pull request #7198 from poettering/stdin-stdout
Add StandardInput=data, StandardInput=file:... and more
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
+2
-2
@@ -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 "./"
|
||||
|
||||
+37
-13
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user