You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
277 lines
9.6 KiB
277 lines
9.6 KiB
From 581edd240f8dd68b1dbb4070353ddb2059eb8a67 Mon Sep 17 00:00:00 2001 |
|
From: Lennart Poettering <lennart@poettering.net> |
|
Date: Fri, 27 Oct 2017 10:56:42 +0200 |
|
Subject: [PATCH] fd-util: add new acquire_data_fd() API helper |
|
|
|
All this function does is place some data in an in-memory read-only fd, |
|
that may be read back to get the original data back. |
|
|
|
Doing this in a way that works everywhere, given the different kernels |
|
we support as well as different privilege levels is surprisingly |
|
complex. |
|
|
|
(cherry picked from commit a548e14d690133dd8cca2d5ab8082bb23259fd5f) |
|
|
|
Related: #1446095 |
|
--- |
|
src/shared/util.c | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++ |
|
src/shared/util.h | 10 ++++ |
|
src/test/test-util.c | 49 ++++++++++++++++ |
|
3 files changed, 215 insertions(+) |
|
|
|
diff --git a/src/shared/util.c b/src/shared/util.c |
|
index af0953273..982f5e044 100644 |
|
--- a/src/shared/util.c |
|
+++ b/src/shared/util.c |
|
@@ -95,6 +95,7 @@ |
|
#include "sparse-endian.h" |
|
#include "conf-parser.h" |
|
#include "cgroup-util.h" |
|
+#include "memfd-util.h" |
|
|
|
int saved_argc = 0; |
|
char **saved_argv = NULL; |
|
@@ -8893,3 +8894,158 @@ uint64_t system_tasks_max_scale(uint64_t v, uint64_t max) { |
|
|
|
return m / max; |
|
} |
|
+ |
|
+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, O_CLOEXEC); |
|
+ 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/shared/util.h b/src/shared/util.h |
|
index 526a6fe84..9c4be0256 100644 |
|
--- a/src/shared/util.h |
|
+++ b/src/shared/util.h |
|
@@ -1112,3 +1112,13 @@ int parse_percent(const char *p); |
|
|
|
uint64_t system_tasks_max(void); |
|
uint64_t system_tasks_max_scale(uint64_t v, uint64_t max); |
|
+ |
|
+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); |
|
diff --git a/src/test/test-util.c b/src/test/test-util.c |
|
index f2c52edce..efb02ff53 100644 |
|
--- a/src/test/test-util.c |
|
+++ b/src/test/test-util.c |
|
@@ -1861,6 +1861,54 @@ static void test_system_tasks_max_scale(void) { |
|
assert_se(system_tasks_max_scale(UINT64_MAX/4, UINT64_MAX) == UINT64_MAX); |
|
} |
|
|
|
+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[]) { |
|
log_parse_environment(); |
|
log_open(); |
|
@@ -1943,6 +1991,7 @@ int main(int argc, char *argv[]) { |
|
test_shell_maybe_quote(); |
|
test_system_tasks_max(); |
|
test_system_tasks_max_scale(); |
|
+ test_acquire_data_fd(); |
|
|
|
return 0; |
|
}
|
|
|