test: add unit test for fds mismatch

This commit is contained in:
Wim Taymans 2026-04-07 18:31:56 +02:00
parent c9ecbf9fab
commit 337801717e

View file

@ -3,6 +3,7 @@
/* SPDX-License-Identifier: MIT */
#include <sys/socket.h>
#include <unistd.h>
#include <spa/pod/builder.h>
#include <spa/pod/parser.h>
@ -165,6 +166,85 @@ static void test_reentering(struct pw_protocol_native_connection *in,
}
}
/*
* Test that a packet claiming more FDs in its header than were actually
* sent via SCM_RIGHTS is rejected. Without the n_fds validation this
* would cause the receiver to read uninitialised / stale FD values.
*/
static void test_spoofed_fds(struct pw_protocol_native_connection *in,
struct pw_protocol_native_connection *out)
{
const struct pw_protocol_native_message *msg;
int res;
/*
* First, send a valid message through the normal API so that the
* receiver's version handshake happens (it switches to HDR_SIZE=16
* on the first message). Use a message with 0 FDs.
*/
{
struct spa_pod_builder *b;
struct pw_protocol_native_message *wmsg;
b = pw_protocol_native_connection_begin(out, 0, 1, &wmsg);
spa_assert_se(b != NULL);
spa_pod_builder_add_struct(b, SPA_POD_Int(0));
pw_protocol_native_connection_end(out, b);
pw_protocol_native_connection_flush(out);
/* Consume it on the reading side */
res = pw_protocol_native_connection_get_next(in, &msg);
spa_assert_se(res == 1);
}
/*
* Now craft a raw packet on the wire that claims n_fds=5 in the
* header but send 0 actual FDs via SCM_RIGHTS.
*
* v3 header layout (16 bytes / 4 uint32s):
* p[0] = id
* p[1] = (opcode << 24) | (payload_size & 0xffffff)
* p[2] = seq
* p[3] = n_fds
*
* We need a minimal valid SPA pod as payload.
*/
{
/* Build a tiny SPA pod: struct { Int(0) } */
uint8_t payload[16];
struct spa_pod_builder pb;
spa_pod_builder_init(&pb, payload, sizeof(payload));
spa_pod_builder_add_struct(&pb, SPA_POD_Int(0));
uint32_t payload_size = pb.state.offset;
uint32_t header[4];
header[0] = 1; /* id */
header[1] = (5u << 24) | (payload_size & 0xffffff); /* opcode=5, size */
header[2] = 0; /* seq */
header[3] = 5; /* SPOOFED: claim 5 fds, send 0 */
struct iovec iov[2];
struct msghdr mh = { 0 };
iov[0].iov_base = header;
iov[0].iov_len = sizeof(header);
iov[1].iov_base = payload;
iov[1].iov_len = payload_size;
mh.msg_iov = iov;
mh.msg_iovlen = 2;
/* No msg_control — 0 FDs via SCM_RIGHTS */
ssize_t sent = sendmsg(out->fd, &mh, MSG_NOSIGNAL);
spa_assert_se(sent == (ssize_t)(sizeof(header) + payload_size));
}
/* The receiver must reject this packet */
res = pw_protocol_native_connection_get_next(in, &msg);
spa_assert_se(res == -EPROTO);
}
int main(int argc, char *argv[])
{
struct pw_main_loop *loop;
@ -198,6 +278,26 @@ int main(int argc, char *argv[])
pw_protocol_native_connection_destroy(in);
pw_protocol_native_connection_destroy(out);
/* test_spoofed_fds needs its own connection pair */
{
int fds2[2];
struct pw_protocol_native_connection *in2, *out2;
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds2) < 0)
spa_assert_not_reached();
in2 = pw_protocol_native_connection_new(context, fds2[0]);
spa_assert_se(in2 != NULL);
out2 = pw_protocol_native_connection_new(context, fds2[1]);
spa_assert_se(out2 != NULL);
test_spoofed_fds(in2, out2);
pw_protocol_native_connection_destroy(in2);
pw_protocol_native_connection_destroy(out2);
}
pw_context_destroy(context);
pw_main_loop_destroy(loop);