1 /*
2 * Copyright (c) 2022, Google Inc. All rights reserved
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files
6 * (the "Software"), to deal in the Software without restriction,
7 * including without limitation the rights to use, copy, modify, merge,
8 * publish, distribute, sublicense, and/or sell copies of the Software,
9 * and to permit persons to whom the Software is furnished to do so,
10 * subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23
24 #include <err.h>
25 #include <list.h>
26 #include <kernel/thread.h>
27 #include <lib/unittest/unittest.h>
28 #include <lib/rand/rand.h>
29 #include <platform.h>
30 #include <shared/lk/macros.h>
31 #include <sys/types.h>
32
33 #define US2NS(us) ((us) * 1000LL)
34 #define MS2NS(ms) (US2NS(ms) * 1000LL)
35 #define S2NS(s) (MS2NS(s) * 1000LL)
36
37 /* run test in thread that will exit on panic rather than halting execution */
threadtest_run_in_thread(const char * thread_name,int (* func)(void * arg),void * arg)38 static int threadtest_run_in_thread(const char* thread_name,
39 int (*func)(void* arg),
40 void* arg) {
41 int ret;
42 int thread_ret;
43 struct thread* thread;
44 thread = thread_create(thread_name, func, arg, DEFAULT_PRIORITY,
45 DEFAULT_STACK_SIZE);
46 if (!thread) {
47 return ERR_NO_MEMORY;
48 }
49
50 thread_set_flag_exit_on_panic(thread, true);
51
52 ret = thread_resume(thread);
53 if (ret) {
54 return ret;
55 }
56
57 ret = thread_join(thread, &thread_ret, INFINITE_TIME);
58 if (ret) {
59 return ret;
60 }
61
62 return thread_ret;
63 }
64
thread_test_corrupt_current_thread_cookie(void)65 static void thread_test_corrupt_current_thread_cookie(void) {
66 thread_t *curr = get_current_thread();
67 curr->cookie = curr->cookie + 1;
68 }
69
thread_test_corrupt_cookie_before_yield_fn(void * unused)70 static int thread_test_corrupt_cookie_before_yield_fn(void *unused) {
71 thread_test_corrupt_current_thread_cookie();
72 /* put thread at the end run queue before calling thread_resched */
73 thread_yield();
74
75 /* should not get here */
76 return ERR_GENERIC;
77 }
78
79 /* TODO(b/300168583): fix test flakyness. */
TEST(threadtest,DISABLED_cookie_corruption_before_yield_must_panic)80 TEST(threadtest, DISABLED_cookie_corruption_before_yield_must_panic) {
81 int ret;
82 ret = threadtest_run_in_thread("yielding_cookie_corrupter_thread",
83 thread_test_corrupt_cookie_before_yield_fn,
84 NULL);
85 /*
86 * The thread will corrupt its own cookie which will cause a panic.
87 * Because the test thread is set to exit on panic, the thread_exit
88 * function will set its return value to ERR_FAULT.
89 */
90 EXPECT_EQ(ret, ERR_FAULT);
91 }
92
thread_test_corrupt_cookie_before_preempt_fn(void * unused)93 static int thread_test_corrupt_cookie_before_preempt_fn(void *unused) {
94 thread_test_corrupt_current_thread_cookie();
95 /*
96 * put thread at the head of run queue before calling thread_resched
97 *
98 * note: this relies on the thread having a positive remaining quantum
99 * which *should* be satisfied given that the thread was just started.
100 */
101 thread_preempt();
102
103 /* should not get here */
104 return ERR_GENERIC;
105 }
106
107 /* TODO(b/300168583): fix test flakyness. */
TEST(threadtest,DISABLED_cookie_corruption_before_preempt_must_panic)108 TEST(threadtest, DISABLED_cookie_corruption_before_preempt_must_panic) {
109 int ret;
110 ret = threadtest_run_in_thread("preempted_cookie_corrupter_thread",
111 thread_test_corrupt_cookie_before_preempt_fn,
112 NULL);
113 EXPECT_EQ(ret, ERR_FAULT);
114 }
115
thread_test_corrupt_cookie_before_exit_fn(void * unused)116 static int thread_test_corrupt_cookie_before_exit_fn(void *unused) {
117 thread_test_corrupt_current_thread_cookie();
118
119 /* exit thread with corrupt cookie */
120 return ERR_GENERIC;
121 }
122
TEST(threadtest,cookie_corruption_before_exit_must_panic)123 TEST(threadtest, cookie_corruption_before_exit_must_panic) {
124 int ret;
125 ret = threadtest_run_in_thread("exiting_cookie_corrupter_thread",
126 thread_test_corrupt_cookie_before_exit_fn,
127 NULL);
128 EXPECT_EQ(ret, ERR_FAULT);
129 }
130
thread_blocking_fn(void * arg)131 static int thread_blocking_fn(void *arg) {
132 wait_queue_t *queue = (wait_queue_t*)arg;
133
134 /* block so parent can corrupt cookie; ignore return value */
135 THREAD_LOCK(state);
136
137 wait_queue_block(queue, INFINITE_TIME);
138
139 /* should not get here - cookie corrupted by parent thread */
140 THREAD_UNLOCK(state);
141
142 return ERR_GENERIC;
143 }
144
145 /* sleep until a newly created thread is blocked on a wait queue */
thread_sleep_until_blocked(thread_t * sleeper)146 static status_t thread_sleep_until_blocked(thread_t *sleeper) {
147 int thread_state;
148 lk_time_ns_t now_ns = current_time_ns();
149 lk_time_ns_t timeout = now_ns + S2NS(10);
150
151 do {
152 thread_sleep_ns(MS2NS(100));
153
154 THREAD_LOCK(state);
155 thread_state = sleeper->state;
156 THREAD_UNLOCK(state);
157 } while ((now_ns = current_time_ns()) < timeout &&
158 thread_state != THREAD_BLOCKED);
159
160 return thread_state == THREAD_BLOCKED ? NO_ERROR : ERR_TIMED_OUT;
161 }
162
163 struct thread_queue_args {
164 wait_queue_t *queue;
165 thread_t *thread;
166 };
167
thread_corrupt_then_wake_fn(void * args)168 static int thread_corrupt_then_wake_fn(void *args) {
169 struct thread_queue_args *test_args = (struct thread_queue_args*)args;
170 wait_queue_t *queue = test_args->queue;
171 thread_t* sleeper = test_args->thread;
172
173 status_t res = thread_sleep_until_blocked(sleeper);
174
175 if (res != NO_ERROR || queue->count != 1)
176 return ERR_NOT_BLOCKED;
177
178 sleeper->cookie += 1; /* corrupt its cookie */
179
180 THREAD_LOCK(state);
181 wait_queue_wake_one(queue, true, NO_ERROR);
182
183 /* should not get here - above call should panic due to corrupt cookie */
184 THREAD_UNLOCK(state);
185 test_abort:
186 return ERR_GENERIC;
187 }
188
TEST(threadtest,cookie_corruption_detected_after_wakeup)189 TEST(threadtest, cookie_corruption_detected_after_wakeup) {
190 int ret;
191 wait_queue_t queue;
192 thread_t *sleeping_thread;
193 uint64_t expected_cookie;
194
195 wait_queue_init(&queue);
196 sleeping_thread = thread_create("sleeping thread", &thread_blocking_fn,
197 &queue, DEFAULT_PRIORITY,
198 DEFAULT_STACK_SIZE);
199 ASSERT_NE(sleeping_thread, NULL);
200 expected_cookie = sleeping_thread->cookie;
201 thread_set_flag_exit_on_panic(sleeping_thread, true);
202
203 thread_resume(sleeping_thread);
204
205 struct thread_queue_args test_args = {
206 .queue = &queue,
207 .thread = sleeping_thread,
208 };
209
210 ret = threadtest_run_in_thread("waking thread",
211 thread_corrupt_then_wake_fn,
212 &test_args);
213
214 ASSERT_EQ(ret, ERR_FAULT);
215
216 /* sleeping thread got taken out of wait queue but is still blocked */
217 ASSERT_EQ(sleeping_thread->state, THREAD_BLOCKED);
218 ASSERT_EQ(list_in_list(&sleeping_thread->queue_node), false);
219 ASSERT_EQ(queue.count, 0);
220
221 test_abort:;
222 THREAD_LOCK(state);
223 if (sleeping_thread && sleeping_thread->cookie != expected_cookie) {
224 /* wake one detected corrupted cookie, recover state before cleanup */
225 sleeping_thread->cookie = expected_cookie;
226
227 /* put the thread back on the wait queue and increment its count */
228 list_add_head(&queue.list, &sleeping_thread->queue_node);
229 queue.count++;
230 }
231
232 /* this will retry wake operation on sleeping thread with valid cookie */
233 wait_queue_destroy(&queue, true);
234 THREAD_UNLOCK(state);
235
236 if (sleeping_thread) {
237 /* release test thread - must happen after we release the thread lock */
238 thread_join(sleeping_thread, NULL, INFINITE_TIME);
239 }
240 }
241
thread_fake_then_wake_fn(void * args)242 static int thread_fake_then_wake_fn(void *args) {
243 struct thread_queue_args *test_args = (struct thread_queue_args*)args;
244 wait_queue_t *queue = test_args->queue;
245 thread_t fake, *sleeper = test_args->thread;
246
247 status_t res = thread_sleep_until_blocked(sleeper);
248
249 if (res != NO_ERROR || queue->count != 1)
250 return ERR_NOT_BLOCKED;
251
252 /*
253 * Create a fake thread without updating its cookie. Since thread cookies
254 * are address dependent, the cookie checks should detect the fake thread.
255 */
256 memcpy(&fake, sleeper, sizeof(thread_t));
257
258 /* add the fake thread to the head of the wait queue */
259 list_add_head(&queue->list, &fake.queue_node);
260 queue->count++;
261
262 THREAD_LOCK(state);
263 wait_queue_wake_one(queue, true, NO_ERROR);
264
265 /* should not get here - above call should panic due to corrupt cookie */
266 THREAD_UNLOCK(state);
267 test_abort:
268 return ERR_GENERIC;
269 }
270
TEST(threadtest,fake_thread_struct_detected_after_wakeup)271 TEST(threadtest, fake_thread_struct_detected_after_wakeup) {
272 int ret;
273 wait_queue_t queue;
274 thread_t *sleeping_thread;
275
276 wait_queue_init(&queue);
277 sleeping_thread = thread_create("sleeping thread", &thread_blocking_fn,
278 &queue, DEFAULT_PRIORITY,
279 DEFAULT_STACK_SIZE);
280 ASSERT_NE(sleeping_thread, NULL);
281 thread_set_flag_exit_on_panic(sleeping_thread, true);
282
283 thread_resume(sleeping_thread);
284
285 struct thread_queue_args test_args = {
286 .queue = &queue,
287 .thread = sleeping_thread,
288 };
289
290 ret = threadtest_run_in_thread("faking thread",
291 thread_fake_then_wake_fn,
292 &test_args);
293
294 ASSERT_EQ(ret, ERR_FAULT);
295
296 /* sleeping thread should still be blocked on wait queue */
297 ASSERT_EQ(sleeping_thread->state, THREAD_BLOCKED);
298 ASSERT_EQ(sleeping_thread->blocking_wait_queue, &queue);
299 ASSERT_EQ(queue.count, 1);
300
301 test_abort:;
302 THREAD_LOCK(state);
303 /* this will unblock the sleeping thread before destroying the wait queue */
304 wait_queue_destroy(&queue, true);
305 THREAD_UNLOCK(state);
306
307 if (sleeping_thread) {
308 /* release test thread - must happen after we release the thread lock */
309 thread_join(sleeping_thread, NULL, INFINITE_TIME);
310 }
311 }
312
cookie_tester(void * _unused)313 static int cookie_tester(void *_unused) {
314 return 0;
315 }
316
TEST(threadtest,threads_have_valid_cookies)317 TEST(threadtest, threads_have_valid_cookies) {
318 thread_t *curr = get_current_thread();
319 thread_t *new;
320
321 new = thread_create("cookie tester", &cookie_tester, NULL, DEFAULT_PRIORITY,
322 DEFAULT_STACK_SIZE);
323
324 /*
325 * Threads must have the same cookie value modulo the effects of
326 * xor'ing the cookie with the address of the associated thread.
327 */
328 EXPECT_EQ(new->cookie ^ (uint64_t)new, curr->cookie ^ (uint64_t)curr);
329
330 /*
331 * xor'ing the cookie with the address of the associated thread should
332 * make thread cookies unique to each thread because addresses differ.
333 */
334 EXPECT_NE(new->cookie, curr->cookie);
335
336 /* start and join the thread so it gets reclaimed */
337 thread_resume(new);
338 thread_join(new, NULL, INFINITE_TIME);
339 }
340
341 #if KERNEL_PAC_ENABLED
342 #include <arch/arm64/sregs.h>
343 #include <arch/ops.h>
344
345 /*
346 * This tests only one key, as used by the current PAC implementation, and
347 * assumes the structure contains no padding, so can be trivially copied and
348 * compared. If the structure is changed, this test will likely need to be
349 * updated to at least check additional keys.
350 */
351 STATIC_ASSERT(sizeof(struct packeys) == sizeof(uint64_t) * 2);
352
pac_tester(void * param)353 static int pac_tester(void *param) {
354 struct packeys *keys = param;
355
356 keys->apia[0] = ARM64_READ_SYSREG(APIAKeyLo_EL1);
357 keys->apia[1] = ARM64_READ_SYSREG(APIAKeyHi_EL1);
358
359 return 0;
360 }
361
TEST(threadtest,threads_have_valid_pac_keys)362 TEST(threadtest, threads_have_valid_pac_keys) {
363 struct packeys actual_keys[4] = { 0 };
364
365 if (!arch_pac_address_supported()) {
366 trusty_unittest_printf("[ SKIPPED ] PAuth is not supported\n");
367 return;
368 }
369
370 /* Test multiple threads */
371 for (uint8_t i = 0; i < countof(actual_keys); i++) {
372 struct packeys expected_keys = { 0 };
373
374 thread_t *new = thread_create("pac thread", &pac_tester, &actual_keys[i],
375 DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
376
377 /* Store the expected keys assigned to the thread */
378 expected_keys = new->arch.packeys;
379
380 /* Start and join the thread so it completes */
381 thread_resume(new);
382 thread_join(new, NULL, INFINITE_TIME);
383
384 /* Check the keys are as expected */
385 EXPECT_EQ(memcmp(&actual_keys[i], &expected_keys,
386 sizeof(struct packeys)),
387 0, "incorrect key assignment");
388
389 /* Check threads don't share keys */
390 for (uint8_t j = 0; j < i; j++) {
391 EXPECT_NE(memcmp(&actual_keys[i], &actual_keys[j],
392 sizeof(struct packeys)),
393 0, "duplicate key assignment");
394 }
395 }
396 }
397 #else
TEST(threadtest,DISABLED_threads_have_valid_pac_keys)398 TEST(threadtest, DISABLED_threads_have_valid_pac_keys) {}
399 #endif
400
401 PORT_TEST(threadtest, "com.android.kernel.threadtest");
402