]> xenbits.xensource.com Git - unikraft/unikraft.git/commitdiff
lib/ukreloc: Implement a `struct uk_reloc` based self relocator
authorSergiu Moga <sergiu.moga@protonmail.com>
Wed, 22 Mar 2023 18:51:57 +0000 (20:51 +0200)
committerUnikraft <monkey@unikraft.io>
Fri, 11 Aug 2023 08:11:27 +0000 (08:11 +0000)
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 <sergiu.moga@protonmail.com>
Reviewed-by: Dragos Petre <dragos.petre27@gmail.com>
Reviewed-by: Michalis Pappas <michalis@unikraft.io>
Reviewed-by: Razvan Deaconescu <razvand@unikraft.io>
Approved-by: Razvan Deaconescu <razvand@unikraft.io>
Tested-by: Unikraft CI <monkey@unikraft.io>
GitHub-Closes: #772

lib/ukreloc/Makefile.uk
lib/ukreloc/include/uk/reloc.h
lib/ukreloc/reloc.c [new file with mode: 0644]

index af44e27e787c7525d31eff2ba31ac24d89bcfbde..1267cd27e8e062dc1a6395eed6b0c9fb468e62ea 100644 (file)
@@ -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
index 1adc249df7fb59126ef7016d3ae64833864eb4f6..eab37222bf9eaea2fd5d14b32e1846a0020847d4 100644 (file)
 #endif /* !CONFIG_LIBUKRELOC */
 .endm
 
+#if !CONFIG_LIBUKRELOC
+.align 4
+do_uk_reloc:
+       ret
+#endif /* !CONFIG_LIBUKRELOC */
+
 #else  /* __ASSEMBLY__ */
 
 #include <uk/arch/types.h>
@@ -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 (file)
index 0000000..bb5e0d1
--- /dev/null
@@ -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 <stddef.h>
+#include <uk/plat/bootstrap.h>
+#include <uk/plat/common/sections.h>
+#include <uk/plat/memory.h>
+#include <uk/reloc.h>
+
+/* `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 &lt_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;
+}