1/*
2 * Copyright 2019, Data61, CSIRO (ABN 41 687 119 230)
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10
11#include <elf/elf.h>
12#include <sel4utils/mapping.h>
13#include <vka/capops.h>
14#include <cpio/cpio.h>
15
16#include <sel4vmmplatsupport/guest_image.h>
17
18#include <sel4vm/guest_vm.h>
19#include <sel4vm/guest_memory.h>
20#include <sel4vm/guest_ram.h>
21
22typedef struct boot_guest_cookie {
23    vm_t *vm;
24    FILE *file;
25} boot_guest_cookie_t;
26
27/* Reads the elf header and elf program headers from a file when given a sufficiently
28 * large memory buffer
29 */
30static int read_elf_headers(void *buf, vm_t *vm, FILE *file, size_t buf_size, elf_t *elf)
31{
32    size_t result;
33    if (buf_size < sizeof(Elf32_Ehdr)) {
34        return -1;
35    }
36    fseek(file, 0, SEEK_SET);
37    result = fread(buf, buf_size, 1, file);
38    if (result != 1) {
39        return -1;
40    }
41
42    return elf_newFile_maybe_unsafe(buf, buf_size, true, false, elf);
43}
44
45static int guest_elf_write_address(vm_t *vm, uintptr_t paddr, void *vaddr, size_t size, size_t offset, void *cookie)
46{
47    memcpy(vaddr, cookie + offset, size);
48    return 0;
49}
50
51static int guest_elf_read_address(vm_t *vm, uintptr_t paddr, void *vaddr, size_t size, size_t offset, void *cookie)
52{
53    memcpy(cookie + offset, vaddr, size);
54    return 0;
55}
56
57int guest_elf_relocate(vm_t *vm, const char *relocs_filename, guest_kernel_image_t *image)
58{
59    int delta = image->kernel_image_arch.relocation_offset;
60    if (delta == 0) {
61        /* No relocation needed. */
62        return 0;
63    }
64
65    uintptr_t load_addr = image->kernel_image_arch.link_paddr;
66    ZF_LOGI("plat: relocating guest kernel from 0x%x --> 0x%x", (unsigned int)load_addr,
67            (unsigned int)(load_addr + delta));
68
69    /* Open the relocs file. */
70    ZF_LOGI("plat: opening relocs file %s", relocs_filename);
71
72    size_t relocs_size = 0;
73    FILE *file = fopen(relocs_filename, "r");
74    if (!file) {
75        ZF_LOGE("ERROR: Guest OS kernel relocation is required, but corresponding"
76                "%s was not found. This is most likely due to a Makefile"
77                "error, or configuration error.\n", relocs_filename);
78        return -1;
79    }
80    fseek(file, 0, SEEK_END);
81    relocs_size = ftell(file);
82    fseek(file, 0, SEEK_SET);
83
84    /* The relocs file is the same relocs file format used by the Linux kernel decompressor to
85     * relocate the Linux kernel:
86     *
87     *     0 - zero terminator for 64 bit relocations
88     *     64 bit relocation repeated
89     *     0 - zero terminator for 32 bit relocations
90     *     32 bit relocation repeated
91     *     <EOF>
92     *
93     * So we work backwards from the end of the file, and modify the guest kernel OS binary.
94     * We only support 32-bit relocations, and ignore the 64-bit data.
95     *
96     * src: Linux kernel 3.5.3 arch/x86/boot/compressed/misc.c
97     */
98    uint32_t last_relocated_vaddr = 0xFFFFFFFF;
99    uint32_t num_relocations = relocs_size / sizeof(uint32_t) - 1;
100    for (int i = 0; ; i++) {
101        uint32_t vaddr;
102        /* Get the next relocation from the relocs file. */
103        uint32_t offset = relocs_size - (sizeof(uint32_t) * (i + 1));
104        fseek(file, offset, SEEK_SET);
105        size_t result = fread(&vaddr, sizeof(uint32_t), 1, file);
106        ZF_LOGF_IF(result != 1, "Read failed unexpectedly");
107        if (!vaddr) {
108            break;
109        }
110        assert(i * sizeof(uint32_t) < relocs_size);
111        last_relocated_vaddr = vaddr;
112
113        /* Calculate the corresponding guest-physical address at which we have already
114           allocated and mapped the ELF contents into. */
115        assert(vaddr >= (uint32_t)image->kernel_image_arch.link_vaddr);
116        uintptr_t guest_paddr = (uintptr_t)vaddr - (uintptr_t)image->kernel_image_arch.link_vaddr +
117                                (uintptr_t)(load_addr + delta);
118
119        /* Perform the relocation. */
120        ZF_LOGI("   reloc vaddr 0x%x guest_addr 0x%x", (unsigned int)vaddr, (unsigned int)guest_paddr);
121        uint32_t addr;
122        vm_ram_touch(vm, guest_paddr, sizeof(int),
123                     guest_elf_read_address, &addr);
124        addr += delta;
125        vm_ram_touch(vm, guest_paddr, sizeof(int),
126                     guest_elf_write_address, &addr);
127
128        if (i && i % 50000 == 0) {
129            ZF_LOGE("    %u relocs done.", i);
130        }
131    }
132    ZF_LOGI("plat: last relocated addr was %d", last_relocated_vaddr);
133    ZF_LOGI("plat: %d kernel relocations completed.", num_relocations);
134    (void) last_relocated_vaddr;
135
136    if (num_relocations == 0) {
137        ZF_LOGE("Relocation required, but Kernel has not been build with CONFIG_RELOCATABLE.");
138        return -1;
139    }
140
141    fclose(file);
142    return 0;
143}
144
145/* TODO: Refactor and stop rewriting fucking elf loading code */
146static int load_guest_segment(vm_t *vm, seL4_Word source_offset,
147                              seL4_Word dest_addr, unsigned int segment_size, unsigned int file_size, FILE *file)
148{
149
150    int ret;
151    unsigned int page_size = seL4_PageBits;
152    assert(file_size <= segment_size);
153
154    /* Allocate a cslot for duplicating frame caps */
155    cspacepath_t dup_slot;
156    ret = vka_cspace_alloc_path(vm->vka, &dup_slot);
157    if (ret) {
158        ZF_LOGE("Failed to allocate slot");
159        return ret;
160    }
161
162    size_t current = 0;
163    size_t remain = file_size;
164    while (current < segment_size) {
165        /* Retrieve the mapping */
166        seL4_CPtr cap;
167        cap = vspace_get_cap(&vm->mem.vm_vspace, (void *)dest_addr);
168        if (!cap) {
169            ZF_LOGE("Failed to find frame cap while loading elf segment at %p", (void *)dest_addr);
170            return -1;
171        }
172        cspacepath_t cap_path;
173        vka_cspace_make_path(vm->vka, cap, &cap_path);
174
175        /* Copy cap and map into our vspace */
176        vka_cnode_copy(&dup_slot, &cap_path, seL4_AllRights);
177        void *map_vaddr = vspace_map_pages(&vm->mem.vmm_vspace, &dup_slot.capPtr, NULL, seL4_AllRights, 1, page_size, 1);
178        if (!map_vaddr) {
179            ZF_LOGE("Failed to map page into host vspace");
180            return -1;
181        }
182
183        /* Copy the contents of page from ELF into the mapped frame. */
184        size_t offset = dest_addr & ((BIT(page_size)) - 1);
185
186        void *copy_vaddr = map_vaddr + offset;
187        size_t copy_len = (BIT(page_size)) - offset;
188
189        if (remain > 0) {
190            if (copy_len > remain) {
191                /* Don't copy past end of data. */
192                copy_len = remain;
193            }
194
195            ZF_LOGI("load page src %zu dest %p remain %zu offset %zu copy vaddr %p "
196                    "copy len %zu\n", source_offset, (void *)dest_addr, remain, offset, copy_vaddr, copy_len);
197
198            fseek(file, source_offset, SEEK_SET);
199            size_t result = fread(copy_vaddr, copy_len, 1, file);
200            ZF_LOGF_IF(result != 1, "Read failed unexpectedly");
201            source_offset += copy_len;
202            remain -= copy_len;
203        } else {
204            memset(copy_vaddr, 0, copy_len);
205        }
206
207        dest_addr += copy_len;
208
209        current += copy_len;
210
211        /* Unamp the page and delete the temporary cap */
212        vspace_unmap_pages(&vm->mem.vmm_vspace, map_vaddr, 1, page_size, NULL);
213        vka_cnode_delete(&dup_slot);
214    }
215
216    return 0;
217}
218
219static int load_guest_elf(vm_t *vm, const char *image_name, uintptr_t load_address, size_t alignment,
220                          guest_kernel_image_t *guest_image)
221{
222    elf_t kernel_elf;
223    char elf_file[256];
224    int ret;
225    FILE *file = fopen(image_name, "r");
226    if (!file) {
227        ZF_LOGE("Guest kernel elf \"%s\" not found.", image_name);
228        return -1;
229    }
230
231    ret = read_elf_headers(elf_file, vm, file, sizeof(elf_file), &kernel_elf);
232    if (ret < 0) {
233        ZF_LOGE("Guest elf \"%s\" invalid.", image_name);
234        return -1;
235    }
236
237    unsigned int n_headers = elf_getNumProgramHeaders(&kernel_elf);
238
239    /* Round up by the alignment. We just hope its still in the memory region.
240     * if it isn't we will just fail when we try and get the frame */
241    uintptr_t load_addr = ROUND_UP(load_address, alignment);
242    /* Calculate relocation offset. */
243    uintptr_t guest_kernel_addr = 0xFFFFFFFF;
244    uintptr_t guest_kernel_vaddr = 0xFFFFFFFF;
245    for (int i = 0; i < n_headers; i++) {
246        if (elf_getProgramHeaderType(&kernel_elf, i) != PT_LOAD) {
247            continue;
248        }
249        uint32_t addr = elf_getProgramHeaderPaddr(&kernel_elf, i);
250        if (addr < guest_kernel_addr) {
251            guest_kernel_addr = addr;
252        }
253        uint32_t vaddr = elf_getProgramHeaderVaddr(&kernel_elf, i);
254        if (vaddr < guest_kernel_vaddr) {
255            guest_kernel_vaddr = vaddr;
256        }
257    }
258
259    int guest_relocation_offset = (int)((int64_t)load_addr - (int64_t)guest_kernel_addr);
260
261    ZF_LOGI("Guest kernel is compiled to be located at paddr 0x%x vaddr 0x%x",
262            (unsigned int)guest_kernel_addr, (unsigned int)guest_kernel_vaddr);
263    ZF_LOGI("Guest kernel allocated 1:1 start is at paddr = 0x%x", (unsigned int)load_addr);
264    ZF_LOGI("Therefore relocation offset is %d (%s0x%x)",
265            guest_relocation_offset,
266            guest_relocation_offset < 0 ? "-" : "",
267            abs(guest_relocation_offset));
268
269    for (int i = 0; i < n_headers; i++) {
270        seL4_Word source_offset, dest_addr;
271        unsigned int file_size, segment_size;
272
273        /* Skip unloadable program headers. */
274        if (elf_getProgramHeaderType(&kernel_elf, i) != PT_LOAD) {
275            continue;
276        }
277
278        /* Fetch information about this segment. */
279        source_offset = elf_getProgramHeaderOffset(&kernel_elf, i);
280        file_size = elf_getProgramHeaderFileSize(&kernel_elf, i);
281        segment_size = elf_getProgramHeaderMemorySize(&kernel_elf, i);
282
283        dest_addr = elf_getProgramHeaderPaddr(&kernel_elf, i);
284        dest_addr += guest_relocation_offset;
285
286        if (!segment_size) {
287            /* Zero sized segment, ignore. */
288            continue;
289        }
290
291        /* Load this ELf segment. */
292        ret = load_guest_segment(vm, source_offset, dest_addr, segment_size, file_size, file);
293        if (ret) {
294            return ret;
295        }
296
297        /* Record it as allocated */
298        vm_ram_mark_allocated(vm, dest_addr, segment_size);
299    }
300
301    /* Record the entry point. */
302    guest_image->kernel_image_arch.entry = elf_getEntryPoint(&kernel_elf);
303    guest_image->kernel_image_arch.entry += guest_relocation_offset;
304
305    /* Remember where we started loading the kernel to fix up relocations in future */
306    guest_image->kernel_image.load_paddr = load_addr;
307    guest_image->kernel_image_arch.link_paddr = guest_kernel_addr;
308    guest_image->kernel_image_arch.link_vaddr = guest_kernel_vaddr;
309    guest_image->kernel_image_arch.relocation_offset = guest_relocation_offset;
310    guest_image->kernel_image.alignment = alignment;
311
312    fclose(file);
313
314    return 0;
315}
316
317int vm_load_guest_kernel(vm_t *vm, const char *kernel_name, uintptr_t load_address, size_t alignment,
318                         guest_kernel_image_t *guest_kernel_image)
319{
320    int err;
321    err = load_guest_elf(vm, kernel_name, load_address, alignment, guest_kernel_image);
322    if (err) {
323        ZF_LOGE("Failed to load guest elf");
324        return err;
325    }
326    if (guest_kernel_image->kernel_image_arch.is_reloc_enabled) {
327        err = guest_elf_relocate(vm, guest_kernel_image->kernel_image_arch.relocs_file, guest_kernel_image);
328        if (err) {
329            ZF_LOGE("Failed to relocation guest kernel elf");
330        }
331    }
332    return err;
333}
334
335static int load_module_continued(vm_t *vm, uintptr_t paddr, void *addr, size_t size, size_t offset, void *cookie)
336{
337    boot_guest_cookie_t *pass = (boot_guest_cookie_t *) cookie;
338    fseek(pass->file, offset, SEEK_SET);
339    size_t result = fread(addr, size, 1, pass->file);
340    ZF_LOGF_IF(result != 1, "Read failed unexpectedly");
341
342    return 0;
343}
344
345int vm_load_guest_module(vm_t *vm, const char *module_name, uintptr_t load_address, size_t alignment,
346                         guest_image_t *guest_image)
347{
348    ZF_LOGI("Loading module \"%s\" at 0x%x\n", module_name, (unsigned int)load_address);
349
350    size_t module_size = 0;
351    FILE *file = fopen(module_name, "r");
352    if (!file) {
353        ZF_LOGE("Module \"%s\" not found.", module_name);
354        return -1;
355    }
356    fseek(file, 0, SEEK_END);
357    module_size = ftell(file);
358    fseek(file, 0, SEEK_SET);
359    if (!module_size) {
360        ZF_LOGE("Module has zero size. This is probably not what you want.");
361        return -1;
362    }
363
364    vm_ram_mark_allocated(vm, load_address, module_size);
365    boot_guest_cookie_t pass = { .vm = vm, .file = file};
366    vm_ram_touch(vm, load_address, module_size, load_module_continued, &pass);
367
368    fclose(file);
369
370    guest_image->load_paddr = load_address;
371    guest_image->size = module_size;
372
373    return 0;
374}
375