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

4 changes: 4 additions & 0 deletions CI-Examples/nginx/nginx.manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ 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' }}

# One trusted-file chunk of 16k size is enough to cover common Nginx static HTTP file of 10k size
# which will be cached by Gramine
sgx.tf_max_chunks_in_cache = 1

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 have an optimization of keeping the trusted
file's content in enclave memory, which implies re-reading and re-hashing the
same file contents every time the file is read at the same offset. 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.
14 changes: 14 additions & 0 deletions common/src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ common_src_utils = files(
'string_utils.c',
)

common_src_lru_cache = files(
'lru_cache.c',
)

common_src_internal = files(
'avl_tree.c',
'init.c',
Expand Down Expand Up @@ -45,6 +49,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,
]
)
97 changes: 93 additions & 4 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;
extern int64_t g_tf_max_chunks_in_cache;

/*
* SGX's EGETKEY(SEAL_KEY) uses three masks as key-derivation material:
Expand Down Expand Up @@ -402,7 +403,7 @@ int sgx_get_seal_key(uint16_t key_policy, sgx_key_128bit_t* out_seal_key) {

DEFINE_LISTP(trusted_file);
static LISTP_TYPE(trusted_file) g_trusted_file_list = LISTP_INIT;
static spinlock_t g_trusted_file_lock = INIT_SPINLOCK_UNLOCKED;
spinlock_t g_trusted_file_lock = INIT_SPINLOCK_UNLOCKED;
static int g_file_check_policy = FILE_CHECK_POLICY_STRICT;

static void find_path_in_uri(const char* uri, size_t uri_len, const char** out_path,
Expand Down Expand Up @@ -516,8 +517,11 @@ int load_trusted_or_allowed_file(struct trusted_file* tf, PAL_HANDLE file, bool
goto fail;
}
}

assert(!(file->file.cache));
spinlock_lock(&g_trusted_file_lock);
if (g_tf_max_chunks_in_cache > 0 && !(file->file.cache = lruc_create()))
return -PAL_ERROR_NOMEM;

if (tf->chunk_hashes) {
*out_chunk_hashes = tf->chunk_hashes;
spinlock_unlock(&g_trusted_file_lock);
Expand Down Expand Up @@ -624,7 +628,53 @@ static void set_file_check_policy(int policy) {
g_file_check_policy = policy;
}

int copy_and_verify_trusted_file(const char* path, uint8_t* buf, const void* umem,
static int tf_append_chunk(PAL_HANDLE handle, uint8_t* chunk,
uint64_t chunk_size, uint64_t chunk_number) {
if (g_tf_max_chunks_in_cache == 0)
return 0;

// Counts the number of times a file is open and reused
if (chunk_number == 0 && handle->file.usage_count <= 10) {
spinlock_lock(&g_trusted_file_lock);
handle->file.usage_count++;
spinlock_unlock(&g_trusted_file_lock);
}

// Add file chunks to cache only if the file is reused for 10 times or more
if (handle->file.usage_count > 10) {
struct tf_chunk* new_chunk = (struct tf_chunk*)malloc(sizeof(struct tf_chunk));
if (!new_chunk) {
return -PAL_ERROR_NOMEM;
}
new_chunk->chunk_number = chunk_number;
memcpy(new_chunk->data, chunk, chunk_size);

spinlock_lock(&g_trusted_file_lock);
if (!lruc_add(handle->file.cache, chunk_number, new_chunk)) {
free(new_chunk);
return -PAL_ERROR_NOMEM;
}

if (lruc_size(handle->file.cache) > (size_t) g_tf_max_chunks_in_cache) {
free(lruc_get_last(handle->file.cache));
lruc_remove_last(handle->file.cache);
#ifdef DEBUG
static int tf_cache_log_throttler = 0;
if (++tf_cache_log_throttler == 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.");
tf_cache_log_throttler = 0;
}
#endif /* DEBUG */
}
spinlock_unlock(&g_trusted_file_lock);

}
return 0;
}

