summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2017-10-27 10:56:42 +0200
committerLennart Poettering <lennart@poettering.net>2017-11-17 11:13:44 +0100
commita548e14d690133dd8cca2d5ab8082bb23259fd5f (patch)
tree3de3fab023051eb731b97833290ed5fad7727467
parent9bd6a50e90427dd120d0c9495b50fda2f72887b6 (diff)
downloadsystemd-a548e14d690133dd8cca2d5ab8082bb23259fd5f.tar.gz
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.
-rw-r--r--src/basic/fd-util.c157
-rw-r--r--src/basic/fd-util.h10
-rw-r--r--src/test/test-fd-util.c52
3 files changed, 219 insertions, 0 deletions
diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c
index 0fd271318f..b93c3bd743 100644
--- a/src/basic/fd-util.c
+++ b/src/basic/fd-util.c
@@ -26,8 +26,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"
@@ -421,3 +423,158 @@ int move_fd(int from, int to, int cloexec) {
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;
+}
diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h
index 7c527850ed..df5a5fad98 100644
--- a/src/basic/fd-util.h
+++ b/src/basic/fd-util.h
@@ -77,6 +77,16 @@ 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/test/test-fd-util.c b/src/test/test-fd-util.c
index 4425b5fe5f..dc18ea3d3a 100644
--- a/src/test/test-fd-util.c
+++ b/src/test/test-fd-util.c
@@ -24,6 +24,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];
@@ -103,11 +106,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;
}