Skip to content
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
]
)
98 changes: 93 additions & 5 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 @@ -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 @@ -518,6 +519,10 @@ int load_trusted_or_allowed_file(struct trusted_file* tf, PAL_HANDLE file, bool
}

spinlock_lock(&g_trusted_file_lock);
if (!(tf->cache = lruc_create()))
return -PAL_ERROR_NOMEM;
tf->usage_count = 0;

if (tf->chunk_hashes) {
*out_chunk_hashes = tf->chunk_hashes;
spinlock_unlock(&g_trusted_file_lock);
Expand Down Expand Up @@ -624,7 +629,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(struct trusted_file* tf, 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 && tf->usage_count <= 10) {
spinlock_lock(&g_trusted_file_lock);
tf->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 (tf->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(tf->cache, chunk_number, new_chunk)) {
free(new_chunk);
return -PAL_ERROR_NOMEM;
}

if (lruc_size(tf->cache) > g_tf_max_chunks_in_cache) {
free(lruc_get_last(tf->cache));
lruc_remove_last(tf->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(struct trusted_file* tf, 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 +693,36 @@ 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;

spinlock_lock(&g_trusted_file_lock);
chunk = (struct tf_chunk*)lruc_get(tf->cache, chunk_number);
spinlock_unlock(&g_trusted_file_lock);

if (g_tf_max_chunks_in_cache > 0 && 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,22 +734,32 @@ 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(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;
}

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 Down Expand Up @@ -702,6 +789,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
8 changes: 5 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 @@ -72,9 +73,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(struct trusted_file* tf, 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);
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 usage_count; /* gives a hint on how many times a file was used */
char uri[]; /* must be NULL-terminated */
};

struct tf_chunk {
uint64_t chunk_number;
uint8_t data[TRUSTED_CHUNK_SIZE];
};
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
25 changes: 18 additions & 7 deletions pal/src/host/linux-sgx/pal_files.c
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,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 +185,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->file.tf, 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 +225,17 @@ static void file_destroy(PAL_HANDLE handle) {
ocall_munmap_untrusted(handle->file.umem, handle->file.total);
}

spinlock_lock(&g_trusted_file_lock);
if (handle->file.tf->cache) {
struct tf_chunk *chunk;
while ((chunk = lruc_get_last(handle->file.tf->cache)) != NULL) {
free(chunk);
lruc_remove_last(handle->file.tf->cache);
}
lruc_destroy(handle->file.tf->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 +320,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->file.tf, 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
1 change: 1 addition & 0 deletions pal/src/host/linux-sgx/pal_host.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ typedef struct {
sgx_chunk_hash_t* chunk_hashes; /* array of hashes of file chunks */
void* umem; /* valid only when chunk_hashes != NULL */
bool seekable; /* regular files are seekable, FIFO pipes are not */
struct trusted_file* tf;
} file;

struct {
Expand Down
Loading