Skip to content

Commit

Permalink
[PAL/Linux-SGX] Add EDMM support for dynamic thread creation
Browse files Browse the repository at this point in the history
This commit adds support for dynamically adding threads during enclave
lifetime, based on the EDMM feature of changing regular enclave pages
(REG) to thread-describing (TCS) pages. This allows to remove the hard
limit of maximum number of enclave threads (`sgx.max_threads` in the
manifest), and Gramine will automatically adjust to the app demands.

Now, if EDMM is enabled (`sgx.edmm_enable = true`), then
`sgx.max_threads` specifies the number of pre-allocated thread slots
(must be at least `1`). However, the maximum number of threads can
exceed this limit during enclave execution, by dynamically allocating
new thread slots.

Manifests of all examples and tests are modified to have
`sgx.max_threads = 1` when EDMM is enabled. This is done purely for
increased testing of this new dynamic thread creation feature.

Signed-off-by: Li, Xun <xun.li@intel.com>
  • Loading branch information
llly authored and dimakuv committed Dec 1, 2023
1 parent 68b5b5e commit fa437cb
Show file tree
Hide file tree
Showing 41 changed files with 525 additions and 177 deletions.
2 changes: 1 addition & 1 deletion CI-Examples/bash/manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ fs.mounts = [
sgx.debug = true
sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}
sgx.enclave_size = "512M"
sgx.max_threads = 4
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '4' }}

sgx.trusted_files = [
"file:{{ gramine.libos }}",
Expand Down
2 changes: 1 addition & 1 deletion CI-Examples/blender/blender.manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ sgx.debug = true
sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}
sys.stack.size = "8M"
sgx.enclave_size = "2048M"
sgx.max_threads = 64
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '64' }}

sgx.trusted_files = [
"file:{{ gramine.libos }}",
Expand Down
2 changes: 1 addition & 1 deletion CI-Examples/lighttpd/lighttpd.manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ fs.mounts = [
sgx.debug = true
sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}
sgx.enclave_size = "256M"
sgx.max_threads = 3
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '3' }}

sgx.trusted_files = [
"file:{{ gramine.libos }}",
Expand Down
2 changes: 1 addition & 1 deletion CI-Examples/memcached/memcached.manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ fs.mounts = [

sgx.debug = true
sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}
sgx.max_threads = 16
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '16' }}

# Memcached does not fail explicitly when enclave memory is exhausted. Instead, Memcached goes into
# infinite loop without a listening socket. You can trigger this incorrect behavior by increasing
Expand Down
2 changes: 1 addition & 1 deletion CI-Examples/nginx/nginx.manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ fs.mounts = [
sgx.debug = true
sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}
sgx.enclave_size = "512M"
sgx.max_threads = 4
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '4' }}

sgx.trusted_files = [
"file:{{ gramine.libos }}",
Expand Down
2 changes: 1 addition & 1 deletion CI-Examples/python/python.manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ sys.enable_extra_runtime_domain_names_conf = true
sgx.debug = true
sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}
sgx.enclave_size = "1G"
sgx.max_threads = 32
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '32' }}

sgx.remote_attestation = "{{ ra_type }}"
sgx.ra_client_spid = "{{ ra_client_spid }}"
Expand Down
2 changes: 1 addition & 1 deletion CI-Examples/ra-tls-mbedtls/client.manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ sys.enable_extra_runtime_domain_names_conf = true
sgx.debug = true
sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}
sgx.enclave_size = "512M"
sgx.max_threads = 4
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '4' }}

sgx.trusted_files = [
"file:{{ gramine.libos }}",
Expand Down
6 changes: 5 additions & 1 deletion CI-Examples/redis/redis-server.manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,15 @@ sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}
# that SGX v1 requires to specify the maximum number of simultaneous threads at
# enclave creation time.
#
# Note that when EDMM is enabled, there is no need to specify a particular
# number of threads, as Gramine will automatically adjust to the application
# demands.
#
# Note that internally Gramine may spawn two additional threads, one for IPC
# and one for asynchronous events/alarms. Redis is technically single-threaded
# but spawns couple additional threads to do background bookkeeping. Therefore,
# specifying '8' allows to run a maximum of 6 Redis threads which is enough.
sgx.max_threads = 8
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '8' }}

