chiark / gitweb /
fd-util: move certain fds above fd #2 (#8129)
authorLennart Poettering <lennart@poettering.net>
Fri, 9 Feb 2018 16:53:28 +0000 (17:53 +0100)
committerSven Eden <yamakuzure@gmx.net>
Wed, 30 May 2018 05:58:51 +0000 (07:58 +0200)
This adds some paranoia code that moves some of the fds we allocate for
longer periods of times to fds > 2 if they are allocated below this
boundary. This is a paranoid safety thing, in order to avoid that
external code might end up erroneously use our fds under the assumption
they were valid stdin/stdout/stderr. Think: some app closes
stdin/stdout/stderr and then invokes 'fprintf(stderr, …' which causes
writes on our fds.

This both adds the helper to do the moving as well as ports over a
number of users to this new logic. Since we don't want to litter all our
code with invocations of this I tried to strictly focus on fds we keep
open for long periods of times only and only in code that is frequently
loaded into foreign programs (under the assumptions that in our own
codebase we are smart enough to always keep stdin/stdout/stderr
allocated to avoid this pitfall). Specifically this means all code used
by NSS and our sd-xyz API:

1. our logging APIs
2. sd-event
3. sd-bus
4. sd-resolve
5. sd-netlink

This changed was inspired by this:

https://github.com/systemd/systemd/issues/8075#issuecomment-363689755

This shows that apparently IRL there are programs that do close
stdin/stdout/stderr, and we should accomodate for that.

Note that this won't fix any bugs, this just makes sure that buggy
programs are less likely to interfere with out own code.

src/basic/fd-util.c
src/basic/fd-util.h
src/basic/log.c
src/libelogind/sd-bus/bus-container.c
src/libelogind/sd-bus/bus-socket.c
src/libelogind/sd-event/sd-event.c
src/test/test-fd-util.c

index 3e882287ce116f7d2b5e9afa72e8eb7011a70f97..8eb65312a906e749f26a81a11477d8224e004bf6 100644 (file)
@@ -582,3 +582,40 @@ try_dev_shm_without_o_tmpfile:
 
         return -EOPNOTSUPP;
 }
+
+int fd_move_above_stdio(int fd) {
+        int flags, copy;
+        PROTECT_ERRNO;
+
+        /* Moves the specified file descriptor if possible out of the range [0…2], i.e. the range of
+         * stdin/stdout/stderr. If it can't be moved outside of this range the original file descriptor is
+         * returned. This call is supposed to be used for long-lasting file descriptors we allocate in our code that
+         * might get loaded into foreign code, and where we want ensure our fds are unlikely used accidentally as
+         * stdin/stdout/stderr of unrelated code.
+         *
+         * Note that this doesn't fix any real bugs, it just makes it less likely that our code will be affected by
+         * buggy code from others that mindlessly invokes 'fprintf(stderr, …' or similar in places where stderr has
+         * been closed before.
+         *
+         * This function is written in a "best-effort" and "least-impact" style. This means whenever we encounter an
+         * error we simply return the original file descriptor, and we do not touch errno. */
+
+        if (fd < 0 || fd > 2)
+                return fd;
+
+        flags = fcntl(fd, F_GETFD, 0);
+        if (flags < 0)
+                return fd;
+
+        if (flags & FD_CLOEXEC)
+                copy = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+        else
+                copy = fcntl(fd, F_DUPFD, 3);
+        if (copy < 0)
+                return fd;
+
+        assert(copy > 2);
+
+        (void) close(fd);
+        return copy;
+}
index eeded4417727daae95d2c14ff3ed4101ec8d2629..6a9006421685a820676264891ecfa0a2e02d0fa5 100644 (file)
@@ -95,3 +95,5 @@ 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)
+
+int fd_move_above_stdio(int fd);
index cb4689cef8710526bf814edee2a3a0c5dceaa51d..3963e03719fad9b901aa7d736c122e7ab7bbace1 100644 (file)
@@ -107,6 +107,8 @@ static int log_open_console(void) {
                 console_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC);
                 if (console_fd < 0)
                         return console_fd;
+
+                console_fd = fd_move_above_stdio(console_fd);
         }
 
         return 0;
@@ -125,6 +127,7 @@ static int log_open_kmsg(void) {
         if (kmsg_fd < 0)
                 return -errno;
 
+        kmsg_fd = fd_move_above_stdio(kmsg_fd);
         return 0;
 }
 