int copy_and_verify_trusted_file(PAL_HANDLE handle, 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) {
int ret = 0;
Expand All @@ -642,10 +692,38 @@ 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++) {

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;
struct tf_chunk *chunk = NULL;

if (g_tf_max_chunks_in_cache > 0) {
spinlock_lock(&g_trusted_file_lock);
chunk = (struct tf_chunk*)lruc_get(handle->file.cache, chunk_number);
spinlock_unlock(&g_trusted_file_lock);
}

if (chunk != 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;
}

/* we didn't find the chunk in the trusted-file cache, must copy into enclave and add to*/
/* the trusted-file cache */
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 @@ -657,9 +735,14 @@ int copy_and_verify_trusted_file(const char* path, uint8_t* buf, const void* ume
/* 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)) {
ret = -PAL_ERROR_DENIED;
goto failed;
}

ret = tf_append_chunk(handle, 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;
Expand All @@ -670,9 +753,14 @@ int copy_and_verify_trusted_file(const char* path, uint8_t* buf, const void* ume
* 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;
}

ret = tf_append_chunk(handle, 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 Down Expand Up @@ -702,6 +790,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: 6 additions & 3 deletions pal/src/host/linux-sgx/enclave_tf.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "pal.h"
#include "pal_linux_types.h"

extern spinlock_t g_trusted_file_lock;
int init_seal_key_material(void);

int init_file_check_policy(void);
Expand Down Expand Up @@ -60,6 +61,7 @@ int load_trusted_or_allowed_file(struct trusted_file* tf, PAL_HANDLE file, bool
/*!
* \brief Copy and check file contents from untrusted outside buffer to in-enclave buffer
*
* \param tf Trusted file struct corresponding to this file.
* \param path File path (currently only for a log message).
* \param buf In-enclave buffer where contents of the file are copied.
* \param umem Start of untrusted file memory mapped outside the enclave.
Expand All @@ -72,9 +74,10 @@ int load_trusted_or_allowed_file(struct trusted_file* tf, PAL_HANDLE file, bool
*
* \returns 0 on success, negative error code on failure
*/
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);
int copy_and_verify_trusted_file(PAL_HANDLE handle, 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);

int init_trusted_files(void);
int init_allowed_files(void);
1 change: 1 addition & 0 deletions pal/src/host/linux-sgx/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ libpal_sgx = shared_library('pal',
],

dependencies: [
common_lru_cache_dep,
common_dep,
cryptoadapter_dep,
ioctls_dep,
Expand Down
29 changes: 22 additions & 7 deletions pal/src/host/linux-sgx/pal_files.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#include "path_utils.h"
#include "stat.h"

extern int64_t g_tf_max_chunks_in_cache;

/* this macro is used to emulate mmap() via pread() in chunks of 128MB (mmapped files may be many
* GBs in size, and a pread OCALL could fail with -ENOMEM, so we cap to reasonably small size) */
#define MAX_READ_SIZE (PRESET_PAGESIZE * 1024 * 32)
Expand Down Expand Up @@ -64,6 +66,8 @@ static int file_open(PAL_HANDLE* handle, const char* type, const char* uri,
}

init_handle_hdr(hdl, PAL_TYPE_FILE);
hdl->file.cache = NULL;
hdl->file.usage_count = 0;
hdl->flags |= PAL_HANDLE_FD_READABLE | PAL_HANDLE_FD_WRITABLE;

hdl->file.realpath = normpath;
Expand Down Expand Up @@ -144,7 +148,7 @@ static int file_open(PAL_HANDLE* handle, const char* type, const char* uri,
hdl->file.chunk_hashes = chunk_hashes;
hdl->file.total = total;
hdl->file.umem = umem;

hdl->file.tf = tf;
*handle = hdl;
return 0;

Expand Down Expand Up @@ -185,9 +189,9 @@ static int64_t file_read(PAL_HANDLE handle, uint64_t offset, uint64_t count, voi
off_t aligned_offset = ALIGN_DOWN(offset, TRUSTED_CHUNK_SIZE);
off_t aligned_end = ALIGN_UP(end, TRUSTED_CHUNK_SIZE);

ret = copy_and_verify_trusted_file(handle->file.realpath, buffer, handle->file.umem,
aligned_offset, aligned_end, offset, end, chunk_hashes,
total);
ret = copy_and_verify_trusted_file(handle, handle->file.realpath, buffer,
handle->file.umem, aligned_offset, aligned_end, offset, end,
chunk_hashes, total);
if (ret < 0)
return ret;

Expand Down Expand Up @@ -225,6 +229,17 @@ static void file_destroy(PAL_HANDLE handle) {
ocall_munmap_untrusted(handle->file.umem, handle->file.total);
}

if (g_tf_max_chunks_in_cache > 0 && handle->file.cache) {
spinlock_lock(&g_trusted_file_lock);
struct tf_chunk* chunk;
while ((chunk = lruc_get_last(handle->file.cache)) != NULL) {
free(chunk);
lruc_remove_last(handle->file.cache);
}
lruc_destroy(handle->file.cache);
spinlock_unlock(&g_trusted_file_lock);
}

int ret = ocall_close(handle->file.fd);
if (ret < 0) {
log_error("closing file host fd %d failed: %s", handle->file.fd, unix_strerror(ret));
Expand Down Expand Up @@ -309,9 +324,9 @@ static int file_map(PAL_HANDLE handle, void* addr, pal_prot_flags_t prot, uint64
goto out;
}

ret = copy_and_verify_trusted_file(handle->file.realpath, addr, handle->file.umem,
aligned_offset, aligned_end, offset, end, chunk_hashes,
handle->file.total);
ret = copy_and_verify_trusted_file(handle, handle->file.realpath, addr,
handle->file.umem, aligned_offset, aligned_end,
offset, end, chunk_hashes, handle->file.total);
if (ret < 0) {
log_error("file_map - copy & verify on trusted file: %s", pal_strerror(ret));
goto out;
Expand Down
Loading