xref: /xnu-12377.61.12/tests/posix_sem.c (revision 4d495c6e23c53686cf65f45067f79024cf5dcee8)
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