From 337801717ed88a0001fd4a470aaeaefc74356437 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 7 Apr 2026 18:31:56 +0200 Subject: [PATCH] test: add unit test for fds mismatch --- .../module-protocol-native/test-connection.c | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/src/modules/module-protocol-native/test-connection.c b/src/modules/module-protocol-native/test-connection.c index eabcbbd67..ef50fe9ba 100644 --- a/src/modules/module-protocol-native/test-connection.c +++ b/src/modules/module-protocol-native/test-connection.c @@ -3,6 +3,7 @@ /* SPDX-License-Identifier: MIT */ #include +#include #include #include @@ -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);