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.*"), T_META_CHECK_LEAKS(false), T_META_TAG_VM_PREFERRED)
115 {
116 #if TARGET_OS_BRIDGE
117 T_SKIP("Not running on target platforms");
118 #endif /* TARGET_OS_BRIDGE */
119
120 int child_pid = spawn_child_process_with_limits(250, 500, 1);
121
122 int child_status;
123 /* Wait for child and check for exception */
124 if (-1 == waitpid(child_pid, &child_status, 0)) {
125 T_FAIL("waitpid: child mia");
126 }
127
128 T_ASSERT_EQ(WIFEXITED(child_status), 0, "Child did not exit normally");
129
130 if (WIFSIGNALED(child_status)) {
131 T_ASSERT_EQ(child_status, 9, "Child exited with status = %x", child_status);
132 }
133 }
134
135 // Tests which define a resource notify server and verify that the soft/hard
136 // limit violations are correctly delivered to the server
137
138 typedef struct {
139 mach_msg_header_t header;
140 mach_msg_body_t body;
141 mach_msg_port_descriptor_t port_descriptor;
142 mach_msg_trailer_t trailer; // subtract this when sending
143 } ipc_complex_message;
144
145 struct args {
146 const char *progname;
147 int verbose;
148 int voucher;
149 int num_msgs;
150 const char *server_port_name;
151 mach_port_t server_port;
152 mach_port_t reply_port;
153 int request_msg_size;
154 void *request_msg;
155 int reply_msg_size;
156 void *reply_msg;
157 uint32_t persona_id;
158 long client_pid;
159 };
160
161 void parse_args(struct args *args);
162 void server_setup(struct args* args);
163 void* exception_server_thread(void *arg);
164 mach_port_t create_exception_port(void);
165 static mach_port_t create_resource_notify_port(void);
166 static ipc_complex_message icm_request = {};
167 static ipc_complex_message icm_reply = {};
168 static mach_port_t resource_notify_port = MACH_PORT_NULL;
169
170 #define TEST_TIMEOUT 10
171
172 void
setup_server_args(struct args * args)173 setup_server_args(struct args *args)
174 {
175 args->server_port_name = "TEST_KQWORKLOOP_LIMITS";
176 args->server_port = MACH_PORT_NULL;
177 args->reply_msg_size = sizeof(ipc_complex_message) - sizeof(mach_msg_trailer_t);
178 args->request_msg_size = sizeof(ipc_complex_message) - sizeof(mach_msg_trailer_t);
179 args->reply_msg_size = sizeof(ipc_complex_message) - sizeof(mach_msg_trailer_t);
180 args->request_msg = &icm_request;
181 args->reply_msg = &icm_reply;
182 }
183
184 /* Create a mach IPC listener which will respond to the client's message */
185 void
server_setup(struct args * args)186 server_setup(struct args *args)
187 {
188 kern_return_t ret;
189 mach_port_t bsport;
190
191 ret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE,
192 &args->server_port);
193 T_ASSERT_MACH_SUCCESS(ret, "server: mach_port_allocate()");
194
195 ret = mach_port_insert_right(mach_task_self(), args->server_port, args->server_port,
196 MACH_MSG_TYPE_MAKE_SEND);
197 T_ASSERT_MACH_SUCCESS(ret, "server: mach_port_insert_right()");
198
199 ret = task_get_bootstrap_port(mach_task_self(), &bsport);
200 T_ASSERT_MACH_SUCCESS(ret, "server: task_get_bootstrap_port()");
201
202 ret = bootstrap_register(bsport, (const char *)args->server_port_name, args->server_port);
203 T_ASSERT_MACH_SUCCESS(ret, "server: bootstrap_register()");
204
205 T_LOG("server: waiting for IPC messages from client on port '%s'.\n",
206 args->server_port_name);
207
208 /* Make the server port as the resource notify port */
209 resource_notify_port = args->server_port;
210 }
211
212 T_DECL(test_kqworkloop_hard_limit_with_resource_notify_port,
213 "Allocate kqworkloops up to hard limit and trigger notification",
214 T_META_IGNORECRASHES(".*kqworkloop_limits_client.*"), T_META_CHECK_LEAKS(false), T_META_TAG_VM_PREFERRED)
215 {
216 #if TARGET_OS_BRIDGE
217 T_SKIP("Not running on target platforms");
218 #endif /* TARGET_OS_BRIDGE */
219 struct args* server_args = (struct args*)malloc(sizeof(struct args));
220
221 /* Create the bootstrap port */
222 setup_server_args(server_args);
223 server_setup(server_args);
224
225 server_args->client_pid = spawn_child_process_with_limits(0, 500, 2);
226
227 T_LOG("server: Let's see if we can catch some kqworkloop leak");
228
229 kern_return_t kr = mach_msg_server_once(resource_notify_server, 4096, resource_notify_port, 0);
230 T_ASSERT_MACH_SUCCESS(kr, "mach_msg_server_once resource_notify_port");
231 }
232
233 // MIG's resource_notify_server() expects receive_cpu_usage_triggers as well
234 // This must match the definition in xnu's resource_notify.defs
235 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)236 receive_cpu_usage_violation(__unused mach_port_t receiver,
237 __unused proc_name_t procname,
238 __unused pid_t pid,
239 __unused posix_path_t killed_proc_path,
240 __unused mach_timespec_t timestamp,
241 __unused int64_t observed_cpu_nsecs,
242 __unused int64_t observation_nsecs,
243 __unused int64_t cpu_nsecs_allowed,
244 __unused int64_t limit_window_nsecs,
245 __unused resource_notify_flags_t flags)
246 {
247 return KERN_FAILURE;
248 }
249
250 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)251 receive_cpu_wakes_violation(__unused mach_port_t receiver,
252 __unused proc_name_t procname,
253 __unused pid_t pid,
254 __unused posix_path_t killed_proc_path,
255 __unused mach_timespec_t timestamp,
256 __unused int64_t observed_cpu_wakes,
257 __unused int64_t observation_nsecs,
258 __unused int64_t cpu_wakes_allowed,
259 __unused int64_t limit_window_nsecs,
260 __unused resource_notify_flags_t flags)
261 {
262 return KERN_FAILURE;
263 }
264
265 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)266 receive_disk_writes_violation(__unused mach_port_t receiver,
267 __unused proc_name_t procname,
268 __unused pid_t pid,
269 __unused posix_path_t killed_proc_path,
270 __unused mach_timespec_t timestamp,
271 __unused int64_t observed_bytes_dirtied,
272 __unused int64_t observation_nsecs,
273 __unused int64_t bytes_dirtied_allowed,
274 __unused int64_t limit_window_nsecs,
275 __unused resource_notify_flags_t flags)
276 {
277 return KERN_FAILURE;
278 }
279
280 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)281 receive_port_space_violation(__unused mach_port_t receiver,
282 __unused proc_name_t procname,
283 __unused pid_t pid,
284 __unused mach_timespec_t timestamp,
285 __unused int64_t observed_ports,
286 __unused int64_t ports_allowed,
287 __unused mach_port_t fatal_port,
288 __unused resource_notify_flags_t flags)
289 {
290 return KERN_FAILURE;
291 }
292
293 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)294 receive_file_descriptors_violation(__unused mach_port_t receiver,
295 __unused proc_name_t procname,
296 __unused pid_t pid,
297 __unused mach_timespec_t timestamp,
298 __unused int64_t observed_filedesc,
299 __unused int64_t filedesc_allowed,
300 __unused mach_port_t fatal_port,
301 __unused resource_notify_flags_t flags)
302 {
303 return KERN_FAILURE;
304 }
305
306 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)307 receive_kqworkloops_violation( __unused mach_port_t receiver,
308 __unused proc_name_t procname,
309 __unused pid_t pid,
310 __unused mach_timespec_t timestamp,
311 __unused int64_t observed_kqworkloops,
312 __unused int64_t kqworkloops_allowed,
313 __unused mach_port_t fatal_port,
314 __unused resource_notify_flags_t flags)
315 {
316 T_LOG("Received a notification on the resource notify port");
317 T_LOG("kqworkloops_allowed = %lld, kqworkloops observed = %lld", kqworkloops_allowed, observed_kqworkloops);
318 if (fatal_port) {
319 mach_port_deallocate(mach_task_self(), fatal_port);
320 }
321 return KERN_SUCCESS;
322 }
323