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

13 changes: 12 additions & 1 deletion CI-Examples/nginx/nginx-gramine.conf.template
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# Uncomment "user nobody;" below to switch to this user. If you run under root, use
# "user root;" instead. Typically there is no need to specify a non-default user.
#user nobody;
worker_processes 4;
worker_processes auto;

#error_log logs/error.log;
#error_log logs/error.log notice;
Expand All @@ -30,6 +30,17 @@ events {
}

http {
access_log off;
client_body_buffer_size 80k;
client_max_body_size 9m;
client_header_buffer_size 1k;
client_body_timeout 10;
client_header_timeout 10;
send_timeout 10;
open_file_cache max=1024 inactive=10s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
include mime.types;
default_type application/octet-stream;
sendfile on;
Expand Down
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.
1 change: 1 addition & 0 deletions common/src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ common_src_internal = files(
'avl_tree.c',
'init.c',
'location.c',
'lru_cache.c',
'pal_error.c',
'printf.c',
'socket_utils.c',
Expand Down
8 changes: 5 additions & 3 deletions common/src/protected_files/meson.build
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
protected_files_inc = [
include_directories('.'),
include_directories('.',
'../../../pal/include/pal',
'../../../pal/include/arch' / host_machine.cpu_family(),
'../../../pal/include/arch' / host_machine.cpu_family() / 'linux'),
common_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 @@ -18,5 +19,6 @@ protected_files_dep = declare_dependency(

dependencies: [
uthash_dep,
common_dep,
]
)
144 changes: 106 additions & 38 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;
}

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 frequenty of this log indicates Trusted files chunks exceed the"
" `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,59 +678,91 @@ 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;

sgx_chunk_hash_t chunk_hash[2]; /* each chunk_hash is 128 bits in size but we need 256 */
if (lruc_find(tf->cache, chunk_number) != NULL) {
tf_chunk_t* chunk = lruc_get(tf->cache, chunk_number);

LIB_SHA256_CONTEXT chunk_sha;
ret = lib_SHA256Init(&chunk_sha);
if (ret < 0)
goto failed;
if (chunk_offset >= offset && chunk_end <= end) {
memcpy(buf_pos, chunk->data, chunk_size);

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 */
if (!sgx_copy_to_enclave(buf_pos, chunk_size, umem + chunk_offset, chunk_size)) {
goto failed;
buf_pos += chunk_size;
} else {
memcpy(tmp_chunk, chunk->data, chunk_size);

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, tmp_chunk + copy_start - chunk_offset, copy_end - copy_start);
buf_pos += copy_end - copy_start;
}
}
else {
sgx_chunk_hash_t chunk_hash[2]; /* each chunk_hash is 128 bits in size but we need 256 */

ret = lib_SHA256Update(&chunk_sha, buf_pos, chunk_size);
LIB_SHA256_CONTEXT chunk_sha;
ret = lib_SHA256Init(&chunk_sha);
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 */
if (!sgx_copy_to_enclave(tmp_chunk, chunk_size, umem + chunk_offset, chunk_size)) {
goto failed;
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 */
if (!sgx_copy_to_enclave(buf_pos, chunk_size, umem + chunk_offset, chunk_size)) {
goto failed;
}

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 */
if (!sgx_copy_to_enclave(tmp_chunk, chunk_size, umem + chunk_offset, chunk_size)) {
goto failed;
}

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;

/* determine which part of the chunk is needed by the caller */
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, tmp_chunk + copy_start - chunk_offset, copy_end - copy_start);
buf_pos += copy_end - copy_start;
}

ret = lib_SHA256Update(&chunk_sha, tmp_chunk, chunk_size);
ret = lib_SHA256Final(&chunk_sha, (uint8_t*)&chunk_hash[0]);
if (ret < 0)
goto failed;

/* determine which part of the chunk is needed by the caller */
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, tmp_chunk + copy_start - chunk_offset, copy_end - copy_start);
buf_pos += copy_end - copy_start;
}

ret = lib_SHA256Final(&chunk_sha, (uint8_t*)&chunk_hash[0]);
if (ret < 0)
goto failed;

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);
ret = -PAL_ERROR_DENIED;
goto failed;
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);
ret = -PAL_ERROR_DENIED;
goto failed;
}
}
}

Expand Down
12 changes: 12 additions & 0 deletions pal/src/host/linux-sgx/enclave_tf.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,15 @@ int copy_and_verify_trusted_file(const char* path, uint8_t* buf, const void* ume

int init_trusted_files(void);
int init_allowed_files(void);

/*!
* \brief Add trusted file chunk to the cache
* \param tf Trusted file structure.
* \param chunk Trusted file chunk data.
* \param chunk_size Trusted file chunk size.
* \param chunk_number Trusted file chunk number.
*
* \returns 0 on success, negative error code on failure
*/
int tf_append_chunk(struct trusted_file* tf, uint8_t* chunk,
uint64_t chunk_size, uint64_t chunk_number);
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