1 #include <darwintest.h>
2 #include <mach/mach.h>
3 #include <stdlib.h>
4 #include <stdio.h>
5 #include <sys/sysctl.h>
6 #include <unistd.h>
7 #include <darwintest_multiprocess.h>
8 #include <spawn.h>
9 #include <spawn_private.h>
10 #include <libproc_internal.h>
11 #include <signal.h>
12 #include <string.h>
13
14 #include <err.h>
15 #include <stdio.h>
16 #include <sysexits.h>
17 #include <stdbool.h>
18
19 #include "kqwl_rnServer.h" // generated by MIG from rnserver.defs
20
21 #include <servers/bootstrap.h>
22 #include <libproc_internal.h> // proc*cpumon*()
23
24 T_GLOBAL_META(
25 T_META_NAMESPACE("xnu.kevent"),
26 T_META_RUN_CONCURRENTLY(TRUE),
27 T_META_RADAR_COMPONENT_NAME("xnu"),
28 T_META_RADAR_COMPONENT_VERSION("kevent"));
29
30 #define TEST_PROGRAM_NAME "./kqworkloop_limits_client"
31 #define MAX_ARGV 5
32
33 extern char **environ;
34
35 static int
spawn_child_process_with_limits(int soft_limit,int hard_limit,int test_num)36 spawn_child_process_with_limits(int soft_limit, int hard_limit, int test_num)
37 {
38 char *child_args[MAX_ARGV];
39 int child_pid;
40 posix_spawnattr_t attrs;
41 int err;
42
43 /* Initialize posix_spawn attributes */
44 posix_spawnattr_init(&attrs);
45
46 err = posix_spawnattr_set_kqworklooplimit_ext(&attrs, soft_limit, hard_limit);
47 T_ASSERT_POSIX_SUCCESS(err, "posix_spawnattr_set_kqworklooplimit_ext");
48
49 char soft_limit_str[32];
50 sprintf(soft_limit_str, "%d", soft_limit);
51
52 char hard_limit_str[32];
53 sprintf(hard_limit_str, "%d", hard_limit);
54
55 char test_num_str[32];
56 sprintf(test_num_str, "%d", test_num);
57
58 child_args[0] = TEST_PROGRAM_NAME;
59 child_args[1] = soft_limit_str; // soft limit
60 child_args[2] = hard_limit_str; // hard limit
61 child_args[3] = test_num_str; // test num
62 child_args[4] = NULL;
63
64 err = posix_spawn(&child_pid, child_args[0], NULL, &attrs, child_args, environ);
65 T_ASSERT_POSIX_SUCCESS(err, "posix_spawn kqworkloop_limits_client");
66 return child_pid;
67 }
68
69 T_DECL(test_kqworkloop_soft_limit, "Allocate kqworkloops up to soft limit",
70 T_META_IGNORECRASHES(".*kqworkloop_limits_client.*"), T_META_CHECK_LEAKS(false), T_META_TAG_VM_PREFERRED)
71 {
72 #if TARGET_OS_BRIDGE
73 T_SKIP("Not running on target platforms");
74 #endif /* TARGET_OS_BRIDGE */
75
76 int child_pid = spawn_child_process_with_limits(200, 0, 1);
77
78 int child_status;
79 /* Wait for child and check for exception */
80 if (-1 == waitpid(child_pid, &child_status, 0)) {
81 T_FAIL("waitpid: child mia");
82 }
83
84 if (WIFSIGNALED(child_status)) {
85 T_FAIL("Child exited with signal = %d", WTERMSIG(child_status));
86 }
87
88 T_ASSERT_EQ(WIFEXITED(child_status), 1, "Child exited normally with exit value %d", WEXITSTATUS(child_status));
89 }
90
91 T_DECL(test_kqworkloop_hard_limit, "Allocate kqworkloops up to hard limit",
92 T_META_IGNORECRASHES(".*kqworkloop_limits_client.*"), T_META_CHECK_LEAKS(false), T_META_TAG_VM_PREFERRED)
93 {
94 #if TARGET_OS_BRIDGE
95 T_SKIP("Not running on target platforms");
96 #endif /* TARGET_OS_BRIDGE */
97
98 int child_pid = spawn_child_process_with_limits(0, 500, 1);
99
100 int child_status;
101 /* Wait for child and check for exception */
102 if (-1 == waitpid(child_pid, &child_status, 0)) {
103 T_FAIL("waitpid: child mia");
104 }
105
106 T_ASSERT_EQ(WIFEXITED(child_status), 0, "Child did not exit normally");
107
108 if (WIFSIGNALED(child_status)) {
109 T_ASSERT_EQ(child_status, 9, "Child exited with status = %x", child_status);
110 }
111 }
112
113 T_DECL(test_kqworkloop_soft_and_hard_limit, "Allocate kqworkloops with soft and hard limit",
114 T_META_IGNORECRASHES(".*kqworkloop_limits_client.*"),
115 T_META_CHECK_LEAKS(false),
116 T_META_TAG_VM_PREFERRED,
117 T_META_ENABLED(false /* rdar://133461542 */)
118 )
119 {
120 #if TARGET_OS_BRIDGE
121 T_SKIP("Not running on target platforms");
122 #endif /* TARGET_OS_BRIDGE */
123
124 int child_pid = spawn_child_process_with_limits(250, 500, 1);
125
126 int child_status;
127 /* Wait for child and check for exception */
128 if (-1 == waitpid(child_pid, &child_status, 0)) {
129 T_FAIL("waitpid: child mia");
130 }
131
132 T_ASSERT_EQ(WIFEXITED(child_status), 0, "Child did not exit normally");
133
134 if (WIFSIGNALED(child_status)) {
135 T_ASSERT_EQ(child_status, 9, "Child exited with status = %x", child_status);
136 }
137 }
138
139 // Tests which define a resource notify server and verify that the soft/hard
140 // limit violations are correctly delivered to the server
141
142 typedef struct {
143 mach_msg_header_t header;
144 mach_msg_body_t body;
145 mach_msg_port_descriptor_t port_descriptor;
146 mach_msg_trailer_t trailer; // subtract this when sending
147 } ipc_complex_message;
148
149 struct args {
150 const char *progname;
151 int verbose;
152 int voucher;
153 int num_msgs;
154 const char *server_port_name;
155 mach_port_t server_port;
156 mach_port_t reply_port;
157 int request_msg_size;
158 void *request_msg;
159 int reply_msg_size;
160 void *reply_msg;
161 uint32_t persona_id;
162 long client_pid;
163 };
164
165 void parse_args(struct args *args);
166 void server_setup(struct args* args);
167 void* exception_server_thread(void *arg);
168 mach_port_t create_exception_port(void);
169 static mach_port_t create_resource_notify_port(void);
170 static ipc_complex_message icm_request = {};
171 static ipc_complex_message icm_reply = {};
172 static mach_port_t resource_notify_port = MACH_PORT_NULL;
173
174 #define TEST_TIMEOUT 10
175
176 void
setup_server_args(struct args * args)177 setup_server_args(struct args *args)
178 {
179 args->server_port_name = "TEST_KQWORKLOOP_LIMITS";
180 args->server_port = MACH_PORT_NULL;
181 args->reply_msg_size = sizeof(ipc_complex_message) - sizeof(mach_msg_trailer_t);
182 args->request_msg_size = sizeof(ipc_complex_message) - sizeof(mach_msg_trailer_t);
183 args->reply_msg_size = sizeof(ipc_complex_message) - sizeof(mach_msg_trailer_t);
184 args->request_msg = &icm_request;
185 args->reply_msg = &icm_reply;
186 }
187
188 /* Create a mach IPC listener which will respond to the client's message */
189 void
server_setup(struct args * args)190 server_setup(struct args *args)
191 {
192 kern_return_t ret;
193 mach_port_t bsport;
194
195 ret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE,
196 &args->server_port);
197 T_ASSERT_MACH_SUCCESS(ret, "server: mach_port_allocate()");
198
199 ret = mach_port_insert_right(mach_task_self(), args->server_port, args->server_port,
200 MACH_MSG_TYPE_MAKE_SEND);
201 T_ASSERT_MACH_SUCCESS(ret, "server: mach_port_insert_right()");
202
203 ret = task_get_bootstrap_port(mach_task_self(), &bsport);
204 T_ASSERT_MACH_SUCCESS(ret, "server: task_get_bootstrap_port()");
205
206 ret = bootstrap_register(bsport, (const char *)args->server_port_name, args->server_port);
207 T_ASSERT_MACH_SUCCESS(ret, "server: bootstrap_register()");
208
209 T_LOG("server: waiting for IPC messages from client on port '%s'.\n",
210 args->server_port_name);
211
212 /* Make the server port as the resource notify port */
213 resource_notify_port = args->server_port;
214 }
215
216 T_DECL(test_kqworkloop_hard_limit_with_resource_notify_port,
217 "Allocate kqworkloops up to hard limit and trigger notification",
218 T_META_IGNORECRASHES(".*kqworkloop_limits_client.*"), T_META_CHECK_LEAKS(false), T_META_TAG_VM_PREFERRED)
219 {
220 #if TARGET_OS_BRIDGE
221 T_SKIP("Not running on target platforms");
222 #endif /* TARGET_OS_BRIDGE */
223 struct args* server_args = (struct args*)malloc(sizeof(struct args));
224
225 /* Create the bootstrap port */
226 setup_server_args(server_args);
227 server_setup(server_args);
228
229 server_args->client_pid = spawn_child_process_with_limits(0, 500, 2);
230
231 T_LOG("server: Let's see if we can catch some kqworkloop leak");
232
233 kern_return_t kr = mach_msg_server_once(resource_notify_server, 4096, resource_notify_port, 0);
234 T_ASSERT_MACH_SUCCESS(kr, "mach_msg_server_once resource_notify_port");
235 }
236
237 // MIG's resource_notify_server() expects receive_cpu_usage_triggers as well
238 // This must match the definition in xnu's resource_notify.defs
239 kern_return_t
receive_cpu_usage_violation(__unused mach_port_t receiver,__unused proc_name_t procname,__unused pid_t pid,__unused posix_path_t killed_proc_path,__unused mach_timespec_t timestamp,__unused int64_t observed_cpu_nsecs,__unused int64_t observation_nsecs,__unused int64_t cpu_nsecs_allowed,__unused int64_t limit_window_nsecs,__unused resource_notify_flags_t flags)240 receive_cpu_usage_violation(__unused mach_port_t receiver,
241 __unused proc_name_t procname,
242 __unused pid_t pid,
243 __unused posix_path_t killed_proc_path,
244 __unused mach_timespec_t timestamp,
245 __unused int64_t observed_cpu_nsecs,
246 __unused int64_t observation_nsecs,
247 __unused int64_t cpu_nsecs_allowed,
248 __unused int64_t limit_window_nsecs,
249 __unused resource_notify_flags_t flags)
250 {
251 return KERN_FAILURE;
252 }
253
254 kern_return_t
receive_cpu_wakes_violation(__unused mach_port_t receiver,__unused proc_name_t procname,__unused pid_t pid,__unused posix_path_t killed_proc_path,__unused mach_timespec_t timestamp,__unused int64_t observed_cpu_wakes,__unused int64_t observation_nsecs,__unused int64_t cpu_wakes_allowed,__unused int64_t limit_window_nsecs,__unused resource_notify_flags_t flags)255 receive_cpu_wakes_violation(__unused mach_port_t receiver,
256 __unused proc_name_t procname,
257 __unused pid_t pid,
258 __unused posix_path_t killed_proc_path,
259 __unused mach_timespec_t timestamp,
260 __unused int64_t observed_cpu_wakes,
261 __unused int64_t observation_nsecs,
262 __unused int64_t cpu_wakes_allowed,
263 __unused int64_t limit_window_nsecs,
264 __unused resource_notify_flags_t flags)
265 {
266 return KERN_FAILURE;
267 }
268
269 kern_return_t
receive_disk_writes_violation(__unused mach_port_t receiver,__unused proc_name_t procname,__unused pid_t pid,__unused posix_path_t killed_proc_path,__unused mach_timespec_t timestamp,__unused int64_t observed_bytes_dirtied,__unused int64_t observation_nsecs,__unused int64_t bytes_dirtied_allowed,__unused int64_t limit_window_nsecs,__unused resource_notify_flags_t flags)270 receive_disk_writes_violation(__unused mach_port_t receiver,
271 __unused proc_name_t procname,
272 __unused pid_t pid,
273 __unused posix_path_t killed_proc_path,
274 __unused mach_timespec_t timestamp,
275 __unused int64_t observed_bytes_dirtied,
276 __unused int64_t observation_nsecs,
277 __unused int64_t bytes_dirtied_allowed,
278 __unused int64_t limit_window_nsecs,
279 __unused resource_notify_flags_t flags)
280 {
281 return KERN_FAILURE;
282 }
283
284 kern_return_t
receive_port_space_violation(__unused mach_port_t receiver,__unused proc_name_t procname,__unused pid_t pid,__unused mach_timespec_t timestamp,__unused int64_t observed_ports,__unused int64_t ports_allowed,__unused mach_port_t fatal_port,__unused resource_notify_flags_t flags)285 receive_port_space_violation(__unused mach_port_t receiver,
286 __unused proc_name_t procname,
287 __unused pid_t pid,
288 __unused mach_timespec_t timestamp,
289 __unused int64_t observed_ports,
290 __unused int64_t ports_allowed,
291 __unused mach_port_t fatal_port,
292 __unused resource_notify_flags_t flags)
293 {
294 return KERN_FAILURE;
295 }
296
297 kern_return_t
receive_file_descriptors_violation(__unused mach_port_t receiver,__unused proc_name_t procname,__unused pid_t pid,__unused mach_timespec_t timestamp,__unused int64_t observed_filedesc,__unused int64_t filedesc_allowed,__unused mach_port_t fatal_port,__unused resource_notify_flags_t flags)298 receive_file_descriptors_violation(__unused mach_port_t receiver,
299 __unused proc_name_t procname,
300 __unused pid_t pid,
301 __unused mach_timespec_t timestamp,
302 __unused int64_t observed_filedesc,
303 __unused int64_t filedesc_allowed,
304 __unused mach_port_t fatal_port,
305 __unused resource_notify_flags_t flags)
306 {
307 return KERN_FAILURE;
308 }
309
310 kern_return_t
receive_kqworkloops_violation(__unused mach_port_t receiver,__unused proc_name_t procname,__unused pid_t pid,__unused mach_timespec_t timestamp,__unused int64_t observed_kqworkloops,__unused int64_t kqworkloops_allowed,__unused mach_port_t fatal_port,__unused resource_notify_flags_t flags)311 receive_kqworkloops_violation( __unused mach_port_t receiver,
312 __unused proc_name_t procname,
313 __unused pid_t pid,
314 __unused mach_timespec_t timestamp,
315 __unused int64_t observed_kqworkloops,
316 __unused int64_t kqworkloops_allowed,
317 __unused mach_port_t fatal_port,
318 __unused resource_notify_flags_t flags)
319 {
320 T_LOG("Received a notification on the resource notify port");
321 T_LOG("kqworkloops_allowed = %lld, kqworkloops observed = %lld", kqworkloops_allowed, observed_kqworkloops);
322 if (fatal_port) {
323 mach_port_deallocate(mach_task_self(), fatal_port);
324 }
325 return KERN_SUCCESS;
326 }
327