uint64_t creds_mask;
+ /* Accumulated fds from multiple recvmsg() calls for a single D-Bus message */
int *fds;
size_t n_fds;
+ bool got_ctrunc; /* MSG_CTRUNC was seen during any recvmsg() */
char *exec_path;
char **exec_argv;
#include "utf8.h"
static int message_append_basic(sd_bus_message *m, char type, const void *p, const void **stored);
-static int message_parse_fields(sd_bus_message *m);
+static int message_parse_fields(sd_bus_message *m, bool got_ctrunc);
static void* adjust_pointer(const void *p, void *old_base, size_t sz, void *new_base) {
size_t length,
int *fds,
size_t n_fds,
+ bool got_ctrunc,
const char *label,
sd_bus_message **ret) {
m->iovec = m->iovec_fixed;
m->iovec[0] = IOVEC_MAKE(buffer, length);
- r = message_parse_fields(m);
+ r = message_parse_fields(m, got_ctrunc);
if (r < 0)
return r;
}
}
-static int message_parse_fields(sd_bus_message *m) {
- uint32_t unix_fds = 0;
+static int message_parse_fields(sd_bus_message *m, bool got_ctrunc) {
+ uint32_t n_unix_fds_declared = 0;
bool unix_fds_set = false;
int r;
if (!streq(signature, "u"))
return -EBADMSG;
- r = message_peek_field_uint32(m, &ri, item_size, &unix_fds);
+ r = message_peek_field_uint32(m, &ri, item_size, &n_unix_fds_declared);
if (r < 0)
return -EBADMSG;
return r;
}
- if (m->n_fds != unix_fds)
- return -EBADMSG;
+ /* Validate that the number of fds we actually received via SCM_RIGHTS matches (or is compatible
+ * with) the number declared in the message header.
+ *
+ * Normally these must match exactly. However, when MSG_CTRUNC was set during recvmsg(), the kernel
+ * might have truncated the fd array (e.g., due to LSM denials blocking fd passing). In that case,
+ * we also accept fewer fds than declared. Any attempt to actually use a truncated fd will fail later
+ * when sd_bus_message_read_basic() finds the fd index out of range. Too many fds is always wrong. */
+ if (m->n_fds > n_unix_fds_declared)
+ return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
+ "Received a bus message with too many fds: %" PRIu32 " received vs. %" PRIu32 " declared",
+ m->n_fds, n_unix_fds_declared);
+
+ if (m->n_fds < n_unix_fds_declared && !got_ctrunc)
+ return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
+ "Received a bus message with too few fds: %" PRIu32 " received vs. %" PRIu32 " declared",
+ m->n_fds, n_unix_fds_declared);
+
+ if (got_ctrunc)
+ log_error("Received a bus message with MSG_CTRUNC set with %" PRIu32 " fds received vs %" PRIu32 " declared",
+ m->n_fds, n_unix_fds_declared);
switch (m->header->type) {
size_t length,
int *fds,
size_t n_fds,
+ bool got_ctrunc,
const char *label,
sd_bus_message **ret);
return 0;
}
- if (!GREEDY_REALLOC(bus->fds, bus->n_fds + n_fds))
- return -ENOMEM;
+ /* Check if we previously received fds with MSG_CTRUNC set. When the kernel truncates the fd array,
+ * it drops all fds from the blocked one onwards - we have no way to know how many were lost or which
+ * indexes are affected. Any fds we receive now would be appended at the wrong indexes, corrupting
+ * the message's fd table. We must silently drop them to avoid a worse mess. Reading the message will
+ * still fail later when the user tries to access a truncated fd. */
+ if (!bus->got_ctrunc) {
+ if (!GREEDY_REALLOC(bus->fds, bus->n_fds + n_fds))
+ return -ENOMEM;
+
+ FOREACH_ARRAY(i, fds, n_fds)
+ bus->fds[bus->n_fds++] = fd_move_above_stdio(*i);
+
+ TAKE_PTR(fds);
+ n_fds = 0;
+ }
- FOREACH_ARRAY(i, fds, n_fds)
- bus->fds[bus->n_fds++] = fd_move_above_stdio(*i);
+ if (FLAGS_SET(mh->msg_flags, MSG_CTRUNC))
+ bus->got_ctrunc = true;
- TAKE_PTR(fds);
- n_fds = 0;
return 0;
}
.msg_controllen = sizeof(control),
};
- k = recvmsg_safe(b->input_fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
+ k = RET_NERRNO(recvmsg(b->input_fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC));
if (k == -ENOTSOCK) {
b->prefer_readv = true;
k = readv(b->input_fd, &iov, 1);
r = bus_message_from_malloc(bus,
bus->rbuffer, size,
bus->fds, bus->n_fds,
+ bus->got_ctrunc,
NULL,
&t);
if (r == -EBADMSG) {
bus->fds = NULL;
bus->n_fds = 0;
+ bus->got_ctrunc = false;
if (t) {
t->read_counter = ++bus->read_counter;
.msg_controllen = sizeof(control),
};
- k = recvmsg_safe(bus->input_fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
+ k = RET_NERRNO(recvmsg(bus->input_fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC));
if (k == -ENOTSOCK) {
bus->prefer_readv = true;
k = readv(bus->input_fd, &iov, 1);
assert_se(buffer = memdup(data, size));
- r = bus_message_from_malloc(bus, buffer, size, NULL, 0, NULL, &m);
+ r = bus_message_from_malloc(bus, buffer, size, NULL, 0, /* got_ctrunc= */ false, NULL, &m);
if (r == -EBADMSG)
return 0;
assert_se(r >= 0);
m = sd_bus_message_unref(m);
- r = bus_message_from_malloc(bus, buffer, sz, NULL, 0, NULL, &m);
+ r = bus_message_from_malloc(bus, buffer, sz, NULL, 0, /* got_ctrunc= */ false, NULL, &m);
assert_se(r >= 0);
sd_bus_message_dump(m, stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);