The Pedigree Project 0.1
scm-rights-regressions.cc
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 "pedigree/kernel/Log.h"
9#include "pedigree/kernel/errors.h"
10#include "pedigree/kernel/process/Process.h"
11#include "pedigree/kernel/process/Thread.h"
12#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
13#include "pedigree/kernel/processor/Processor.h"
14#include "pedigree/kernel/utilities/Pointers.h"
15#include "pedigree/kernel/utilities/lib.h"
16
17#include <fcntl.h>
18#include <stddef.h>
19
20#include "modules/subsys/posix/FileDescriptor.h"
21#include "modules/subsys/posix/PosixProcess.h"
22#include "modules/subsys/posix/PosixSubsystem.h"
23#include "modules/subsys/posix/UnixFilesystem.h"
24#include "modules/subsys/posix/file-syscalls.h"
25#include "modules/subsys/posix/net-syscalls.h"
27#include "modules/system/vfs/VFS.h"
28#include <sys/socket.h>
29#include <sys/un.h>
30
31namespace {
32constexpr size_t SourceDescriptor = 90;
33constexpr size_t SecondSourceDescriptor = 91;
34
35union ControlBuffer {
36 struct cmsghdr alignment;
37 uint8_t bytes[CMSG_SPACE(2 * sizeof(int))];
38};
39
40struct ScmRightsUserFixture {
41 struct sockaddr_un address;
42 char sendPayload;
43 char receivedPayload;
44 struct iovec sendVector;
45 struct iovec receiveVector;
46 ControlBuffer sendControl;
47 ControlBuffer receiveControl;
48 struct msghdr sendMessage;
49 struct msghdr receiveMessage;
50};
51
52struct ScmRightsContext {
53 explicit ScmRightsContext(Process* process)
54 : process(process),
55 completed(false),
56 result(false),
57 receivedDescriptor(-1),
58 receivedFlags(-1) {}
59
60 Process* process;
61 bool completed;
62 bool result;
63 int receivedDescriptor;
64 int receivedFlags;
65};
66
67void prepareControl(struct msghdr& message, ControlBuffer& control, const int* descriptors,
68 size_t count) {
69 ByteSet(&control, 0, sizeof(control));
70 message.msg_control = control.bytes;
71 message.msg_controllen = CMSG_SPACE(count * sizeof(int));
72 struct cmsghdr* header = CMSG_FIRSTHDR(&message);
73 header->cmsg_len = CMSG_LEN(count * sizeof(int));
74 header->cmsg_level = SOL_SOCKET;
75 header->cmsg_type = SCM_RIGHTS;
76 MemoryCopy(CMSG_DATA(header), descriptors, count * sizeof(int));
77}
78
79int runScmRightsWorker(void* parameter) {
80 ScmRightsContext* context = reinterpret_cast<ScmRightsContext*>(parameter);
81 Thread* thread = Processor::information().getCurrentThread();
82 PosixSubsystem* subsystem = static_cast<PosixSubsystem*>(thread->getParent()->getSubsystem());
83 const size_t pageSize = PhysicalMemoryManager::getPageSize();
84 uintptr_t mappingAddress = 0;
85 if (!context->process->allocateUserRange(Process::UserRegion::Normal, pageSize, mappingAddress)) {
86 context->completed = true;
87 return 1;
88 }
89
90 uintptr_t mappedAddress = mappingAddress;
92 mappedAddress, pageSize, MemoryMappedObject::Read | MemoryMappedObject::Write);
93 if (!mapping || mappedAddress != mappingAddress || sizeof(ScmRightsUserFixture) > pageSize) {
94 if (mapping) {
95 MemoryMapManager::instance().remove(mappedAddress, pageSize);
96 }
97 context->process->freeUserRange(Process::UserRegion::Normal, mappingAddress, pageSize);
98 context->completed = true;
99 return 1;
100 }
101
102 ScmRightsUserFixture* fixture = reinterpret_cast<ScmRightsUserFixture*>(mappingAddress);
103 ByteSet(fixture, 0, sizeof(*fixture));
104 struct sockaddr_un& address = fixture->address;
105 char& payload = fixture->sendPayload;
106 char& receivedPayload = fixture->receivedPayload;
107 struct iovec& sendVector = fixture->sendVector;
108 struct iovec& receiveVector = fixture->receiveVector;
109 ControlBuffer& sendControl = fixture->sendControl;
110 ControlBuffer& receiveControl = fixture->receiveControl;
111 struct msghdr& sendMessage = fixture->sendMessage;
112 struct msghdr& receiveMessage = fixture->receiveMessage;
113
114 const char path[] = "/hosted-scm-rights";
115 address.sun_family = AF_UNIX;
116 StringCopy(address.sun_path, path);
117 const socklen_t addressLength = offsetof(struct sockaddr_un, sun_path) + sizeof(path);
118
119 const int receiver = posix_socket(AF_UNIX, SOCK_DGRAM, 0);
120 const int sender = posix_socket(AF_UNIX, SOCK_DGRAM, 0);
121 bool passed = receiver >= 0 && sender >= 0 &&
122 posix_bind(receiver, reinterpret_cast<struct sockaddr_storage*>(&address),
123 addressLength) == 0;
124
125 payload = 'r';
126 sendVector = {&payload, sizeof(payload)};
127 sendMessage.msg_name = &address;
128 sendMessage.msg_namelen = addressLength;
129 sendMessage.msg_iov = &sendVector;
130 sendMessage.msg_iovlen = 1;
131 int source = SourceDescriptor;
132 prepareControl(sendMessage, sendControl, &source, 1);
133
134 FileDescriptor* sourceDescriptor = new FileDescriptor;
135 sourceDescriptor->fd = SourceDescriptor;
136 sourceDescriptor->setFlags(FD_CLOEXEC);
137 subsystem->addFileDescriptor(SourceDescriptor, sourceDescriptor);
138 passed = passed && posix_sendmsg(sender, &sendMessage, 0) == 1 &&
139 SocketRights::inFlightForTest() == 1 && posix_close(SourceDescriptor) == 0 &&
140 SocketRights::inFlightForTest() == 1;
141
142 receivedPayload = 0;
143 receiveVector = {&receivedPayload, sizeof(receivedPayload)};
144 receiveMessage.msg_iov = &receiveVector;
145 receiveMessage.msg_iovlen = 1;
146 receiveMessage.msg_control = receiveControl.bytes;
147 receiveMessage.msg_controllen = sizeof(receiveControl.bytes);
148 passed = passed && posix_recvmsg(receiver, &receiveMessage, MSG_CMSG_CLOEXEC) == 1 &&
149 receivedPayload == payload && !(receiveMessage.msg_flags & MSG_CTRUNC) &&
150 SocketRights::inFlightForTest() == 0;
151
152 struct cmsghdr* receivedHeader = CMSG_FIRSTHDR(&receiveMessage);
153 if (receivedHeader && receivedHeader->cmsg_level == SOL_SOCKET &&
154 receivedHeader->cmsg_type == SCM_RIGHTS &&
155 receivedHeader->cmsg_len == CMSG_LEN(sizeof(int))) {
156 MemoryCopy(&context->receivedDescriptor, CMSG_DATA(receivedHeader), sizeof(int));
157 context->receivedFlags =
158 posix_fcntl(context->receivedDescriptor, F_GETFD, reinterpret_cast<void*>(0));
159 } else {
160 passed = false;
161 }
162 passed = passed && context->receivedDescriptor >= 0 && context->receivedFlags == FD_CLOEXEC &&
163 posix_close(context->receivedDescriptor) == 0;
164
165 FileDescriptor* truncatedFirst = new FileDescriptor;
166 truncatedFirst->fd = SourceDescriptor;
167 FileDescriptor::OpenFileDescriptionLease truncatedFirstDescription =
168 truncatedFirst->acquireOpenFileDescription();
169 subsystem->addFileDescriptor(SourceDescriptor, truncatedFirst);
170 FileDescriptor* truncatedSecond = new FileDescriptor;
171 truncatedSecond->fd = SecondSourceDescriptor;
172 FileDescriptor::OpenFileDescriptionLease truncatedSecondDescription =
173 truncatedSecond->acquireOpenFileDescription();
174 subsystem->addFileDescriptor(SecondSourceDescriptor, truncatedSecond);
175 int truncatedSources[2] = {static_cast<int>(SourceDescriptor),
176 static_cast<int>(SecondSourceDescriptor)};
177 prepareControl(sendMessage, sendControl, truncatedSources, 2);
178 payload = 't';
179 passed = passed && posix_sendmsg(sender, &sendMessage, 0) == 1 &&
180 posix_close(SourceDescriptor) == 0 && posix_close(SecondSourceDescriptor) == 0 &&
181 truncatedFirstDescription->descriptorOwnerCount() == 1 &&
182 truncatedSecondDescription->descriptorOwnerCount() == 1;
183
184 ByteSet(&receiveControl, 0, sizeof(receiveControl));
185 receivedPayload = 0;
186 receiveMessage.msg_controllen = CMSG_LEN(sizeof(int));
187 receiveMessage.msg_flags = 0;
188 passed = passed && posix_recvmsg(receiver, &receiveMessage, 0) == 1 &&
189 receivedPayload == payload && (receiveMessage.msg_flags & MSG_CTRUNC) &&
190 SocketRights::inFlightForTest() == 0 &&
191 truncatedFirstDescription->descriptorOwnerCount() == 1 &&
192 truncatedSecondDescription->descriptorOwnerCount() == 0;
193 receivedHeader = CMSG_FIRSTHDR(&receiveMessage);
194 int truncatedReceived = -1;
195 if (receivedHeader && receivedHeader->cmsg_len == CMSG_LEN(sizeof(int))) {
196 MemoryCopy(&truncatedReceived, CMSG_DATA(receivedHeader), sizeof(truncatedReceived));
197 } else {
198 passed = false;
199 }
200 passed = passed && truncatedReceived >= 0 && posix_close(truncatedReceived) == 0 &&
201 truncatedFirstDescription->descriptorOwnerCount() == 0;
202
203 FileDescriptor* queuedDescriptor = new FileDescriptor;
204 queuedDescriptor->fd = SecondSourceDescriptor;
205 subsystem->addFileDescriptor(SecondSourceDescriptor, queuedDescriptor);
206 source = SecondSourceDescriptor;
207 prepareControl(sendMessage, sendControl, &source, 1);
208 payload = 'q';
209 passed = passed && posix_sendmsg(sender, &sendMessage, 0) == 1 &&
210 posix_close(SecondSourceDescriptor) == 0 && SocketRights::inFlightForTest() == 1 &&
211 posix_close(receiver) == 0 && SocketRights::inFlightForTest() == 0;
212
213 passed = passed && posix_close(sender) == 0;
214 MemoryMapManager::instance().remove(mappingAddress, pageSize);
215 context->process->freeUserRange(Process::UserRegion::Normal, mappingAddress, pageSize);
216 context->result = passed;
217 context->completed = true;
218 return 0;
219}
220
221bool inFlightCeiling() {
222 constexpr size_t FullRecords = SocketRights::MaximumInFlight / SocketRights::MaximumDescriptors;
223 constexpr size_t Remainder = SocketRights::MaximumInFlight % SocketRights::MaximumDescriptors;
225 UniqueArray<SharedPointer<SocketRights>>::allocate(FullRecords + (Remainder ? 1 : 0));
226
227 bool passed = SocketRights::inFlightForTest() == 0;
228 for (size_t i = 0; i < FullRecords; ++i) {
229 passed = SocketRights::create(SocketRights::MaximumDescriptors, owners.get()[i]) && passed;
230 }
231 if (Remainder) {
232 passed = SocketRights::create(Remainder, owners.get()[FullRecords]) && passed;
233 }
235 passed = !SocketRights::create(1, excess) &&
236 SocketRights::inFlightForTest() == SocketRights::MaximumInFlight && passed;
238 return passed && SocketRights::inFlightForTest() == 0;
239}
240} // namespace
241
242bool runHostedScmRightsRegressions(Process* kernelProcess) {
243 bool passed = inFlightCeiling();
244
246 auto* priorView = VFS::instance().mountView();
247 VFS::HostedRootViewScope fixture;
248 UnixFilesystem* filesystem = new UnixFilesystem;
249 if (!fixture.open(filesystem)) {
250 delete filesystem;
251 return false;
252 }
253
254 Process* process =
255 new PosixProcess(kernelProcess, true, Process::FilesystemContextMode::Deferred);
256 process->setSubsystem(new PosixSubsystem);
257 const bool contextInstalled = fixture.installContext(*process);
258 ScmRightsContext context(process);
259 Thread* worker = new Thread(process, runScmRightsWorker, &context, nullptr, false, true, true);
260 worker->setName("hosted AF_UNIX SCM_RIGHTS");
261 const bool started = contextInstalled && worker->start();
262 const bool joined = started && worker->joinForCompletion();
263 if (!started) {
264 delete worker;
265 }
266
267 passed = started && joined && context.completed && context.result && passed;
268 delete process;
269
270 const bool rootRestored = fixture.close();
271 if (!rootRestored)
272 FATAL("Hosted filesystem fixture retained owners after teardown");
273 passed = rootRestored && VFS::instance().getRootFilesystem() == priorRoot &&
274 VFS::instance().mountView() == priorView && passed;
275 delete filesystem;
276 passed = SocketRights::inFlightForTest() == 0 && passed;
277
278 if (!passed) {
279 ERROR(
280 "HOSTED-SYSCALL-TEST: FAIL scm-rights-datagram: descriptor ownership, cloexec, "
281 "queue drain, or in-flight limit regressed");
282 return false;
283 }
284
285 NOTICE("HOSTED-SYSCALL-TEST: PASS scm-rights-datagram");
286 return true;
287}
Memory-mapped file interface.
OpenFileDescriptionLease acquireOpenFileDescription() const
void setFlags(int newFlags)
Set flags, distributing any associated changes as needed.
size_t fd
Descriptor number.
MemoryMappedObject * mapAnon(uintptr_t &address, size_t length, MemoryMappedObject::Permissions perms)
size_t remove(uintptr_t base, size_t length)
static MemoryMapManager & instance()
void addFileDescriptor(size_t fd, FileDescriptor *pFd)
static ProcessorInformation & information()
static bool create(size_t descriptorCount, SharedPointer< SocketRights > &rights)
Process * getParent() const
Definition Thread.h:338
Filesystem * getRootFilesystem() const
Definition VFS.cc:631
static VFS & instance()
Definition VFS.cc:291