Static-pie binaries normally include a startup routine to perform any ELF
relocations from .rela.dyn. Since the enclave loading process is different
and glibc is not included, do the necessary relocation for encl_op_array
entries manually at runtime relative to the enclave base to ensure correct
function pointers.

When keeping encl_op_array as a local variable on the stack, gcc without
optimizations generates code that explicitly gets the right function
addresses and stores them to create the array on the stack:

encl_body:
    /* snipped */
    lea    do_encl_op_put_to_buf(%rip), %rax
    mov    %rax, -0x50(%rbp)
    lea    do_encl_op_get_from_buf(%rip), %rax
    mov    %rax,-0x48(%rbp)
    lea    do_encl_op_put_to_addr(%rip), %rax
    /* snipped */

However, gcc -Os or clang generate more efficient code that initializes
encl_op_array by copying a "prepared copy" containing the absolute
addresses of the functions (i.e., relative to the image base starting from
0) generated by the compiler/linker:

encl_body:
    /* snipped */
    lea    prepared_copy(%rip), %rsi
    lea    -0x48(%rsp), %rdi
    mov    $0x10,%ecx
    rep movsl %ds:(%rsi),%es:(%rdi)
    /* snipped */

When building the enclave with -static-pie, the compiler/linker includes
relocation entries for the function symbols in the "prepared copy":

Relocation section '.rela.dyn' at offset 0x4000 contains 12 entries:
  Offset          Info           Type         Symbol
/* snipped; "prepared_copy" starts at 0x6000 */
000000006000  000000000008 R_X86_64_RELATIVE  <do_encl_emodpe>
000000006008  000000000008 R_X86_64_RELATIVE  <do_encl_eaccept>
000000006010  000000000008 R_X86_64_RELATIVE  <do_encl_op_put_to_buf>
000000006018  000000000008 R_X86_64_RELATIVE  <do_encl_op_get_from_buf>
000000006020  000000000008 R_X86_64_RELATIVE  <do_encl_op_put_to_addr>
000000006028  000000000008 R_X86_64_RELATIVE  <do_encl_op_get_from_addr>
000000006030  000000000008 R_X86_64_RELATIVE  <do_encl_op_nop>
000000006038  000000000008 R_X86_64_RELATIVE  <do_encl_init_tcs_page>

Static-pie binaries normally include a glibc "_dl_relocate_static_pie"
routine that will perform these relocations as part of the startup.
However, since the enclave loading process is different and glibc is not
included, we cannot rely on these relocations to be performed. Without
relocations, the code would erroneously jump to the _absolute_ function
address loaded from the local copy.

Thus, declare "encl_op_array" as global and manually relocate the loaded
function-pointer entries relative to the enclave base at runtime. This
generates the following code:

encl_body:
    /* snipped */
    lea    encl_op_array(%rip), %rcx
    lea    __encl_base(%rip), %rax
    add    (%rcx,%rdx,8),%rax
    jmp    *%rax

Link: 
https://lore.kernel.org/all/150d8ca8-2c66-60d1-f9fc-8e6279824...@cs.kuleuven.be/
Link: 
https://lore.kernel.org/all/5c22de5a-4b3b-1f38-9771-409b4ec7f...@cs.kuleuven.be/#r
Signed-off-by: Jo Van Bulck <jo.vanbu...@cs.kuleuven.be>
Reviewed-by: Jarkko Sakkinen <jar...@kernel.org>
Acked-by: Kai Huang <kai.hu...@intel.com>
---
 tools/testing/selftests/sgx/test_encl.c | 50 +++++++++++++++++--------
 1 file changed, 35 insertions(+), 15 deletions(-)

diff --git a/tools/testing/selftests/sgx/test_encl.c 
b/tools/testing/selftests/sgx/test_encl.c
index ae791df3e5a5..649604c526e7 100644
--- a/tools/testing/selftests/sgx/test_encl.c
+++ b/tools/testing/selftests/sgx/test_encl.c
@@ -121,21 +121,41 @@ static void do_encl_op_nop(void *_op)
 
 }
 
+/*
+ * Symbol placed at the start of the enclave image by the linker script.
+ * Declare this extern symbol with visibility "hidden" to ensure the compiler
+ * does not access it through the GOT and generates position-independent
+ * addressing as __encl_base(%rip), so we can get the actual enclave base
+ * during runtime.
+ */
+extern const uint8_t __attribute__((visibility("hidden"))) __encl_base;
+
+typedef void (*encl_op_t)(void *);
+static const encl_op_t encl_op_array[ENCL_OP_MAX] = {
+       do_encl_op_put_to_buf,
+       do_encl_op_get_from_buf,
+       do_encl_op_put_to_addr,
+       do_encl_op_get_from_addr,
+       do_encl_op_nop,
+       do_encl_eaccept,
+       do_encl_emodpe,
+       do_encl_init_tcs_page,
+};
+
 void encl_body(void *rdi,  void *rsi)
 {
-       const void (*encl_op_array[ENCL_OP_MAX])(void *) = {
-               do_encl_op_put_to_buf,
-               do_encl_op_get_from_buf,
-               do_encl_op_put_to_addr,
-               do_encl_op_get_from_addr,
-               do_encl_op_nop,
-               do_encl_eaccept,
-               do_encl_emodpe,
-               do_encl_init_tcs_page,
-       };
-
-       struct encl_op_header *op = (struct encl_op_header *)rdi;
-
-       if (op->type < ENCL_OP_MAX)
-               (*encl_op_array[op->type])(op);
+       struct encl_op_header *header = (struct encl_op_header *)rdi;
+       encl_op_t op;
+
+       if (header->type >= ENCL_OP_MAX)
+               return;
+
+       /*
+        * The enclave base address needs to be added, as this call site
+        * *cannot be* made rip-relative by the compiler, or fixed up by
+        * any other possible means.
+        */
+       op = ((uint64_t)&__encl_base) + encl_op_array[header->type];
+
+       (*op)(header);
 }
-- 
2.25.1

Reply via email to