diff --git a/Documentation/devel/performance.rst b/Documentation/devel/performance.rst index 1c7e4f6f00..601f94a3c8 100644 --- a/Documentation/devel/performance.rst +++ b/Documentation/devel/performance.rst @@ -386,10 +386,10 @@ workloads. The manifest options include: - ``libos.check_invalid_pointers = false`` -- disable checks of invalid pointers on system call invocations. Most real-world applications never provide invalid arguments to system calls, so there is no need in additional checks. -- ``sgx.preheat_enclave = true`` -- pre-fault all enclave pages during enclave - initialization. This shifts the overhead of page faults on non-present enclave - pages from runtime to enclave startup time. Using this option makes sense only - if the whole enclave memory fits into :term:`EPC`. +- ``sgx.preheat_enclave_sz = "1"`` -- pre-fault all enclave pages during enclave + initialization when ``sgx.edmm_enable_heap = false``. This shifts the overhead + of page faults on non-present enclave pages from runtime to enclave startup time. + Using this option makes sense only if the whole enclave memory fits into :term:`EPC`. If your application periodically fails and complains about seemingly irrelevant things, it may be due to insufficient enclave memory. Please try to increase diff --git a/Documentation/manifest-syntax.rst b/Documentation/manifest-syntax.rst index b35eaf67ca..d01f1e46ae 100644 --- a/Documentation/manifest-syntax.rst +++ b/Documentation/manifest-syntax.rst @@ -420,6 +420,59 @@ more CPU cores and burning more CPU cycles. For example, a single-threaded Redis instance on Linux becomes 5-threaded on Graphene with Exitless. Thus, Exitless may negatively impact throughput but may improve latency. +EDMM dynamic heap (Experimental) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sgx.edmm_enable_heap = [true|false] + (Default: false) + +This syntax enables EDMM dynamic heap feature available as part of Intel +":term:`SGX2`" capable hardware. When enabled, EPC pages are not added when +creating the enclave but allocated dynamically using EACCEPT when Graphene +requests more heap memory. This triggers a page fault (#PF) which is handled by +the Intel SGX driver (legacy driver) by EAUGing the page and returning the +control back to the enclave. The enclave now continues from the same EACCEPT +instruction (but this time the instruction succeeds). + +One of the key advantages of EDMM is that the enclave ends up using only the +EPC pages that it requires and the user does not need to tailor the enclave +size precisely for each workload. EDMM does help to reduce the loading time of +a large enclave application but can impact the runtime as there is a penalty +for additional asynchronous enclave exits (AEXs) caused by #PFs. + +EDMM Batch Allocation (Experimental) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sgx.edmm_batch_allocation = [true|false] + (Default: false) + +SGX driver allocates EPC pages dynamically by faulting in pages one at a time. +This incurs a huge overhead due to enclave exit for each page. This syntax enables +use of a new IOCTL has been introduced in the SGX driver which can take the +requested range and EAUG all the pages in one shot. Enclave then EACCEPTs all +the pages requested. + +.. note :: + New SGX driver IOCTL is experimental and is not yet available as part of official + Intel SGX OOT driver release. This option is not yet ready for public usage. + +EDMM Lazy Free optimization (Experimental) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sgx.edmm_lazyfree_th = [NUM] + (Default: 0) + +This syntax specifies the **percentage** of total heap that can be freed in a lazy manner. +Until this threshold is met, graphene doesn't release any dynamically allocated memory. +This optimization helps reduce the expensive enclave entries/exits associated with dynamic +freeing of EPC pages. + Optional CPU features (AVX, AVX512, MPX, PKRU) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -562,11 +615,15 @@ Pre-heating enclave :: - sgx.preheat_enclave = [true|false] - (Default: false) + sgx.preheat_enclave_sz = ["0"|"1"|"SIZE"] + (Default: "0") + +When set to "1", this option instructs Graphene to pre-fault all heap pages during +initialization and setting it to "0" disables the feature. When ``sgx.edmm_enable_heap`` +is enabled, user can precisely set the amount of heap to preheat by setting the ``SIZE``. +For example, when size is "64M" Graphene will pre-fault top 64M of heap pages. -When enabled, this option instructs Graphene to pre-fault all heap pages during -initialization. This has a negative impact on the total run time, but shifts the +This has a negative impact on the total run time, but shifts the :term:`EPC` page faults cost to the initialization phase, which can be useful in a scenario where a server starts and receives connections / work packages only after some time. It also makes the later run time and latency much more diff --git a/LibOS/shim/test/regression/.gitignore b/LibOS/shim/test/regression/.gitignore index 5c90dba487..b60ddd52b7 100644 --- a/LibOS/shim/test/regression/.gitignore +++ b/LibOS/shim/test/regression/.gitignore @@ -19,6 +19,7 @@ /devfs /device_passthrough /double_fork +/edmm_heap_mmap /env_from_file /env_from_host /epoll_epollet diff --git a/LibOS/shim/test/regression/Makefile b/LibOS/shim/test/regression/Makefile index 707380604e..cdf795aaee 100644 --- a/LibOS/shim/test/regression/Makefile +++ b/LibOS/shim/test/regression/Makefile @@ -17,6 +17,7 @@ c_executables = \ devfs \ device_passthrough \ double_fork \ + edmm_heap_mmap \ epoll_epollet \ epoll_wait_timeout \ eventfd \ @@ -156,6 +157,9 @@ LDLIBS-attestation += ../../../../common/src/crypto/mbedtls/install/lib/libmbedc CFLAGS-fp_multithread += -pthread -fno-builtin # see comment in the test's source LDLIBS-fp_multithread += -lm +CFLAGS-edmm_heap_mmap += -pthread +LDLIBS-edmm_heap_mmap += -lm + proc_common: proc_common.o dump.o $(call cmd,cmulti) diff --git a/LibOS/shim/test/regression/edmm_heap_mmap.c b/LibOS/shim/test/regression/edmm_heap_mmap.c new file mode 100644 index 0000000000..f940708a33 --- /dev/null +++ b/LibOS/shim/test/regression/edmm_heap_mmap.c @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: LGPL-3.0-or-later */ +/* Copyright (C) 2020 Intel Corporation */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#define MAIN_THREAD_CNT 1 +#define INTERNAL_THREAD_CNT 2 +#define MANIFEST_SGX_THREAD_CNT 16 /* corresponds to sgx.thread_num in the manifest template */ + +/* barrier to synchronize between parent and children */ +pthread_barrier_t barrier; + +static pid_t mygettid(void) { + return syscall(SYS_gettid); +} + +double g_per_mmap_diff[MANIFEST_SGX_THREAD_CNT] = {0}; +double g_per_munmap_diff[MANIFEST_SGX_THREAD_CNT] = {0}; + +static void mmap_munmap_memory(int val) { + size_t mmap_length = 0x4000; + struct timeval tv1 = {0}; + struct timeval tv2 = {0}; + long long mmap_diff; + long long munmap_diff; + + for (int i = 0; i < 500; i++) { + if (gettimeofday(&tv1, NULL)) { + printf("Cannot get time 1: %m\n"); + } + void* a = mmap(NULL, mmap_length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, + -1, 0); + if (a == MAP_FAILED) { + err(EXIT_FAILURE, "mmap failed for tid=%d for size 0x%lx\n", mygettid(), + mmap_length); + } + if (gettimeofday(&tv2, NULL)) { + printf("Cannot get time 2: %m\n"); + } + + mmap_diff += ((tv2.tv_sec - tv1.tv_sec) * 1000000ll); + mmap_diff += tv2.tv_usec - tv1.tv_usec; + + memset(a, val, mmap_length); + if (gettimeofday(&tv1, NULL)) { + printf("Cannot get time 1: %m\n"); + } + int rv = munmap(a, mmap_length); + if (rv) { + err(EXIT_FAILURE, "munmap failed for tid =%d for size 0x%lx\n", mygettid(), + mmap_length); + } + if (gettimeofday(&tv2, NULL)) { + printf("Cannot get time 2: %m\n"); + } + + munmap_diff += ((tv2.tv_sec - tv1.tv_sec) * 1000000ll); + munmap_diff += tv2.tv_usec - tv1.tv_usec; + } + + int tid = mygettid(); + assert(tid); + g_per_mmap_diff[tid-1] = ((double)(mmap_diff/500))/1000; + g_per_munmap_diff[tid-1] = ((double)(munmap_diff/500))/1000; +} + +/* Run a busy loop for some iterations, so that we can verify affinity with htop manually */ +static void* dowork(void* args) { + uint32_t val = *(uint32_t*)args; + + mmap_munmap_memory(val); + + /* child waits on barrier */ + int ret = pthread_barrier_wait(&barrier); + if (ret != 0 && ret != PTHREAD_BARRIER_SERIAL_THREAD) { + errx(EXIT_FAILURE, "Child did not wait on barrier!"); + } + + return NULL; +} + +static int run(int sgx_thread_cnt) { + int ret; + long numprocs = sysconf(_SC_NPROCESSORS_ONLN); + if (numprocs < 0) { + err(EXIT_FAILURE, "Failed to retrieve the number of logical processors!"); + } + + /* If you want to run on all cores then increase sgx.thread_num in the manifest.template and + * also set MANIFEST_SGX_THREAD_CNT to the same value. + */ + numprocs = min(numprocs, (sgx_thread_cnt - (INTERNAL_THREAD_CNT + MAIN_THREAD_CNT))); + printf("NO. of threads created = %ld\n", numprocs); + + pthread_t* threads = (pthread_t*)malloc(numprocs * sizeof(pthread_t)); + if (!threads) { + errx(EXIT_FAILURE, "memory allocation failed"); + } + + /*per-thread unique values */ + int* per_thread_val = (int*)malloc(numprocs * sizeof(int)); + if (!per_thread_val) { + errx(EXIT_FAILURE, "per-thread memory allocation failed"); + } + + if (pthread_barrier_init(&barrier, NULL, numprocs + 1)) { + free(threads); + errx(EXIT_FAILURE, "pthread barrier init failed"); + } + + /* Validate parent set/get affinity for child */ + for (uint32_t i = 0; i < numprocs; i++) { + per_thread_val[i] = i + 1; + ret = pthread_create(&threads[i], NULL, dowork, (void*)&per_thread_val[i]); + if (ret != 0) { + free(threads); + errx(EXIT_FAILURE, "pthread_create failed!"); + } + } + + /* parent waits on barrier */ + ret = pthread_barrier_wait(&barrier); + if (ret != 0 && ret != PTHREAD_BARRIER_SERIAL_THREAD) { + free(threads); + errx(EXIT_FAILURE, "Parent did not wait on barrier!"); + } + + mmap_munmap_memory(0); + + for (int i = 0; i < numprocs; i++) { + ret = pthread_join(threads[i], NULL); + if (ret != 0) { + free(threads); + errx(EXIT_FAILURE, "pthread_join failed!"); + } + } + + /* Validating parent set/get affinity for children done. Free resources */ + pthread_barrier_destroy(&barrier); + free(per_thread_val); + free(threads); + + double total_mmap_diff = 0; + double total_munmap_diff = 0; + for (int i = 1; i < numprocs+1; i++) { + printf("Average mmap_time(ms): %lf, munmap_time(ms): %lf for thread %d\n", + g_per_mmap_diff[i], g_per_munmap_diff[i], i); + total_mmap_diff += g_per_mmap_diff[i]; + total_munmap_diff += g_per_munmap_diff[i]; + } + printf("Avg across all threads, mmap_time(ms): %lf, munmap_time(ms): %lf\n", + (float)(total_mmap_diff/numprocs), (float)(total_munmap_diff/numprocs) ); + + printf("===================================================================================\n"); + return 0; +} + +#define MAX_THREADS 64 +int main(int argc, const char** argv) { + + int num_threads = min(MAX_THREADS, MANIFEST_SGX_THREAD_CNT); + /*Run tests for 1, 2, 4, 8 ...threads until num_threads */ + for (int i = 1, j = 4; j < num_threads; i++) { + run(j); + j = pow(2, i) + INTERNAL_THREAD_CNT + MAIN_THREAD_CNT; + sleep(5); + } + + printf("TEST OK\n"); + return 0; +} \ No newline at end of file diff --git a/LibOS/shim/test/regression/openmp.manifest.template b/LibOS/shim/test/regression/openmp.manifest.template index 61e2201634..53bbc0901e 100644 --- a/LibOS/shim/test/regression/openmp.manifest.template +++ b/LibOS/shim/test/regression/openmp.manifest.template @@ -11,7 +11,7 @@ loader.env.LD_LIBRARY_PATH = "/lib:/usrlib" # the manifest options below are added only for testing, they have no significance for OpenMP libos.check_invalid_pointers = false sys.enable_sigterm_injection = true -sgx.preheat_enclave = true +sgx.preheat_enclave_sz = "1" fs.mount.lib.type = "chroot" fs.mount.lib.path = "/lib" diff --git a/LibOS/shim/test/regression/test_libos.py b/LibOS/shim/test/regression/test_libos.py index 33f68e0a04..bfe0af8ef9 100644 --- a/LibOS/shim/test/regression/test_libos.py +++ b/LibOS/shim/test/regression/test_libos.py @@ -451,7 +451,10 @@ def test_043_futex_wake_op(self): self.assertIn('Test successful!', stdout) - def test_050_mmap(self): + @unittest.skipIf(HAS_SGX, + 'On SGX, SIGBUS isn\'t always implemented correctly, for lack ' + 'of memory protection. For now, some of these cases won\'t work.') + def test_051_mmap(self): stdout, _ = self.run_binary(['mmap_file'], timeout=60) # Private mmap beyond file range @@ -464,14 +467,6 @@ def test_050_mmap(self): self.assertIn('mmap test 3 passed', stdout) self.assertIn('mmap test 4 passed', stdout) - # "test 5" and "test 8" are checked below, in test_051_mmap_sgx - - @unittest.skipIf(HAS_SGX, - 'On SGX, SIGBUS isn\'t always implemented correctly, for lack ' - 'of memory protection. For now, some of these cases won\'t work.') - def test_051_mmap_sgx(self): - stdout, _ = self.run_binary(['mmap_file'], timeout=60) - # SIGBUS test self.assertIn('mmap test 5 passed', stdout) self.assertIn('mmap test 8 passed', stdout) diff --git a/Pal/include/pal_internal.h b/Pal/include/pal_internal.h index 5ea01095ba..47c5c4c4f7 100644 --- a/Pal/include/pal_internal.h +++ b/Pal/include/pal_internal.h @@ -270,6 +270,8 @@ void free(void* mem); #error Unsupported compiler #endif +PAL_IDX pal_get_cur_tid(void); + int _DkInitDebugStream(const char* path); int _DkDebugLog(const void* buf, size_t size); diff --git a/Pal/src/host/Linux-SGX/Makefile b/Pal/src/host/Linux-SGX/Makefile index 89a91da268..f10985b68c 100644 --- a/Pal/src/host/Linux-SGX/Makefile +++ b/Pal/src/host/Linux-SGX/Makefile @@ -50,6 +50,7 @@ enclave-objs = \ db_sockets.o \ db_streams.o \ db_threading.o \ + edmm_pages.o \ enclave_ecalls.o \ enclave_framework.o \ enclave_ocalls.o \ diff --git a/Pal/src/host/Linux-SGX/db_main.c b/Pal/src/host/Linux-SGX/db_main.c index 15b5facc11..aac04841a8 100644 --- a/Pal/src/host/Linux-SGX/db_main.c +++ b/Pal/src/host/Linux-SGX/db_main.c @@ -585,6 +585,34 @@ noreturn void pal_linux_main(char* uptr_libpal_uri, size_t libpal_uri_len, char* g_pal_sec.in_gdb = sec_info.in_gdb; #endif + /* Extract EDMM mode */ + g_pal_sec.edmm_enable_heap = sec_info.edmm_enable_heap; + + /* Extract enclave heap preheat options */ + g_pal_sec.preheat_enclave_sz = sec_info.preheat_enclave_sz; + if (g_pal_sec.preheat_enclave_sz > 0) { + uint8_t* i; + /* Heap allocation requests are serviced starting from highest heap address when ASLR is + * disabled. So optimizing for this case by preheating from the top of heap. */ + if (g_pal_sec.edmm_enable_heap == 1) + i = (uint8_t*)g_pal_sec.heap_max - g_pal_sec.preheat_enclave_sz; + else + i = (uint8_t*)g_pal_sec.heap_min; + + log_debug("%s: preheat start = %p, end = %p\n", __func__, (void*)i, g_pal_sec.heap_max); + for (; i < (uint8_t*)g_pal_sec.heap_max; i += g_page_size) + READ_ONCE(*(size_t*)i); + } + + /* EDMM batch allocation */ + g_pal_sec.edmm_batch_alloc = sec_info.edmm_batch_alloc; + + /* Extract the mmap'd region to share addr and number of EPC pages requested with driver. */ + g_pal_sec.eaug_base = sec_info.eaug_base; + + /* Extract enclave heap lazy free threshold */ + g_pal_sec.edmm_lazyfree_th = sec_info.edmm_lazyfree_th; + /* For {p,u,g}ids we can at least do some minimal checking. */ /* ppid should be positive when interpreted as signed. It's 0 if we don't @@ -701,18 +729,6 @@ noreturn void pal_linux_main(char* uptr_libpal_uri, size_t libpal_uri_len, char* g_pal_state.raw_manifest_data = manifest_addr; g_pal_state.manifest_root = manifest_root; - bool preheat_enclave; - ret = toml_bool_in(g_pal_state.manifest_root, "sgx.preheat_enclave", /*defaultval=*/false, - &preheat_enclave); - if (ret < 0) { - log_error("Cannot parse \'sgx.preheat_enclave\' (the value must be `true` or `false`)"); - ocall_exit(1, true); - } - if (preheat_enclave) { - for (uint8_t* i = g_pal_sec.heap_min; i < (uint8_t*)g_pal_sec.heap_max; i += g_page_size) - READ_ONCE(*(size_t*)i); - } - ret = toml_sizestring_in(g_pal_state.manifest_root, "loader.pal_internal_mem_size", /*defaultval=*/0, &g_pal_internal_mem_size); if (ret < 0) { diff --git a/Pal/src/host/Linux-SGX/db_threading.c b/Pal/src/host/Linux-SGX/db_threading.c index 6c429fa215..1b385c4c68 100644 --- a/Pal/src/host/Linux-SGX/db_threading.c +++ b/Pal/src/host/Linux-SGX/db_threading.c @@ -28,6 +28,9 @@ static spinlock_t g_thread_list_lock = INIT_SPINLOCK_UNLOCKED; DEFINE_LISTP(pal_handle_thread); static LISTP_TYPE(pal_handle_thread) g_thread_list = LISTP_INIT; +/* tid 1 is assigned to the first thread; see pal_linux_main() */ +static struct atomic_int g_tid = ATOMIC_INIT(1); + struct thread_param { int (*callback)(void*); const void* param; @@ -35,6 +38,19 @@ struct thread_param { extern void* g_enclave_base; +/* This function returns the current thread TID. Note: a TID of 0 is considered invalid in PAL as + * we start TID from 1. */ +PAL_IDX pal_get_cur_tid(void) { + uint32_t tid = 0; + + struct pal_handle_thread *thread = NULL; + thread = GET_ENCLAVE_TLS(thread); + if (thread) + tid = thread->tid; + + return tid; +} + /* * We do not currently handle tid counter wrap-around, and could, in * principle, end up with two threads with the same ID. This is ok, as strict @@ -42,9 +58,7 @@ extern void* g_enclave_base; * ensure uniqueness if needed in the future */ static PAL_IDX pal_assign_tid(void) { - /* tid 1 is assigned to the first thread; see pal_linux_main() */ - static struct atomic_int tid = ATOMIC_INIT(1); - return __atomic_add_fetch(&tid.counter, 1, __ATOMIC_SEQ_CST); + return __atomic_add_fetch(&g_tid.counter, 1, __ATOMIC_SEQ_CST); } /* Initialization wrapper of a newly-created thread. This function finds a newly-created thread in @@ -150,6 +164,9 @@ void _DkThreadYieldExecution(void) { noreturn void _DkThreadExit(int* clear_child_tid) { struct pal_handle_thread* exiting_thread = GET_ENCLAVE_TLS(thread); + /* Decrement global tid counter to prevent overflow and match the number of threads present */ + __atomic_sub_fetch(&g_tid.counter, 1, __ATOMIC_SEQ_CST); + /* thread is ready to exit, must inform LibOS by erasing clear_child_tid; * note that we don't do it now (because this thread still occupies SGX * TCS slot) but during handle_thread_reset in assembly code */ diff --git a/Pal/src/host/Linux-SGX/edmm_pages.c b/Pal/src/host/Linux-SGX/edmm_pages.c new file mode 100644 index 0000000000..38f4a2a593 --- /dev/null +++ b/Pal/src/host/Linux-SGX/edmm_pages.c @@ -0,0 +1,361 @@ +#include "edmm_pages.h" + +#include +#include + +#include "api.h" +#include "list.h" +#include "pal_error.h" +#include "pal_internal.h" +#include "pal_linux.h" +#include "pal_security.h" + +extern void* g_heap_top; +extern spinlock_t g_heap_vma_lock; +static uint64_t g_pending_free_size; +uint64_t g_edmm_lazyfree_th_bytes; + +static LISTP_TYPE(edmm_heap_pool) g_edmm_heap_pool_list = LISTP_INIT; + +static struct edmm_heap_pool g_edmm_heap_pool[MAX_EDMM_HEAP_RANGE]; +static size_t g_edmm_heap_rg_cnt; +static struct edmm_heap_pool* g_edmm_heap_rg = NULL; + +/* returns uninitialized edmm heap range */ +static struct edmm_heap_pool* __alloc_heap(void) { + assert(spinlock_is_locked(&g_heap_vma_lock)); + + if (g_edmm_heap_rg) { + /* simple optimization: if there is a cached free vma object, use it */ + assert((uintptr_t)g_edmm_heap_rg >= (uintptr_t)&g_edmm_heap_pool[0]); + assert((uintptr_t)g_edmm_heap_rg <= (uintptr_t)&g_edmm_heap_pool[MAX_EDMM_HEAP_RANGE - 1]); + + struct edmm_heap_pool* ret = g_edmm_heap_rg; + g_edmm_heap_rg = NULL; + g_edmm_heap_rg_cnt++; + return ret; + } + + for (size_t i = 0; i < MAX_EDMM_HEAP_RANGE; i++) { + if (!g_edmm_heap_pool[i].addr && !g_edmm_heap_pool[i].size) { + /* found empty slot in the pool, use it */ + g_edmm_heap_rg_cnt++; + return &g_edmm_heap_pool[i]; + } + } + + return NULL; +} + +static void __free_heap(struct edmm_heap_pool* heap_rg) { + assert(spinlock_is_locked(&g_heap_vma_lock)); + assert((uintptr_t)heap_rg >= (uintptr_t)&g_edmm_heap_pool[0]); + assert((uintptr_t)heap_rg <= (uintptr_t)&g_edmm_heap_pool[MAX_EDMM_HEAP_RANGE - 1]); + + heap_rg->addr = NULL; + heap_rg->size = 0; + g_edmm_heap_rg = heap_rg; + g_edmm_heap_rg_cnt--; +} + +/* Returns size that is non overlapping with the pre-allocated heap when preheat option is true. + * 0 means entire request overlaps with the pre-allocated region. */ +size_t find_preallocated_heap_nonoverlap(void* addr, size_t size) { + if (!g_pal_sec.preheat_enclave_sz) { + return size; + } + + size_t non_overlapping_sz; + if ((char*)addr >= (char*)g_heap_top - g_pal_sec.preheat_enclave_sz) + /* Full overlap: Entire request lies in the pre-allocated region */ + non_overlapping_sz = 0; + else if ((char*)addr + size > (char*)g_heap_top - g_pal_sec.preheat_enclave_sz) + /* Partial overlap: Update size to skip the overlapped region. */ + non_overlapping_sz = (char*)g_heap_top - g_pal_sec.preheat_enclave_sz - (char*)addr; + else + /* No overlap */ + non_overlapping_sz = size; + + + return non_overlapping_sz; +} + +/* This function adds free EPC page requests to a global list and frees the EPC pages in a lazy + * manner once the amount of free EPC pages exceeds a certain threshold. Returns 0 on success and + * negative unix error code on failure. */ +int add_to_pending_free_epc(void* addr, size_t size) { + assert(spinlock_is_locked(&g_heap_vma_lock)); + + /* Allocate new entry for pending_free_epc range */ + struct edmm_heap_pool* new_pending_free = __alloc_heap(); + if (!new_pending_free) { + log_error("Adding to pending free EPC pages failed %p\n", addr); + return -PAL_ERROR_NOMEM; + } + new_pending_free->addr = addr; + new_pending_free->size = size; + + struct edmm_heap_pool* pending_free_epc; + struct edmm_heap_pool* pending_above = NULL; + LISTP_FOR_EACH_ENTRY(pending_free_epc, &g_edmm_heap_pool_list, list) { + if (pending_free_epc->addr < addr) + break; + pending_above = pending_free_epc; + } + + struct edmm_heap_pool* pending_below = NULL; + if (pending_above) { + pending_below = LISTP_NEXT_ENTRY(pending_above, &g_edmm_heap_pool_list, list); + } else { + /* no previous entry found. This is the first entry which is below [addr, addr+size) */ + pending_below = LISTP_FIRST_ENTRY(&g_edmm_heap_pool_list, struct edmm_heap_pool, list); + } + + if (pending_above && pending_above->addr == addr + size) { + new_pending_free->size += pending_above->size; + struct edmm_heap_pool* pending_above_above = LISTP_PREV_ENTRY(pending_above, + &g_edmm_heap_pool_list, list); + LISTP_DEL(pending_above, &g_edmm_heap_pool_list, list); + __free_heap(pending_above); + + pending_above = pending_above_above; + } + + if (pending_below && pending_below->addr + pending_below->size == addr) { + new_pending_free->addr = pending_below->addr; + new_pending_free->size += pending_below->size; + + LISTP_DEL(pending_below, &g_edmm_heap_pool_list, list); + __free_heap(pending_below); + } + + INIT_LIST_HEAD(new_pending_free, list); + LISTP_ADD_AFTER(new_pending_free, pending_above, &g_edmm_heap_pool_list, list); + + /* update the pending free size */ + g_pending_free_size += size; + + /* Keep freeing last entry from the pending_free_epc list until the pending free falls + * below the threshold */ + while (g_pending_free_size > g_edmm_lazyfree_th_bytes) { + struct edmm_heap_pool* last_pending_free = LISTP_LAST_ENTRY(&g_edmm_heap_pool_list, + struct edmm_heap_pool, list); + + int ret = free_edmm_page_range(last_pending_free->addr, last_pending_free->size); + if (ret < 0) { + log_error("%s:Free failed! g_edmm_lazyfree_th_bytes = 0x%lx, g_pending_free_size = 0x%lx," + " last_addr = %p, last_size = 0x%lx, req_addr = %p, req_size = 0x%lx\n", + __func__, g_edmm_lazyfree_th_bytes, g_pending_free_size, + last_pending_free->addr, last_pending_free->size, addr, size); + return ret; + } + + if (g_pending_free_size >= last_pending_free->size) { + g_pending_free_size -= last_pending_free->size; + } else { + g_pending_free_size = 0; + } + + LISTP_DEL(last_pending_free, &g_edmm_heap_pool_list, list); + __free_heap(last_pending_free); + } + + return 0; +} + +/* This function checks if the requested EPC range overlaps with range in pending free EPC list. + * If so, removes overlapping requested range from the EPC list. This can cause the requested range + * be fragmented into smaller requests. On success, returns number of fragmented requests and + * negative unix error code on failure. */ +int remove_from_pending_free_epc(void* addr, size_t size, + struct edmm_heap_pool* updated_heap_alloc) { + assert(spinlock_is_locked(&g_heap_vma_lock)); + size_t allocated = 0; + int alloc_cnt = 0; + + if (!g_pal_sec.edmm_lazyfree_th || !g_pending_free_size) + goto out; + + struct edmm_heap_pool* pending_free_epc; + struct edmm_heap_pool* temp; + LISTP_FOR_EACH_ENTRY_SAFE(pending_free_epc, temp, &g_edmm_heap_pool_list, list) { + void* pendingfree_top = (char*)pending_free_epc->addr + pending_free_epc->size; + void* pendingfree_bottom = pending_free_epc->addr; + + if (pendingfree_bottom >= (void*)((char*)addr + size)) + continue; + if (pendingfree_top <= addr) + break; + + if (pendingfree_bottom < addr) { + /* create a new entry for [pendingfree_bottom, addr) */ + struct edmm_heap_pool* new_pending_free = __alloc_heap(); + if (!new_pending_free) { + log_error("Updating pending free EPC pages failed during allocation %p\n", addr); + return -ENOMEM; + } + new_pending_free->addr = pendingfree_bottom; + new_pending_free->size = (char*)addr - (char*)pendingfree_bottom; + + /* update size of the current pending_free entry after inserting new entry */ + pending_free_epc->addr = addr; + pending_free_epc->size -= new_pending_free->size; + + /* Adjust helper variable */ + pendingfree_bottom = pending_free_epc->addr; + + INIT_LIST_HEAD(new_pending_free, list); + LIST_ADD(new_pending_free, pending_free_epc, list); + } + + if (pendingfree_top <= (void*)((char*)addr + size)) { + /* Special case when [addr, addr + size) exceeds a pending free region. + * So split into [addr, pendingfree_bottom) and [pendingfree_top, addr + size) */ + if (pendingfree_bottom > addr && pendingfree_top < (void*)((char*)addr + size)) { + updated_heap_alloc[alloc_cnt].addr = pendingfree_top; + updated_heap_alloc[alloc_cnt].size = (char*)addr + size - (char*)pendingfree_top; + alloc_cnt++; + allocated += pending_free_epc->size; + size = (char*)pendingfree_bottom - (char*)addr; + goto release_entry; + } + + /* Requested region either fully/partially overlaps with pending free epc range. So we + * can remove it from pending_free_epc list and update addr and size accordingly. + * Note: Here pendingfree_bottom >= addr condition will always be true even for + * pendingfree_bottom < *addr case due to earlier adjustment. */ + if (pendingfree_top < (void*)((char*)addr + size)) { + addr = pendingfree_top; + } + + allocated += pending_free_epc->size; + size = size - pending_free_epc->size; + +release_entry: + LISTP_DEL(pending_free_epc, &g_edmm_heap_pool_list, list); + __free_heap(pending_free_epc); + } else { + /* Adjust pending_free_epc [addr + size, pendingfree_top) to remove allocated region */ + pending_free_epc->addr = (void*)((char*)addr + size); + pending_free_epc->size = (char*)pendingfree_top - ((char*)addr + size); + + if (pendingfree_bottom >= addr) { + allocated += (char*)addr + size - (char*)pendingfree_bottom; + size = (char*)pendingfree_bottom - (char*)addr; + } else { + allocated = size; + size = 0; + } + } + } + +out: + if (size) { + updated_heap_alloc[alloc_cnt].addr = addr; + updated_heap_alloc[alloc_cnt].size = size; + alloc_cnt++; + } + + /* update the pending free size amount allocated*/ + if (allocated) + g_pending_free_size -= allocated; + + return alloc_cnt; +} + +/* This function trims EPC pages on enclave's request. The sequence is as below: + * 1. Enclave calls SGX driver IOCTL to change the page's type to PT_TRIM. + * 2. Driver invokes ETRACK to track page's address on all CPUs and issues IPI to flush stale TLB + * entries. + * 3. Enclave issues an EACCEPT to accept changes to each EPC page. + * 4. Enclave notifies the driver to remove EPC pages (using an IOCTL). + * 5. Driver issues EREMOVE to complete the request. */ +int free_edmm_page_range(void* start, size_t size) { + void* end = (void*)((char*)start + size); + int ret = 0; + + alignas(64) sgx_arch_sec_info_t secinfo; + secinfo.flags = SGX_SECINFO_FLAGS_TRIM | SGX_SECINFO_FLAGS_MODIFIED; + memset(&secinfo.reserved, 0, sizeof(secinfo.reserved)); + + size_t nr_pages = size / g_pal_state.alloc_align; + ret = ocall_trim_epc_pages(start, nr_pages); + if (ret < 0) { + log_error("EPC trim page on [%p, %p) failed (errno = %d)\n", start, end, ret); + return ret; + } + + for (void* page_addr = start; page_addr < end; + page_addr = (void*)((char*)page_addr + g_pal_state.alloc_align)) { + ret = sgx_accept(&secinfo, page_addr); + if (ret) { + log_error("EDMM accept page failed with %d while trimming %p\n", ret, page_addr); + return -EFAULT; + } + } + + ret = ocall_notify_accept(start, nr_pages); + if (ret < 0) { + log_error("EPC notify_accept failed for range [%p, %p), %ld pages (errno = %d)\n", start, + end, nr_pages, ret); + return ret; + } + + return 0; +} + +/* This function allocates EPC pages within ELRANGE of an enclave. If EPC pages contain + * executable code, page permissions are extended once the page is in a valid state. The + * allocation sequence is described below: + * 1. Enclave invokes EACCEPT on a new page request which triggers a page fault (#PF) as the page + * is not available yet. + * 2. Driver catches this #PF and issues EAUG for the page (at this point the page becomes VALID and + * may be used by the enclave). The control returns back to enclave. + * 3. Enclave continues the same EACCEPT and the instruction succeeds this time. */ +int get_edmm_page_range(void* start, size_t size, bool executable) { + if (g_pal_sec.edmm_batch_alloc) { + /* Pass faulting address to the driver for EAUGing the range */ + int tid = pal_get_cur_tid(); + assert(tid); + + struct sgx_eaug_range_param *eaug_range = (struct sgx_eaug_range_param*)g_pal_sec.eaug_base; + eaug_range[tid-1].fault_addr = (unsigned long)((char*)start + size - + g_pal_state.alloc_align); + eaug_range[tid-1].mem_seg = HEAP; + eaug_range[tid-1].num_pages = size / g_pal_state.alloc_align; + + log_debug("TID= %d, fault_addr = 0x%lx, mem_seg = %d, num_pages = %d\n", + tid-1, eaug_range[tid-1].fault_addr, eaug_range[tid-1].mem_seg, + eaug_range[tid-1].num_pages); + } + + void* lo = start; + void* addr = (void*)((char*)lo + size); + + alignas(64) sgx_arch_sec_info_t secinfo; + secinfo.flags = SGX_SECINFO_FLAGS_R | SGX_SECINFO_FLAGS_W | SGX_SECINFO_FLAGS_REG | + SGX_SECINFO_FLAGS_PENDING; + memset(&secinfo.reserved, 0, sizeof(secinfo.reserved)); + + while (lo < addr) { + addr = (void*)((char*)addr - g_pal_state.alloc_align); + + int ret = sgx_accept(&secinfo, addr); + if (ret) { + log_error("EDMM accept page failed: %p org_start = %p, org_size=0x%lx ret=%d\n", addr, + start, size, ret); + return -EFAULT; + } + + /* All new pages will have RW permissions initially, so after EAUG/EACCEPT, extend + * permission of a VALID enclave page (if needed). */ + if (executable) { + alignas(64) sgx_arch_sec_info_t secinfo_extend = secinfo; + + secinfo_extend.flags |= SGX_SECINFO_FLAGS_X; + sgx_modpe(&secinfo_extend, addr); + } + } + + return 0; +} \ No newline at end of file diff --git a/Pal/src/host/Linux-SGX/edmm_pages.h b/Pal/src/host/Linux-SGX/edmm_pages.h new file mode 100644 index 0000000000..b493be7eb4 --- /dev/null +++ b/Pal/src/host/Linux-SGX/edmm_pages.h @@ -0,0 +1,39 @@ +#ifndef EDMM_PAGES_H +#define EDMM_PAGES_H + +#include "pal_linux.h" + +/* TODO: Setting this as 64 to start with, but will need to revisit */ +#define EDMM_HEAP_RANGE_CNT 64 + +/* edmm_heap_range objects are taken from pre-allocated pool to avoid recursive mallocs */ +#define MAX_EDMM_HEAP_RANGE 10000 + +typedef enum { + HEAP = 0, + STACK, + MEMORY_SEG_MAX, +} SGX_MEMORY_SEG; + +struct sgx_eaug_range_param { + SGX_MEMORY_SEG mem_seg; + unsigned int num_pages; + unsigned long fault_addr; +}; + +DEFINE_LIST(edmm_heap_pool); +DEFINE_LISTP(edmm_heap_pool); +struct edmm_heap_pool { + LIST_TYPE(edmm_heap_pool) list; + void* addr; + size_t size; +}; + +size_t find_preallocated_heap_nonoverlap(void* addr, size_t size); +int free_edmm_page_range(void* start, size_t size); +int get_edmm_page_range(void* start, size_t size, bool executable); +int add_to_pending_free_epc(void* addr, size_t size); +int remove_from_pending_free_epc(void* addr, size_t size, + struct edmm_heap_pool* updated_heap_alloc); + +#endif /* EDMM_PAGES_H */ \ No newline at end of file diff --git a/Pal/src/host/Linux-SGX/enclave_ocalls.c b/Pal/src/host/Linux-SGX/enclave_ocalls.c index 23d765c8cc..502d37341b 100644 --- a/Pal/src/host/Linux-SGX/enclave_ocalls.c +++ b/Pal/src/host/Linux-SGX/enclave_ocalls.c @@ -1705,3 +1705,47 @@ int ocall_sched_getaffinity(void* tcs, size_t cpumask_size, void* cpu_mask) { sgx_reset_ustack(old_ustack); return retval; } + +int ocall_trim_epc_pages(void* addr, size_t nr_pages) { + int retval = 0; + ms_ocall_sgx_range_t* ms; + + void* old_ustack = sgx_prepare_ustack(); + ms = sgx_alloc_on_ustack_aligned(sizeof(*ms), alignof(*ms)); + if (!ms) { + retval = -ENOMEM; + goto out; + } + ms->start_addr = addr; + ms->nr_pages = nr_pages; + + do { + retval = sgx_exitless_ocall(OCALL_TRIM_EPC_PAGES, ms); + } while (retval == -EINTR); + +out: + sgx_reset_ustack(old_ustack); + return retval; +} + +int ocall_notify_accept(void* addr, size_t nr_pages) { + int retval = 0; + ms_ocall_sgx_range_t* ms; + + void* old_ustack = sgx_prepare_ustack(); + ms = sgx_alloc_on_ustack_aligned(sizeof(*ms), alignof(*ms)); + if (!ms) { + retval = -ENOMEM; + goto out; + } + ms->start_addr = addr; + ms->nr_pages = nr_pages; + + do { + retval = sgx_exitless_ocall(OCALL_NOTIFY_ACCEPT, ms); + } while (retval == -EINTR); + +out: + sgx_reset_ustack(old_ustack); + return retval; +} diff --git a/Pal/src/host/Linux-SGX/enclave_ocalls.h b/Pal/src/host/Linux-SGX/enclave_ocalls.h index f4737ce259..f1b815d11c 100644 --- a/Pal/src/host/Linux-SGX/enclave_ocalls.h +++ b/Pal/src/host/Linux-SGX/enclave_ocalls.h @@ -114,3 +114,7 @@ int ocall_eventfd(unsigned int initval, int flags); */ int ocall_get_quote(const sgx_spid_t* spid, bool linkable, const sgx_report_t* report, const sgx_quote_nonce_t* nonce, char** quote, size_t* quote_len); + +int ocall_trim_epc_pages(void* addr, size_t nr_pages); + +int ocall_notify_accept(void* addr, size_t nr_pages); diff --git a/Pal/src/host/Linux-SGX/enclave_pages.c b/Pal/src/host/Linux-SGX/enclave_pages.c index 6868304783..e95caadcd1 100644 --- a/Pal/src/host/Linux-SGX/enclave_pages.c +++ b/Pal/src/host/Linux-SGX/enclave_pages.c @@ -1,6 +1,10 @@ #include "enclave_pages.h" +#include +#include + #include "api.h" +#include "edmm_pages.h" #include "list.h" #include "pal_error.h" #include "pal_internal.h" @@ -8,10 +12,12 @@ #include "pal_security.h" #include "spinlock.h" +extern uint64_t g_edmm_lazyfree_th_bytes; + struct atomic_int g_allocated_pages; -static void* g_heap_bottom; -static void* g_heap_top; +void* g_heap_bottom; +void* g_heap_top; static size_t g_pal_internal_mem_used = 0; @@ -28,7 +34,7 @@ struct heap_vma { DEFINE_LISTP(heap_vma); static LISTP_TYPE(heap_vma) g_heap_vma_list = LISTP_INIT; -static spinlock_t g_heap_vma_lock = INIT_SPINLOCK_UNLOCKED; +spinlock_t g_heap_vma_lock = INIT_SPINLOCK_UNLOCKED; /* heap_vma objects are taken from pre-allocated pool to avoid recursive mallocs */ #define MAX_HEAP_VMAS 100000 @@ -78,11 +84,20 @@ static void __free_vma(struct heap_vma* vma) { int init_enclave_pages(void) { g_heap_bottom = g_pal_sec.heap_min; g_heap_top = g_pal_sec.heap_max; + + /* Extract EDMM lazy free threashold from the percentage */ + if (g_pal_sec.edmm_enable_heap && g_pal_sec.edmm_lazyfree_th > 0) { + g_edmm_lazyfree_th_bytes = (g_heap_top - g_heap_bottom) * g_pal_sec.edmm_lazyfree_th / 100; + log_debug("g_heap_bottom: %p, g_heap_top: %p, g_edmm_lazyfree_th_bytes= 0x%lx\n", + g_heap_bottom, g_heap_top, g_edmm_lazyfree_th_bytes); + } + return 0; } static void* __create_vma_and_merge(void* addr, size_t size, bool is_pal_internal, - struct heap_vma* vma_above) { + struct heap_vma* vma_above, + struct edmm_heap_pool* heap_ranges_to_alloc) { assert(spinlock_is_locked(&g_heap_vma_lock)); assert(addr && size); @@ -132,16 +147,31 @@ static void* __create_vma_and_merge(void* addr, size_t size, bool is_pal_interna * (1) start from `vma_above` and iterate through VMAs with higher-addresses for merges * (2) start from `vma_below` and iterate through VMAs with lower-addresses for merges. * Note that we never merge normal VMAs with pal-internal VMAs. */ + int unallocated_cnt = 0; + void* unallocated_start_addr = (vma_below) ? MAX(vma_below->top, vma->bottom) : vma->bottom; while (vma_above && vma_above->bottom <= vma->top && vma_above->is_pal_internal == vma->is_pal_internal) { /* newly created VMA grows into above VMA; expand newly created VMA and free above-VMA */ freed += vma_above->top - vma_above->bottom; struct heap_vma* vma_above_above = LISTP_PREV_ENTRY(vma_above, &g_heap_vma_list, list); + /* Track unallocated memory regions between VMAs while merging `vma_above`. */ + if (g_pal_sec.edmm_enable_heap && vma_above->bottom > unallocated_start_addr) { + assert(unallocated_cnt < EDMM_HEAP_RANGE_CNT); + heap_ranges_to_alloc[unallocated_cnt].size = vma_above->bottom - unallocated_start_addr; + heap_ranges_to_alloc[unallocated_cnt].addr = unallocated_start_addr; + unallocated_cnt++; + } + vma->bottom = MIN(vma_above->bottom, vma->bottom); vma->top = MAX(vma_above->top, vma->top); LISTP_DEL(vma_above, &g_heap_vma_list, list); + /* Store vma_above->top to check for any free region between vma_above->top and + * vma_above_above->bottom. */ + if (g_pal_sec.edmm_enable_heap) + unallocated_start_addr = vma_above->top; + __free_vma(vma_above); vma_above = vma_above_above; } @@ -170,6 +200,13 @@ static void* __create_vma_and_merge(void* addr, size_t size, bool is_pal_interna assert(vma->top - vma->bottom >= (ptrdiff_t)freed); size_t allocated = vma->top - vma->bottom - freed; + + /* No unallocated memory regions between VMAs found */ + if (g_pal_sec.edmm_enable_heap && unallocated_cnt == 0 && allocated > 0) { + heap_ranges_to_alloc[0].size = allocated; + heap_ranges_to_alloc[0].addr = unallocated_start_addr; + } + __atomic_add_fetch(&g_allocated_pages.counter, allocated / g_page_size, __ATOMIC_SEQ_CST); if (is_pal_internal) { @@ -182,10 +219,10 @@ static void* __create_vma_and_merge(void* addr, size_t size, bool is_pal_interna void* get_enclave_pages(void* addr, size_t size, bool is_pal_internal) { void* ret = NULL; - + /* TODO: Should we introduce a compiler switch for EDMM? */ + struct edmm_heap_pool heap_ranges_to_alloc[EDMM_HEAP_RANGE_CNT] = {0}; if (!size) return NULL; - size = ALIGN_UP(size, g_page_size); addr = ALIGN_DOWN_PTR(addr, g_page_size); @@ -213,7 +250,7 @@ void* get_enclave_pages(void* addr, size_t size, bool is_pal_internal) { } vma_above = vma; } - ret = __create_vma_and_merge(addr, size, is_pal_internal, vma_above); + ret = __create_vma_and_merge(addr, size, is_pal_internal, vma_above, heap_ranges_to_alloc); } else { /* caller did not specify address; find first (highest-address) empty slot that fits */ void* vma_above_bottom = g_heap_top; @@ -221,7 +258,7 @@ void* get_enclave_pages(void* addr, size_t size, bool is_pal_internal) { LISTP_FOR_EACH_ENTRY(vma, &g_heap_vma_list, list) { if (vma->top < vma_above_bottom - size) { ret = __create_vma_and_merge(vma_above_bottom - size, size, is_pal_internal, - vma_above); + vma_above, heap_ranges_to_alloc); goto out; } vma_above = vma; @@ -230,16 +267,59 @@ void* get_enclave_pages(void* addr, size_t size, bool is_pal_internal) { /* corner case: there may be enough space between heap bottom and the lowest-address VMA */ if (g_heap_bottom < vma_above_bottom - size) - ret = __create_vma_and_merge(vma_above_bottom - size, size, is_pal_internal, vma_above); + ret = __create_vma_and_merge(vma_above_bottom - size, size, is_pal_internal, vma_above, + heap_ranges_to_alloc); } out: + /* In order to prevent already accepted pages from being accepted again, we track EPC pages that + * aren't accepted yet (unallocated heap) and call EACCEPT only on those EPC pages. */ + if (g_pal_sec.edmm_enable_heap && ret != NULL) { + for (int i = 0; i < EDMM_HEAP_RANGE_CNT; i++) { + if (!heap_ranges_to_alloc[i].size) + break; + + void* start_addr = heap_ranges_to_alloc[i].addr; + size_t req_sz = heap_ranges_to_alloc[i].size; + size_t non_overlapping_sz = find_preallocated_heap_nonoverlap(start_addr, req_sz); + + log_debug("%s: preallocated heap addr = %p, org_size = %lx, updated_size=%lx\n", + __func__, start_addr, req_sz, non_overlapping_sz); + + /* Entire request overlaps with preallocated heap, so simply continue. */ + if (non_overlapping_sz == 0) + continue; + + /* Check if the req. range is available in the pending_free EPC list, if so update the + * list and continue to the next reqested range. */ + struct edmm_heap_pool updated_heap_alloc[EDMM_HEAP_RANGE_CNT] = {0}; + int req_cnt = remove_from_pending_free_epc(start_addr, non_overlapping_sz, + updated_heap_alloc); + if (req_cnt < 0 ) { + return NULL; + } + + for (int j= 0; j < req_cnt; j++) { + log_debug("%s: updated_start = %p, updated_size = 0x%lx\n", __func__, + updated_heap_alloc[j].addr, updated_heap_alloc[j].size); + int retval = get_edmm_page_range(updated_heap_alloc[j].addr, + updated_heap_alloc[j].size, /*executable=*/true); + if (retval < 0) { + ret = NULL; + break; + } + } + } + } spinlock_unlock(&g_heap_vma_lock); return ret; } int free_enclave_pages(void* addr, size_t size) { int ret = 0; + /* TODO: Should we introduce a compiler switch for EDMM? */ + struct edmm_heap_pool heap_ranges_to_free[EDMM_HEAP_RANGE_CNT] = {0}; + int free_cnt = 0; if (!size) return -PAL_ERROR_NOMEM; @@ -283,7 +363,26 @@ int free_enclave_pages(void* addr, size_t size) { goto out; } - freed += MIN(vma->top, addr + size) - MAX(vma->bottom, addr); + void* free_heap_top = MIN(vma->top, addr + size); + void* free_heap_bottom = MAX(vma->bottom, addr); + size_t range = free_heap_top - free_heap_bottom; + freed += range; + if (g_pal_sec.edmm_enable_heap) { + /* if range is contiguous with previous entry, update addr and size accordingly; + * this case may be rare but the below optimization still saves us 2 OCALLs and 2 + * IOCTLs, so should be worth it */ + if (free_cnt > 0 && + free_heap_top == heap_ranges_to_free[free_cnt-1].addr) { + heap_ranges_to_free[free_cnt-1].addr = free_heap_bottom; + heap_ranges_to_free[free_cnt-1].size += range; + } else { + assert(free_cnt < EDMM_HEAP_RANGE_CNT); + /* found a new non-contiguous range */ + heap_ranges_to_free[free_cnt].addr = free_heap_bottom; + heap_ranges_to_free[free_cnt].size = range; + free_cnt++; + } + } if (vma->bottom < addr) { /* create VMA [vma->bottom, addr); this may leave VMA [addr + size, vma->top), see below */ @@ -317,6 +416,31 @@ int free_enclave_pages(void* addr, size_t size) { } out: + if (ret >=0 && g_pal_sec.edmm_enable_heap) { + for (int i = 0; i < free_cnt; i++) { + void* start_addr = heap_ranges_to_free[i].addr; + size_t req_sz = heap_ranges_to_free[i].size; + + size_t non_overlapping_sz = find_preallocated_heap_nonoverlap(start_addr, req_sz); + log_debug("%s: preallocated heap addr = %p, org_size = %lx, updated_size=%lx\n", + __func__, start_addr, req_sz, non_overlapping_sz); + + /* Entire request overlaps with preallocated heap, so simply continue. */ + if (non_overlapping_sz == 0) + continue; + + if (g_pal_sec.edmm_lazyfree_th > 0) { + ret = add_to_pending_free_epc(start_addr, non_overlapping_sz); + } else { + ret = free_edmm_page_range(start_addr, non_overlapping_sz); + } + + if (ret < 0) { + ret = -PAL_ERROR_INVAL; + break; + } + } + } spinlock_unlock(&g_heap_vma_lock); return ret; } diff --git a/Pal/src/host/Linux-SGX/ocall_types.h b/Pal/src/host/Linux-SGX/ocall_types.h index 6687b211da..078ee48f8a 100644 --- a/Pal/src/host/Linux-SGX/ocall_types.h +++ b/Pal/src/host/Linux-SGX/ocall_types.h @@ -63,6 +63,8 @@ enum { OCALL_DEBUG_MAP_REMOVE, OCALL_EVENTFD, OCALL_GET_QUOTE, + OCALL_TRIM_EPC_PAGES, + OCALL_NOTIFY_ACCEPT, OCALL_NR, }; @@ -305,4 +307,9 @@ typedef struct { size_t ms_quote_len; } ms_ocall_get_quote_t; +typedef struct { + void* start_addr; + size_t nr_pages; +} ms_ocall_sgx_range_t; + #pragma pack(pop) diff --git a/Pal/src/host/Linux-SGX/pal_security.h b/Pal/src/host/Linux-SGX/pal_security.h index 0de95c7449..91f4117f9f 100644 --- a/Pal/src/host/Linux-SGX/pal_security.h +++ b/Pal/src/host/Linux-SGX/pal_security.h @@ -25,6 +25,11 @@ struct pal_sec { /* remaining heap usable by application */ PAL_PTR heap_min, heap_max; + bool edmm_enable_heap; + PAL_NUM preheat_enclave_sz; + bool edmm_batch_alloc; + PAL_NUM eaug_base; + PAL_NUM edmm_lazyfree_th; /* child's stream FD created and sent over by parent */ PAL_IDX stream_fd; diff --git a/Pal/src/host/Linux-SGX/sgx_api.h b/Pal/src/host/Linux-SGX/sgx_api.h index d244733aab..c73a1de163 100644 --- a/Pal/src/host/Linux-SGX/sgx_api.h +++ b/Pal/src/host/Linux-SGX/sgx_api.h @@ -50,4 +50,34 @@ static inline int64_t sgx_getkey(sgx_key_request_t* keyrequest, sgx_key_128bit_t return rax; } +/*! + * \brief Low-level wrapper around EACCEPT instruction leaf. + * + * Caller is responsible for parameter alignment: 64B for `si` and 4KB (page size) for `addr`. + */ +static inline int64_t sgx_accept(sgx_arch_sec_info_t* si, const void* addr) { + int64_t rax = EACCEPT; + __asm__ volatile( + ENCLU "\n" + : "+a"(rax) + : "b"(si), "c"(addr) + : "memory"); + return rax; +} + +/*! + * \brief Low-level wrapper around EMODPE instruction leaf. + * + * Caller is responsible for parameter alignment: 64B for `si` and 4KB (page size) for `addr`. + */ +static inline int64_t sgx_modpe(sgx_arch_sec_info_t* si, const void* addr) { + int64_t rax = EMODPE; + __asm__ volatile( + ENCLU "\n" + : "+a"(rax) + : "b"(si), "c"(addr) + : "memory"); + return rax; +} + #endif /* SGX_API_H */ diff --git a/Pal/src/host/Linux-SGX/sgx_arch.h b/Pal/src/host/Linux-SGX/sgx_arch.h index 7b2a936f6b..637ded58db 100644 --- a/Pal/src/host/Linux-SGX/sgx_arch.h +++ b/Pal/src/host/Linux-SGX/sgx_arch.h @@ -206,6 +206,12 @@ typedef struct { #define SGX_SECINFO_FLAGS_SECS 0x000 #define SGX_SECINFO_FLAGS_TCS 0x100 #define SGX_SECINFO_FLAGS_REG 0x200 +#define SGX_SECINFO_FLAGS_TRIM 0x400 + +/* EDMM PAGE STATUS */ +#define SGX_SECINFO_FLAGS_PENDING 0x08 +#define SGX_SECINFO_FLAGS_MODIFIED 0x010 +#define SGX_SECINFO_FLAGS_PR 0x020 typedef struct _css_header_t { uint8_t header[12]; @@ -365,6 +371,8 @@ typedef uint8_t sgx_key_128bit_t[16]; #define EREPORT 0 #define EGETKEY 1 #define EEXIT 4 +#define EACCEPT 5 +#define EMODPE 6 #define LAUNCH_KEY 0 #define PROVISION_KEY 1 diff --git a/Pal/src/host/Linux-SGX/sgx_enclave.c b/Pal/src/host/Linux-SGX/sgx_enclave.c index 97ab9caead..5725b8d829 100644 --- a/Pal/src/host/Linux-SGX/sgx_enclave.c +++ b/Pal/src/host/Linux-SGX/sgx_enclave.c @@ -28,6 +28,8 @@ #include "sgx_tls.h" #include "sigset.h" +#include "gsgx.h" + #define ODEBUG(code, ms) \ do { \ } while (0) @@ -690,6 +692,30 @@ static long sgx_ocall_get_quote(void* pms) { &ms->ms_nonce, &ms->ms_quote, &ms->ms_quote_len); } +static long sgx_ocall_trim_epc_pages(void* pms) { + extern int g_isgx_device; + ms_ocall_sgx_range_t* ms = (ms_ocall_sgx_range_t*)pms; + ODEBUG(OCALL_TRIM_EPC_PAGES, ms); + struct sgx_range trim_range; + + trim_range.start_addr = (unsigned long)ms->start_addr; + trim_range.nr_pages = (unsigned int)ms->nr_pages; + + return INLINE_SYSCALL(ioctl, 3, g_isgx_device, SGX_IOC_ENCLAVE_TRIM, &trim_range); +} + +static long sgx_ocall_notify_accept(void* pms) { + extern int g_isgx_device; + ms_ocall_sgx_range_t* ms = (ms_ocall_sgx_range_t*)pms; + ODEBUG(OCALL_NOTIFY_ACCEPT, ms); + struct sgx_range accept_range; + + accept_range.start_addr = (unsigned long)ms->start_addr; + accept_range.nr_pages = (unsigned int)ms->nr_pages; + + return INLINE_SYSCALL(ioctl, 3, g_isgx_device, SGX_IOC_ENCLAVE_NOTIFY_ACCEPT, &accept_range); +} + sgx_ocall_fn_t ocall_table[OCALL_NR] = { [OCALL_EXIT] = sgx_ocall_exit, [OCALL_MMAP_UNTRUSTED] = sgx_ocall_mmap_untrusted, @@ -732,6 +758,8 @@ sgx_ocall_fn_t ocall_table[OCALL_NR] = { [OCALL_DEBUG_MAP_REMOVE] = sgx_ocall_debug_map_remove, [OCALL_EVENTFD] = sgx_ocall_eventfd, [OCALL_GET_QUOTE] = sgx_ocall_get_quote, + [OCALL_TRIM_EPC_PAGES] = sgx_ocall_trim_epc_pages, + [OCALL_NOTIFY_ACCEPT] = sgx_ocall_notify_accept, }; #define EDEBUG(code, ms) \ diff --git a/Pal/src/host/Linux-SGX/sgx_framework.c b/Pal/src/host/Linux-SGX/sgx_framework.c index 5b3601d93c..976ae95364 100644 --- a/Pal/src/host/Linux-SGX/sgx_framework.c +++ b/Pal/src/host/Linux-SGX/sgx_framework.c @@ -11,11 +11,13 @@ #include "sgx_log.h" static int g_gsgx_device = -1; -static int g_isgx_device = -1; +int g_isgx_device = -1; static void* g_zero_pages = NULL; static size_t g_zero_pages_size = 0; +extern struct pal_enclave g_pal_enclave; + int open_sgx_driver(bool need_gsgx) { if (need_gsgx) { g_gsgx_device = INLINE_SYSCALL(open, 3, GSGX_FILE, O_RDWR | O_CLOEXEC, 0); @@ -140,13 +142,21 @@ int create_enclave(sgx_arch_secs_t* secs, sgx_arch_token_t* token) { } #endif - uint64_t addr = INLINE_SYSCALL(mmap, 6, request_mmap_addr, request_mmap_size, - PROT_NONE, /* newer DCAP driver requires such initial mmap */ + uint64_t addr; + if (g_pal_enclave.pal_sec.edmm_enable_heap) { + /* currently edmm support is available with legacy intel driver */ + addr = INLINE_SYSCALL(mmap, 6, request_mmap_addr, request_mmap_size, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_FIXED | MAP_SHARED, g_isgx_device, 0); + } else { + addr = INLINE_SYSCALL(mmap, 6, request_mmap_addr, request_mmap_size, + PROT_NONE, /* newer DCAP driver requires such initial mmap */ #ifdef SGX_DCAP - MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); #else - MAP_FIXED | MAP_SHARED, g_isgx_device, 0); + MAP_FIXED | MAP_SHARED, g_isgx_device, 0); #endif + } if (IS_ERR_P(addr)) { if (ERRNO_P(addr) == EPERM) { @@ -190,6 +200,15 @@ int create_enclave(sgx_arch_secs_t* secs, sgx_arch_token_t* token) { return 0; } +void prot_flags_to_permissions_str(char* p, int prot) { + if (prot & PROT_READ) + p[0] = 'R'; + if (prot & PROT_WRITE) + p[1] = 'W'; + if (prot & PROT_EXEC) + p[2] = 'X'; +} + int add_pages_to_enclave(sgx_arch_secs_t* secs, void* addr, void* user_addr, unsigned long size, enum sgx_page_type type, int prot, bool skip_eextend, const char* comment) { @@ -227,17 +246,13 @@ int add_pages_to_enclave(sgx_arch_secs_t* secs, void* addr, void* user_addr, uns break; } - char p[4] = "---"; + const char* t = (type == SGX_PAGE_TCS) ? "TCS" : "REG"; const char* m = skip_eextend ? "" : " measured"; + char p[4] = "---"; if (type == SGX_PAGE_REG) { - if (prot & PROT_READ) - p[0] = 'R'; - if (prot & PROT_WRITE) - p[1] = 'W'; - if (prot & PROT_EXEC) - p[2] = 'X'; + prot_flags_to_permissions_str(p, prot); } if (size == g_page_size) @@ -414,6 +429,33 @@ int init_enclave(sgx_arch_secs_t* secs, sgx_arch_enclave_css_t* sigstruct, return ret; } + /* create shared memory region to pass EPC range info */ + if (g_pal_enclave.pal_sec.edmm_enable_heap && g_pal_enclave.pal_sec.edmm_batch_alloc) { + unsigned int num_threads = g_pal_enclave.thread_num; + unsigned long eaug_base = (unsigned long) INLINE_SYSCALL(mmap, 6, NULL, + sizeof(struct sgx_eaug_range_param) * num_threads, + PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); + + if (IS_ERR_P(eaug_base)) { + log_error("Cannot mmap eaug_base %ld\n", ERRNO_P(eaug_base)); + return -ENOMEM; + } + + memset((void*)eaug_base, 0, sizeof(struct sgx_eaug_range_param) * num_threads); + g_pal_enclave.pal_sec.eaug_base = eaug_base; + + struct sgx_eaug_base_init init_param = { + .encl_addr = enclave_valid_addr, + .eaug_info_base = eaug_base, + .num_threads = num_threads, + }; + int ret = INLINE_SYSCALL(ioctl, 3, g_isgx_device, SGX_IOC_EAUG_INFO_BASE, &init_param); + if (ret < 0) { + log_error("IOCTL to initialize shared eaug_base failed! (errno = %d)\n", ret); + return ret; + } + } + return 0; } diff --git a/Pal/src/host/Linux-SGX/sgx_internal.h b/Pal/src/host/Linux-SGX/sgx_internal.h index 2ae0990358..cec75dae4d 100644 --- a/Pal/src/host/Linux-SGX/sgx_internal.h +++ b/Pal/src/host/Linux-SGX/sgx_internal.h @@ -77,6 +77,7 @@ struct pal_enclave { extern struct pal_enclave g_pal_enclave; +void prot_flags_to_permissions_str(char* p, int prot); int open_sgx_driver(bool need_gsgx); bool is_wrfsbase_supported(void); diff --git a/Pal/src/host/Linux-SGX/sgx_main.c b/Pal/src/host/Linux-SGX/sgx_main.c index 7bda1c3d9f..a52ce5c3d5 100644 --- a/Pal/src/host/Linux-SGX/sgx_main.c +++ b/Pal/src/host/Linux-SGX/sgx_main.c @@ -458,9 +458,40 @@ static int initialize_enclave(struct pal_enclave* enclave, const char* manifest_ assert(areas[i].data_src == ZERO); } - ret = add_pages_to_enclave(&enclave_secs, (void*)areas[i].addr, data, areas[i].size, - areas[i].type, areas[i].prot, areas[i].skip_eextend, - areas[i].desc); + /* Hybrid approach: To reduce the enclave exits, pre-allocate minimal heap from top + * as required by application and allocate the remaining dynamically using EDMM. */ + if (enclave->pal_sec.edmm_enable_heap && !strcmp(areas[i].desc, "free")) { + char p[4] = "---"; + prot_flags_to_permissions_str(p, areas[i].prot); + size_t preheat_enclave_sz = enclave->pal_sec.preheat_enclave_sz; + + if (preheat_enclave_sz == 0 ) { + log_debug("SKIP adding pages to enclave: %p-%p [%s:%s] (%s)%s\n", + (void *)areas[i].addr, (void *)areas[i].addr + areas[i].size, + (areas[i].type == SGX_PAGE_TCS) ? "TCS" : "REG", + p, areas[i].desc, areas[i].skip_eextend ? "" : " measured"); + } else if (preheat_enclave_sz <= areas[i].size) { + /* Add preheat_enclave_sz of heap to the enclave */ + ret = add_pages_to_enclave(&enclave_secs, + (void*)(areas[i].addr + areas[i].size - preheat_enclave_sz), + data, preheat_enclave_sz, areas[i].type, areas[i].prot, + areas[i].skip_eextend, areas[i].desc); + log_debug("SKIP adding pages to enclave: %p-%p [%s:%s] (%s)%s\n", + (void *)areas[i].addr, + (void*)(areas[i].addr + areas[i].size - preheat_enclave_sz), + (areas[i].type == SGX_PAGE_TCS) ? "TCS" : "REG", + p, areas[i].desc, areas[i].skip_eextend ? "" : " measured"); + } else { + log_error("Adding pages (%s) to enclave failed! preheat_enclave_sz should be less" + " than total heap size %ld\n", areas[i].desc, areas[i].size); + ret = -EINVAL; + goto out; + } + } else { + ret = add_pages_to_enclave(&enclave_secs, (void*)areas[i].addr, data, areas[i].size, + areas[i].type, areas[i].prot, areas[i].skip_eextend, + areas[i].desc); + } if (data) INLINE_SYSCALL(munmap, 2, data, areas[i].size); @@ -707,6 +738,60 @@ static int parse_loader_config(char* manifest, struct pal_enclave* enclave_info) /* EPID is used if SPID is a non-empty string in manifest, otherwise DCAP/ECDSA */ enclave_info->use_epid_attestation = sgx_ra_client_spid_str && strlen(sgx_ra_client_spid_str); + bool edmm_enable_heap; + ret = toml_bool_in(manifest_root, "sgx.edmm_enable_heap", /*defaultval=*/false, + &edmm_enable_heap); + if (ret < 0 ) { + log_error("Cannot parse 'sgx.edmm_enable_heap' (the value must be true or false)\n"); + ret = -EINVAL; + goto out; + } + enclave_info->pal_sec.edmm_enable_heap = edmm_enable_heap; + + uint64_t preheat_enclave_sz = 0; + ret = toml_sizestring_in(manifest_root, "sgx.preheat_enclave_sz", /*defaultval=*/0, + &preheat_enclave_sz); + if (ret < 0) { + log_error("Cannot parse 'sgx.preheat_enclave_sz' (value must be put in double quotes!)\n"); + ret = -EINVAL; + goto out; + } + + /* Only when EDMM is enabled, preheat_enclave_sz is taken into consideration else it is used as + * an on/off switch for preheating the enclave heap. */ + if (!edmm_enable_heap && preheat_enclave_sz > 1) { + log_error("Cannot parse 'sgx.preheat_enclave_sz'" + " (value must be either 0 or 1 when sgx.edmm_enable_heap is disabled !)\n"); + ret = -EINVAL; + goto out; + } + + if (edmm_enable_heap && !IS_ALIGNED(preheat_enclave_sz, g_page_size)) { + log_error("preheat_enclave_sz should be page aligned: %ld\n", g_page_size); + ret = -EINVAL; + goto out; + } + enclave_info->pal_sec.preheat_enclave_sz = preheat_enclave_sz; + + bool edmm_batch_alloc; + ret = toml_bool_in(manifest_root, "sgx.edmm_batch_allocation", /*defaultval=*/false, + &edmm_batch_alloc); + if (ret < 0 ) { + log_error("Cannot parse 'sgx.edmm_batch_alloc' (the value must be true or false)\n"); + ret = -EINVAL; + goto out; + } + enclave_info->pal_sec.edmm_batch_alloc = edmm_batch_alloc; + + int64_t edmm_lazyfree_th = 0; + ret = toml_int_in(manifest_root, "sgx.edmm_lazyfree_th", /*defaultval=*/0, &edmm_lazyfree_th); + if (ret < 0 || (edmm_lazyfree_th < 0)) { + log_error("Cannot parse 'sgx.edmm_lazyfree_th' (the value must be 0 or greater)\n"); + ret = -EINVAL; + goto out; + } + enclave_info->pal_sec.edmm_lazyfree_th = edmm_lazyfree_th; + char* profile_str = NULL; ret = toml_string_in(manifest_root, "sgx.profile.enable", &profile_str); if (ret < 0) { diff --git a/python/graphenelibos/sgx_sign.py b/python/graphenelibos/sgx_sign.py index 91de164bee..f9da5f9927 100644 --- a/python/graphenelibos/sgx_sign.py +++ b/python/graphenelibos/sgx_sign.py @@ -510,6 +510,8 @@ def load_file(digest, file, offset, addr, filesize, memsize, desc, flags): include_page(digest, page, flags, start_zero + data + end_zero, True) + preheat_enclave_sz = attr['preheat_enclave_sz'] + edmm_enable_heap = attr['edmm_enable_heap'] for area in areas: if area.elf_filename is not None: with open(area.elf_filename, 'rb') as file: @@ -537,6 +539,20 @@ def load_file(digest, file, offset, addr, filesize, memsize, desc, flags): load_file(mrenclave, file, offset, baseaddr_ + addr, filesize, memsize, desc, flags) else: + # Hybrid approach: To reduce the enclave exits, pre-allocate minimal heap from top + # as required by application and allocate the remaining dynamically using EDMM. + if edmm_enable_heap == 1 and area.desc == "free": + if preheat_enclave_sz == 0: + print_area(area.addr, area.size, area.flags, area.desc, area.measure) + continue + else: + if preheat_enclave_sz > area.size: + raise Exception("preheat_enclave_sz must be less than total heap size: 0x{0:x}" + .format(area.size)) + + area.addr = area.addr + area.size - preheat_enclave_sz + area.size = preheat_enclave_sz + for addr in range(area.addr, area.addr + area.size, offs.PAGESIZE): data = ZERO_PAGE if area.content is not None: @@ -681,15 +697,19 @@ def read_manifest(path): sgx.setdefault('thread_num', DEFAULT_THREAD_NUM) sgx.setdefault('isvprodid', 0) sgx.setdefault('isvsvn', 0) - sgx.setdefault('remote_attestation', False) - sgx.setdefault('debug', True) - sgx.setdefault('require_avx', False) - sgx.setdefault('require_avx512', False) - sgx.setdefault('require_mpx', False) - sgx.setdefault('require_pkru', False) - sgx.setdefault('support_exinfo', False) - sgx.setdefault('nonpie_binary', False) - sgx.setdefault('enable_stats', False) + sgx.setdefault('remote_attestation', 0) + sgx.setdefault('debug', 1) + sgx.setdefault('require_avx', 0) + sgx.setdefault('require_avx512', 0) + sgx.setdefault('require_mpx', 0) + sgx.setdefault('require_pkru', 0) + sgx.setdefault('support_exinfo', 0) + sgx.setdefault('nonpie_binary', 0) + sgx.setdefault('enable_stats', 0) + sgx.setdefault('preheat_enclave_sz', '0') + sgx.setdefault('edmm_enable_heap', 0) + sgx.setdefault('edmm_batch_allocation', 0) + sgx.setdefault('edmm_lazyfree_th', 0) loader = manifest.setdefault('loader', {}) loader.setdefault('preload', '') @@ -716,16 +736,32 @@ def main_sign(manifest, args): attr['year'] = today.year attr['month'] = today.month attr['day'] = today.day + attr['preheat_enclave_sz'] = parse_size(manifest_sgx['preheat_enclave_sz']) + attr['edmm_enable_heap'] = manifest_sgx['edmm_enable_heap'] + attr['edmm_batch_allocation'] = manifest_sgx['edmm_batch_allocation'] + attr['edmm_lazyfree_th'] = manifest_sgx['edmm_lazyfree_th'] + + if attr['preheat_enclave_sz'] < 0: + raise Exception("preheat_enclave_sz: {0} should be greater than or equal to 0!" + .format(attr['preheat_enclave_sz'])) + + if attr['edmm_lazyfree_th'] < 0 or attr['edmm_lazyfree_th'] > 100: + raise Exception("edmm_lazyfree_th: {0} is a percent value and so ranges between 0 and 100!" + .format(attr['edmm_lazyfree_th'])) print('Attributes:') - print(f' size: {attr["enclave_size"]:#x}') - print(f' thread_num: {attr["thread_num"]}') - print(f' isv_prod_id: {attr["isv_prod_id"]}') - print(f' isv_svn: {attr["isv_svn"]}') - print(f' attr.flags: {attr["flags"].hex()}') - print(f' attr.xfrm: {attr["xfrms"].hex()}') - print(f' misc_select: {attr["misc_select"].hex()}') - print(f' date: {attr["year"]:04d}-{attr["month"]:02d}-{attr["day"]:02d}') + print(f' size: {attr["enclave_size"]:#x}') + print(f' thread_num: {attr["thread_num"]}') + print(f' isv_prod_id: {attr["isv_prod_id"]}') + print(f' isv_svn: {attr["isv_svn"]}') + print(f' attr.flags: {attr["flags"].hex()}') + print(f' attr.xfrm: {attr["xfrms"].hex()}') + print(f' misc_select: {attr["misc_select"].hex()}') + print(f' date: {attr["year"]:04d}-{attr["month"]:02d}-{attr["day"]:02d}') + print(f' edmm_enable_heap: {attr["edmm_enable_heap"]}') + print(f' preheat_enclave_sz: {attr["preheat_enclave_sz"]}') + print(f' edmm_batch_allocation: {attr["edmm_batch_allocation"]}') + print(f' edmm_lazyfree_th: {attr["edmm_lazyfree_th"]}') if manifest_sgx['remote_attestation']: spid = manifest_sgx.get('ra_client_spid', '')