1 /*
2 * Copyright (c) 2025 Apple Inc. All rights reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
28 #include <darwintest.h>
29 #include <darwintest_utils.h>
30 #include <semaphore.h>
31 #include <unistd.h>
32 #include <libgen.h>
33 #include <sys/posix_sem.h>
34 #include <sys/code_signing.h>
35 #include <mach-o/dyld.h>
36
37
38 T_GLOBAL_META(
39 T_META_RADAR_COMPONENT_NAME("xnu"),
40 T_META_RADAR_COMPONENT_VERSION("bsd"),
41 T_META_OWNER("m_staveleytaylor"),
42 T_META_RUN_CONCURRENTLY(true));
43
44 #define NUM_TEST_SEMAPHORES 50
45
46 static char open_test_prefix[PSEMNAMLEN + 1];
47 static char open_sem_invalid[PSEMNAMLEN + 1];
48 static char open_sem_a[PSEMNAMLEN + 1];
49 static char open_sem_b[PSEMNAMLEN + 1];
50
51 static void
cleanup_open()52 cleanup_open()
53 {
54 sem_unlink(open_sem_invalid);
55 sem_unlink(open_sem_a);
56 sem_unlink(open_sem_b);
57
58 for (int i = 0; i < NUM_TEST_SEMAPHORES; i++) {
59 char name_buf[PSEMNAMLEN];
60 snprintf(name_buf, sizeof(name_buf), "%s/many%d", open_test_prefix, i);
61 sem_unlink(name_buf);
62 }
63 }
64
65 T_DECL(posix_sem_open, "POSIX sem_open",
66 T_META_TAG_VM_PREFERRED)
67 {
68 sem_t *sem;
69
70 T_SETUPBEGIN;
71 srand(time(NULL));
72 snprintf(open_test_prefix, sizeof(open_test_prefix), "xnutest%d", rand() % 10000);
73 snprintf(open_sem_invalid, sizeof(open_sem_invalid), "%s/invalid", open_test_prefix);
74 snprintf(open_sem_a, sizeof(open_sem_a), "%s/a", open_test_prefix);
75 snprintf(open_sem_b, sizeof(open_sem_b), "%s/b", open_test_prefix);
76
77 T_ATEND(cleanup_open);
78 T_SETUPEND;
79
80 sem = sem_open(open_sem_invalid, 0);
81 T_EXPECT_EQ_PTR(sem, SEM_FAILED, "sem_open without O_CREAT fails");
82 T_EXPECT_EQ(errno, ENOENT, "sem_open without O_CREAT gives ENOENT");
83
84 sem = sem_open(open_sem_a, O_CREAT, 0755, 0);
85 T_WITH_ERRNO;
86 T_EXPECT_NE_PTR(sem, SEM_FAILED, "sem_open(O_CREAT) succeeds");
87
88 sem = sem_open(open_sem_a, O_CREAT, 0755, 0);
89 T_WITH_ERRNO;
90 T_EXPECT_NE_PTR(sem, SEM_FAILED, "sem_open(O_CREAT) on existing succeeds");
91
92 sem = sem_open(open_sem_a, O_CREAT | O_EXCL, 0755, 0);
93 T_EXPECT_EQ_PTR(sem, SEM_FAILED, "sem_open(O_CREAT | O_EXCL) on existing fails");
94 T_EXPECT_EQ(errno, EEXIST, "sem_open(O_CREAT | O_EXCL) on existing gives EEXIST");
95
96 sem = sem_open(open_sem_b, O_CREAT | O_EXCL, 0755, 0);
97 T_WITH_ERRNO;
98 T_EXPECT_NE_PTR(sem, SEM_FAILED, "sem_open(O_CREAT | O_EXCL) on non-existing succeeds");
99
100 for (int i = 0; i < NUM_TEST_SEMAPHORES; i++) {
101 char name_buf[PSEMNAMLEN];
102 snprintf(name_buf, sizeof(name_buf), "%s/many%d", open_test_prefix, i);
103
104 int oflag = O_CREAT;
105 if (rand() % 2 == 0) {
106 oflag |= O_EXCL;
107 }
108
109 sem = sem_open(name_buf, oflag, 0755, 0);
110 T_WITH_ERRNO;
111 T_EXPECT_NE_PTR(sem, SEM_FAILED, "sem_open name=%s oflag=%d succeeds", name_buf, oflag);
112 }
113
114 /* Fisher-Yates shuffle to randomize order in which we unlink semaphores */
115 int unlink_order[NUM_TEST_SEMAPHORES] = { 0 };
116 for (int i = 0; i < NUM_TEST_SEMAPHORES; i++) {
117 unlink_order[i] = i;
118 }
119 for (int i = 0; i < NUM_TEST_SEMAPHORES; i++) {
120 int next_index = rand() % (NUM_TEST_SEMAPHORES - i);
121
122 int semaphore = unlink_order[i + next_index];
123 unlink_order[i + next_index] = unlink_order[i];
124
125 char name_buf[PSEMNAMLEN + 1];
126 snprintf(name_buf, sizeof(name_buf), "%s/many%d", open_test_prefix, semaphore);
127
128 T_WITH_ERRNO;
129 T_EXPECT_POSIX_SUCCESS(sem_unlink(name_buf), "sem_unlink(%s)", name_buf);
130 }
131 }
132
133 static char namespace_test_sem_name[PSEMNAMLEN + 1];
134
135 static int
find_helper(char * test_path,int team_id)136 find_helper(char* test_path, int team_id)
137 {
138 char binpath[MAXPATHLEN];
139 char* dirpath;
140 uint32_t size = sizeof(binpath);
141 int retval;
142
143 retval = _NSGetExecutablePath(binpath, &size);
144 assert(retval == 0);
145 dirpath = dirname(binpath);
146
147 snprintf(test_path, MAXPATHLEN, "%s/posix_sem_namespace_helper_team%d", dirpath, team_id);
148 if (access(test_path, F_OK) == 0) {
149 return 0;
150 } else {
151 return -1;
152 }
153 }
154
155 static void
do_namespace_op(const char * namespace,const char * op)156 do_namespace_op(const char *namespace, const char *op)
157 {
158 int ret, exit_status, signum;
159
160 dt_pipe_data_handler_t stdout_handler = ^bool (char *data, __unused size_t data_size, __unused dt_pipe_data_handler_context_t *context) {
161 T_LOG("%s: %s", (char *)context->user_context, data);
162 return false;
163 };
164 dt_pipe_data_handler_t stderr_handler = ^bool (char *data, __unused size_t data_size, __unused dt_pipe_data_handler_context_t *context) {
165 T_LOG("%s (stderr): %s", (char *)context->user_context, data);
166 return false;
167 };
168
169 pid_t pid = dt_launch_tool_pipe((char *[]){ (char *)namespace, namespace_test_sem_name, (char *)op, NULL}, false, NULL, stdout_handler, stderr_handler, BUFFER_PATTERN_LINE, (void *)namespace);
170
171 T_QUIET;
172 T_ASSERT_POSIX_SUCCESS(pid, "dt_launch_tool_pipe %s (%s) - %s", op, namespace, namespace_test_sem_name);
173
174 ret = dt_waitpid(pid, &exit_status, &signum, 60 * 5);
175 T_QUIET; T_ASSERT_EQ(ret, 1, "dt_waitpid (exit=%d,signum=%d)", exit_status, signum);
176 T_QUIET; T_ASSERT_EQ(exit_status, 0, "dt_waitpid: exit_status");
177 T_QUIET; T_ASSERT_EQ(signum, 0, "dt_waitpid: signum");
178 }
179
180 static void
cleanup_namespace()181 cleanup_namespace()
182 {
183 sem_unlink(namespace_test_sem_name);
184 }
185
186 /*
187 * Unfortunately this test suffers from two issues that mean we must leave it disabled on BATS:
188 * 1. rdar://75835929 means that XBS strips the team ID from our helper binaries after we've signed them.
189 * 2. BATS infrastructure boots with amfi_get_out_of_my_way=1, which treats signatures as CS_PLATFORM_BINARY and causes the team ID to be ignored.
190 */
191 T_DECL(posix_sem_open_team_id_namespace, "POSIX sem_open team ID namespace",
192 T_META_BOOTARGS_SET("amfi_allow_any_signature=1"),
193 T_META_ENABLED(FALSE),
194 T_META_TAG_VM_PREFERRED)
195 {
196 T_SETUPBEGIN;
197 srand(time(NULL));
198 snprintf(namespace_test_sem_name, sizeof(namespace_test_sem_name), "xnutest%d/ns", rand() % 10000);
199
200 T_ATEND(cleanup_namespace);
201
202 char team0_helper[MAXPATHLEN], team1_helper[MAXPATHLEN];
203 find_helper(team0_helper, 0);
204 find_helper(team1_helper, 1);
205 printf("found helpers at '%s' and '%s'\n", team0_helper, team1_helper);
206
207 /* Quite difficult to register cleanup handlers for this, so we'll perform cleanup now */
208 T_LOG("Performing sem_unlink cleanup");
209 do_namespace_op(team0_helper, "unlink_force");
210 do_namespace_op(team1_helper, "unlink_force");
211
212 T_SETUPEND;
213
214 /* Check that semaphores created by 1st party applications can be discovered by 3rd party applications. */
215 T_LOG("Check 3rd party sees 1st party");
216
217 sem_t *sem = sem_open(namespace_test_sem_name, O_CREAT | O_EXCL, 0755, 0);
218 T_EXPECT_NE_PTR(sem, SEM_FAILED, "sem_open(O_CREAT | O_EXCL)");
219 sem_close(sem);
220
221 do_namespace_op(team0_helper, "check_access");
222 T_ASSERT_POSIX_SUCCESS(sem_unlink(namespace_test_sem_name), "sem_unlink");
223 do_namespace_op(team0_helper, "check_no_access");
224
225 #if TARGET_OS_OSX
226 T_LOG("macOS only: check 1st party sees 3rd party");
227 do_namespace_op(team0_helper, "open_excl");
228 do_namespace_op(team0_helper, "check_access");
229
230 sem = sem_open(namespace_test_sem_name, 0);
231 T_EXPECT_NE_PTR(sem, SEM_FAILED, "sem_open on 3rd party semaphore");
232 sem_close(sem);
233
234 do_namespace_op(team0_helper, "unlink");
235
236 T_LOG("macOS only: check 3rd party sees other 3rd party" );
237 do_namespace_op(team0_helper, "check_no_access");
238 do_namespace_op(team1_helper, "check_no_access");
239
240 do_namespace_op(team0_helper, "open_excl");
241 do_namespace_op(team0_helper, "check_access");
242 do_namespace_op(team1_helper, "check_access");
243
244 do_namespace_op(team1_helper, "unlink");
245 do_namespace_op(team0_helper, "check_no_access");
246 do_namespace_op(team1_helper, "check_no_access");
247 #else
248 /* 1st party applications should not be able to look up semaphores created by 3rd party applications. */
249 T_LOG("Check 1st party doesn't see 3rd party");
250
251 do_namespace_op(team0_helper, "open_excl");
252 do_namespace_op(team0_helper, "check_access");
253
254 sem = sem_open(namespace_test_sem_name, 0);
255 T_EXPECT_EQ_PTR(sem, SEM_FAILED, "sem_open on 3rd party semaphore");
256 sem_close(sem);
257
258 do_namespace_op(team0_helper, "unlink");
259
260 /* 3rd party applications should not be able to interfere with eachother. */
261 T_LOG("Check 3rd party doesn't see other 3rd party");
262
263 do_namespace_op(team0_helper, "check_no_access");
264 do_namespace_op(team1_helper, "check_no_access");
265
266 do_namespace_op(team0_helper, "open_excl");
267 do_namespace_op(team0_helper, "check_access");
268 do_namespace_op(team1_helper, "check_no_access");
269
270 do_namespace_op(team1_helper, "open_excl");
271 do_namespace_op(team0_helper, "check_access");
272 do_namespace_op(team1_helper, "check_access");
273
274 do_namespace_op(team0_helper, "unlink");
275 do_namespace_op(team0_helper, "check_no_access");
276 do_namespace_op(team1_helper, "check_access");
277
278 do_namespace_op(team1_helper, "unlink");
279 do_namespace_op(team0_helper, "check_no_access");
280 do_namespace_op(team1_helper, "check_no_access");
281 #endif
282 }
283