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
ret
Link: https://lore.kernel.org/all/150d8ca8-2c66-60d1-f9fc-8e6279824e94@cs.kuleuven.be/
Link: https://lore.kernel.org/all/5c22de5a-4b3b-1f38-9771-409b4ec7f96d@cs.kuleuven.be/#r
Signed-off-by: Jo Van Bulck <jo.vanbulck@cs.kuleuven.be>
Reviewed-by: Jarkko Sakkinen <jarkko@kernel.org>
---
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
On Thu, 2023-08-31 at 15:41 +0200, Jo Van Bulck wrote: > 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 call *%rax ? > ret > > Link: https://lore.kernel.org/all/150d8ca8-2c66-60d1-f9fc-8e6279824e94@cs.kuleuven.be/ > Link: https://lore.kernel.org/all/5c22de5a-4b3b-1f38-9771-409b4ec7f96d@cs.kuleuven.be/#r > Signed-off-by: Jo Van Bulck <jo.vanbulck@cs.kuleuven.be> > Reviewed-by: Jarkko Sakkinen <jarkko@kernel.org> Acked-by: Kai Huang <kai.huang@intel.com>
On 01.09.23 01:13, Huang, Kai wrote: >> encl_body: >> /* snipped */ >> lea encl_op_array(%rip), %rcx >> lea __encl_base(%rip), %rax >> add (%rcx,%rdx,8),%rax >> jmp *%rax > > call *%rax > > ? > >> ret Good catch, but this is indeed the code as generated with gcc -Os (for readability). It seems the compiler choose a JMP over a CALL (as the stack is untouched and the callee can immediately return to the caller). Somehow, the compiler still emits a RET after the JMP (which is not supposed to return here) though. I agree this is unnecessarily confusing and can simply remove the RET from the commit message. Best, Jo
On Fri, 2023-09-01 at 01:26 +0200, Jo Van Bulck wrote: > On 01.09.23 01:13, Huang, Kai wrote: > > > encl_body: > > > /* snipped */ > > > lea encl_op_array(%rip), %rcx > > > lea __encl_base(%rip), %rax > > > add (%rcx,%rdx,8),%rax > > > jmp *%rax > > > > call *%rax > > > > ? > > > > > ret > > Good catch, but this is indeed the code as generated with gcc -Os (for > readability). It seems the compiler choose a JMP over a CALL (as the > stack is untouched and the callee can immediately return to the caller). Ah, OK. I thought it's a typo, but obviously the compiler is smarter. :-) > > Somehow, the compiler still emits a RET after the JMP (which is not > supposed to return here) though. I agree this is unnecessarily confusing > and can simply remove the RET from the commit message. The RET is for encl_body itself I suppose. > > Best, > Jo
© 2016 - 2025 Red Hat, Inc.