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