xref: /xnu-11215.41.3/tests/kqworkloop_limits.c (revision 33de042d024d46de5ff4e89f2471de6608e37fa4)
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