From 3691bcf3c5eebdcca5b4f1c51c745441c57a6cd1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 23 Apr 2020 09:40:03 +0200 Subject: [PATCH] tree-wide: use recvmsg_safe() at various places Let's be extra careful whenever we return from recvmsg() and see MSG_CTRUNC set. This generally means we ran into a programming error, as we didn't size the control buffer large enough. It's an error condition we should at least log about, or propagate up. Hence do that. This is particularly important when receiving fds, since for those the control data can be of any size. In particular on stream sockets that's nasty, because if we miss an fd because of control data truncation we cannot recover, we might not even realize that we are one off. (Also, when failing early, if there's any chance the socket might be AF_UNIX let's close all received fds, all the time. We got this right most of the time, but there were a few cases missing. God, UNIX is hard to use) --- src/basic/socket-util.c | 13 +++++++------ src/core/manager.c | 21 +++++++++++---------- src/coredump/coredump.c | 14 +++++++------- src/home/homed-manager.c | 4 ++-- src/import/importd.c | 12 +++++------- src/journal/journald-server.c | 26 ++++++++++++++++---------- src/journal/journald-stream.c | 1 + src/libsystemd/sd-bus/bus-socket.c | 30 ++++++++++++++++++++++-------- src/nspawn/nspawn.c | 11 +++++------ src/portable/portable.c | 4 ++-- src/resolve/resolved-manager.c | 13 +++++-------- src/shared/ask-password-api.c | 9 ++++----- src/timesync/timesyncd-manager.c | 9 ++++----- src/udev/udev-ctrl.c | 10 ++++------ src/udev/udevd.c | 20 +++++++++++--------- 15 files changed, 106 insertions(+), 91 deletions(-) diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index 2d0564e66f..b797a52180 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -901,9 +901,9 @@ ssize_t receive_one_fd_iov( * combination with send_one_fd(). */ - k = recvmsg(transport_fd, &mh, MSG_CMSG_CLOEXEC | flags); + k = recvmsg_safe(transport_fd, &mh, MSG_CMSG_CLOEXEC | flags); if (k < 0) - return (ssize_t) -errno; + return k; CMSG_FOREACH(cmsg, &mh) { if (cmsg->cmsg_level == SOL_SOCKET && @@ -915,12 +915,13 @@ ssize_t receive_one_fd_iov( } } - if (!found) + if (!found) { cmsg_close_all(&mh); - /* If didn't receive an FD or any data, return an error. */ - if (k == 0 && !found) - return -EIO; + /* If didn't receive an FD or any data, return an error. */ + if (k == 0) + return -EIO; + } if (found) *ret_fd = *(int*) CMSG_DATA(found); diff --git a/src/core/manager.c b/src/core/manager.c index 7536a9ca84..43b8a02e48 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -2360,20 +2360,20 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t return 0; } - n = recvmsg(m->notify_fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC|MSG_TRUNC); - if (n < 0) { - if (IN_SET(errno, EAGAIN, EINTR)) - return 0; /* Spurious wakeup, try again */ - - /* If this is any other, real error, then let's stop processing this socket. This of course means we - * won't take notification messages anymore, but that's still better than busy looping around this: - * being woken up over and over again but being unable to actually read the message off the socket. */ - return log_error_errno(errno, "Failed to receive notification message: %m"); - } + n = recvmsg_safe(m->notify_fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC|MSG_TRUNC); + if (IN_SET(n, -EAGAIN, -EINTR)) + return 0; /* Spurious wakeup, try again */ + if (n < 0) + /* If this is any other, real error, then let's stop processing this socket. This of course + * means we won't take notification messages anymore, but that's still better than busy + * looping around this: being woken up over and over again but being unable to actually read + * the message off the socket. */ + return log_error_errno(n, "Failed to receive notification message: %m"); CMSG_FOREACH(cmsg, &msghdr) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + assert(!fd_array); fd_array = (int*) CMSG_DATA(cmsg); n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); @@ -2381,6 +2381,7 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t cmsg->cmsg_type == SCM_CREDENTIALS && cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) { + assert(!ucred); ucred = (struct ucred*) CMSG_DATA(cmsg); } } diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c index ee4268b965..e4266baa62 100644 --- a/src/coredump/coredump.c +++ b/src/coredump/coredump.c @@ -911,10 +911,10 @@ static int process_socket(int fd) { mh.msg_iov = &iovec; - n = recvmsg(fd, &mh, MSG_CMSG_CLOEXEC); + n = recvmsg_safe(fd, &mh, MSG_CMSG_CLOEXEC); if (n < 0) { free(iovec.iov_base); - r = log_error_errno(errno, "Failed to receive datagram: %m"); + r = log_error_errno(n, "Failed to receive datagram: %m"); goto finish; } @@ -935,15 +935,17 @@ static int process_socket(int fd) { } if (!found) { - log_error("Coredump file descriptor missing."); - r = -EBADMSG; + cmsg_close_all(&mh); + r = log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Coredump file descriptor missing."); goto finish; } assert(input_fd < 0); input_fd = *(int*) CMSG_DATA(found); break; - } + } else + cmsg_close_all(&mh); /* Add trailing NUL byte, in case these are strings */ ((char*) iovec.iov_base)[n] = 0; @@ -952,8 +954,6 @@ static int process_socket(int fd) { r = iovw_put(&iovw, iovec.iov_base, iovec.iov_len); if (r < 0) goto finish; - - cmsg_close_all(&mh); } /* Make sure we got all data we really need */ diff --git a/src/home/homed-manager.c b/src/home/homed-manager.c index ed2a54615c..5ce7be4fac 100644 --- a/src/home/homed-manager.c +++ b/src/home/homed-manager.c @@ -981,9 +981,9 @@ static ssize_t read_datagram(int fd, struct ucred *ret_sender, void **ret) { .msg_controllen = sizeof(control), }; - m = recvmsg(fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); + m = recvmsg_safe(fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); if (m < 0) - return -errno; + return m; cmsg_close_all(&mh); diff --git a/src/import/importd.c b/src/import/importd.c index 8977ebd835..340b12ecd5 100644 --- a/src/import/importd.c +++ b/src/import/importd.c @@ -566,13 +566,11 @@ static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void ssize_t n; int r; - n = recvmsg(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); - if (n < 0) { - if (IN_SET(errno, EAGAIN, EINTR)) - return 0; - - return -errno; - } + n = recvmsg_safe(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); + if (IN_SET(n, -EAGAIN, -EINTR)) + return 0; + if (n < 0) + return (int) n; cmsg_close_all(&msghdr); diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index 64cb3279f6..9616e161cc 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -1317,29 +1317,35 @@ int server_process_datagram( iovec = IOVEC_MAKE(s->buffer, s->buffer_size - 1); /* Leave room for trailing NUL we add later */ - n = recvmsg(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); - if (n < 0) { - if (IN_SET(errno, EINTR, EAGAIN)) - return 0; - - return log_error_errno(errno, "recvmsg() failed: %m"); + n = recvmsg_safe(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); + if (IN_SET(n, -EINTR, -EAGAIN)) + return 0; + if (n == -EXFULL) { + log_warning("Got message with truncated control data (too many fds sent?), ignoring."); + return 0; } + if (n < 0) + return log_error_errno(n, "recvmsg() failed: %m"); CMSG_FOREACH(cmsg, &msghdr) if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS && - cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) + cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) { + assert(!ucred); ucred = (struct ucred*) CMSG_DATA(cmsg); - else if (cmsg->cmsg_level == SOL_SOCKET && + } else if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_SECURITY) { + assert(!label); label = (char*) CMSG_DATA(cmsg); label_len = cmsg->cmsg_len - CMSG_LEN(0); } else if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SO_TIMESTAMP && - cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval))) + cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval))) { + assert(!tv); tv = (struct timeval*) CMSG_DATA(cmsg); - else if (cmsg->cmsg_level == SOL_SOCKET && + } else if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + assert(!fds); fds = (int*) CMSG_DATA(cmsg); n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); } diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c index 609af506a4..ec6dad62e8 100644 --- a/src/journal/journald-stream.c +++ b/src/journal/journald-stream.c @@ -545,6 +545,7 @@ static int stdout_stream_process(sd_event_source *es, int fd, uint32_t revents, if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS && cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) { + assert(!ucred); ucred = (struct ucred *)CMSG_DATA(cmsg); break; } diff --git a/src/libsystemd/sd-bus/bus-socket.c b/src/libsystemd/sd-bus/bus-socket.c index 18d30d010a..f54a5d1976 100644 --- a/src/libsystemd/sd-bus/bus-socket.c +++ b/src/libsystemd/sd-bus/bus-socket.c @@ -557,17 +557,24 @@ static int bus_socket_read_auth(sd_bus *b) { mh.msg_control = &control; mh.msg_controllen = sizeof(control); - k = recvmsg(b->input_fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); - if (k < 0 && errno == ENOTSOCK) { + k = recvmsg_safe(b->input_fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); + if (k == -ENOTSOCK) { b->prefer_readv = true; k = readv(b->input_fd, &iov, 1); + if (k < 0) + k = -errno; } else handle_cmsg = true; } + if (k == -EAGAIN) + return 0; if (k < 0) - return errno == EAGAIN ? 0 : -errno; - if (k == 0) + return (int) k; + if (k == 0) { + if (handle_cmsg) + cmsg_close_all(&mh); /* paranoia, we shouldn't have gotten any fds on EOF */ return -ECONNRESET; + } b->rbuffer_size += k; @@ -1193,17 +1200,24 @@ int bus_socket_read_message(sd_bus *bus) { mh.msg_control = &control; mh.msg_controllen = sizeof(control); - k = recvmsg(bus->input_fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); - if (k < 0 && errno == ENOTSOCK) { + k = recvmsg_safe(bus->input_fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); + if (k == -ENOTSOCK) { bus->prefer_readv = true; k = readv(bus->input_fd, &iov, 1); + if (k < 0) + k = -errno; } else handle_cmsg = true; } + if (k == -EAGAIN) + return 0; if (k < 0) - return errno == EAGAIN ? 0 : -errno; - if (k == 0) + return (int) k; + if (k == 0) { + if (handle_cmsg) + cmsg_close_all(&mh); /* On EOF we shouldn't have gotten an fd, but let's make sure */ return -ECONNRESET; + } bus->rbuffer_size += k; diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 9888c9e294..b0d2edac95 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -3716,13 +3716,12 @@ static int nspawn_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t r return 0; } - n = recvmsg(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); - if (n < 0) { - if (IN_SET(errno, EAGAIN, EINTR)) - return 0; + n = recvmsg_safe(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); + if (IN_SET(n, -EAGAIN, -EINTR)) + return 0; + if (n < 0) + return log_warning_errno(n, "Couldn't read notification socket: %m"); - return log_warning_errno(errno, "Couldn't read notification socket: %m"); - } cmsg_close_all(&msghdr); CMSG_FOREACH(cmsg, &msghdr) { diff --git a/src/portable/portable.c b/src/portable/portable.c index 58e4a6670d..49087179c8 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -190,9 +190,9 @@ static int recv_item( assert(ret_name); assert(ret_fd); - n = recvmsg(socket_fd, &mh, MSG_CMSG_CLOEXEC); + n = recvmsg_safe(socket_fd, &mh, MSG_CMSG_CLOEXEC); if (n < 0) - return -errno; + return (int) n; CMSG_FOREACH(cmsg, &mh) { if (cmsg->cmsg_level == SOL_SOCKET && diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 94590e3038..5055800b04 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -775,17 +775,14 @@ int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) { iov = IOVEC_MAKE(DNS_PACKET_DATA(p), p->allocated); - l = recvmsg(fd, &mh, 0); - if (l < 0) { - if (IN_SET(errno, EAGAIN, EINTR)) - return 0; - - return -errno; - } + l = recvmsg_safe(fd, &mh, 0); + if (IN_SET(l, -EAGAIN, -EINTR)) + return 0; + if (l < 0) + return l; if (l == 0) return 0; - assert(!(mh.msg_flags & MSG_CTRUNC)); assert(!(mh.msg_flags & MSG_TRUNC)); p->size = (size_t) l; diff --git a/src/shared/ask-password-api.c b/src/shared/ask-password-api.c index 9b74d909b1..b7b7426058 100644 --- a/src/shared/ask-password-api.c +++ b/src/shared/ask-password-api.c @@ -925,12 +925,11 @@ int ask_password_agent( msghdr.msg_control = &control; msghdr.msg_controllen = sizeof(control); - n = recvmsg(socket_fd, &msghdr, 0); + n = recvmsg_safe(socket_fd, &msghdr, 0); + if (IN_SET(n, -EAGAIN, -EINTR)) + continue; if (n < 0) { - if (IN_SET(errno, EAGAIN, EINTR)) - continue; - - r = -errno; + r = (int) n; goto finish; } diff --git a/src/timesync/timesyncd-manager.c b/src/timesync/timesyncd-manager.c index e18e1e6c04..2980f79b16 100644 --- a/src/timesync/timesyncd-manager.c +++ b/src/timesync/timesyncd-manager.c @@ -438,12 +438,11 @@ static int manager_receive_response(sd_event_source *source, int fd, uint32_t re return manager_connect(m); } - len = recvmsg(fd, &msghdr, MSG_DONTWAIT); + len = recvmsg_safe(fd, &msghdr, MSG_DONTWAIT); + if (len == -EAGAIN) + return 0; if (len < 0) { - if (errno == EAGAIN) - return 0; - - log_warning("Error receiving message. Disconnecting."); + log_warning_errno(len, "Error receiving message, disconnecting: %m"); return manager_connect(m); } diff --git a/src/udev/udev-ctrl.c b/src/udev/udev-ctrl.c index b8c0d83a02..d067279f3e 100644 --- a/src/udev/udev-ctrl.c +++ b/src/udev/udev-ctrl.c @@ -212,13 +212,11 @@ static int udev_ctrl_connection_event_handler(sd_event_source *s, int fd, uint32 if (size == 0) return 0; /* Client disconnects? */ - size = recvmsg(fd, &smsg, 0); - if (size < 0) { - if (errno != EINTR) - return log_error_errno(errno, "Failed to receive ctrl message: %m"); - + size = recvmsg_safe(fd, &smsg, 0); + if (size == -EINTR) return 0; - } + if (size < 0) + return log_error_errno(size, "Failed to receive ctrl message: %m"); cmsg_close_all(&smsg); diff --git a/src/udev/udevd.c b/src/udev/udevd.c index 456bc8c479..4b15159c5b 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -921,16 +921,18 @@ static int on_worker(sd_event_source *s, int fd, uint32_t revents, void *userdat struct ucred *ucred = NULL; struct worker *worker; - size = recvmsg(fd, &msghdr, MSG_DONTWAIT); - if (size < 0) { - if (errno == EINTR) - continue; - else if (errno == EAGAIN) - /* nothing more to read */ - break; + size = recvmsg_safe(fd, &msghdr, MSG_DONTWAIT); + if (size == -EINTR) + continue; + if (size == -EAGAIN) + /* nothing more to read */ + break; + if (size < 0) + return log_error_errno(size, "Failed to receive message: %m"); - return log_error_errno(errno, "Failed to receive message: %m"); - } else if (size != sizeof(struct worker_message)) { + cmsg_close_all(&msghdr); + + if (size != sizeof(struct worker_message)) { log_warning("Ignoring worker message with invalid size %zi bytes", size); continue; }