From: Sergiu Moga Date: Wed, 22 Mar 2023 18:51:57 +0000 (+0200) Subject: lib/ukreloc: Implement a `struct uk_reloc` based self relocator X-Git-Tag: RELEASE-0.14.0~155 X-Git-Url: http://xenbits.xensource.com/gitweb?a=commitdiff_plain;h=2850403b9bf36d189bed4a03006b2cee670d632a;p=unikraft%2Funikraft.git lib/ukreloc: Implement a `struct uk_reloc` based self relocator Add `do_uk_reloc` a method that parses the `.uk_reloc` section and applies the relocations accordingly. As arguments, this method receives a base physical address `r_paddr` for relocations that employ the `UKRELOC_FLAGS_PHYS_REL` flag and a base virtual address `r_vaddr` for those that do not want to be relocated against a physical address, but rather by a given virtual address (e.g. can be used with KASLR). In ARM's case, the `-fPIC`/`-fPIE`, with the help of `adrp`, makes the code reference the page-aligned address of the section and the offset this symbol has in this section where the relocations are to be applied to and thus the desired relocated value is obtained. Thus, unlike `x86` that simply does a `%rip` relative access, ARM ends up dereferencing that in-section offset when trying to get the runtime value of `__BASE_ADDR` which creates a chicken-egg problem: we apply the relocations based on the current base address, but in order to find the current base address we need to relocate the value that is placed at the address of where the value of the base address is supposed to be. We solve this with the help of the `get_rt_addr()` function, which forces an `adrp`, `add :lo12:` assembly sequence, which leads to achieving something similar to x86's `%rip` relative addressing. A case that may explain the need for `r_paddr` and `r_vaddr` would be a situation where the previous program loader would place us at a random physical address (r_paddr) and we then want to apply our own virtual mappings for KASLR (r_vaddr), over the already applied virtual mappings, if any. Furthermore, right before applying relocations, the initial, added through `mkbootinfo.py`, memory region descriptors are also relocated. This is done by subtracting the initial link time base address, that has been statically resolved by the linker in `lt_baddr`, and adding the runtime base address. Note: `lt_baddr` will generate a relocation so it will be overwritten by the relocation loop. We want to keep it in case someone may want to run the relocator multiple times for the same image. So, instead of slowing down the relocator by checking each relocation against `lt_baddr`'s address so that we do not overwrite it, simply back it up in a temporary variable and restore it after relocations are done. Signed-off-by: Sergiu Moga Reviewed-by: Dragos Petre Reviewed-by: Michalis Pappas Reviewed-by: Razvan Deaconescu Approved-by: Razvan Deaconescu Tested-by: Unikraft CI GitHub-Closes: #772 --- diff --git a/lib/ukreloc/Makefile.uk b/lib/ukreloc/Makefile.uk index af44e27e7..1267cd27e 100644 --- a/lib/ukreloc/Makefile.uk +++ b/lib/ukreloc/Makefile.uk @@ -14,3 +14,4 @@ LIBUKRELOC_CFLAGS-y += -DUK_USE_SECTION_SEGMENTS LIBUKRELOC_CXXFLAGS-y += -DUK_USE_SECTION_SEGMENTS LIBUKRELOC_SRCS-$(CONFIG_LIBUKRELOC) += $(LIBUKRELOC_BASE)/reloc.lds.S +LIBUKRELOC_SRCS-$(CONFIG_LIBUKRELOC) += $(LIBUKRELOC_BASE)/reloc.c diff --git a/lib/ukreloc/include/uk/reloc.h b/lib/ukreloc/include/uk/reloc.h index 1adc249df..eab37222b 100644 --- a/lib/ukreloc/include/uk/reloc.h +++ b/lib/ukreloc/include/uk/reloc.h @@ -88,6 +88,12 @@ #endif /* !CONFIG_LIBUKRELOC */ .endm +#if !CONFIG_LIBUKRELOC +.align 4 +do_uk_reloc: + ret +#endif /* !CONFIG_LIBUKRELOC */ + #else /* __ASSEMBLY__ */ #include @@ -157,6 +163,22 @@ apply_uk_reloc(struct uk_reloc *ur, __u64 val, void *baddr) } } +/* Relocates initial mkbootinfo.py memory regions and applies all uk_reloc + * entries in the current image's .uk_reloc section. + * + * @param r_paddr The physical address relative to which we apply a relocation, + * used if UKRELOC_FLAGS_PHYS_REL flag is set. + * If 0, the current runtime base address is used. + * @param r_vaddr The virtual address relative to which we apply a relocation. + * If 0, the current runtime base address is used. + */ +#if CONFIG_LIBUKRELOC +void do_uk_reloc(__paddr_t r_paddr, __vaddr_t r_vaddr); +#else /* CONFIG_LIBUKRELOC */ +static inline void do_uk_reloc(__paddr_t r_paddr __unused, + __vaddr_t r_vaddr __unused) { } +#endif /* !CONFIG_LIBUKRELOC */ + #endif /* !__ASSEMBLY__ */ #endif /* __UK_RELOC_H__ */ diff --git a/lib/ukreloc/reloc.c b/lib/ukreloc/reloc.c new file mode 100644 index 000000000..bb5e0d1aa --- /dev/null +++ b/lib/ukreloc/reloc.c @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* Copyright (c) 2023, Unikraft GmbH and The Unikraft Authors. + * Licensed under the BSD-3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + */ + +#include +#include +#include +#include +#include + +/* `lt_baddr` contains the link time absolute symbol value of + * `_base_addr`, while `rt_baddr` will end up, through `get_rt_addr()`, + * to contain the current, runtime, base address of the loaded image. + * This works because `lt_baddr` will make the linker generate an + * absolute 64-bit value relocation, that will be statically resolved + * anyway in the final binary. + */ +static unsigned long lt_baddr = __BASE_ADDR; +static unsigned long rt_baddr; + +/* Use `get_rt_addr()` to obtain the runtime base address */ +#if defined(__X86_64__) + +/* For x86, this is resolved to a `%rip` relative access anyway */ +static inline unsigned long get_rt_addr(unsigned long sym) +{ + return sym; +} + +#elif defined(__ARM_64__) + +static inline unsigned long get_rt_addr(unsigned long sym) +{ + __asm__ __volatile__( + "adrp x0, _base_addr\n\t" + "add x0, x0, :lo12:_base_addr\n\t" + "str x0, %0\n\t" + : "=m"(rt_baddr) + : + : "x0", "memory" + ); + + return (sym - lt_baddr) + rt_baddr; +} +#endif + +#define uk_reloc_crash(s) ukplat_halt() + +static inline struct uk_reloc_hdr *get_uk_reloc_hdr() +{ + struct uk_reloc_hdr *ur_hdr; + + ur_hdr = (struct uk_reloc_hdr *)get_rt_addr(__UKRELOC_START); + if (unlikely(!ur_hdr) || + unlikely(ur_hdr->signature != UKRELOC_SIGNATURE)) + return NULL; + + return ur_hdr; +} + +void __used do_uk_reloc(__paddr_t r_paddr, __vaddr_t r_vaddr) +{ + struct ukplat_memregion_desc *mrdp; + unsigned long bkp_lt_baddr; + struct uk_reloc_hdr *ur_hdr; + struct uk_reloc *ur; + __u64 val; + + /* Check .uk_reloc signature */ + ur_hdr = get_uk_reloc_hdr(); + if (unlikely(!ur_hdr)) + uk_reloc_crash("Invalid UKRELOC signature"); + + rt_baddr = get_rt_addr(__BASE_ADDR); + if (r_paddr == 0) + r_paddr = (__paddr_t)rt_baddr; + if (r_vaddr == 0) + r_vaddr = (__vaddr_t)rt_baddr; + + /* Since we may have been placed at a random physical address, adjust + * the initial memory region descriptors added through mkbootinfo.py + * since they contain the link-time addresses, relative to rt_baddr. + * Do this before anything else, since `lt_baddr`'s relocation has + * no been resolved yet and contains the link time address. + */ + ukplat_memregion_foreach(&mrdp, UKPLAT_MEMRT_KERNEL, 0, 0) { + mrdp->pbase -= (__paddr_t)lt_baddr; + mrdp->pbase += r_paddr; + mrdp->vbase -= (__vaddr_t)lt_baddr; + mrdp->vbase += r_vaddr; + } + + /* Back up the original link time base address. We are going to lose + * it once we apply all relocations. Instead of impacting the runtime + * performance of the relocator by doing a check for every relocation + * address to be different from <_baddr, restore it at the end, when + * the relocator has finished its job. + */ + bkp_lt_baddr = lt_baddr; + + for (ur = ur_hdr->urs; ur->r_sz; ur++) { + if (ur->flags & UKRELOC_FLAGS_PHYS_REL) + val = (__u64)r_paddr + ur->r_addr; + else + val = (__u64)r_vaddr + ur->r_addr; + + apply_uk_reloc(ur, val, (void *)rt_baddr); + } + + /* Restore link time base address previously relocated to contain the + * runtime base address. + */ + lt_baddr = bkp_lt_baddr; +}