Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Page Cache support for trusted files #1776

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
3 changes: 3 additions & 0 deletions CI-Examples/nginx/nginx.manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}
sgx.enclave_size = "512M"
sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '4' }}

# Tune this parameters based on your application and system configuration for best performance
sgx.tf_max_chunks_in_cache = 48

sgx.trusted_files = [
"file:{{ gramine.libos }}",
"file:{{ install_dir }}/sbin/nginx",
Expand Down
14 changes: 14 additions & 0 deletions Documentation/manifest-syntax.rst
Original file line number Diff line number Diff line change
Expand Up @@ -968,6 +968,20 @@ Marking files as trusted is especially useful for shared libraries: a |~|
trusted library cannot be silently replaced by a malicious host because the hash
verification will fail.

.. _trusted-files-max-chunks-in-cache:

Trusted files Page Cache optimization: maximum chunks in cache
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

::

sgx.tf_max_chunks_in_cache = [NUM]
(Default: 0)

This syntax specifies a limit on the number of chunks in the cache for trusted
files. By default, this optimization is disabled as the limit should be
application-specific.

.. _encrypted-files:

Encrypted files
Expand Down
13 changes: 13 additions & 0 deletions Documentation/performance.rst
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,19 @@ in Gramine:
all IPC is transparently encrypted/decrypted using the TLS-PSK with AES-GCM
crypto.

Cache optimization for trusted files
------------------------------------

As the trusted file is read, it is split into chunks, and we compute SHA256
hash for each chunk. Gramine doesn't implement a Page Cache feature, so it
doesn't have the optimization of keeping the trusted file in enclave memory.
To address this performance bottleneck, instead of loading file chunks in the
enclave each time the file is read, the file chunks are kept in cache. This
optimization is by default disabled, but can be enabled and tuned according to
the needs of the application via the manifest option
``sgx.tf_max_chunks_in_cache``. See :ref:`trusted-files-max-chunks-in-cache`
for more information.

.. _choice_of_sgx_machine:

Choice of SGX machine
Expand Down
File renamed without changes.
File renamed without changes.
15 changes: 15 additions & 0 deletions common/src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ common_src_utils = files(
'string_utils.c',
)

common_src_lru_cache = files(
'lru_cache.c',
)

common_src_internal = files(
'avl_tree.c',
'init.c',
'location.c',
'lru_cache.c',
'pal_error.c',
'printf.c',
'socket_utils.c',
Expand Down Expand Up @@ -45,6 +50,16 @@ common_utils_dep = declare_dependency(
include_directories: common_inc,
)

common_lru_cache_dep = declare_dependency(
sources: common_src_lru_cache,

include_directories: common_inc,

dependencies: [
uthash_dep,
],
)

common_dep = declare_dependency(
sources: common_src_internal,

Expand Down
5 changes: 1 addition & 4 deletions common/src/protected_files/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ protected_files_inc = [

protected_files_dep = declare_dependency(
sources: [
'lru_cache.c',
'protected_files.c',

'lru_cache.h',
'protected_files_format.h',
'protected_files.h',
'protected_files_internal.h',
Expand All @@ -17,6 +14,6 @@ protected_files_dep = declare_dependency(
include_directories: protected_files_inc,

dependencies: [
uthash_dep,
common_lru_cache_dep,
]
)
82 changes: 76 additions & 6 deletions pal/src/host/linux-sgx/enclave_framework.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ static int register_file(const char* uri, const char* hash_str, bool check_dupli
uintptr_t g_enclave_base;
uintptr_t g_enclave_top;
bool g_allowed_files_warn = false;
size_t g_tf_max_chunks_in_cache = 0;

/*
* SGX's EGETKEY(SEAL_KEY) uses three masks as key-derivation material:
Expand Down Expand Up @@ -624,6 +625,41 @@ static void set_file_check_policy(int policy) {
g_file_check_policy = policy;
}

static int tf_append_chunk(struct trusted_file* tf, uint8_t* chunk,
uint64_t chunk_size, uint64_t chunk_number) {
if (chunk_number == 0) {
tf->times_first_chunk_loaded++;
}

if (tf->times_first_chunk_loaded > 1) {
tf_chunk_t* new_chunk = calloc(1, sizeof(*new_chunk));
if (!new_chunk) {
return -PAL_ERROR_NOMEM;
}
new_chunk->chunk_number = chunk_number;
memcpy(new_chunk->data, chunk, chunk_size);

if (!lruc_add(tf->cache, chunk_number, new_chunk)) {
free(new_chunk);
return -PAL_ERROR_NOMEM;
}
if (lruc_size(tf->cache) > (size_t)g_tf_max_chunks_in_cache) {
free(lruc_get_last(tf->cache));
lruc_remove_last(tf->cache);
#ifdef DEBUG
static int lcu_remove_count = 0;
if (g_tf_max_chunks_in_cache > 0 && ++lcu_remove_count == 100) {
log_always("High frequency of this log indicates trusted files chunks exceed the"
" `sgx.tf_max_chunks_in_cache` limit. Please increase it in the manifest"
" file to get the best performance.");
lcu_remove_count = 0;
}
#endif /* DEBUG */
}
}
return 0;
}

int copy_and_verify_trusted_file(const char* path, uint8_t* buf, const void* umem,
off_t aligned_offset, off_t aligned_end, off_t offset, off_t end,
sgx_chunk_hash_t* chunk_hashes, size_t file_size) {
Expand All @@ -642,10 +678,29 @@ int copy_and_verify_trusted_file(const char* path, uint8_t* buf, const void* ume

uint8_t* buf_pos = buf;
off_t chunk_offset = aligned_offset;
for (; chunk_offset < aligned_end; chunk_offset += TRUSTED_CHUNK_SIZE, chunk_hashes_item++) {

struct trusted_file* tf = get_trusted_or_allowed_file(path);
int chunk_number = chunk_offset/TRUSTED_CHUNK_SIZE;

for (; chunk_offset < aligned_end; chunk_offset += TRUSTED_CHUNK_SIZE, chunk_hashes_item++, chunk_number++) {
size_t chunk_size = MIN(file_size - chunk_offset, TRUSTED_CHUNK_SIZE);
off_t chunk_end = chunk_offset + chunk_size;

tf_chunk_t* chunk;
if (g_tf_max_chunks_in_cache > 0 && (chunk = lruc_get(tf->cache, chunk_number)) != NULL) {
if (chunk_offset >= offset && chunk_end <= end) {
memcpy(buf_pos, chunk->data, chunk_size);

buf_pos += chunk_size;
} else {
off_t copy_start = MAX(chunk_offset, offset);
off_t copy_end = MIN(chunk_offset + (off_t)chunk_size, end);
assert(copy_end > copy_start);

memcpy(buf_pos, chunk->data + copy_start - chunk_offset, copy_end - copy_start);
buf_pos += copy_end - copy_start;
}
continue;
}
sgx_chunk_hash_t chunk_hash[2]; /* each chunk_hash is 128 bits in size but we need 256 */

LIB_SHA256_CONTEXT chunk_sha;
Expand All @@ -655,24 +710,38 @@ int copy_and_verify_trusted_file(const char* path, uint8_t* buf, const void* ume

if (chunk_offset >= offset && chunk_end <= end) {
/* if current chunk-to-copy completely resides in the requested region-to-copy,
* directly copy into buf (without a scratch buffer) and hash in-place */
* directly copy into buf (without a scratch buffer) and hash in-place */
if (!sgx_copy_to_enclave(buf_pos, chunk_size, umem + chunk_offset, chunk_size)) {
ret = -PAL_ERROR_DENIED;
goto failed;
}

if(g_tf_max_chunks_in_cache > 0) {
ret = tf_append_chunk(tf, buf_pos, chunk_size, chunk_number);
if (ret < 0)
goto failed;
}

ret = lib_SHA256Update(&chunk_sha, buf_pos, chunk_size);
if (ret < 0)
goto failed;

buf_pos += chunk_size;
} else {
/* if current chunk-to-copy only partially overlaps with the requested region-to-copy,
* read the file contents into a scratch buffer, verify hash and then copy only the part
* needed by the caller */
* read the file contents into a scratch buffer, verify hash and then copy only the part
* needed by the caller */
if (!sgx_copy_to_enclave(tmp_chunk, chunk_size, umem + chunk_offset, chunk_size)) {
ret = -PAL_ERROR_DENIED;
goto failed;
}

if(g_tf_max_chunks_in_cache > 0) {
ret = tf_append_chunk(tf, tmp_chunk, chunk_size, chunk_number);
if (ret < 0)
goto failed;
}

ret = lib_SHA256Update(&chunk_sha, tmp_chunk, chunk_size);
if (ret < 0)
goto failed;
Expand All @@ -692,7 +761,7 @@ int copy_and_verify_trusted_file(const char* path, uint8_t* buf, const void* ume

if (memcmp(chunk_hashes_item, &chunk_hash[0], sizeof(*chunk_hashes_item))) {
log_error("Accessing file '%s' is denied: incorrect hash of file chunk at %lu-%lu.",
path, chunk_offset, chunk_end);
path, chunk_offset, chunk_end);
ret = -PAL_ERROR_DENIED;
goto failed;
}
Expand All @@ -702,6 +771,7 @@ int copy_and_verify_trusted_file(const char* path, uint8_t* buf, const void* ume
return 0;

failed:
assert(ret < 0);
free(tmp_chunk);
memset(buf, 0, end - offset);
return ret;
Expand Down
9 changes: 9 additions & 0 deletions pal/src/host/linux-sgx/enclave_tf_structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include <stdint.h>

#include "list.h"
#include "lru_cache.h"
#include "pal_linux_defs.h"

enum {
FILE_CHECK_POLICY_STRICT = 0,
Expand Down Expand Up @@ -35,5 +37,12 @@ struct trusted_file {
sgx_file_hash_t file_hash; /* hash over the whole file, retrieved from the manifest */
sgx_chunk_hash_t* chunk_hashes; /* array of hashes over separate file chunks */
size_t uri_len;
lruc_context_t* cache;
uint64_t times_first_chunk_loaded; /* gives a hint how many times a file's content was used */
char uri[]; /* must be NULL-terminated */
};

typedef struct _tf_chunk {
uint64_t chunk_number;
uint8_t data[TRUSTED_CHUNK_SIZE];
} tf_chunk_t;
2 changes: 2 additions & 0 deletions pal/src/host/linux-sgx/pal_files.c
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ static int file_open(PAL_HANDLE* handle, const char* type, const char* uri,

/* we lazily update the size of the trusted file */
tf->size = st.st_size;
tf->cache = lruc_create();
tf->times_first_chunk_loaded = 0;
ret = load_trusted_or_allowed_file(tf, hdl, do_create, &chunk_hashes, &total, &umem);
if (ret < 0)
goto fail;
Expand Down
12 changes: 12 additions & 0 deletions pal/src/host/linux-sgx/pal_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ extern void* g_enclave_top;
extern bool g_allowed_files_warn;
extern uint64_t g_tsc_hz;
extern size_t g_unused_tcs_pages_num;
extern int64_t g_tf_max_chunks_in_cache;

static int print_warnings_on_insecure_configs(PAL_HANDLE parent_process) {
int ret;
Expand Down Expand Up @@ -864,6 +865,17 @@ noreturn void pal_linux_main(void* uptr_libpal_uri, size_t libpal_uri_len, void*
ocall_exit(1, /*is_exitgroup=*/true);
}

ret = toml_int_in(g_pal_public_state.manifest_root, "sgx.tf_max_chunks_in_cache",
/*defaultval=*/0, &g_tf_max_chunks_in_cache);
if (ret < 0) {
log_error("Cannot parse 'sgx.tf_max_chunks_in_cache'");
ocall_exit(1, /*is_exitgroup=*/true);
}
if (g_tf_max_chunks_in_cache < 0) {
log_error("Invalid 'sgx.tf_max_chunks_in_cache' value");
ocall_exit(1, /*is_exitgroup=*/true);
}

ret = toml_bool_in(g_pal_public_state.manifest_root,
"sys.enable_extra_runtime_domain_names_conf", /*defaultval*/false,
&g_pal_public_state.extra_runtime_domain_names_conf);
Expand Down