1/*
2 * Copyright 2017, Data61
3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO)
4 * ABN 41 687 119 230.
5 *
6 * This software may be distributed and modified according to the terms of
7 * the BSD 2-Clause license. Note that NO WARRANTY is provided.
8 * See "LICENSE_BSD2.txt" for details.
9 *
10 * @TAG(DATA61_BSD)
11 */
12
13#include <autoconf.h>
14#include <sel4test-driver/gen_config.h>
15#include <assert.h>
16#include <stddef.h>
17#include <stdio.h>
18#include <stdlib.h>
19
20#include <sel4/sel4.h>
21#include <vka/object.h>
22#include <vka/capops.h>
23#include <sel4utils/util.h>
24#include <sel4utils/mapping.h>
25
26#include "../helpers.h"
27#include "frame_type.h"
28
29void touch_data(void *vaddr, char old_data, char new_data, size_t size_bits)
30{
31    char *data = (char *)vaddr;
32    /* we test a sample of the bytes in the frame to ensure a couple of things
33     1. a frame of the correct size is mapped in
34     2. for larger frames that no part of the large frame shares a region with another part.
35        this could happen with ARM large pages and super sections as they are comprised of
36        16 entries in a paging structure, and not just a single entry in a higher level structure
37     */
38    for (size_t i = 0; i < 16; i++) {
39        size_t offset = BIT(size_bits) / 16 * i;
40        test_eq(data[offset], old_data);
41        data[offset] = new_data;
42        test_eq(data[offset], new_data);
43    }
44}
45
46static int test_frame_exported(env_t env)
47{
48    /* Reserve a location that is aligned and large enough to map our
49     * largest kind of frame */
50    void *vaddr;
51    reservation_t reserve = vspace_reserve_range_aligned(&env->vspace, VSPACE_RV_SIZE, VSPACE_RV_ALIGN_BITS,
52                                                         seL4_AllRights, 1, &vaddr);
53    test_assert(reserve.res);
54
55    /* loop through frame sizes, allocate, map and touch them until we run out
56     * of memory. */
57    size_t mem_total = 0;
58    int err;
59    for (int i = 0; i < ARRAY_SIZE(frame_types); i++) {
60        bool once = false;
61        while (1) {
62            /* Allocate the frame */
63            seL4_CPtr frame = vka_alloc_frame_leaky(&env->vka, frame_types[i].size_bits);
64            if (!frame) {
65                break;
66            }
67            once = true;
68            mem_total += BIT(frame_types[i].size_bits);
69
70            uintptr_t cookie = 0;
71            err = vspace_map_pages_at_vaddr(&env->vspace, &frame, &cookie, (void *)vaddr, 1, frame_types[i].size_bits, reserve);
72            test_error_eq(err, seL4_NoError);
73
74            /* Touch the memory */
75            char *data = (char *)vaddr;
76            touch_data(data, 0, 'U', frame_types[i].size_bits);
77
78            err = seL4_ARCH_Page_Map(frame,
79                                     env->page_directory,
80                                     (seL4_Word) vaddr,
81                                     seL4_AllRights,
82                                     seL4_ARCH_Default_VMAttributes);
83            test_error_eq(err, seL4_NoError);
84            /* ensure the memory is what it was before and touch it again */
85            touch_data(vaddr, 'U', 'V', frame_types[i].size_bits);
86
87            vspace_unmap_pages(&env->vspace, (void *)vaddr, 1, frame_types[i].size_bits, VSPACE_PRESERVE);
88            test_error_eq(err, seL4_NoError);
89        }
90        test_assert(once);
91    }
92    return sel4test_get_result();
93}
94DEFINE_TEST(FRAMEEXPORTS0001, "Test that we can access all exported frames", test_frame_exported, true)
95
96/* Wait for a VM fault originating on the given EP the return the virtual
97 * address it occurred at. Returns the sentinel 0xffffffff if the message
98 * received was not a VM fault.
99 */
100static int handle(seL4_CPtr fault_ep, seL4_CPtr reply, seL4_Word arg3, seL4_Word arg4)
101{
102    seL4_MessageInfo_t info = api_recv(fault_ep, NULL, reply);
103    if (seL4_MessageInfo_get_label(info) == seL4_Fault_VMFault) {
104        return (int)seL4_GetMR(1);
105    } else {
106        return (int)0xffffffff;
107    }
108}
109
110#if defined(CONFIG_ARCH_ARM)
111/* XN support is only implemented for ARM currently. */
112
113/* Function that generates a fault. If we're mapped XN we should instruction
114 * fault at the start of the function. If not we should data fault on 0x42.
115 */
116static int fault(seL4_Word arg1, seL4_Word arg2, seL4_Word arg3, seL4_Word arg4)
117{
118    *(char *)0x42 = 'c';
119    return 0;
120}
121
122static int test_xn(env_t env, seL4_ArchObjectType frame_type)
123{
124    int err;
125    /* Find the size of the frame type we want to test. */
126    int sz_bits = 0;
127    for (unsigned int i = 0; i < ARRAY_SIZE(frame_types); i++) {
128        if (frame_types[i].type == frame_type) {
129            sz_bits = frame_types[i].size_bits;
130            break;
131        }
132    }
133    test_assert(sz_bits != 0);
134
135    /* Get ourselves a frame. */
136    seL4_CPtr frame_cap = vka_alloc_frame_leaky(&env->vka, sz_bits);
137    test_assert(frame_cap != seL4_CapNull);
138
139    /* Map it in */
140    uintptr_t cookie = 0;
141    void *dest = vspace_map_pages(&env->vspace, &frame_cap, &cookie, seL4_AllRights, 1, sz_bits, 1);
142    test_assert(dest != NULL);
143
144    /* Set up a function we're going to have another thread call. Assume that
145     * the function is no more than 100 bytes long.
146     */
147    memcpy(dest, (void *)fault, 100);
148
149    /* Unify the instruction and data caches so our code is seen */
150    seL4_ARM_Page_Unify_Instruction(frame_cap, 0, BIT(sz_bits));
151
152    /* First setup a fault endpoint.
153     */
154    seL4_CPtr fault_ep = vka_alloc_endpoint_leaky(&env->vka);
155    cspacepath_t path;
156    vka_cspace_make_path(&env->vka, fault_ep, &path);
157    test_assert(fault_ep != seL4_CapNull);
158
159    /* Then setup the thread that will, itself, fault. */
160    helper_thread_t faulter;
161    create_helper_thread(env, &faulter);
162    set_helper_priority(env, &faulter, 100);
163    err = api_tcb_set_space(get_helper_tcb(&faulter),
164                            fault_ep,
165                            env->cspace_root,
166                            api_make_guard_skip_word(seL4_WordBits - env->cspace_size_bits),
167                            env->page_directory, seL4_NilData);
168    start_helper(env, &faulter, dest, 0, 0, 0, 0);
169
170    /* Now a fault handler that will catch and diagnose its fault. */
171    helper_thread_t handler;
172    create_helper_thread(env, &handler);
173    set_helper_priority(env, &handler, 100);
174    start_helper(env, &handler, handle, fault_ep, get_helper_reply(&faulter), 0, 0);
175
176    /* Wait for the fault to happen */
177    void *res = (void *)(seL4_Word)wait_for_helper(&handler);
178
179    test_assert(res == (void *)0x42);
180
181    cleanup_helper(env, &handler);
182    cleanup_helper(env, &faulter);
183
184    /* Now let's remap the page with XN set and confirm that we can't execute
185     * in it any more.
186     */
187    err = seL4_ARM_Page_Map(frame_cap, env->page_directory, (seL4_Word) dest, seL4_AllRights,
188                            seL4_ARM_Default_VMAttributes | seL4_ARM_ExecuteNever);
189    test_error_eq(err, 0);
190
191    /* The page should still contain our code from before. */
192    test_assert(!memcmp(dest, (void *)fault, 100));
193
194    /* We need to reallocate a fault EP because the thread cleanup above
195     * inadvertently destroys the EP we were using.
196     */
197    fault_ep = vka_alloc_endpoint_leaky(&env->vka);
198    test_assert(fault_ep != seL4_CapNull);
199
200    /* Recreate our two threads. */
201    create_helper_thread(env, &faulter);
202    set_helper_priority(env, &faulter, 100);
203    err = api_tcb_configure(get_helper_tcb(&faulter),
204                            fault_ep, seL4_CapNull,
205                            get_helper_sched_context(&faulter),
206                            env->cspace_root,
207                            api_make_guard_skip_word(seL4_WordBits - env->cspace_size_bits),
208                            env->page_directory, seL4_NilData,
209                            faulter.thread.ipc_buffer_addr,
210                            faulter.thread.ipc_buffer);
211    start_helper(env, &faulter, dest, 0, 0, 0, 0);
212    create_helper_thread(env, &handler);
213    set_helper_priority(env, &handler, 100);
214    start_helper(env, &handler, handle, fault_ep, get_helper_reply(&faulter), 0, 0);
215
216    /* Wait for the fault to happen */
217    res = (void *)(seL4_Word)wait_for_helper(&handler);
218
219    /* Confirm that, this time, we faulted at the start of the XN-mapped page. */
220    test_assert(res == (void *)dest);
221
222    /* Resource tear down. */
223    cleanup_helper(env, &handler);
224    cleanup_helper(env, &faulter);
225
226    return sel4test_get_result();
227}
228
229static int test_xn_small_frame(env_t env)
230{
231    return test_xn(env, seL4_ARM_SmallPageObject);
232}
233DEFINE_TEST(FRAMEXN0001, "Test that we can map a small frame XN", test_xn_small_frame, config_set(CONFIG_ARCH_ARM))
234
235static int test_xn_large_frame(env_t env)
236{
237    return test_xn(env, seL4_ARM_LargePageObject);
238}
239DEFINE_TEST(FRAMEXN0002, "Test that we can map a large frame XN", test_xn_large_frame, config_set(CONFIG_ARCH_ARM))
240#endif /* CONFIG_ARCH_ARM */
241
242static int test_device_frame_ipcbuf(env_t env)
243{
244    cspacepath_t path;
245    cspacepath_t frame_path;
246    int error;
247    error = vka_cspace_alloc_path(&env->vka, &path);
248    vka_cspace_make_path(&env->vka, env->device_frame, &frame_path);
249    vka_cnode_copy(&path, &frame_path, seL4_AllRights);
250    test_error_eq(error, 0);
251
252    helper_thread_t other;
253    create_helper_thread(env, &other);
254    /* Try and create a thread with a device frame as its IPC buffer */
255    error = api_tcb_configure(get_helper_tcb(&other),
256                              0, seL4_CapNull,
257                              get_helper_sched_context(&other),
258                              env->cspace_root,
259                              api_make_guard_skip_word(seL4_WordBits - env->cspace_size_bits),
260                              env->page_directory, seL4_NilData,
261                              get_helper_ipc_buffer_addr(&other),
262                              path.capPtr);
263    test_neq(error, 0);
264    cleanup_helper(env, &other);
265
266    return sel4test_get_result();
267}
268DEFINE_TEST(FRAMEDIPC0001, "Test that we cannot create a thread with an IPC buffer that is a frame",
269            test_device_frame_ipcbuf, !config_set(CONFIG_PLAT_SPIKE))
270
271static int wait_func(seL4_Word ep)
272{
273    seL4_Send(ep, seL4_MessageInfo_new(0, 0, 0, 0));
274    seL4_Send(ep, seL4_MessageInfo_new(0, 0, 0, 0));
275    return 0;
276}
277
278static int test_switch_device_frame_ipcbuf(env_t env)
279{
280    cspacepath_t path;
281    cspacepath_t frame_path;
282    int error;
283    seL4_CPtr ep;
284    error = vka_cspace_alloc_path(&env->vka, &path);
285    vka_cspace_make_path(&env->vka, env->device_frame, &frame_path);
286    vka_cnode_copy(&path, &frame_path, seL4_AllRights);
287    test_error_eq(error, 0);
288
289    ep = vka_alloc_endpoint_leaky(&env->vka);
290    test_assert(ep != seL4_CapNull);
291
292    helper_thread_t other;
293    create_helper_thread(env, &other);
294    /* start the thread and make sure it works */
295    start_helper(env, &other, (helper_fn_t)wait_func, (seL4_Word)ep, 0, 0, 0);
296    seL4_Wait(ep, NULL);
297    /* now switch its IPC buffer, which should fail */
298    error = seL4_TCB_SetIPCBuffer(get_helper_tcb(&other), get_helper_ipc_buffer_addr(&other), path.capPtr);
299    test_neq(error, 0);
300    /* thread should still be working */
301    seL4_Wait(ep, NULL);
302    /* all done */
303    wait_for_helper(&other);
304    cleanup_helper(env, &other);
305    return sel4test_get_result();
306}
307DEFINE_TEST(FRAMEDIPC0002, "Test that we cannot switch a threads IPC buffer to a device frame",
308            test_switch_device_frame_ipcbuf, !config_set(CONFIG_PLAT_SPIKE))
309
310static int touch_data_fault(seL4_Word data, seL4_Word fault_ep, seL4_Word arg3, seL4_Word arg4)
311{
312    *(volatile int *)data = 42;
313    /* if we got here we should wake the fault handler up with an error so the test doesn't hang forever.
314     * we do that by generating a different fault */
315    utils_undefined_instruction();
316    return sel4test_get_result();
317}
318
319static int test_unmap_on_delete(env_t env)
320{
321    int err;
322    /* Get ourselves a frame. */
323    cspacepath_t frame_path;
324    seL4_CPtr frame_cap = vka_alloc_frame_leaky(&env->vka, seL4_PageBits);
325    test_assert(frame_cap != seL4_CapNull);
326    vka_cspace_make_path(&env->vka, frame_cap, &frame_path);
327
328    /* create a copy of the frame cap */
329    cspacepath_t frame_copy;
330    err = vka_cspace_alloc_path(&env->vka, &frame_copy);
331    test_error_eq(err, seL4_NoError);
332    vka_cnode_copy(&frame_copy, &frame_path, seL4_AllRights);
333
334    /* Map it in */
335    uintptr_t cookie = 0;
336    void *dest = vspace_map_pages(&env->vspace, &frame_copy.capPtr, &cookie, seL4_AllRights, 1, seL4_PageBits, 1);
337    test_assert(dest != NULL);
338
339    /* verify we can access it */
340    *(volatile int *)dest = 0;
341
342    /* now delete the copy of the frame we mapped in */
343    vka_cnode_delete(&frame_copy);
344
345    /* Setup a fault endpoint. */
346    seL4_CPtr fault_ep = vka_alloc_endpoint_leaky(&env->vka);
347    cspacepath_t path;
348    vka_cspace_make_path(&env->vka, fault_ep, &path);
349    test_assert(fault_ep != seL4_CapNull);
350
351    /* Then setup the thread that will fault. */
352    helper_thread_t faulter;
353    create_helper_thread(env, &faulter);
354    set_helper_priority(env, &faulter, 100);
355    err = api_tcb_set_space(get_helper_tcb(&faulter),
356                            fault_ep,
357                            env->cspace_root,
358                            api_make_guard_skip_word(seL4_WordBits - env->cspace_size_bits),
359                            env->page_directory, seL4_NilData);
360    start_helper(env, &faulter, touch_data_fault, (seL4_Word)dest, 0, 0, 0);
361
362    /* Now a fault handler that will catch and diagnose its fault. */
363    helper_thread_t handler;
364    create_helper_thread(env, &handler);
365    set_helper_priority(env, &handler, 100);
366    start_helper(env, &handler, handle, fault_ep, get_helper_reply(&faulter), 0, 0);
367
368    /* Wait for the fault to happen */
369    void *res = (void *)(seL4_Word)wait_for_helper(&handler);
370
371    /* check that we got a fault as expected */
372    test_eq((uintptr_t)res, (uintptr_t)dest);
373
374    cleanup_helper(env, &handler);
375    cleanup_helper(env, &faulter);
376
377    return sel4test_get_result();
378}
379DEFINE_TEST(FRAMEDIPC0003, "Test that deleting a frame cap unmaps the frame", test_unmap_on_delete, true)
380