]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
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)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Fri, 9 Feb 2018 16:53:28 +0000 (17:53 +0100)
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/libsystemd/sd-bus/bus-container.c
src/libsystemd/sd-bus/bus-socket.c
src/libsystemd/sd-event/sd-event.c
src/libsystemd/sd-netlink/netlink-socket.c
src/libsystemd/sd-resolve/sd-resolve.c
src/test/test-fd-util.c

index 404361e8c1ec9ba6e6878bf6ab8a2bd24cd07fc1..61a93fcb4a73a795d1eeed8469e117f75432a179 100644 (file)
@@ -578,3 +578,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 84a0816f03199df24e8f2d9e0307c5076a228292..284856ae6defa0c403d94738ca6094dd3f992cdb 100644 (file)
@@ -91,3 +91,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 12ee65a5c377fd9692f064af036927b545457fd8..b3751299d06b349c6cb960d9f39257300dca336d 100644 (file)
@@ -105,6 +105,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;
@@ -123,6 +125,7 @@ static int log_open_kmsg(void) {
         if (kmsg_fd < 0)
                 return -errno;
 
+        kmsg_fd = fd_move_above_stdio(kmsg_fd);
         return 0;
 }
 
@@ -138,11 +141,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 d2101028418faf45dead4628587eef5a9f247557..90132bb87b0aba87896c18a9d9bf160c7fdc1a07 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 e08248c9f65fecb8debd0552407a407c8933eef7..3474ad9ddb1b4c0a11b49be1fe7d84ac5d12ee80 100644 (file)
@@ -25,6 +25,7 @@
 #include "sd-netlink.h"
 
 #include "alloc-util.h"
+#include "fd-util.h"
 #include "format-util.h"
 #include "missing.h"
 #include "netlink-internal.h"
@@ -41,7 +42,7 @@ int socket_open(int family) {
         if (fd < 0)
                 return -errno;
 
-        return fd;
+        return fd_move_above_stdio(fd);
 }
 
 static int broadcast_groups_get(sd_netlink *nl) {
index 787642a7fb9c16aa421192071544eeedec85d7bc..1c68a96fec6c22eb13c2159c3a5dfebf4f846bdf 100644 (file)
@@ -507,12 +507,15 @@ _public_ int sd_resolve_new(sd_resolve **ret) {
                 goto fail;
         }
 
-        fd_inc_sndbuf(resolve->fds[REQUEST_SEND_FD], QUERIES_MAX * BUFSIZE);
-        fd_inc_rcvbuf(resolve->fds[REQUEST_RECV_FD], QUERIES_MAX * BUFSIZE);
-        fd_inc_sndbuf(resolve->fds[RESPONSE_SEND_FD], QUERIES_MAX * BUFSIZE);
-        fd_inc_rcvbuf(resolve->fds[RESPONSE_RECV_FD], QUERIES_MAX * BUFSIZE);
+        for (i = 0; i < _FD_MAX; i++)
+                resolve->fds[i] = fd_move_above_stdio(resolve->fds[i]);
+
+        (void) fd_inc_sndbuf(resolve->fds[REQUEST_SEND_FD], QUERIES_MAX * BUFSIZE);
+        (void) fd_inc_rcvbuf(resolve->fds[REQUEST_RECV_FD], QUERIES_MAX * BUFSIZE);
+        (void) fd_inc_sndbuf(resolve->fds[RESPONSE_SEND_FD], QUERIES_MAX * BUFSIZE);
+        (void) fd_inc_rcvbuf(resolve->fds[RESPONSE_RECV_FD], QUERIES_MAX * BUFSIZE);
 
-        fd_nonblock(resolve->fds[RESPONSE_RECV_FD], true);
+        (void) fd_nonblock(resolve->fds[RESPONSE_RECV_FD], true);
 
         *ret = resolve;
         return 0;
index e5a0e9d51bf8bdbb85d7b59fbba3c9cbb6d17192..db4a7f8fda0464faefe143a6edecbbf17674a266 100644 (file)
@@ -155,12 +155,31 @@ 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();
         test_same_fd();
         test_open_serialization_fd();
         test_acquire_data_fd();
+        test_fd_move_above_stdio();
 
         return 0;
 }