@@ -140,11 +143,11 @@ static int create_log_socket(int type) {
         if (fd < 0)
                 return -errno;
 
+        fd = fd_move_above_stdio(fd);
         (void) fd_inc_sndbuf(fd, SNDBUF_SIZE);
 
-        /* We need a blocking fd here since we'd otherwise lose
-        messages way too early. However, let's not hang forever in the
-        unlikely case of a deadlock. */
+        /* We need a blocking fd here since we'd otherwise lose messages way too early. However, let's not hang forever
+         * in the unlikely case of a deadlock. */
         if (getpid_cached() == 1)
                 timeval_store(&tv, 10 * USEC_PER_MSEC);
         else
index 16156d8823ec9045ed6ca1fa3cfdb2968e52cd83..477f7053eda6b17ab1895482a6d02247826e6b39 100644 (file)
@@ -54,6 +54,8 @@ int bus_container_connect_socket(sd_bus *b) {
         if (b->input_fd < 0)
                 return -errno;
 
+        b->input_fd = fd_move_above_stdio(b->input_fd);
+
         b->output_fd = b->input_fd;
 
         bus_socket_setup(b);
index 348f715809a6075dfecf3dade7d8d7aade192d0e..4890895bc09913a557b78465509f65544ef595ff 100644 (file)
@@ -713,6 +713,8 @@ static int bus_socket_inotify_setup(sd_bus *b) {
                 b->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
                 if (b->inotify_fd < 0)
                         return -errno;
+
+                b->inotify_fd = fd_move_above_stdio(b->inotify_fd);
         }
 
         /* Make sure the path is NUL terminated */
@@ -879,6 +881,8 @@ int bus_socket_connect(sd_bus *b) {
                 if (b->input_fd < 0)
                         return -errno;
 
+                b->input_fd = fd_move_above_stdio(b->input_fd);
+
                 b->output_fd = b->input_fd;
                 bus_socket_setup(b);
 
@@ -978,7 +982,7 @@ int bus_socket_exec(sd_bus *b) {
         }
 
         safe_close(s[1]);
-        b->output_fd = b->input_fd = s[0];
+        b->output_fd = b->input_fd = fd_move_above_stdio(s[0]);
 
         bus_socket_setup(b);
 
@@ -1206,7 +1210,7 @@ int bus_socket_read_message(sd_bus *bus) {
                 CMSG_FOREACH(cmsg, &mh)
                         if (cmsg->cmsg_level == SOL_SOCKET &&
                             cmsg->cmsg_type == SCM_RIGHTS) {
-                                int n, *f;
+                                int n, *f, i;
 
                                 n = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
 
@@ -1225,9 +1229,9 @@ int bus_socket_read_message(sd_bus *bus) {
                                         return -ENOMEM;
                                 }
 
-                                memcpy_safe(f + bus->n_fds, CMSG_DATA(cmsg), n * sizeof(int));
+                                for (i = 0; i < n; i++)
+                                        f[bus->n_fds++] = fd_move_above_stdio(((int*) CMSG_DATA(cmsg))[i]);
                                 bus->fds = f;
-                                bus->n_fds += n;
                         } else
                                 log_debug("Got unexpected auxiliary data with level=%d and type=%d",
                                           cmsg->cmsg_level, cmsg->cmsg_type);
index cb9b3a4545c10e8e236f2128e415de3fbe1b064f..7355921d302f53ed0b75a08b6f8293819a3e00e6 100644 (file)
@@ -457,6 +457,8 @@ _public_ int sd_event_new(sd_event** ret) {
                 goto fail;
         }
 
+        e->epoll_fd = fd_move_above_stdio(e->epoll_fd);
+
         if (secure_getenv("SD_EVENT_PROFILE_DELAYS")) {
                 log_debug("Event loop profiling enabled. Logarithmic histogram of event loop iterations in the range 2^0 ... 2^63 us will be logged every 5s.");
                 e->profile_delays = true;
@@ -695,7 +697,7 @@ static int event_make_signal_data(
                 return 0;
         }
 
-        d->fd = r;
+        d->fd = fd_move_above_stdio(r);
 
         ev.events = EPOLLIN;
         ev.data.ptr = d;
@@ -1045,6 +1047,8 @@ static int event_setup_timer_fd(
         if (fd < 0)
                 return -errno;
 
+        fd = fd_move_above_stdio(fd);
+
         ev.events = EPOLLIN;
         ev.data.ptr = d;
 
index 6b611b4b9cf6be028035dbdeb6e6d099be720f14..76b36053b935b74ccfbe7f7374cd7b63ebda9663 100644 (file)
@@ -157,6 +157,24 @@ static void test_acquire_data_fd(void) {
         test_acquire_data_fd_one(ACQUIRE_NO_DEV_NULL|ACQUIRE_NO_MEMFD|ACQUIRE_NO_PIPE|ACQUIRE_NO_TMPFILE);
 }
 
+static void test_fd_move_above_stdio(void) {
+        int original_stdin, new_fd;
+
+        original_stdin = fcntl(0, F_DUPFD, 3);
+        assert_se(original_stdin >= 3);
+        assert_se(close_nointr(0) != EBADF);
+
+        new_fd = open("/dev/null", O_RDONLY);
+        assert_se(new_fd == 0);
+
+        new_fd = fd_move_above_stdio(new_fd);
+        assert_se(new_fd >= 3);
+
+        assert_se(dup(original_stdin) == 0);
+        assert_se(close_nointr(original_stdin) != EBADF);
+        assert_se(close_nointr(new_fd) != EBADF);
+}
+
 int main(int argc, char *argv[]) {
         test_close_many();
         test_close_nointr();
@@ -165,6 +183,7 @@ int main(int argc, char *argv[]) {
 #endif // 0
         test_open_serialization_fd();
         test_acquire_data_fd();
+        test_fd_move_above_stdio();
 
         return 0;
 }