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)
This commit is contained in:
Lennart Poettering 2020-04-23 09:40:03 +02:00
parent 47eae6ce0c
commit 3691bcf3c5
15 changed files with 106 additions and 91 deletions

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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 */

View File

@ -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);

View File

@ -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);

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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) {

View File

@ -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 &&

View File

@ -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;

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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;
}