############################# SGX: TRUSTED FILES ###############################

Expand Down
2 changes: 1 addition & 1 deletion CI-Examples/rust/rust-hyper-http-server.manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ sys.insecure__allow_eventfd = true
# - any threads and threadpools you might be starting
# - helper threads internal to Gramine — see:
# https://gramine.readthedocs.io/en/stable/manifest-syntax.html#number-of-threads
sgx.max_threads = 8
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '8' }}
2 changes: 1 addition & 1 deletion CI-Examples/sqlite/manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ fs.insecure__keys.default = "ffeeddccbbaa99887766554433221100"
sgx.debug = true
sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}
sgx.enclave_size = "256M"
sgx.max_threads = 4
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '4' }}

sgx.trusted_files = [
"file:{{ gramine.libos }}",
Expand Down
18 changes: 13 additions & 5 deletions Documentation/manifest-syntax.rst
Original file line number Diff line number Diff line change
Expand Up @@ -723,11 +723,16 @@ Number of threads
sgx.max_threads = [NUM]
(Default: 4)

This syntax specifies the maximum number of threads that can be created inside
the enclave (recall that SGX |~| v1 requires a |~| predetermined maximum number
of thread slots). The application cannot have more threads than this limit *at
a time* (however, it is possible to create new threads after old threads are
destroyed).
If :term:`EDMM` is not enabled (``sgx.edmm_enable = false``), then this syntax
specifies the maximum number of threads that can be created inside the enclave
(recall that SGX |~| v1 requires a |~| predetermined maximum number of thread
slots). The application cannot have more threads than this limit *at a time*
(however, it is possible to create new threads after old threads are destroyed).

If :term:`EDMM` is enabled (``sgx.edmm_enable = true``), then this syntax
specifies the number of pre-allocated thread slots (must be at least ``1``).
However, the maximum number of threads can exceed this limit during enclave
execution, by dynamically allocating new thread slots.

Note that Gramine uses several helper threads internally:

Expand All @@ -746,6 +751,9 @@ Given these internal threads, ``sgx.max_threads`` should be set to at least
``4`` even for single-threaded applications (to accommodate for the main thread,
the IPC thread, the Async thread and one TLS-handshake thread).

.. note::
This option will be renamed after non-:term:`EDMM` platform support is
dropped.

Number of RPC threads (Exitless feature)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
2 changes: 1 addition & 1 deletion libos/test/abi/x86_64/manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ fs.mounts = [

sgx.debug = true
sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}
sgx.max_threads = 4
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '4' }}

sgx.trusted_files = [
"file:{{ gramine.libos }}",
Expand Down
2 changes: 1 addition & 1 deletion libos/test/abi/x86_64/stack_arg.manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fs.mounts = [

sgx.debug = true
sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}
sgx.max_threads = 4
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '4' }}

sgx.trusted_files = [
"file:{{ gramine.libos }}",
Expand Down
2 changes: 1 addition & 1 deletion libos/test/abi/x86_64/stack_env.manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fs.mounts = [

sgx.debug = true
sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}
sgx.max_threads = 4
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '4' }}

sgx.trusted_files = [
"file:{{ gramine.libos }}",
Expand Down
2 changes: 1 addition & 1 deletion libos/test/fs/manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ fs.insecure__keys.default = "ffeeddccbbaa99887766554433221100"

sgx.debug = true
sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}
sgx.max_threads = 16
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '16' }}

sgx.allowed_files = [
"file:tmp/",
Expand Down
2 changes: 1 addition & 1 deletion libos/test/regression/bootstrap_cpp.manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ fs.mounts = [
{ path = "/usr/{{ arch_libdir }}", uri = "file:/usr/{{ arch_libdir }}" },
]

sgx.max_threads = 8
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '8' }}
sgx.debug = true
sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}

