xref: /xnu-11215/tests/kqworkloop_limits.c (revision 4f1223e8)
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