The Pedigree Project 0.1
scm-rights.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 <stddef.h>
12#include <stdio.h>
13#include <string.h>
14#include <unistd.h>
15
16#include <sys/socket.h>
17#include <sys/un.h>
18
19extern void fail(void) __attribute__((noreturn));
20
21union control_buffer {
22 struct cmsghdr alignment;
23 unsigned char bytes[CMSG_SPACE(2 * sizeof(int))];
24};
25
26static socklen_t unix_address(const char* path, struct sockaddr_un* address) {
27 memset(address, 0, sizeof(*address));
28 address->sun_family = AF_UNIX;
29 if (strlen(path) >= sizeof(address->sun_path))
30 fail();
31 strcpy(address->sun_path, path);
32 return (socklen_t)(offsetof(struct sockaddr_un, sun_path) + strlen(path) + 1);
33}
34
35static ssize_t send_rights(int socketFd, const struct sockaddr_un* address, socklen_t addressLength,
36 const int* descriptors, size_t descriptorCount, char payload) {
37 union control_buffer control = {0};
38 struct iovec vector = {&payload, sizeof(payload)};
39 struct msghdr message = {
40 .msg_name = (void*)address,
41 .msg_namelen = addressLength,
42 .msg_iov = &vector,
43 .msg_iovlen = 1,
44 .msg_control = control.bytes,
45 .msg_controllen = CMSG_SPACE(descriptorCount * sizeof(int)),
46 };
47 struct cmsghdr* header = CMSG_FIRSTHDR(&message);
48 header->cmsg_len = CMSG_LEN(descriptorCount * sizeof(int));
49 header->cmsg_level = SOL_SOCKET;
50 header->cmsg_type = SCM_RIGHTS;
51 memcpy(CMSG_DATA(header), descriptors, descriptorCount * sizeof(int));
52 return sendmsg(socketFd, &message, 0);
53}
54
55static int receive_rights(int socketFd, int flags, size_t controlCapacity, int expectedCount,
56 int* received, int* outputFlags, char expectedPayload) {
57 union control_buffer control = {0};
58 char payload = 0;
59 struct iovec vector = {&payload, sizeof(payload)};
60 struct msghdr message = {
61 .msg_iov = &vector,
62 .msg_iovlen = 1,
63 .msg_control = control.bytes,
64 .msg_controllen = controlCapacity,
65 };
66
67 if (recvmsg(socketFd, &message, flags) != 1 || payload != expectedPayload)
68 return -1;
69 *outputFlags = message.msg_flags;
70 if (!expectedCount) {
71 return message.msg_controllen == 0 ? 0 : -1;
72 }
73
74 struct cmsghdr* header = CMSG_FIRSTHDR(&message);
75 if (!header || header->cmsg_level != SOL_SOCKET || header->cmsg_type != SCM_RIGHTS ||
76 header->cmsg_len != CMSG_LEN((size_t)expectedCount * sizeof(int)))
77 return -1;
78 memcpy(received, CMSG_DATA(header), (size_t)expectedCount * sizeof(int));
79 return 0;
80}
81
82void test_scm_rights(void) {
83 static const char socketPath[] = "/scm-rights.sock";
84 struct sockaddr_un address;
85 socklen_t addressLength = unix_address(socketPath, &address);
86 int receiver = -1;
87 int sender = -1;
88 int stream = -1;
89
90 puts("Testing AF_UNIX datagram descriptor passing... ");
91 fflush(stdout);
92 (void)unlink(socketPath);
93 receiver = socket(AF_UNIX, SOCK_DGRAM, 0);
94 sender = socket(AF_UNIX, SOCK_DGRAM, 0);
95 if (receiver < 0 || sender < 0 || bind(receiver, (struct sockaddr*)&address, addressLength))
96 fail();
97
98 int firstPipe[2];
99 if (pipe(firstPipe) || fcntl(firstPipe[0], F_SETFD, FD_CLOEXEC) ||
100 send_rights(sender, &address, addressLength, &firstPipe[0], 1, 'a') != 1 ||
101 close(firstPipe[0]))
102 fail();
103
104 int received[2] = {-1, -1};
105 int outputFlags = 0;
106 if (receive_rights(receiver, 0, CMSG_SPACE(sizeof(int)), 1, received, &outputFlags, 'a') ||
107 outputFlags || fcntl(received[0], F_GETFD) != 0 || write(firstPipe[1], "A", 1) != 1)
108 fail();
109 char byte = 0;
110 if (read(received[0], &byte, 1) != 1 || byte != 'A' || close(received[0]) || close(firstPipe[1]))
111 fail();
112
113 int cloexecPipe[2];
114 if (pipe(cloexecPipe) ||
115 send_rights(sender, &address, addressLength, &cloexecPipe[0], 1, 'b') != 1 ||
116 close(cloexecPipe[0]))
117 fail();
118 if (receive_rights(receiver, MSG_CMSG_CLOEXEC, CMSG_SPACE(sizeof(int)), 1, received, &outputFlags,
119 'b') ||
120 fcntl(received[0], F_GETFD) != FD_CLOEXEC || close(received[0]) || close(cloexecPipe[1]))
121 fail();
122
123 int truncated[2][2];
124 int sent[2];
125 if (pipe(truncated[0]) || pipe(truncated[1]))
126 fail();
127 sent[0] = truncated[0][0];
128 sent[1] = truncated[1][0];
129 if (send_rights(sender, &address, addressLength, sent, 2, 'c') != 1 || close(sent[0]) ||
130 close(sent[1]))
131 fail();
132 if (receive_rights(receiver, 0, CMSG_LEN(sizeof(int)), 1, received, &outputFlags, 'c') ||
133 !(outputFlags & MSG_CTRUNC) || write(truncated[0][1], "C", 1) != 1)
134 fail();
135 byte = 0;
136 if (read(received[0], &byte, 1) != 1 || byte != 'C' || close(received[0]) ||
137 close(truncated[0][1]))
138 fail();
139 void (*previousSigpipe)(int) = signal(SIGPIPE, SIG_IGN);
140 if (previousSigpipe == SIG_ERR)
141 fail();
142 errno = 0;
143 int hiddenWrite = (int)write(truncated[1][1], "x", 1);
144 int hiddenWriteError = errno;
145 int hiddenClose = close(truncated[1][1]);
146 if (signal(SIGPIPE, previousSigpipe) == SIG_ERR || hiddenWrite != -1 ||
147 hiddenWriteError != EPIPE || hiddenClose)
148 fail();
149
150 int faultPipe[2];
151 if (pipe(faultPipe) || send_rights(sender, &address, addressLength, &faultPipe[0], 1, 'd') != 1 ||
152 close(faultPipe[0]))
153 fail();
154 char payload = 0;
155 struct iovec vector = {&payload, sizeof(payload)};
156 struct msghdr badReceive = {
157 .msg_iov = &vector,
158 .msg_iovlen = 1,
159 .msg_control = (void*)-1,
160 .msg_controllen = CMSG_SPACE(sizeof(int)),
161 };
162 errno = 0;
163 if (recvmsg(receiver, &badReceive, 0) != -1 || errno != EFAULT ||
164 receive_rights(receiver, 0, CMSG_SPACE(sizeof(int)), 1, received, &outputFlags, 'd') ||
165 close(received[0]) || close(faultPipe[1]))
166 fail();
167
168 int unsupportedPipe[2];
169 if (pipe(unsupportedPipe))
170 fail();
171 errno = 0;
172 if (send_rights(sender, &address, addressLength, &sender, 1, 'e') != -1 || errno != EOPNOTSUPP)
173 fail();
174 stream = socket(AF_UNIX, SOCK_STREAM, 0);
175 errno = 0;
176 if (stream < 0 || send_rights(stream, 0, 0, &unsupportedPipe[0], 1, 'f') != -1 ||
177 errno != ENOTCONN)
178 fail();
179
180 char malformedPayload = 'm';
181 struct iovec malformedVector = {&malformedPayload, sizeof(malformedPayload)};
182 union control_buffer malformedControl = {0};
183 struct msghdr malformedMessage = {
184 .msg_name = &address,
185 .msg_namelen = addressLength,
186 .msg_iov = &malformedVector,
187 .msg_iovlen = 1,
188 .msg_control = malformedControl.bytes,
189 .msg_controllen = CMSG_SPACE(sizeof(int)),
190 };
191 struct cmsghdr* malformedHeader = CMSG_FIRSTHDR(&malformedMessage);
192 malformedHeader->cmsg_len = CMSG_LEN(sizeof(int)) - 1;
193 malformedHeader->cmsg_level = SOL_SOCKET;
194 malformedHeader->cmsg_type = SCM_RIGHTS;
195 memcpy(CMSG_DATA(malformedHeader), &unsupportedPipe[0], sizeof(int));
196 errno = 0;
197 if (sendmsg(sender, &malformedMessage, 0) != -1 || errno != EINVAL)
198 fail();
199
200 malformedHeader->cmsg_len = CMSG_LEN(sizeof(int));
201 malformedHeader->cmsg_level = SOL_SOCKET;
202 malformedHeader->cmsg_type = 0x7fffffff;
203 errno = 0;
204 if (sendmsg(sender, &malformedMessage, 0) != -1 || errno != EOPNOTSUPP)
205 fail();
206
207 union control_buffer misalignedControl = {0};
208 malformedMessage.msg_control = misalignedControl.bytes + 1;
209 malformedMessage.msg_controllen = CMSG_LEN(sizeof(int));
210 struct cmsghdr alignedHeader = {
211 .cmsg_len = CMSG_LEN(sizeof(int)),
212 .cmsg_level = SOL_SOCKET,
213 .cmsg_type = SCM_RIGHTS,
214 };
215 memcpy(misalignedControl.bytes + 1, &alignedHeader, sizeof(alignedHeader));
216 memcpy(misalignedControl.bytes + 1 + CMSG_LEN(0), &unsupportedPipe[0], sizeof(int));
217 errno = 0;
218 if (sendmsg(sender, &malformedMessage, 0) != 1 ||
219 receive_rights(receiver, 0, CMSG_SPACE(sizeof(int)), 1, received, &outputFlags, 'm') ||
220 close(received[0]))
221 fail();
222
223 if (send_rights(sender, &address, addressLength, &unsupportedPipe[0], 1, 'n') != 1)
224 fail();
225 union control_buffer misalignedReceiveControl = {0};
226 char misalignedPayload = 0;
227 struct iovec misalignedVector = {&misalignedPayload, sizeof(misalignedPayload)};
228 struct msghdr misalignedReceive = {
229 .msg_iov = &misalignedVector,
230 .msg_iovlen = 1,
231 .msg_control = misalignedReceiveControl.bytes + 1,
232 .msg_controllen = CMSG_LEN(sizeof(int)),
233 };
234 if (recvmsg(receiver, &misalignedReceive, 0) != 1 || misalignedPayload != 'n' ||
235 misalignedReceive.msg_controllen != CMSG_LEN(sizeof(int)))
236 fail();
237 struct cmsghdr receivedHeader = {0};
238 int misalignedReceived = -1;
239 memcpy(&receivedHeader, misalignedReceiveControl.bytes + 1, sizeof(receivedHeader));
240 memcpy(&misalignedReceived, misalignedReceiveControl.bytes + 1 + CMSG_LEN(0), sizeof(int));
241 if (receivedHeader.cmsg_len != CMSG_LEN(sizeof(int)) || receivedHeader.cmsg_level != SOL_SOCKET ||
242 receivedHeader.cmsg_type != SCM_RIGHTS || misalignedReceived < 0 || close(misalignedReceived))
243 fail();
244
245 errno = 0;
246 int invalid = -1;
247 if (send_rights(sender, &address, addressLength, &invalid, 1, 'g') != -1 || errno != EBADF)
248 fail();
249
250 if (close(unsupportedPipe[0]) || close(unsupportedPipe[1]) || close(stream) || close(sender) ||
251 close(receiver))
252 fail();
253 (void)unlink(socketPath);
254 puts("OK\n");
255 fflush(stdout);
256}