Expand Down
2 changes: 1 addition & 1 deletion libos/test/regression/manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ fs.mounts = [
{ type = "encrypted", path = "/encrypted_file_mrsigner.dat", uri = "file:encrypted_file_mrsigner.dat", key_name = "_sgx_mrsigner" },
]

sgx.max_threads = 16
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '16' }}
sgx.debug = true
sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}

Expand Down
2 changes: 1 addition & 1 deletion libos/test/regression/multi_pthread.manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ fs.mounts = [
]

# app runs with 4 parallel threads + Gramine has couple internal threads
sgx.max_threads = 8
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '8' }}

sgx.debug = true
sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ fs.mounts = [
]

# app runs with 4 parallel threads + Gramine has couple internal threads
sgx.max_threads = 8
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '8' }}
sgx.insecure__rpc_thread_num = 8

sgx.debug = true
Expand Down
2 changes: 1 addition & 1 deletion libos/test/regression/openmp.manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ fs.mounts = [
{ path = "/usr/{{ arch_libdir }}", uri = "file:/usr/{{ arch_libdir }}" },
]

sgx.max_threads = 32
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '32' }}
sgx.debug = true
sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}

Expand Down
2 changes: 1 addition & 1 deletion libos/test/regression/rwlock.manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ fs.mounts = [
{ path = "/{{ entrypoint }}", uri = "file:{{ binary_dir }}/{{ entrypoint }}" },
]

sgx.max_threads = 200
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '200' }}
sgx.debug = true
sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fs.mounts = [
{ path = "/bin", uri = "file:/bin" },
]

sgx.max_threads = 16
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '16' }}
sgx.debug = true
sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}

Expand Down
2 changes: 1 addition & 1 deletion libos/test/regression/socket_ioctl.manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ fs.mounts = [
{ path = "/{{ entrypoint }}", uri = "file:{{ binary_dir }}/{{ entrypoint }}" },
]

sgx.max_threads = 4
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '4' }}
sgx.debug = true
sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}

Expand Down
2 changes: 1 addition & 1 deletion pal/regression/Thread2.manifest.template
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
loader.entrypoint = "file:{{ binary_dir }}/{{ entrypoint }}"

sgx.max_threads = 2
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '2' }}
sgx.enable_stats = true
sgx.debug = true
sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}
Expand Down
9 changes: 9 additions & 0 deletions pal/regression/Thread2_edmm.manifest.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{% set entrypoint = "Thread2" -%}

loader.entrypoint = "file:{{ binary_dir }}/{{ entrypoint }}"

sgx.max_threads = 1
sgx.debug = true
sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}

sgx.trusted_files = [ "file:{{ binary_dir }}/{{ entrypoint }}" ]
2 changes: 1 addition & 1 deletion pal/regression/Thread2_exitless.manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

loader.entrypoint = "file:{{ binary_dir }}/{{ entrypoint }}"

sgx.max_threads = 2
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '2' }}
sgx.insecure__rpc_thread_num = 2
sgx.enable_stats = true
sgx.debug = true
Expand Down
16 changes: 16 additions & 0 deletions pal/regression/test_pal.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,22 @@ def test_511_thread2_exitless(self):
# Thread Cleanup: Can still start threads.
self.assertIn('Thread 4 ok.', stderr)

@unittest.skipUnless(HAS_SGX, 'This test is only meaningful on SGX PAL')
def test_512_thread2_edmm(self):
if HAS_EDMM:
_, stderr = self.run_binary(['Thread2_edmm'])
self.assertIn('Thread 2 ok.', stderr)
self.assertIn('Thread 3 ok.', stderr)
self.assertNotIn('Exiting thread 3 failed.', stderr)
self.assertIn('Thread 4 ok.', stderr)
else:
try:
self.run_binary(['Thread2_edmm'])
self.fail('expected to return nonzero')
except subprocess.CalledProcessError as e:
stderr = e.stderr.decode()
self.assertIn("PalThreadCreate failed for thread 2.", stderr)

