xref: /xnu-10002.81.5/tests/iokit/ioserviceusernotification_race.c (revision 5e3eaea39dcf651e66cb99ba7d70e32cc4a99587)
1 #include <darwintest.h>
2 #include <darwintest_utils.h>
3 #include <mach/mach.h>
4 #include <mach/message.h>
5 #include <stdlib.h>
6 #include <sys/sysctl.h>
7 #include <unistd.h>
8 #include <signal.h>
9 #include <mach/mach_vm.h>
10 #include <libproc.h>
11 #include <IOKit/IOKitLib.h>
12 #include <CoreFoundation/CoreFoundation.h>
13 #include <dispatch/dispatch.h>
14 
15 #include "service_helpers.h"
16 
17 T_GLOBAL_META(
18 	T_META_NAMESPACE("xnu.iokit"),
19 	T_META_RUN_CONCURRENTLY(true),
20 	T_META_ASROOT(true),
21 	T_META_RADAR_COMPONENT_NAME("xnu"),
22 	T_META_RADAR_COMPONENT_VERSION("IOKit"),
23 	T_META_OWNER("souvik_b"));
24 
25 
26 static bool
ioclasscount(const char * className,size_t * result)27 ioclasscount(const char * className, size_t * result)
28 {
29 	bool ret = false;
30 	kern_return_t status;
31 	io_registry_entry_t root = IO_OBJECT_NULL; //must release
32 	CFMutableDictionaryRef rootProps   = NULL; //must release
33 	CFStringRef classStr = CFStringCreateWithCString(kCFAllocatorDefault, className, kCFStringEncodingUTF8); //must release
34 
35 	CFDictionaryRef diagnostics = NULL; //do not release
36 	CFDictionaryRef classes = NULL; //do not release
37 	CFNumberRef num = NULL; //do not release
38 	int32_t num32;
39 
40 	root = IORegistryGetRootEntry(kIOMainPortDefault);
41 	status = IORegistryEntryCreateCFProperties(root,
42 	    &rootProps, kCFAllocatorDefault, kNilOptions);
43 	if (KERN_SUCCESS != status) {
44 		T_LOG("Error: Can't read registry root properties.");
45 		goto finish;
46 	}
47 	if (CFDictionaryGetTypeID() != CFGetTypeID(rootProps)) {
48 		T_LOG("Error: Registry root properties not a dictionary.");
49 		goto finish;
50 	}
51 
52 	diagnostics = (CFDictionaryRef)CFDictionaryGetValue(rootProps,
53 	    CFSTR(kIOKitDiagnosticsKey));
54 	if (!diagnostics) {
55 		T_LOG("Error: Allocation information missing.");
56 		goto finish;
57 	}
58 	if (CFDictionaryGetTypeID() != CFGetTypeID(diagnostics)) {
59 		T_LOG("Error: Allocation information not a dictionary.");
60 		goto finish;
61 	}
62 
63 	classes = (CFDictionaryRef)CFDictionaryGetValue(diagnostics, CFSTR("Classes"));
64 	if (!classes) {
65 		T_LOG("Error: Class information missing.");
66 		goto finish;
67 	}
68 	if (CFDictionaryGetTypeID() != CFGetTypeID(classes)) {
69 		T_LOG("Error: Class information not a dictionary.");
70 		goto finish;
71 	}
72 
73 	num = (CFNumberRef)CFDictionaryGetValue(classes, classStr);
74 	if (!num) {
75 		T_LOG("Error: Could not find class %s in dictionary.", className);
76 		goto finish;
77 	}
78 
79 	if (CFNumberGetTypeID() != CFGetTypeID(num)) {
80 		T_LOG("Error: Instance information not a number.");
81 		goto finish;
82 	}
83 
84 	if (!CFNumberGetValue(num, kCFNumberSInt32Type, &num32)) {
85 		T_LOG("Error: Failed to get number.");
86 		goto finish;
87 	}
88 
89 	if (num32 < 0) {
90 		T_LOG("Instance count is negative.");
91 		goto finish;
92 	}
93 
94 	*result = (size_t)num32;
95 
96 	ret = true;
97 
98 finish:
99 	if (root != IO_OBJECT_NULL) {
100 		IOObjectRelease(root);
101 	}
102 	if (rootProps != NULL) {
103 		CFRelease(rootProps);
104 	}
105 	if (classStr != NULL) {
106 		CFRelease(classStr);
107 	}
108 
109 	return ret;
110 }
111 
112 static size_t
absoluteDifference(size_t first,size_t second)113 absoluteDifference(size_t first, size_t second)
114 {
115 	if (first > second) {
116 		return first - second;
117 	} else {
118 		return second - first;
119 	}
120 }
121 
122 static void
notificationReceived(void * refcon __unused,io_iterator_t iter __unused,uint32_t msgType __unused,void * msgArg __unused)123 notificationReceived(void * refcon __unused, io_iterator_t iter __unused, uint32_t msgType __unused, void * msgArg __unused)
124 {
125 	// T_LOG("notification received");
126 }
127 
128 struct Context {
129 	IONotificationPortRef notifyPort;
130 	io_iterator_t iter;
131 };
132 
133 static void
notificationReceived2(void * refcon,io_iterator_t iter __unused,uint32_t msgType __unused,void * msgArg __unused)134 notificationReceived2(void * refcon, io_iterator_t iter __unused, uint32_t msgType __unused, void * msgArg __unused)
135 {
136 	struct Context * ctx = (struct Context *)refcon;
137 	IONotificationPortDestroy(ctx->notifyPort);
138 	IOObjectRelease(ctx->iter);
139 	free(ctx);
140 	T_LOG("notification received, destroyed");
141 }
142 
143 T_HELPER_DECL(ioserviceusernotification_race_helper, "ioserviceusernotification_race_helper")
144 {
145 	dispatch_async(dispatch_get_main_queue(), ^{
146 		io_iterator_t iter;
147 		io_iterator_t iter2;
148 		IONotificationPortRef notifyPort;
149 		IONotificationPortRef notifyPort2;
150 		io_service_t service;
151 
152 		notifyPort = IONotificationPortCreate(kIOMainPortDefault);
153 		IONotificationPortSetDispatchQueue(notifyPort, dispatch_get_main_queue());
154 		notifyPort2 = IONotificationPortCreate(kIOMainPortDefault);
155 		IONotificationPortSetDispatchQueue(notifyPort2, dispatch_get_main_queue());
156 
157 		service = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching("TestIOServiceUserNotificationUserClient"));
158 		T_ASSERT_NE(service, IO_OBJECT_NULL, "service is nonnull");
159 
160 		// The first notification object is kept for the lifetime of the helper
161 		T_ASSERT_MACH_SUCCESS(
162 			IOServiceAddInterestNotification(notifyPort, service, kIOBusyInterest, notificationReceived, NULL, &iter),
163 			"add notification");
164 
165 		struct Context * c = calloc(1, sizeof(struct Context));
166 
167 		// The second notification object is released after a notification is received
168 		T_ASSERT_MACH_SUCCESS(
169 			IOServiceAddInterestNotification(notifyPort2, service, kIOBusyInterest, notificationReceived2, c, &iter2),
170 			"add notification 2");
171 
172 		c->notifyPort = notifyPort2;
173 		c->iter = iter2;
174 
175 		IOObjectRelease(service);
176 	});
177 
178 	dispatch_main();
179 }
180 
181 // how many notification objects to create
182 #define NUM_NOTIFICATION_ITERS 500
183 
184 // how many times we should run the helper
185 #define NUM_HELPER_INVOCATIONS 50
186 
187 // when calling the external method, call in groups of N
188 #define EXTERNAL_METHOD_GROUP_SIZE 5
189 
190 // How much ioclasscount variation to tolerate before we think we have a leak
191 #define IOCLASSCOUNT_LEAK_TOLERANCE 20
192 
193 T_DECL(ioserviceusernotification_race, "Test IOServiceUserNotification race")
194 {
195 	io_service_t service = IO_OBJECT_NULL;
196 	io_connect_t connect = IO_OBJECT_NULL;
197 	IONotificationPortRef notifyPort = IONotificationPortCreate(kIOMainPortDefault);
198 	char test_path[MAXPATHLEN] = {0};
199 	char * helper_args[] = { test_path, "-n", "ioserviceusernotification_race_helper", NULL };
200 	io_iterator_t notificationIters[NUM_NOTIFICATION_ITERS];
201 	size_t initialIOServiceUserNotificationCount;
202 	size_t initialIOServiceMessageUserNotificationCount;
203 	size_t initialIOUserNotificationCount;
204 	size_t finalIOServiceUserNotificationCount;
205 	size_t finalIOServiceMessageUserNotificationCount;
206 	size_t finalIOUserNotificationCount;
207 
208 	// Initial class counts
209 	T_ASSERT_TRUE(ioclasscount("IOServiceUserNotification", &initialIOServiceUserNotificationCount), "ioclasscount IOServiceUserNotification");
210 	T_ASSERT_TRUE(ioclasscount("IOServiceMessageUserNotification", &initialIOServiceMessageUserNotificationCount), "ioclasscount IOServiceMessageUserNotification");
211 	T_ASSERT_TRUE(ioclasscount("IOUserNotification", &initialIOUserNotificationCount), "ioclasscount IOUserNotification");
212 
213 	T_QUIET; T_ASSERT_POSIX_SUCCESS(proc_pidpath(getpid(), test_path, MAXPATHLEN), "get pid path");
214 	T_QUIET; T_ASSERT_POSIX_SUCCESS(IOTestServiceFindService("TestIOServiceUserNotification", &service),
215 	    "Find service");
216 	T_QUIET; T_ASSERT_NE(service, MACH_PORT_NULL, "got service");
217 
218 	for (size_t i = 0; i < NUM_HELPER_INVOCATIONS; i++) {
219 		pid_t child;
220 		if (connect == IO_OBJECT_NULL) {
221 			T_ASSERT_MACH_SUCCESS(IOServiceOpen(service, mach_task_self(), 1, &connect), "open service");
222 		}
223 		// Call the external method. This re-registers the service
224 		T_QUIET; T_ASSERT_MACH_SUCCESS(IOConnectCallMethod(connect, 0,
225 		    NULL, 0, NULL, 0, NULL, 0, NULL, NULL), "call external method");
226 
227 		sleep(1);
228 		dt_launch_tool(&child, helper_args, false, NULL, NULL);
229 		T_LOG("launch helper -> pid %d", child);
230 		sleep(1);
231 
232 		while (true) {
233 			for (size_t k = 0; k < EXTERNAL_METHOD_GROUP_SIZE; k++) {
234 				T_QUIET; T_ASSERT_MACH_SUCCESS(IOConnectCallMethod(connect, 0,
235 				    NULL, 0, NULL, 0, NULL, 0, NULL, NULL), "call external method");
236 				usleep(100);
237 			}
238 			if ((random() % 1000) == 0) {
239 				break;
240 			}
241 		}
242 
243 		T_LOG("kill helper %d", child);
244 		kill(child, SIGKILL);
245 
246 		if ((random() % 3) == 0) {
247 			IOServiceClose(connect);
248 			connect = IO_OBJECT_NULL;
249 		}
250 	}
251 
252 	// Register for notifications
253 	for (size_t i = 0; i < sizeof(notificationIters) / sizeof(notificationIters[0]); i++) {
254 		T_QUIET; T_ASSERT_MACH_SUCCESS(
255 			IOServiceAddInterestNotification(notifyPort, service, kIOBusyInterest, notificationReceived, NULL, &notificationIters[i]),
256 			"add notification");
257 	}
258 
259 	sleep(1);
260 
261 	// Release the notifications
262 	for (size_t i = 0; i < sizeof(notificationIters) / sizeof(notificationIters[0]); i++) {
263 		T_QUIET; T_ASSERT_MACH_SUCCESS(
264 			IOObjectRelease(notificationIters[i]),
265 			"remove notification");
266 		notificationIters[i] = MACH_PORT_NULL;
267 	}
268 
269 	// Check for leaks
270 	T_ASSERT_TRUE(ioclasscount("IOServiceUserNotification", &finalIOServiceUserNotificationCount), "ioclasscount IOServiceUserNotification");
271 	T_ASSERT_TRUE(ioclasscount("IOServiceMessageUserNotification", &finalIOServiceMessageUserNotificationCount), "ioclasscount IOServiceMessageUserNotification");
272 	T_ASSERT_TRUE(ioclasscount("IOUserNotification", &finalIOUserNotificationCount), "ioclasscount IOUserNotification");
273 	T_ASSERT_LT(absoluteDifference(initialIOServiceUserNotificationCount, finalIOServiceUserNotificationCount), (size_t)IOCLASSCOUNT_LEAK_TOLERANCE, "did not leak IOServiceUserNotification");
274 	T_ASSERT_LT(absoluteDifference(initialIOServiceMessageUserNotificationCount, finalIOServiceMessageUserNotificationCount), (size_t)IOCLASSCOUNT_LEAK_TOLERANCE, "did not leak IOServiceMessageUserNotification");
275 	T_ASSERT_LT(absoluteDifference(initialIOUserNotificationCount, finalIOUserNotificationCount), (size_t)IOCLASSCOUNT_LEAK_TOLERANCE, "did not leak IOUserNotification");
276 
277 	IOObjectRelease(service);
278 	IONotificationPortDestroy(notifyPort);
279 }
280