The Pedigree Project 0.1
scm-rights-stream.c
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#include <errno.h>
9#include <fcntl.h>
10#include <signal.h>
11#include <stdio.h>
12#include <string.h>
13#include <unistd.h>
14
15#include <sys/socket.h>
16
17extern void fail(void) __attribute__((noreturn));
18
19union stream_control_buffer {
20 struct cmsghdr alignment;
21 unsigned char bytes[CMSG_SPACE(2 * sizeof(int))];
22};
23
24static ssize_t send_stream_rights(int socketFd, const int* descriptors, size_t descriptorCount,
25 const void* payload, size_t payloadLength) {
26 union stream_control_buffer control = {0};
27 struct iovec vector = {(void*)payload, payloadLength};
28 struct msghdr message = {
29 .msg_iov = &vector,
30 .msg_iovlen = 1,
31 .msg_control = control.bytes,
32 .msg_controllen = CMSG_SPACE(descriptorCount * sizeof(int)),
33 };
34 struct cmsghdr* header = CMSG_FIRSTHDR(&message);
35 header->cmsg_len = CMSG_LEN(descriptorCount * sizeof(int));
36 header->cmsg_level = SOL_SOCKET;
37 header->cmsg_type = SCM_RIGHTS;
38 memcpy(CMSG_DATA(header), descriptors, descriptorCount * sizeof(int));
39 return sendmsg(socketFd, &message, MSG_NOSIGNAL);
40}
41
42static ssize_t receive_stream_rights(int socketFd, int flags, void* payload, size_t payloadCapacity,
43 size_t controlCapacity, int expectedCount, int* received,
44 int* outputFlags) {
45 union stream_control_buffer control = {0};
46 struct iovec vector = {payload, payloadCapacity};
47 struct msghdr message = {
48 .msg_iov = &vector,
49 .msg_iovlen = 1,
50 .msg_control = control.bytes,
51 .msg_controllen = controlCapacity,
52 };
53 const ssize_t result = recvmsg(socketFd, &message, flags);
54 if (result < 0)
55 return result;
56
57 *outputFlags = message.msg_flags;
58 if (!expectedCount) {
59 if (message.msg_controllen)
60 fail();
61 return result;
62 }
63
64 struct cmsghdr* header = CMSG_FIRSTHDR(&message);
65 if (!header || header->cmsg_level != SOL_SOCKET || header->cmsg_type != SCM_RIGHTS ||
66 header->cmsg_len != CMSG_LEN((size_t)expectedCount * sizeof(int)))
67 fail();
68 memcpy(received, CMSG_DATA(header), (size_t)expectedCount * sizeof(int));
69 return result;
70}
71
72static void expect_broken_pipe(int descriptor) {
73 void (*previousSigpipe)(int) = signal(SIGPIPE, SIG_IGN);
74 if (previousSigpipe == SIG_ERR)
75 fail();
76 errno = 0;
77 const ssize_t result = write(descriptor, "x", 1);
78 const int error = errno;
79 if (signal(SIGPIPE, previousSigpipe) == SIG_ERR || result != -1 || error != EPIPE)
80 fail();
81}
82
83static void test_ordering_and_sender_close(void) {
84 int sockets[2];
85 int channel[2];
86 if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, sockets) || pipe(channel))
87 fail();
88
89 char empty = 0;
90 int received[2] = {-1, -1};
91 int outputFlags = 0;
92 errno = 0;
93 if (receive_stream_rights(sockets[1], 0, &empty, 1, sizeof(union stream_control_buffer), 0,
94 received, &outputFlags) != -1 ||
95 errno != EAGAIN)
96 fail();
97
98 if (write(sockets[0], "ab", 2) != 2 ||
99 send_stream_rights(sockets[0], &channel[0], 1, "cd", 2) != 2 ||
100 write(sockets[0], "ef", 2) != 2 || close(channel[0]) || close(sockets[0]))
101 fail();
102
103 char byte = 0;
104 if (read(sockets[1], &byte, 1) != 1 || byte != 'a')
105 fail();
106
107 char marked[8] = {0};
108 const ssize_t markedLength =
109 receive_stream_rights(sockets[1], MSG_CMSG_CLOEXEC, marked, sizeof(marked),
110 CMSG_SPACE(sizeof(int)), 1, received, &outputFlags);
111 if (markedLength != 2 || memcmp(marked, "bc", 2) || outputFlags || received[0] < 0 ||
112 fcntl(received[0], F_GETFD) != FD_CLOEXEC || write(channel[1], "R", 1) != 1 ||
113 read(received[0], &byte, 1) != 1 || byte != 'R' || close(received[0]) || close(channel[1]))
114 fail();
115
116 char tail[4] = {0};
117 if (read(sockets[1], tail, sizeof(tail)) != 3 || memcmp(tail, "def", 3) ||
118 read(sockets[1], tail, sizeof(tail)) != 0 || close(sockets[1]))
119 fail();
120}
121
122static void test_split_iovec_marker(void) {
123 int sockets[2];
124 int channel[2];
125 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) || pipe(channel) ||
126 write(sockets[0], "a", 1) != 1)
127 fail();
128
129 union stream_control_buffer sendControl = {0};
130 struct iovec sendVectors[2] = {
131 {(void*)"", 0},
132 {(void*)"bc", 2},
133 };
134 struct msghdr sendMessage = {
135 .msg_iov = sendVectors,
136 .msg_iovlen = 2,
137 .msg_control = sendControl.bytes,
138 .msg_controllen = CMSG_SPACE(sizeof(int)),
139 };
140 struct cmsghdr* sendHeader = CMSG_FIRSTHDR(&sendMessage);
141 sendHeader->cmsg_len = CMSG_LEN(sizeof(int));
142 sendHeader->cmsg_level = SOL_SOCKET;
143 sendHeader->cmsg_type = SCM_RIGHTS;
144 memcpy(CMSG_DATA(sendHeader), &channel[0], sizeof(channel[0]));
145 if (sendmsg(sockets[0], &sendMessage, MSG_NOSIGNAL) != 2 || close(channel[0]))
146 fail();
147
148 char payload[4] = {0};
149 union stream_control_buffer receiveControl = {0};
150 struct iovec receiveVectors[2] = {
151 {payload, 1},
152 {payload + 1, sizeof(payload) - 1},
153 };
154 struct msghdr receiveMessage = {
155 .msg_iov = receiveVectors,
156 .msg_iovlen = 2,
157 .msg_control = receiveControl.bytes,
158 .msg_controllen = CMSG_SPACE(sizeof(int)),
159 };
160 if (recvmsg(sockets[1], &receiveMessage, 0) != 2 || memcmp(payload, "ab", 2) ||
161 receiveMessage.msg_flags)
162 fail();
163 struct cmsghdr* receiveHeader = CMSG_FIRSTHDR(&receiveMessage);
164 if (!receiveHeader || receiveHeader->cmsg_level != SOL_SOCKET ||
165 receiveHeader->cmsg_type != SCM_RIGHTS || receiveHeader->cmsg_len != CMSG_LEN(sizeof(int)))
166 fail();
167 int received = -1;
168 memcpy(&received, CMSG_DATA(receiveHeader), sizeof(received));
169 char byte = 0;
170 if (received < 0 || write(channel[1], "I", 1) != 1 || read(received, &byte, 1) != 1 ||
171 byte != 'I' || close(received) || read(sockets[1], &byte, 1) != 1 || byte != 'c' ||
172 close(channel[1]) || close(sockets[0]) || close(sockets[1]))
173 fail();
174}
175
176static void test_plain_read_discards_rights(void) {
177 int sockets[2];
178 int channel[2];
179 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) || pipe(channel) ||
180 send_stream_rights(sockets[0], &channel[0], 1, "p", 1) != 1 || close(channel[0]))
181 fail();
182
183 char payload = 0;
184 if (read(sockets[1], &payload, 1) != 1 || payload != 'p')
185 fail();
186 expect_broken_pipe(channel[1]);
187 if (close(channel[1]) || close(sockets[0]) || close(sockets[1]))
188 fail();
189}
190
191static void test_truncation(void) {
192 int sockets[2];
193 int channels[2][2];
194 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) || pipe(channels[0]) || pipe(channels[1]))
195 fail();
196
197 int sent[2] = {channels[0][0], channels[1][0]};
198 if (send_stream_rights(sockets[0], sent, 2, "t", 1) != 1 || close(sent[0]) || close(sent[1]))
199 fail();
200
201 char payload = 0;
202 int received[2] = {-1, -1};
203 int outputFlags = 0;
204 if (receive_stream_rights(sockets[1], 0, &payload, 1, CMSG_LEN(sizeof(int)), 1, received,
205 &outputFlags) != 1 ||
206 payload != 't' || !(outputFlags & MSG_CTRUNC) || write(channels[0][1], "T", 1) != 1)
207 fail();
208 if (read(received[0], &payload, 1) != 1 || payload != 'T' || close(received[0]) ||
209 close(channels[0][1]))
210 fail();
211 expect_broken_pipe(channels[1][1]);
212 if (close(channels[1][1]) || close(sockets[0]) || close(sockets[1]))
213 fail();
214}
215
216static void test_multiple_controls_and_fault_retry(void) {
217 int sockets[2];
218 int channels[2][2];
219 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) || pipe(channels[0]) || pipe(channels[1]))
220 fail();
221
222 if (send_stream_rights(sockets[0], &channels[0][0], 1, "12", 2) != 2 ||
223 send_stream_rights(sockets[0], &channels[1][0], 1, "34", 2) != 2 || close(channels[0][0]) ||
224 close(channels[1][0]))
225 fail();
226
227 char payload[8] = {0};
228 int received[2] = {-1, -1};
229 int outputFlags = 0;
230 if (receive_stream_rights(sockets[1], 0, payload, sizeof(payload), CMSG_SPACE(sizeof(int)), 1,
231 received, &outputFlags) != 1 ||
232 payload[0] != '1' || write(channels[0][1], "A", 1) != 1 ||
233 read(received[0], payload, 1) != 1 || payload[0] != 'A' || close(received[0]) ||
234 close(channels[0][1]))
235 fail();
236
237 memset(payload, 0, sizeof(payload));
238 if (receive_stream_rights(sockets[1], 0, payload, sizeof(payload), CMSG_SPACE(sizeof(int)), 1,
239 received, &outputFlags) != 2 ||
240 memcmp(payload, "23", 2) || write(channels[1][1], "B", 1) != 1 ||
241 read(received[0], payload, 1) != 1 || payload[0] != 'B' || close(received[0]) ||
242 close(channels[1][1]) || read(sockets[1], payload, 1) != 1 || payload[0] != '4')
243 fail();
244
245 int faultChannel[2];
246 if (pipe(faultChannel) || send_stream_rights(sockets[0], &faultChannel[0], 1, "f", 1) != 1 ||
247 close(faultChannel[0]))
248 fail();
249
250 char faultPayload = 0;
251 struct iovec faultVector = {&faultPayload, sizeof(faultPayload)};
252 struct msghdr faultMessage = {
253 .msg_iov = &faultVector,
254 .msg_iovlen = 1,
255 .msg_control = (void*)-1,
256 .msg_controllen = CMSG_SPACE(sizeof(int)),
257 };
258 errno = 0;
259 if (recvmsg(sockets[1], &faultMessage, 0) != -1 || errno != EFAULT ||
260 receive_stream_rights(sockets[1], 0, &faultPayload, 1, CMSG_SPACE(sizeof(int)), 1, received,
261 &outputFlags) != 1 ||
262 faultPayload != 'f' || close(received[0]) || close(faultChannel[1]) || close(sockets[0]) ||
263 close(sockets[1]))
264 fail();
265}
266
267static void test_empty_payload_and_close_drain(void) {
268 int sockets[2];
269 int channel[2];
270 if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, sockets) || pipe(channel))
271 fail();
272
273 struct iovec emptyVector = {(void*)"", 0};
274 struct msghdr emptyMessage = {
275 .msg_iov = &emptyVector,
276 .msg_iovlen = 1,
277 };
278 errno = 0;
279 if (send_stream_rights(sockets[0], &channel[0], 1, "", 0) != -1 || errno != EINVAL ||
280 sendmsg(sockets[0], &emptyMessage, MSG_NOSIGNAL) != 0 || close(channel[0]))
281 fail();
282 expect_broken_pipe(channel[1]);
283 char payload = 0;
284 errno = 0;
285 if (recv(sockets[1], &payload, 1, 0) != -1 || errno != EAGAIN || close(channel[1]) ||
286 close(sockets[0]) || close(sockets[1]))
287 fail();
288
289 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) || pipe(channel) ||
290 send_stream_rights(sockets[0], &channel[0], 1, "c", 1) != 1 || close(channel[0]) ||
291 close(sockets[1]))
292 fail();
293 expect_broken_pipe(channel[1]);
294 if (close(channel[1]) || close(sockets[0]))
295 fail();
296}
297
298void test_scm_rights_stream(void) {
299 puts("Testing AF_UNIX stream descriptor passing... ");
300 fflush(stdout);
301 test_ordering_and_sender_close();
302 test_split_iovec_marker();
303 test_plain_read_discards_rights();
304 test_truncation();
305 test_multiple_controls_and_fault_retry();
306 test_empty_payload_and_close_drain();
307 puts("OK\n");
308 fflush(stdout);
309}