def test_900_misc(self):
_, stderr = self.run_binary(['Misc'])
# Query System Time
Expand Down
1 change: 1 addition & 0 deletions pal/regression/tests.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ manifests = [
"Process4",
"Symbols",
"Thread2",
"Thread2_edmm",
"Thread2_exitless",
"normalize_path",
"printf_test",
Expand Down
1 change: 1 addition & 0 deletions pal/src/host/linux-sgx/enclave_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@ int64_t sgx_getkey(sgx_key_request_t* keyrequest, sgx_key_128bit_t* key);
int sgx_edmm_add_pages(uint64_t addr, size_t count, uint64_t prot);
int sgx_edmm_remove_pages(uint64_t addr, size_t count);
int sgx_edmm_set_page_permissions(uint64_t addr, size_t count, uint64_t prot);
int sgx_edmm_convert_pages_to_tcs(uint64_t addr, size_t count);
20 changes: 20 additions & 0 deletions pal/src/host/linux-sgx/enclave_edmm.c
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,23 @@ int sgx_edmm_set_page_permissions(uint64_t addr, size_t count, uint64_t prot) {

return 0;
}

int sgx_edmm_convert_pages_to_tcs(uint64_t addr, size_t count) {
int ret = ocall_edmm_modify_pages_type(addr, count, SGX_PAGE_TYPE_TCS);
if (ret < 0) {
return unix_to_pal_error(ret);
}

for (size_t i = 0; i < count; i++) {
ret = sgx_eaccept(addr + i * PAGE_SIZE, (SGX_PAGE_TYPE_TCS << SGX_SECINFO_FLAGS_TYPE_SHIFT)
| SGX_SECINFO_FLAGS_MODIFIED);
if (ret < 0) {
log_error("failed to accept modified page type at address %#lx: %d",
addr + i * PAGE_SIZE, ret);
/* Since these errors do not happen in legitimate cases and restoring already accepted
* pages would be cumbersome, we just kill the whole process. */
die_or_inf_loop();
}
}
return 0;
}
5 changes: 2 additions & 3 deletions pal/src/host/linux-sgx/enclave_ocalls.c
Original file line number Diff line number Diff line change
Expand Up @@ -988,15 +988,14 @@ int ocall_resume_thread(void* tcs) {
return retval;
}

int ocall_clone_thread(void) {
int ocall_clone_thread(void* dynamic_tcs) {
int retval = 0;
void* dummy = NULL;
/* FIXME: if there was an EINTR, there may be an untrusted thread left over */
do {
/* clone must happen in the context of current (enclave) thread, cannot use exitless;
* in particular, the new (enclave) thread must have the same signal mask as the current
* enclave thread (and NOT signal mask of the RPC thread) */
retval = sgx_ocall(OCALL_CLONE_THREAD, dummy);
retval = sgx_ocall(OCALL_CLONE_THREAD, dynamic_tcs);
} while (retval == -EINTR);

if (retval < 0 && retval != -ENOMEM && retval != -EAGAIN && retval != -EINVAL &&
Expand Down
2 changes: 1 addition & 1 deletion pal/src/host/linux-sgx/enclave_ocalls.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ int ocall_sched_setaffinity(void* tcs, unsigned long* cpu_mask, size_t cpu_mask_

int ocall_sched_getaffinity(void* tcs, unsigned long* cpu_mask, size_t cpu_mask_len);

int ocall_clone_thread(void);
int ocall_clone_thread(void* dynamic_tcs);

int ocall_create_process(size_t nargs, const char** args, uintptr_t (*reserved_mem_ranges)[2],
size_t reserved_mem_ranges_len, int* out_stream_fd);
Expand Down

0 comments on commit fa437cb

Please sign in to comment.