1 /*
2 * Copyright (c) 2017, 2020 Intel Corporation
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include "mem_protect.h"
8 #include <kernel_internal.h> /* For z_main_thread */
9 #include <zephyr/sys/libc-hooks.h> /* for z_libc_partition */
10
11 struct k_thread child_thread;
12 K_THREAD_STACK_DEFINE(child_stack, KOBJECT_STACK_SIZE);
13
14 /* Special memory domain for test case purposes */
15 static struct k_mem_domain test_domain;
16
17 #if Z_LIBC_PARTITION_EXISTS
18 #define PARTS_USED 3
19 #else
20 #define PARTS_USED 2
21 #endif
22 /* Maximum number of allowable memory partitions defined by the build */
23 #define NUM_RW_PARTS (CONFIG_MAX_DOMAIN_PARTITIONS - PARTS_USED)
24
25 /* Max number of allowable partitions, derived at runtime. Might be less. */
26 ZTEST_BMEM int num_rw_parts;
27
28 /* Set of read-write buffers each in their own partition */
29 static volatile uint8_t __aligned(MEM_REGION_ALLOC)
30 rw_bufs[NUM_RW_PARTS][MEM_REGION_ALLOC];
31 static struct k_mem_partition rw_parts[NUM_RW_PARTS];
32
33 /* A single read-only partition */
34 static volatile uint8_t __aligned(MEM_REGION_ALLOC) ro_buf[MEM_REGION_ALLOC];
35 K_MEM_PARTITION_DEFINE(ro_part, ro_buf, sizeof(ro_buf),
36 K_MEM_PARTITION_P_RO_U_RO);
37 /* A partition to test overlap that has same ro_buf as a partition ro_part */
38 K_MEM_PARTITION_DEFINE(overlap_part, ro_buf, sizeof(ro_buf),
39 K_MEM_PARTITION_P_RW_U_RW);
40
41 /* Static thread, used by a couple tests */
zzz_entry(void * p1,void * p2,void * p3)42 static void zzz_entry(void *p1, void *p2, void *p3)
43 {
44 k_sleep(K_FOREVER);
45 }
46
47 static K_THREAD_DEFINE(zzz_thread, 256 + CONFIG_TEST_EXTRA_STACK_SIZE,
48 zzz_entry, NULL, NULL, NULL, 0, 0, 0);
49
test_mem_domain_setup(void)50 void test_mem_domain_setup(void)
51 {
52 int max_parts = arch_mem_domain_max_partitions_get();
53 struct k_mem_partition *parts[] = {
54 #if Z_LIBC_PARTITION_EXISTS
55 &z_libc_partition,
56 #endif
57 &ro_part, &ztest_mem_partition
58 };
59
60 num_rw_parts = max_parts - PARTS_USED;
61 zassert_true(num_rw_parts <= NUM_RW_PARTS,
62 "CONFIG_MAX_DOMAIN_PARTITIONS incorrectly tuned, %d should be at least %d",
63 CONFIG_MAX_DOMAIN_PARTITIONS, max_parts);
64 zassert_true(num_rw_parts > 0, "no free memory partitions");
65
66 zassert_equal(
67 k_mem_domain_init(&test_domain, ARRAY_SIZE(parts), parts),
68 0, "failed to initialize memory domain");
69
70 for (unsigned int i = 0; i < num_rw_parts; i++) {
71 rw_parts[i].start = (uintptr_t)&rw_bufs[i];
72 rw_parts[i].size = MEM_REGION_ALLOC;
73 rw_parts[i].attr = K_MEM_PARTITION_P_RW_U_RW;
74
75 for (unsigned int j = 0; j < MEM_REGION_ALLOC; j++) {
76 rw_bufs[i][j] = (j % 256U);
77 }
78
79 zassert_equal(
80 k_mem_domain_add_partition(&test_domain, &rw_parts[i]),
81 0, "cannot add memory partition");
82 }
83
84 for (unsigned int j = 0; j < MEM_REGION_ALLOC; j++) {
85 ro_buf[j] = (j % 256U);
86 }
87 }
88
89 /* Helper function; run a function under a child user thread.
90 * If domain is not NULL, add the child thread to that domain, instead of
91 * whatever it would inherit.
92 */
spawn_child_thread(k_thread_entry_t entry,struct k_mem_domain * domain,bool should_fault)93 static void spawn_child_thread(k_thread_entry_t entry,
94 struct k_mem_domain *domain, bool should_fault)
95 {
96 set_fault_valid(should_fault);
97
98 k_thread_create(&child_thread, child_stack,
99 K_THREAD_STACK_SIZEOF(child_stack), entry,
100 NULL, NULL, NULL, 0, K_USER, K_FOREVER);
101 k_thread_name_set(&child_thread, "child_thread");
102 if (domain != NULL) {
103 k_mem_domain_add_thread(domain, &child_thread);
104 }
105 k_thread_start(&child_thread);
106 k_thread_join(&child_thread, K_FOREVER);
107
108 if (should_fault && valid_fault) {
109 /* valid_fault gets cleared if an expected exception
110 * took place
111 */
112 printk("test function %p was supposed to fault but didn't\n",
113 entry);
114 ztest_test_fail();
115 }
116 }
117
118 /* read and write to all the rw_parts */
rw_part_access(void * p1,void * p2,void * p3)119 static void rw_part_access(void *p1, void *p2, void *p3)
120 {
121 for (unsigned int i = 0; i < num_rw_parts; i++) {
122 for (unsigned int j = 0; j < MEM_REGION_ALLOC; j++) {
123 /* Test read */
124 zassert_equal(rw_bufs[i][j], j % 256U,
125 "bad data in rw_buf[%d][%d]", i, j);
126 /* Test writes */
127 rw_bufs[i][j]++;
128 rw_bufs[i][j]--;
129 }
130 }
131 }
132
133 /* read the ro_part */
ro_part_access(void * p1,void * p2,void * p3)134 static void ro_part_access(void *p1, void *p2, void *p3)
135 {
136 for (unsigned int i = 0; i < MEM_REGION_ALLOC; i++) {
137 zassert_equal(ro_buf[i], i % 256U,
138 "bad data in ro_buf[%d]", i);
139 }
140 }
141
142 /* attempt to write to ro_part */
ro_write_entry(void * p1,void * p2,void * p3)143 static void ro_write_entry(void *p1, void *p2, void *p3)
144 {
145 /* Should fault here */
146 ro_buf[0] = 200;
147 }
148
149 /**
150 * @brief Check if the mem_domain is configured and accessible for userspace
151 *
152 * Join a memory domain with a read-write memory partition and a read-only
153 * partition within it, and show that the data in the partition is accessible
154 * as expected by the permissions provided.
155 *
156 * @ingroup kernel_memprotect_tests
157 */
ZTEST(mem_protect_domain,test_mem_domain_valid_access)158 ZTEST(mem_protect_domain, test_mem_domain_valid_access)
159 {
160 spawn_child_thread(rw_part_access, &test_domain, false);
161 spawn_child_thread(ro_part_access, &test_domain, false);
162 }
163
164 /**
165 * @brief Show that a user thread can't touch partitions not in its domain
166 *
167 * @ingroup kernel_memprotect_tests
168 */
ZTEST(mem_protect_domain,test_mem_domain_invalid_access)169 ZTEST(mem_protect_domain, test_mem_domain_invalid_access)
170 {
171 /* child not added to test_domain, will fault for both */
172 spawn_child_thread(rw_part_access, NULL, true);
173 spawn_child_thread(ro_part_access, NULL, true);
174 }
175
176 /**
177 * @brief Show that a read-only partition can't be written to
178 *
179 * @ingroup kernel_memgroup_tests
180 */
ZTEST(mem_protect_domain,test_mem_domain_no_writes_to_ro)181 ZTEST(mem_protect_domain, test_mem_domain_no_writes_to_ro)
182 {
183 /* Show that trying to write to a read-only partition causes a fault */
184 spawn_child_thread(ro_write_entry, &test_domain, true);
185 }
186
187 /**
188 * @brief Show that adding/removing partitions works
189 *
190 * Show that removing a partition doesn't affect access to other partitions.
191 * Show that removing a partition generates a fault if its data is accessed.
192 * Show that adding a partition back restores access from a user thread.
193 *
194 * @ingroup kernel_memprotect_tests
195 */
ZTEST(mem_protect_domain,test_mem_domain_remove_add_partition)196 ZTEST(mem_protect_domain, test_mem_domain_remove_add_partition)
197 {
198 zassert_equal(
199 k_mem_domain_remove_partition(&test_domain, &rw_parts[0]),
200 0, "failed to remove memory partition");
201
202 /* Should still work, we didn't remove ro_part */
203 spawn_child_thread(ro_part_access, &test_domain, false);
204
205 /* This will fault, we removed one of the rw_part from the domain */
206 spawn_child_thread(rw_part_access, &test_domain, true);
207
208 /* Restore test_domain contents so we don't mess up other tests */
209 zassert_equal(
210 k_mem_domain_add_partition(&test_domain, &rw_parts[0]),
211 0, "failed to add memory partition");
212
213 /* Should work again */
214 spawn_child_thread(rw_part_access, &test_domain, false);
215 }
216
217 /* user mode will attempt to initialize this and fail */
218 static struct k_mem_domain no_access_domain;
219
220 /* Extra partition that a user thread can't add to a domain */
221 static volatile uint8_t __aligned(MEM_REGION_ALLOC)
222 no_access_buf[MEM_REGION_ALLOC];
223 K_MEM_PARTITION_DEFINE(no_access_part, no_access_buf, sizeof(no_access_buf),
224 K_MEM_PARTITION_P_RW_U_RW);
225
mem_domain_init_entry(void * p1,void * p2,void * p3)226 static void mem_domain_init_entry(void *p1, void *p2, void *p3)
227 {
228 zassert_equal(
229 k_mem_domain_init(&no_access_domain, 0, NULL),
230 0, "failed to initialize memory domain");
231 }
232
mem_domain_add_partition_entry(void * p1,void * p2,void * p3)233 static void mem_domain_add_partition_entry(void *p1, void *p2, void *p3)
234 {
235 zassert_equal(
236 k_mem_domain_add_partition(&test_domain, &no_access_part),
237 0, "failed to add memory partition");
238 }
239
mem_domain_remove_partition_entry(void * p1,void * p2,void * p3)240 static void mem_domain_remove_partition_entry(void *p1, void *p2, void *p3)
241 {
242 zassert_equal(
243 k_mem_domain_remove_partition(&test_domain, &ro_part),
244 0, "failed to remove memory partition");
245 }
246
mem_domain_add_thread_entry(void * p1,void * p2,void * p3)247 static void mem_domain_add_thread_entry(void *p1, void *p2, void *p3)
248 {
249 k_mem_domain_add_thread(&test_domain, zzz_thread);
250 }
251
252 /**
253 * @brief Test access memory domain APIs allowed to supervisor threads only
254 *
255 * Show that invoking any of the memory domain APIs from user mode leads to
256 * a fault.
257 *
258 * @ingroup kernel_memprotect_tests
259 *
260 * @see k_mem_domain_init(), k_mem_domain_add_partition(),
261 * k_mem_domain_remove_partition(), k_mem_domain_add_thread()
262 */
ZTEST(mem_protect_domain,test_mem_domain_api_supervisor_only)263 ZTEST(mem_protect_domain, test_mem_domain_api_supervisor_only)
264 {
265 /* All of these should fault when invoked from a user thread */
266 spawn_child_thread(mem_domain_init_entry, NULL, true);
267 spawn_child_thread(mem_domain_add_partition_entry, NULL, true);
268 spawn_child_thread(mem_domain_remove_partition_entry, NULL, true);
269 spawn_child_thread(mem_domain_add_thread_entry, NULL, true);
270 }
271
272 /**
273 * @brief Show that boot threads belong to the default memory domain
274 *
275 * Static threads and the main thread are supposed to start as members of
276 * the default memory domain. Prove this is the case by examining the
277 * memory domain membership of z_main_thread and a static thread.
278 *
279 * @ingroup kernel_memprotect_tests
280 */
ZTEST(mem_protect_domain,test_mem_domain_boot_threads)281 ZTEST(mem_protect_domain, test_mem_domain_boot_threads)
282 {
283 /* Check that a static thread got put in the default memory domain */
284 zassert_true(zzz_thread->mem_domain_info.mem_domain ==
285 &k_mem_domain_default, "unexpected mem domain %p",
286 zzz_thread->mem_domain_info.mem_domain);
287
288 /* Check that the main thread is also a member of the default domain */
289 zassert_true(z_main_thread.mem_domain_info.mem_domain ==
290 &k_mem_domain_default, "unexpected mem domain %p",
291 z_main_thread.mem_domain_info.mem_domain);
292
293 k_thread_abort(zzz_thread);
294 }
295
296 static ZTEST_BMEM volatile bool spin_done;
297 static K_SEM_DEFINE(spin_sem, 0, 1);
298
spin_entry(void * p1,void * p2,void * p3)299 static void spin_entry(void *p1, void *p2, void *p3)
300 {
301 printk("spin thread entry\n");
302 k_sem_give(&spin_sem);
303
304 while (!spin_done) {
305 k_busy_wait(1);
306 }
307 printk("spin thread completed\n");
308 }
309
310 /**
311 * @brief Show that moving a thread from one domain to another works
312 *
313 * Start a thread and have it spin. Then while it is spinning, show that
314 * adding it to another memory domain doesn't cause any faults.
315 *
316 * This test is of particular importance on SMP systems where the child
317 * thread is spinning on a different CPU concurrently with the migration
318 * operation.
319 *
320 * @ingroup kernel_memprotect_tests
321 *
322 * @see k_mem_domain_add_thread()
323 */
324
325 #if CONFIG_MP_MAX_NUM_CPUS > 1
326 #define PRIO K_PRIO_COOP(0)
327 #else
328 #define PRIO K_PRIO_PREEMPT(1)
329 #endif
330
ZTEST(mem_protect_domain,test_mem_domain_migration)331 ZTEST(mem_protect_domain, test_mem_domain_migration)
332 {
333 int ret;
334
335 set_fault_valid(false);
336
337 k_thread_create(&child_thread, child_stack,
338 K_THREAD_STACK_SIZEOF(child_stack), spin_entry,
339 NULL, NULL, NULL,
340 PRIO, K_USER | K_INHERIT_PERMS, K_FOREVER);
341 k_thread_name_set(&child_thread, "child_thread");
342 k_object_access_grant(&spin_sem, &child_thread);
343 k_thread_start(&child_thread);
344
345 /* Ensure that the child thread has started */
346 ret = k_sem_take(&spin_sem, K_FOREVER);
347 zassert_equal(ret, 0, "k_sem_take failed");
348
349 /* Now move it to test_domain. This domain also has the ztest partition,
350 * so the child thread should keep running and not explode
351 */
352 printk("migrate to new domain\n");
353 k_mem_domain_add_thread(&test_domain, &child_thread);
354
355 /**TESTPOINT: add to existing domain will do nothing */
356 k_mem_domain_add_thread(&test_domain, &child_thread);
357
358 /* set spin_done so the child thread completes */
359 printk("set test completion\n");
360 spin_done = true;
361
362 k_thread_join(&child_thread, K_FOREVER);
363 }
364
365 /**
366 * @brief Test system assert when new partition overlaps the existing partition
367 *
368 * @details
369 * Test Objective:
370 * - Test assertion if the new partition overlaps existing partition in domain
371 *
372 * Testing techniques:
373 * - System testing
374 *
375 * Prerequisite Conditions:
376 * - N/A
377 *
378 * Input Specifications:
379 * - N/A
380 *
381 * Test Procedure:
382 * -# Define testing memory partition overlap_part with the same start ro_buf
383 * as has the existing memory partition ro_part
384 * -# Try to add overlap_part to the memory domain. When adding the new
385 * partition to the memory domain the system will assert that new partition
386 * overlaps with the existing partition ro_part .
387 *
388 * Expected Test Result:
389 * - Must happen an assertion error indicating that the new partition overlaps
390 * the existing one.
391 *
392 * Pass/Fail Criteria:
393 * - Success if the overlap assertion will happen.
394 * - Failure if the overlap assertion will not happen.
395 *
396 * Assumptions and Constraints:
397 * - N/A
398 *
399 * @ingroup kernel_memprotect_tests
400 *
401 * @see k_mem_domain_add_partition()
402 */
ZTEST(mem_protect_domain,test_mem_part_overlap)403 ZTEST(mem_protect_domain, test_mem_part_overlap)
404 {
405 set_fault_valid(false);
406
407 zassert_not_equal(
408 k_mem_domain_add_partition(&test_domain, &overlap_part),
409 0, "should fail to add memory partition");
410 }
411
412 extern struct k_spinlock z_mem_domain_lock;
413
414 static struct k_mem_domain test_domain_fail;
415
416 static volatile uint8_t __aligned(MEM_REGION_ALLOC)
417 exceed_buf[MEM_REGION_ALLOC];
418
419 K_MEM_PARTITION_DEFINE(exceed_part, exceed_buf, sizeof(exceed_buf),
420 K_MEM_PARTITION_P_RW_U_RW);
421
422 /**
423 * @brief Test system assert when adding memory partitions more than possible
424 *
425 * @details
426 * - Add memory partitions one by one and more than architecture allows to add.
427 * - When partitions added more than it is allowed by architecture, test that
428 * k_mem_domain_add_partition() returns non-zero.
429 *
430 * @ingroup kernel_memprotect_tests
431 */
ZTEST(mem_protect_domain,test_mem_part_assert_add_overmax)432 ZTEST(mem_protect_domain, test_mem_part_assert_add_overmax)
433 {
434 int max_parts = num_rw_parts + PARTS_USED;
435
436 /* Make sure the partitions of the domain is full, used in
437 * previous test cases.
438 */
439 zassert_equal(max_parts, arch_mem_domain_max_partitions_get(),
440 "domain still have room of partitions(%d).",
441 max_parts);
442
443 set_fault_valid(false);
444
445 /* Add one more partition will fail due to exceeding */
446 zassert_not_equal(
447 k_mem_domain_add_partition(&test_domain, &exceed_part),
448 0, "should fail to add memory partition");
449 }
450
451
452 #if defined(CONFIG_ASSERT)
453 static volatile uint8_t __aligned(MEM_REGION_ALLOC) misc_buf[MEM_REGION_ALLOC];
454 K_MEM_PARTITION_DEFINE(find_no_part, misc_buf, sizeof(misc_buf),
455 K_MEM_PARTITION_P_RO_U_RO);
456
457 /**
458 * @brief Test error case of removing memory partition fail
459 *
460 * @details Try to remove a partition not in the domain.
461 * k_mem_domain_remove_partition() should return non-zero.
462 *
463 * @ingroup kernel_memprotect_tests
464 */
ZTEST(mem_protect_domain,test_mem_domain_remove_part_fail)465 ZTEST(mem_protect_domain, test_mem_domain_remove_part_fail)
466 {
467 struct k_mem_partition *no_parts = &find_no_part;
468
469 set_fault_valid(false);
470
471 zassert_not_equal(
472 k_mem_domain_remove_partition(&test_domain, no_parts),
473 0, "should fail to remove memory partition");
474 }
475 #else
ZTEST(mem_protect_domain,test_mem_domain_remove_part_fail)476 ZTEST(mem_protect_domain, test_mem_domain_remove_part_fail)
477 {
478 ztest_test_skip();
479 }
480 #endif
481
482 /**
483 * @brief Test error case of initializing memory domain fail
484 *
485 * @details Try to initialize a domain with invalid partition.
486 * k_mem_domain_init() should return non-zero.
487 *
488 * @ingroup kernel_memprotect_tests
489 */
ZTEST(mem_protect_domain,test_mem_domain_init_fail)490 ZTEST(mem_protect_domain, test_mem_domain_init_fail)
491 {
492 struct k_mem_partition *no_parts[] = {&ro_part, 0};
493
494 /* init another domain fail */
495 set_fault_valid(false);
496
497 zassert_not_equal(
498 k_mem_domain_init(&test_domain_fail, ARRAY_SIZE(no_parts),
499 no_parts),
500 0, "should fail to initialize memory domain");
501 }
502
503 /**
504 * @brief Test error case of adding null memory partition fail
505 *
506 * @details Try to add a null partition to memory domain.
507 * k_mem_domain_add_partition() should return error.
508 *
509 * @ingroup kernel_memprotect_tests
510 */
ZTEST(mem_protect_domain,test_mem_part_add_error_null)511 ZTEST(mem_protect_domain, test_mem_part_add_error_null)
512 {
513 /* add partition fail */
514 set_fault_valid(false);
515
516 zassert_not_equal(
517 k_mem_domain_add_partition(&test_domain_fail, NULL),
518 0, "should fail to add memory partition");
519 }
520
521 static volatile uint8_t __aligned(MEM_REGION_ALLOC) nosize_buf[MEM_REGION_ALLOC];
522 K_MEM_PARTITION_DEFINE(nonsize_part, nosize_buf, sizeof(nosize_buf),
523 K_MEM_PARTITION_P_RO_U_RO);
524
525 /**
526 * @brief Test error case of adding zero sized memory partition fail
527 *
528 * @details Try to add a zero sized partition to memory domain.
529 * k_mem_domain_add_partition() should return error.
530 *
531 * @ingroup kernel_memprotect_tests
532 */
ZTEST(mem_protect_domain,test_mem_part_add_error_zerosize)533 ZTEST(mem_protect_domain, test_mem_part_add_error_zerosize)
534 {
535 struct k_mem_partition *nosize_part = &nonsize_part;
536
537 nosize_part->size = 0U;
538
539 /* add partition fail */
540 set_fault_valid(false);
541
542 zassert_not_equal(
543 k_mem_domain_add_partition(&test_domain_fail, nosize_part),
544 0, "should fail to add memory partition");
545 }
546
547 /**
548 * @brief Test error case of memory partition address wraparound
549 *
550 * @details Try to add a partition whose address is wraparound.
551 * k_mem_domain_add_partition() should return error.
552 *
553 * @ingroup kernel_memprotect_tests
554 */
ZTEST(mem_protect_domain,test_mem_part_error_wraparound)555 ZTEST(mem_protect_domain, test_mem_part_error_wraparound)
556 {
557 #ifdef CONFIG_64BIT
558 K_MEM_PARTITION_DEFINE(wraparound_part, 0xfffffffffffff800, 2048,
559 K_MEM_PARTITION_P_RO_U_RO);
560 #else
561 K_MEM_PARTITION_DEFINE(wraparound_part, 0xfffff800, 2048,
562 K_MEM_PARTITION_P_RO_U_RO);
563 #endif
564
565 /* add partition fail */
566 set_fault_valid(false);
567
568 zassert_not_equal(
569 k_mem_domain_add_partition(&test_domain_fail, &wraparound_part),
570 0, "should fail to add memory partition");
571 }
572
573 /**
574 * @brief Test error case of removing memory partition fail
575 *
576 * @details Try to remove a partition size mismatched will result
577 * in k_mem_domain_remove_partition() returning error.
578 *
579 * @ingroup kernel_memprotect_tests
580 */
ZTEST(mem_protect_domain,test_mem_part_remove_error_zerosize)581 ZTEST(mem_protect_domain, test_mem_part_remove_error_zerosize)
582 {
583 struct k_mem_partition *no_parts = &find_no_part;
584
585 zassert_equal(
586 k_mem_domain_remove_partition(&test_domain, &rw_parts[0]),
587 0, "failed to remove memory partition");
588
589 zassert_equal(
590 k_mem_domain_add_partition(&test_domain, no_parts),
591 0, "failed to add memory partition");
592
593 no_parts->size = 0U;
594
595 /* remove partition fail */
596 set_fault_valid(false);
597
598 zassert_not_equal(
599 k_mem_domain_remove_partition(&test_domain, no_parts),
600 0, "should fail to remove memory partition");
601 }
602
603 /* setup function */
mem_domain_setup(void)604 void *mem_domain_setup(void)
605 {
606 test_mem_domain_setup();
607
608 return NULL;
609 }
610
611 ZTEST_SUITE(mem_protect_domain, NULL, mem_domain_setup, NULL,
612 NULL, NULL);
613