diff --git a/Documentation/devel/encfiles.rst b/Documentation/devel/encfiles.rst index f31bc8c31e..e17af98854 100644 --- a/Documentation/devel/encfiles.rst +++ b/Documentation/devel/encfiles.rst @@ -542,3 +542,14 @@ Additional details file data. Therefore, the usual NIST limits on the total number of invocations of the encryption operation with the same key would be reached much slower. + +- TCB migration: Gramine supports migration between different CPU SVN versions + by enabling it using the ``allow_tcb_migration`` mount parameter in the + Gramine manifest. This feature allows encrypted files to be accessed even when + the CPU SVN has changed after applying microcode updates. The current CPU SVN + is stored in the mount directory in a file named ``gramine.tcb_info``. On + startup if the CPU SVN has changed, Gramine will unseal the files using the + old CPU SVN and reseal them with the current CPU SVN. This feature can negatively + impact integrity of files, as it allows files from a platform with a lower + security level to be used. It is recommended to use this feature only in controlled + environments where the security implications are understood. \ No newline at end of file diff --git a/Documentation/devel/features.md b/Documentation/devel/features.md index f06c36f1ad..3c00fc19a5 100644 --- a/Documentation/devel/features.md +++ b/Documentation/devel/features.md @@ -1314,6 +1314,8 @@ grows with time, as Gramine adds functionality required by real-world workloads. - ☑ `/dev/attestation/keys/` [25](#attestation) - ☑ `/dev/attestation/keys/_sgx_mrenclave` [25](#attestation) - ☑ `/dev/attestation/keys/_sgx_mrsigner` [25](#attestation) + - ☑ `/dev/attestation/keys/svn/_sgx_mrenclave/` [25](#attestation) + - ☑ `/dev/attestation/cpu_svn` [25](#attestation) - ☑ `/dev/null` [23](#misc) - ☑ `/dev/zero` [23](#misc) - ☑ `/dev/random` [21](#randomness) @@ -3245,6 +3247,10 @@ Gramine `. - ☑ `/dev/attestation/keys/` - ☑ `/dev/attestation/keys/_sgx_mrenclave` (only for SGX) - ☑ `/dev/attestation/keys/_sgx_mrsigner` (only for SGX) + - ☑ `/dev/attestation/keys/svn/_sgx_mrenclave/` (only for SGX) + + - ☑ `/dev/attestation/cpu_svn` (only for SGX) +
diff --git a/Documentation/manifest-syntax.rst b/Documentation/manifest-syntax.rst index f110520f21..2808105907 100644 --- a/Documentation/manifest-syntax.rst +++ b/Documentation/manifest-syntax.rst @@ -1088,7 +1088,7 @@ Encrypted files :: fs.mounts = [ - { type = "encrypted", path = "[PATH]", uri = "[URI]", key_name = "[KEY_NAME]", enable_recovery = [true|false] }, + { type = "encrypted", path = "[PATH]", uri = "[URI]", key_name = "[KEY_NAME]", enable_recovery = [true|false], allow_tcb_migration = [true|false] }, ] fs.insecure__keys.[KEY_NAME] = "[32-character hex value]" @@ -1160,6 +1160,14 @@ or disabling of recovery for different mounted files or directories. Note that enabling this feature can negatively impact performance, as it writes to a second shadow file for later recovery purposes on each flush. +The ``allow_tcb_migration`` mount parameter (default: ``false``) determines whether +the TCB migration feature is enabled for the mount. This feature allows sealed files to +be migrated to latest CPU SVN version after applying microcode updates. This feature +is only valid for ``key_name`` of ``"_sgx_mrenclave"``. Enabling this feature can +negatively impact security, as it allow the enclave to unseal files that were created +with an old potentially vulnerable CPU SVN version. It is the responsibility of the +app developer to verify the integrity of the sealed files if this feature is enabled. + .. _untrusted-shared-memory: Untrusted shared memory diff --git a/Documentation/pal/host-abi.rst b/Documentation/pal/host-abi.rst index 13199586df..332552b7fd 100644 --- a/Documentation/pal/host-abi.rst +++ b/Documentation/pal/host-abi.rst @@ -364,6 +364,15 @@ random bits, to obtain an attestation report and quote, etc. .. doxygenfunction:: PalGetSpecialKey :project: pal +.. doxygenfunction:: PalGetSpecialKeyForSVN + :project: pal + +.. doxygenfunction:: PalGetCPUSVN + :project: pal + +.. doxygenfunction:: PalSetCPUSVN + :project: pal + .. doxygenfunction:: PalDeviceMap :project: pal diff --git a/libos/include/libos_fs.h b/libos/include/libos_fs.h index d6e3567903..b816763a48 100644 --- a/libos/include/libos_fs.h +++ b/libos/include/libos_fs.h @@ -66,6 +66,10 @@ struct libos_mount_params { /* Whether to enable file recovery (used by `chroot_encrypted` filesystem), false if not * applicable */ bool enable_recovery; + + /* Whether to allow TCB migration (used by `chroot_encrypted` filesystem), false if not + * applicable */ + bool allow_tcb_migration; }; struct libos_fs_ops { diff --git a/libos/include/libos_fs_encrypted.h b/libos/include/libos_fs_encrypted.h index 3584a77df7..4b0dbde831 100644 --- a/libos/include/libos_fs_encrypted.h +++ b/libos/include/libos_fs_encrypted.h @@ -24,6 +24,16 @@ #define RECOVERY_FILE_URI_SUFFIX ".gramine.recovery" +#define TCB_INFO_PERM_RW PERM_rw_rw_r__ + +#define TCB_INFO_FILE_NAME "gramine.tcb_info" + +#define CPU_SVN_SIZE 16 + +#define OLD_TCB_FILE_URI_SUFFIX ".old_tcb" + +typedef uint8_t cpu_svn_t[CPU_SVN_SIZE]; + /* * Represents a named key for opening files. The key might not be set yet: value of a key can be * specified in the manifest, or set using `update_encrypted_files_key`. Before the key is set, @@ -68,6 +78,13 @@ struct libos_encrypted_file { */ int init_encrypted_files(void); +/* + * \brief sets the default CPU SVN used for encrypted file keys. + * + * This is only used for testing. + */ +int set_cpu_svn(const cpu_svn_t* cpu_svn); + /* * \brief Retrieve a key. * @@ -95,6 +112,15 @@ int list_encrypted_files_keys(int (*callback)(struct libos_encrypted_files_key* */ int get_or_create_encrypted_files_key(const char* name, struct libos_encrypted_files_key** out_key); +/* + * \brief Retrieve a key with a CPU SVN. + * + * Sets `*out_key` to a key with given name and CPU SVN. Creates a new key. + * + */ +int create_encrypted_files_key_for_svn(const char* name, cpu_svn_t* cpu_svn, + struct libos_encrypted_files_key** out_key); + /* * \brief Read value of given key. * @@ -183,3 +209,5 @@ int encrypted_file_get_size(struct libos_encrypted_file* enc, file_off_t* out_si int encrypted_file_set_size(struct libos_encrypted_file* enc, file_off_t size); int parse_pf_key(const char* key_str, pf_key_t* pf_key); + +int handle_tcb_migration(const char* uri, const char* key_name); diff --git a/libos/src/fs/chroot/encrypted.c b/libos/src/fs/chroot/encrypted.c index 24f976b1e7..a961281ab3 100644 --- a/libos/src/fs/chroot/encrypted.c +++ b/libos/src/fs/chroot/encrypted.c @@ -40,6 +40,7 @@ #include "libos_fs.h" #include "libos_fs_encrypted.h" #include "libos_vma.h" +#include "pal.h" #include "perm.h" #include "stat.h" #include "toml_utils.h" @@ -60,16 +61,33 @@ static int chroot_encrypted_mount(struct libos_mount_params* params, void** moun log_error("'%s' is invalid file URI", params->uri); return -EINVAL; } - + int ret; const char* key_name = params->key_name ?: "default"; + if (params->allow_tcb_migration) { + if (!strcmp(key_name, PAL_KEY_NAME_SGX_MRENCLAVE)) { + log_warning("TCB migration will be supported for %s", params->uri); + ret = handle_tcb_migration(params->uri, key_name); + if (ret < 0) { + log_error("TCB migration failed for %s", params->uri); + return ret; + } + + } else { + log_warning( + "TCB migration is only supported for keys named %s, ignoring " + "allow_tcb_migration for %s", + PAL_KEY_NAME_SGX_MRENCLAVE, params->uri); + } + } struct libos_encrypted_files_key* key; - int ret = get_or_create_encrypted_files_key(key_name, &key); + ret = get_or_create_encrypted_files_key(key_name, &key); if (ret < 0) return ret; *mount_data = key; - return 0; + return ret; + } static ssize_t chroot_encrypted_checkpoint(void** checkpoint, void* mount_data) { diff --git a/libos/src/fs/dev/attestation.c b/libos/src/fs/dev/attestation.c index ff1ee7053e..1af8013110 100644 --- a/libos/src/fs/dev/attestation.c +++ b/libos/src/fs/dev/attestation.c @@ -16,6 +16,7 @@ */ #include "api.h" +#include "hex.h" #include "libos_fs_encrypted.h" #include "libos_fs_pseudo.h" #include "pal.h" @@ -84,6 +85,21 @@ static int user_report_data_save(struct libos_dentry* dent, const char* data, si return 0; } +#ifdef DEBUG +static int cpu_svn_save(struct libos_dentry* dent, const char* data, size_t size) { + log_debug("cpu_svn_save: saving %zu bytes", size); + __UNUSED(dent); + cpu_svn_t cpu_svn; + if (size != sizeof(cpu_svn)) { + log_warning("CPU SVN must be exactly %zu bytes, got %zu", sizeof(cpu_svn), size); + return -EINVAL; + } + memcpy(&cpu_svn, data, sizeof(cpu_svn)); + + return set_cpu_svn(&cpu_svn); +} +#endif /* DEBUG */ + /*! * \brief Modify target info used in `report` pseudo-file. * @@ -239,6 +255,32 @@ static int quote_load(struct libos_dentry* dent, char** out_data, size_t* out_si return 0; } +/*! + * \brief Get CPU SVN of the platform. + */ +static int cpu_svn_load(struct libos_dentry* dent, char** out_data, size_t* out_size) { + __UNUSED(dent); + + cpu_svn_t cpu_svn; + size_t cpu_svn_size = sizeof(cpu_svn); + int ret = PalGetCPUSVN(&cpu_svn, &cpu_svn_size); + if (ret < 0) { + log_warning("PalGetCPUSVN failed: %s", pal_strerror(ret)); + return pal_to_unix_errno(ret); + } + + char* str = calloc(1, cpu_svn_size); + + if (!str) + return -ENOMEM; + + memcpy(str, &cpu_svn, sizeof(cpu_svn)); + + *out_data = str; + *out_size = cpu_svn_size; + return 0; +} + /*! * \brief Get remote attestation type used. * @@ -264,6 +306,22 @@ static bool key_name_exists(struct libos_dentry* parent, const char* name) { return key != NULL; } +static bool key_name_exists_svn(struct libos_dentry* parent, const char* name) { + __UNUSED(parent); + if (strlen(name) != 2 * sizeof(cpu_svn_t)) { + log_warning("key_name_exists_svn: invalid key name length %zu of %s, expected %zu", + strlen(name), name, 2 * sizeof(cpu_svn_t)); + return false; + } + cpu_svn_t cpu_svn; + if (!hex2bytes((char*)name, strlen(name), &cpu_svn, sizeof(cpu_svn_t))) { + log_warning("key_name_exists_svn: invalid key name format"); + return false; + } + + return true; +} + struct key_list_names_data { readdir_callback_t callback; void* arg; @@ -284,6 +342,13 @@ static int key_list_names(struct libos_dentry* parent, readdir_callback_t callba return list_encrypted_files_keys(&key_list_names_callback, &data); } +static int key_list_names_svn(struct libos_dentry* parent, readdir_callback_t callback, void* arg) { + __UNUSED(parent); + __UNUSED(callback); + __UNUSED(arg); + return 0; +} + static int key_load(struct libos_dentry* dent, char** out_data, size_t* out_size) { struct libos_encrypted_files_key* key = get_encrypted_files_key(dent->name); if (!key) @@ -307,6 +372,58 @@ static int key_load(struct libos_dentry* dent, char** out_data, size_t* out_size return 0; } +static int key_load_svn(struct libos_dentry* dent, char** out_data, size_t* out_size) { + if (strlen(dent->name) != 2 * sizeof(cpu_svn_t)) { + log_warning("key_name_exists_svn: invalid key name length"); + return false; + } + cpu_svn_t cpu_svn; + + if (!hex2bytes((char*)dent->name, strlen(dent->name), &cpu_svn, sizeof(cpu_svn_t))) { + log_warning("key_name_exists_svn: invalid key name format"); + return false; + } + + int ret; + + char * key_name = dent->parent->name; + struct libos_encrypted_files_key* key = NULL; + key = calloc(1, sizeof(*key)); + if (!key) { + log_error("Cannot allocate memory for key"); + ret = -ENOMEM; + goto out; + } + ret = create_encrypted_files_key_for_svn(key_name, &cpu_svn, &key); + if (ret < 0) { + log_error("Cannot create or get key for SVN"); + goto out; + } + pf_key_t pf_key; + bool is_set = read_encrypted_files_key(key, &pf_key); + + if (is_set) { + char* buf = malloc(sizeof(pf_key)); + if (!buf) + return -ENOMEM; + memcpy(buf, &pf_key, sizeof(pf_key)); + + *out_data = buf; + *out_size = sizeof(pf_key); + } else { + *out_data = NULL; + *out_size = 0; + } + ret = 0; +out: + if (key) { + if (key->name) + free(key->name); + free(key); + } + return ret; +} + static int key_save(struct libos_dentry* dent, const char* data, size_t size) { struct libos_encrypted_files_key* key = get_encrypted_files_key(dent->name); if (!key) @@ -338,6 +455,13 @@ static int init_sgx_attestation(struct pseudo_node* attestation, struct pseudo_n pseudo_add_str(attestation, "attestation_type", attestation_type_load); pseudo_add_str(attestation, "my_target_info", &my_target_info_load); pseudo_add_str(attestation, "report", &report_load); + struct pseudo_node* cpu_svn = pseudo_add_str(attestation, "cpu_svn", &cpu_svn_load); +#ifdef DEBUG + cpu_svn->perm = PSEUDO_PERM_FILE_RW; + cpu_svn->str.save = &cpu_svn_save; +#else + cpu_svn->perm = PSEUDO_PERM_FILE_R; +#endif /* DEBUG */ struct pseudo_node* user_report_data = pseudo_add_str(attestation, "user_report_data", NULL); user_report_data->perm = PSEUDO_PERM_FILE_RW; @@ -362,6 +486,13 @@ static int init_sgx_attestation(struct pseudo_node* attestation, struct pseudo_n pseudo_add_str(keys, PAL_KEY_NAME_SGX_MRENCLAVE, &key_load); pseudo_add_str(keys, PAL_KEY_NAME_SGX_MRSIGNER, &key_load); + struct pseudo_node* keys_svn = pseudo_add_dir(keys, "svn"); + struct pseudo_node* keys_svn_mrenclave_key = + pseudo_add_dir(keys_svn, PAL_KEY_NAME_SGX_MRENCLAVE); + struct pseudo_node* key_cpu_svn = pseudo_add_str(keys_svn_mrenclave_key, NULL, &key_load_svn); + key_cpu_svn->name_exists = &key_name_exists_svn; + key_cpu_svn->list_names = &key_list_names_svn; + if (!strcmp(g_pal_public_state->attestation_type, "none")) { log_debug("host is Linux-SGX and remote attestation type is 'none', skipping " "/dev/attestation/quote file"); diff --git a/libos/src/fs/libos_fs.c b/libos/src/fs/libos_fs.c index e6ac0f168c..ea018841ae 100644 --- a/libos/src/fs/libos_fs.c +++ b/libos/src/fs/libos_fs.c @@ -122,6 +122,7 @@ static int mount_root(void) { char* fs_root_uri = NULL; char* fs_root_key_name = NULL; bool fs_root_enable_recovery; + bool fs_root_allow_tcb_migration; assert(g_manifest_root); @@ -153,11 +154,19 @@ static int mount_root(void) { ret = -EINVAL; goto out; } + ret = toml_bool_in(g_manifest_root, "fs.root.allow_tcb_migration", /*defaultval=*/false, + &fs_root_allow_tcb_migration); + if (ret < 0) { + log_error("Cannot parse 'fs.root.allow_tcb_migration'"); + ret = -EINVAL; + goto out; + } struct libos_mount_params params = { .path = "/", .key_name = fs_root_key_name, .enable_recovery = fs_root_enable_recovery, + .allow_tcb_migration = fs_root_allow_tcb_migration, }; if (!fs_root_type && !fs_root_uri) { @@ -223,6 +232,7 @@ static int mount_one_nonroot(toml_table_t* mount, const char* prefix) { char* mount_uri = NULL; char* mount_key_name = NULL; bool mount_enable_recovery; + bool mount_allow_tcb_migration; ret = toml_string_in(mount, "type", &mount_type); if (ret < 0) { @@ -259,6 +269,14 @@ static int mount_one_nonroot(toml_table_t* mount, const char* prefix) { goto out; } + ret = toml_bool_in(mount, "allow_tcb_migration", /*defaultval=*/false, + &mount_allow_tcb_migration); + if (ret < 0) { + log_error("Cannot parse '%s.allow_tcb_migration'", prefix); + ret = -EINVAL; + goto out; + } + if (!mount_path) { log_error("No value provided for '%s.path'", prefix); ret = -EINVAL; @@ -305,6 +323,7 @@ static int mount_one_nonroot(toml_table_t* mount, const char* prefix) { .uri = mount_uri, .key_name = mount_key_name, .enable_recovery = mount_enable_recovery, + .allow_tcb_migration = mount_allow_tcb_migration, }; ret = mount_fs(¶ms); diff --git a/libos/src/fs/libos_fs_encrypted.c b/libos/src/fs/libos_fs_encrypted.c index 47e78471d0..f1c1964bf5 100644 --- a/libos/src/fs/libos_fs_encrypted.c +++ b/libos/src/fs/libos_fs_encrypted.c @@ -253,7 +253,8 @@ static int encrypted_file_internal_open(struct libos_encrypted_file* enc, PAL_HA enc->recovery_file_pal_handle = recovery_file_pal_handle; ret = 0; out: - free(normpath); + if (normpath) + free(normpath); if (ret < 0) { PalObjectDestroy(pal_handle); if (recovery_file_pal_handle) @@ -280,6 +281,7 @@ int parse_pf_key(const char* key_str, pf_key_t* pf_key) { return 0; } + static void encrypted_file_internal_close(struct libos_encrypted_file* enc) { assert(enc->pf); @@ -303,6 +305,111 @@ static void encrypted_file_internal_close(struct libos_encrypted_file* enc) { enc->recovery_file_pal_handle = NULL; } +static int encrypted_file_copy_contents(struct libos_encrypted_file* dest, + struct libos_encrypted_file* src) { + assert(dest->pf); + assert(src->pf); + int ret; + char * buf = NULL; + file_off_t buf_size = 0; + ret = encrypted_file_get_size(src, &buf_size); + if (ret < 0) { + log_error("copy content: encrypted_file_get_size failed for %s: %d", src->uri, ret); + goto out; + } + + buf = malloc(buf_size); + if (!buf) { + ret = -ENOMEM; + goto out; + } + file_off_t remaining_read = buf_size; + while (remaining_read > 0) { + size_t read_size; + ret = encrypted_file_read(src, buf + (buf_size - remaining_read), remaining_read, + buf_size - remaining_read, &read_size); + if (ret < 0) { + log_error("copy content: encrypted_file_read failed for %s: %d", src->uri, ret); + goto out; + } + remaining_read -= read_size; + } + + // write the data to the new file + size_t remaining_write = buf_size; + while (remaining_write > 0) { + size_t write_size; + ret = encrypted_file_write(dest, buf + (buf_size - remaining_write), remaining_write, + buf_size - remaining_write, &write_size); + if (ret < 0) { + log_error("copy content: encrypted_file_write failed for %s: %d", dest->uri, ret); + goto out; + } + remaining_write -= write_size; + } +out: + if (buf) + free(buf); + return ret; +} + + +static int create_encrypted_files_key(const char* name, + struct libos_encrypted_files_key** out_key) { + if (name[0] != '_') { + return -EINVAL; + } + + int ret; + + struct libos_encrypted_files_key* key = NULL; + key = calloc(1, sizeof(*key)); + if (!key){ + ret = -ENOMEM; + goto out; + } + key->name = strdup(name); + if (!key->name) { + ret = -ENOMEM; + goto out; + } + + pf_key_t pf_key; + size_t size = sizeof(pf_key); + ret = PalGetSpecialKey(name, &pf_key, &size); + + if (ret == 0) { + if (size != sizeof(pf_key)) { + ret = -EINVAL; + goto out; + } + memcpy(&key->pf_key, &pf_key, sizeof(pf_key)); + key->is_set = true; + } else if (ret == PAL_ERROR_NOTIMPLEMENTED) { + log_warning( + "Special key \"%s\" is not supported by current PAL. Mounts using this key " + "will not work.", + name); + /* proceed without setting value */ + } else { + log_error("PalGetSpecialKey(\"%s\") failed: %s", name, pal_strerror(ret)); + ret = pal_to_unix_errno(ret); + goto out; + } + + *out_key = key; + ret = 0; +out: + if (ret < 0) { + if (key) { + if (key->name) + free(key->name); + free(key); + } + } + return ret; +} + static int parse_and_update_key(const char* key_name, const char* key_str) { pf_key_t pf_key; int ret = parse_pf_key(key_str, &pf_key); @@ -320,6 +427,215 @@ static int parse_and_update_key(const char* key_name, const char* key_str) { return 0; } +static int migrate_file(const char* uri, struct libos_encrypted_files_key* old_key, + struct libos_encrypted_files_key* new_key) { + struct libos_encrypted_file* new_encrypted_file = NULL; + struct libos_encrypted_file* old_encrypted_file = NULL; + char* old_file_uri = NULL; + int ret = encrypted_file_open(uri, old_key, /*enable_recovery=*/false, &old_encrypted_file); + if (ret < 0) { + log_error("migrate: encrypted_file_open failed for %s: %d", uri, ret); + return ret; + } + + old_file_uri = alloc_concat(uri, -1, OLD_TCB_FILE_URI_SUFFIX, -1); + // save the old file + ret = encrypted_file_rename(old_encrypted_file, old_file_uri); + free(old_file_uri); + if (ret < 0) { + log_error("migrate: encrypted_file_rename failed for %s: %d", uri, ret); + goto out; + } + PAL_STREAM_ATTR pal_attr; + ret = PalStreamAttributesQueryByHandle(old_encrypted_file->pal_handle, &pal_attr); + if (ret < 0) { + log_warning("PalStreamAttributesQueryByHandle failed: %s", pal_strerror(ret)); + ret = pal_to_unix_errno(ret); + goto out; + } + + ret = encrypted_file_create(uri, pal_attr.share_flags, new_key, /*enable_recovery=*/false, + &new_encrypted_file); + if (ret < 0) { + log_error("migrate: encrypted_file_create failed for %s: %d", uri, ret); + goto out; + } + + ret = encrypted_file_copy_contents(new_encrypted_file, old_encrypted_file); + if (ret < 0) { + log_error("migrate: encrypted_file_copy_contents failed for %s: %d", uri, ret); + goto out; + } + +out: + if (old_encrypted_file) { + encrypted_file_put(old_encrypted_file); + encrypted_file_destroy(old_encrypted_file); + } + if (new_encrypted_file) { + encrypted_file_put(new_encrypted_file); + encrypted_file_destroy(new_encrypted_file); + } + return ret; +} + +static int migrate_dir(const char* uri, struct libos_encrypted_files_key* old_key, + struct libos_encrypted_files_key* new_key) { + char* sub_entry_uri = NULL; + char* buf = NULL; + size_t buf_size = READDIR_BUF_SIZE; + PAL_HANDLE palhdl; + int ret = PalStreamOpen(uri, PAL_ACCESS_RDONLY, /*share_flags=*/0, PAL_CREATE_NEVER, + /*options=*/0, &palhdl); + if (ret < 0) { + return pal_to_unix_errno(ret); + } + buf = malloc(buf_size); + if (!buf) { + ret = -ENOMEM; + goto out; + } + while (true) { + size_t read_size = buf_size; + ret = PalStreamRead(palhdl, /*offset=*/0, &read_size, buf); + if (ret < 0) { + ret = pal_to_unix_errno(ret); + goto out; + } + + if (read_size == 0) { + /* End of directory listing */ + break; + } + + /* Last entry must be null-terminated */ + assert(buf[read_size - 1] == '\0'); + + /* Read all entries (separated by null bytes) and invoke `migrate` on each */ + size_t start = 0; + while (start < read_size - 1) { + size_t end = start + strlen(&buf[start]); + + if (end == start) { + log_error("migrate: empty name returned from PAL"); + BUG(); + } + + if (!strcmp(&buf[start], TCB_INFO_FILE_NAME)) { + start = end + 1; + continue; + } + + /* By the PAL convention, if a name ends with '/', it is a directory. */ + if (buf[end - 1] == '/') { + if (uri[strlen(uri) - 1] == '/') + sub_entry_uri = alloc_concat(uri, -1, &buf[start], -1); + else + sub_entry_uri = alloc_concat3(uri, -1, "/", 1, &buf[start], -1); + if (!sub_entry_uri) { + ret = -ENOMEM; + goto out; + } + log_debug("migrating directory %s", sub_entry_uri); + + if ((ret = migrate_dir(sub_entry_uri, old_key, new_key)) < 0) + goto out; + } else { + if (uri[strlen(uri) - 1] == '/') + sub_entry_uri = alloc_concat3(URI_PREFIX_FILE, URI_PREFIX_FILE_LEN, + uri + URI_PREFIX_DIR_LEN, -1, &buf[start], -1); + else { + size_t sub_entry_uri_len = URI_PREFIX_FILE_LEN + strlen(uri) - + URI_PREFIX_DIR_LEN + 1 + strlen(&buf[start]) + 1; + sub_entry_uri = malloc(sub_entry_uri_len); + if (!sub_entry_uri) { + ret = -ENOMEM; + goto out; + } + snprintf(sub_entry_uri, sub_entry_uri_len, "%s%s/%s", URI_PREFIX_FILE, + uri + URI_PREFIX_DIR_LEN, &buf[start]); + } + if (!sub_entry_uri) { + ret = -ENOMEM; + goto out; + } + log_debug("migrating file %s", sub_entry_uri); + if ((ret = migrate_file(sub_entry_uri, old_key, new_key)) < 0) + goto out; + } + free(sub_entry_uri); + sub_entry_uri = NULL; + start = end + 1; + } + } + ret = 0; + +out: + if (sub_entry_uri) + free(sub_entry_uri); + if (buf) + free(buf); + PalObjectDestroy(palhdl); + return ret; +} + +static int do_migrate(const char* uri, cpu_svn_t* old_cpu_svn, const char* key_name) { + struct libos_encrypted_files_key* old_key = NULL; + struct libos_encrypted_files_key* new_key = NULL; + char* dir_entry_uri = NULL; + + int ret = create_encrypted_files_key_for_svn(key_name, old_cpu_svn, &old_key); + if (ret < 0) + return ret; + + ret = create_encrypted_files_key(key_name, &new_key); + if (ret < 0) { + goto out; + } + + PAL_STREAM_ATTR pal_attr; + ret = PalStreamAttributesQuery(uri, &pal_attr); + if (ret < 0) { + ret = pal_to_unix_errno(ret); + goto out; + } + assert(strstartswith(uri, URI_PREFIX_FILE)); + + switch (pal_attr.handle_type) { + case PAL_TYPE_FILE: + ret = migrate_file(uri, old_key, new_key); + break; + case PAL_TYPE_DIR: + dir_entry_uri = + alloc_concat(URI_PREFIX_DIR, URI_PREFIX_DIR_LEN, uri + URI_PREFIX_FILE_LEN, -1); + if (!dir_entry_uri) { + ret = -ENOMEM; + goto out; + } + ret = migrate_dir(dir_entry_uri, old_key, new_key); + break; + default: + log_warning("trying to access '%s' which is not an encrypted file or directory", uri); + ret = -EACCES; + goto out; + } + ret = 0; +out: + if (dir_entry_uri) + free(dir_entry_uri); + if (old_key) { + if (old_key->name) + free(old_key->name); + free(old_key); + } + if (new_key) { + if (new_key->name) + free(new_key->name); + free(new_key); + } + return ret; +} + int init_encrypted_files(void) { pf_debug_f cb_debug_ptr = NULL; #ifdef DEBUG @@ -366,6 +682,73 @@ int init_encrypted_files(void) { return 0; } +int handle_tcb_migration(const char* uri, const char* key_name) { + PAL_HANDLE tcb_info_file_pal_handle = NULL; + int ret; + + cpu_svn_t current_cpu_svn; + size_t cpu_svn_size = sizeof(current_cpu_svn); + ret = PalGetCPUSVN(¤t_cpu_svn, &cpu_svn_size); + if (ret < 0) { + log_warning("PalGetCPUSVN failed: %s", pal_strerror(ret)); + return pal_to_unix_errno(ret); + } + char cpu_svn_str[CPU_SVN_SIZE * 2 + 1] = {0}; + bytes2hex(current_cpu_svn, cpu_svn_size, cpu_svn_str, sizeof(cpu_svn_str)); + + log_debug("current CPU SVN %s", cpu_svn_str); + + char *tcb_info_uri = NULL; + size_t uri_len = strlen(uri); + if (uri[uri_len - 1] == '/') { + tcb_info_uri = alloc_concat(uri, -1, TCB_INFO_FILE_NAME, -1); + } else { + tcb_info_uri = alloc_concat3(uri, -1, "/\0", -1, TCB_INFO_FILE_NAME, -1); + } + + log_debug("Opening TCB info file URI: %s", tcb_info_uri); + ret = PalStreamOpen(tcb_info_uri, PAL_ACCESS_RDWR, TCB_INFO_PERM_RW, PAL_CREATE_TRY, + /*options=*/0, &tcb_info_file_pal_handle); + free(tcb_info_uri); + if (ret < 0) { + log_warning("tcb_info PalStreamOpen failed: %s", pal_strerror(ret)); + ret = pal_to_unix_errno(ret); + return ret; + } + PAL_STREAM_ATTR pal_attr; + ret = PalStreamAttributesQueryByHandle(tcb_info_file_pal_handle, &pal_attr); + if (ret < 0) { + log_warning("tcb_info PalStreamAttributesQueryByHandle failed: %s", pal_strerror(ret)); + ret = pal_to_unix_errno(ret); + goto out; + } + if (pal_attr.pending_size == 0) { + log_debug("tcb_info file is empty - writing current CPU SVN"); + ret = write_exact(tcb_info_file_pal_handle, current_cpu_svn, CPU_SVN_SIZE); + if (ret < 0) { + log_warning("writing to tcb_info file failed"); + goto out; + } + } else { + cpu_svn_t saved_cpu_svn = {0}; + ret = read_exact(tcb_info_file_pal_handle, saved_cpu_svn, sizeof(saved_cpu_svn)); + if (ret < 0) { + log_warning("reading from tcb_info file failed"); + goto out; + } + if (memcmp(¤t_cpu_svn, &saved_cpu_svn, sizeof(saved_cpu_svn)) != 0) { + log_warning("CPU SVN has changed - doing TCB migration for %s", uri); + ret = do_migrate(uri, &saved_cpu_svn, key_name); + } else { + log_debug("CPU SVN has not changed - no TCB migration needed for %s", uri); + } + } +out: + if (tcb_info_file_pal_handle) + PalObjectDestroy(tcb_info_file_pal_handle); + return ret; +} + static struct libos_encrypted_files_key* get_key(const char* name) { assert(locked(&g_keys_lock)); @@ -379,6 +762,46 @@ static struct libos_encrypted_files_key* get_key(const char* name) { return NULL; } +int set_cpu_svn(const cpu_svn_t* cpu_svn) { + int ret; + pf_key_t pf_key; + size_t size = sizeof(pf_key); + char name[] = PAL_KEY_NAME_SGX_MRENCLAVE; + ret = PalGetSpecialKeyForSVN(cpu_svn, sizeof(*cpu_svn), name, &pf_key, &size); + if (ret == 0) { + if (size != sizeof(pf_key)) { + return -EINVAL; + } + } else if (ret == PAL_ERROR_NOTIMPLEMENTED) { + log_warning( + "Special key \"%s\" is not supported by current PAL. Mounts using this key " + "will not work.", + name); + return -ENOSYS; + } else { + log_error("PalGetSpecialKeyForSVN(\"%s\") failed: %s", name, pal_strerror(ret)); + return pal_to_unix_errno(ret); + } + + struct libos_encrypted_files_key* key = get_encrypted_files_key(PAL_KEY_NAME_SGX_MRENCLAVE); + if (!key) { + log_warning("Key with current SVN not found"); + return -ENOENT; + } + if (!key->is_set) { + log_warning("Key with current SVN not set"); + return -ENOENT; + } + + update_encrypted_files_key(key, &pf_key); + ret = PalSetCPUSVN(cpu_svn, sizeof(*cpu_svn)); + if (ret < 0) { + log_warning("PalSetCPUSVN failed: %s", pal_strerror(ret)); + return pal_to_unix_errno(ret); + } + return 0; +} + static struct libos_encrypted_files_key* get_or_create_key(const char* name, bool* out_created) { assert(locked(&g_keys_lock)); @@ -455,11 +878,13 @@ int get_or_create_encrypted_files_key(const char* name, memcpy(&key->pf_key, &pf_key, sizeof(pf_key)); key->is_set = true; } else if (ret == PAL_ERROR_NOTIMPLEMENTED) { - log_debug("Special key \"%s\" is not supported by current PAL. Mounts using this key " - "will not work.", name); + log_debug( + "Special key \"%s\" is not supported by current PAL. Mounts using this key " + "will not work.", + name); /* proceed without setting value */ } else { - log_debug("PalGetSpecialKey(\"%s\") failed: %s", name, pal_strerror(ret)); + log_error("PalGetSpecialKey(\"%s\") failed: %s", name, pal_strerror(ret)); ret = pal_to_unix_errno(ret); goto out; } @@ -472,6 +897,65 @@ int get_or_create_encrypted_files_key(const char* name, return ret; } +int create_encrypted_files_key_for_svn(const char* name, cpu_svn_t* cpu_svn, + struct libos_encrypted_files_key** out_key) { + if (name[0] != '_') { + return -EINVAL; + } + + int ret; + + struct libos_encrypted_files_key* key = NULL; + key = calloc(1, sizeof(*key)); + if (!key){ + ret = -ENOMEM; + goto out; + } + key->name = strdup(name); + if (!key->name) { + ret = -ENOMEM; + goto out; + } + + pf_key_t pf_key; + size_t size = sizeof(pf_key); + ret = PalGetSpecialKeyForSVN(cpu_svn, sizeof(*cpu_svn), name, &pf_key, &size); + + if (ret == 0) { + if (size != sizeof(pf_key)) { + log_debug("PalGetSpecialKeyForSVN(\"%s\") returned wrong size: %zu", name, size); + ret = -EINVAL; + goto out; + } + log_debug("Successfully retrieved special key for svn \"%s\"", name); + memcpy(&key->pf_key, &pf_key, sizeof(pf_key)); + key->is_set = true; + } else if (ret == PAL_ERROR_NOTIMPLEMENTED) { + log_debug( + "Special key \"%s\" is not supported by current PAL. Mounts using this key " + "will not work.", + name); + /* proceed without setting value */ + } else { + log_error("PalGetSpecialKeyForSVN(\"%s\") failed: %s", name, pal_strerror(ret)); + ret = pal_to_unix_errno(ret); + goto out; + } + + *out_key = key; + ret = 0; +out: + if (ret < 0) { + if (key) { + if (key->name) + free(key->name); + free(key); + } + } + return ret; +} + + bool read_encrypted_files_key(struct libos_encrypted_files_key* key, pf_key_t* pf_key) { lock(&g_keys_lock); bool is_set = key->is_set; diff --git a/libos/test/regression/.gitignore b/libos/test/regression/.gitignore index 0a366a38e3..add8deb693 100644 --- a/libos/test/regression/.gitignore +++ b/libos/test/regression/.gitignore @@ -6,3 +6,7 @@ !/tmp/.gitkeep /tmp_enc/* !/tmp_enc/.gitkeep +/tmp_tcb/* +!/tmp_tcb/.gitkeep +!/tmp_tcb/data/.gitkeep +!/tmp_tcb/info/.gitkeep diff --git a/libos/test/regression/keys.c b/libos/test/regression/keys.c index daa7a90bcc..a6c86761b2 100644 --- a/libos/test/regression/keys.c +++ b/libos/test/regression/keys.c @@ -29,6 +29,10 @@ typedef uint8_t pf_key_t[KEY_SIZE]; /* Special keys (SGX sealing keys), always existing under SGX and read-only */ #define MRENCLAVE_KEY_PATH "/dev/attestation/keys/_sgx_mrenclave" #define MRSIGNER_KEY_PATH "/dev/attestation/keys/_sgx_mrsigner" +#define MRENCLAVE_SVN_KEY_PATH "/dev/attestation/keys/svn/_sgx_mrenclave" +#define MRENCLAVE_SVN_KEY_PATH_LEN (sizeof(MRENCLAVE_SVN_KEY_PATH) - 1) +#define CPU_SVN_PATH "/dev/attestation/cpu_svn" +#define SVN_SIZE 16 static const pf_key_t default_key = { 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00}; @@ -95,6 +99,18 @@ static void fail_write_key(const char* desc, const char* path) { errx(1, "%s: writing to %s unexpectedly succeeded", desc, path); } +static int read_file_binary(const char* path, unsigned char* buffer, size_t size) { + printf("Reading from file: %s\n", path); + FILE* f = fopen(path, "rb"); + if (!f) { + perror("read_file_binary fopen"); + return -1; + } + size_t n = fread(buffer, 1, size, f); + fclose(f); + return (int)n; +} + int main(int argc, char** argv) { if (argc > 2 || (argc == 2 && strcmp(argv[1], "sgx") != 0)) errx(1, "test expects either no arguments, or one argument `sgx`"); @@ -104,6 +120,22 @@ int main(int argc, char** argv) { verify_key_exists("SGX sealing keys", MRSIGNER_KEY_PATH); fail_write_key("SGX sealing keys", MRENCLAVE_KEY_PATH); fail_write_key("SGX sealing keys", MRSIGNER_KEY_PATH); + unsigned char cpu_svn[SVN_SIZE]; + if (read_file_binary(CPU_SVN_PATH, cpu_svn, SVN_SIZE) != SVN_SIZE) { + err(1, "error reading CPU SVN"); + } + char cpu_svn_str[MRENCLAVE_SVN_KEY_PATH_LEN + SVN_SIZE * 2 + 1] = {0}; + sprintf(cpu_svn_str, "%s/", MRENCLAVE_SVN_KEY_PATH); + for (size_t i = 0; i < SVN_SIZE; i++) { + sprintf(cpu_svn_str + MRENCLAVE_SVN_KEY_PATH_LEN + 1 + i * 2, "%02x", cpu_svn[i]); + } + verify_key_exists("SGX mrenclave keys SVN", cpu_svn_str); + fail_write_key("SGX mrenclave keys SVN", cpu_svn_str); + for (size_t i = 0; i < SVN_SIZE; i++) { + sprintf(cpu_svn_str + MRENCLAVE_SVN_KEY_PATH_LEN + 1 + i * 2, "%02X", cpu_svn[i]); + } + verify_key_exists("SGX mrenclave keys SVN", cpu_svn_str); + fail_write_key("SGX mrenclave keys SVN", cpu_svn_str); } expect_key("before writing key", DEFAULT_KEY_PATH, &default_key); diff --git a/libos/test/regression/manifest.template b/libos/test/regression/manifest.template index ab236b1cf2..7ba797ef20 100644 --- a/libos/test/regression/manifest.template +++ b/libos/test/regression/manifest.template @@ -18,6 +18,8 @@ fs.mounts = [ { type = "encrypted", path = "/tmp_enc", uri = "file:tmp_enc", key_name = "my_custom_key" }, { type = "encrypted", path = "/tmp_enc/mrenclaves", uri = "file:tmp_enc/mrenclaves", key_name = "_sgx_mrenclave" }, { type = "encrypted", path = "/tmp_enc/mrsigners", uri = "file:tmp_enc/mrsigners", key_name = "_sgx_mrsigner" }, + { type = "encrypted", path = "/tmp_tcb/data", uri = "file:tmp_tcb/data", key_name = "_sgx_mrenclave", allow_tcb_migration = true }, + { path = "/tmp_tcb/info", uri = "file:tmp_tcb/info" }, ] sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '16' }} @@ -30,6 +32,7 @@ sgx.allowed_files = [ "file:root", # for getdents test "file:testfile", # for large_mmap test "file:scripts/", # for exec_script test + "file:tmp_tcb/info", ] sgx.trusted_files = [ diff --git a/libos/test/regression/meson.build b/libos/test/regression/meson.build index dc66f0e19f..519f10cd61 100644 --- a/libos/test/regression/meson.build +++ b/libos/test/regression/meson.build @@ -124,6 +124,7 @@ tests = { 'source': 'sealed_file.c', 'c_args': '-DMODIFY_MRENCLAVE', # see comment in the test's source }, + 'sealed_file_tcb_migration': {}, 'select': {}, 'send_handle': {}, 'shared_object': { diff --git a/libos/test/regression/sealed_file_tcb_migration.c b/libos/test/regression/sealed_file_tcb_migration.c new file mode 100644 index 0000000000..0a52edd752 --- /dev/null +++ b/libos/test/regression/sealed_file_tcb_migration.c @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2023 Gramine contributors + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include + +#define SEALED_DATA_DIR "/tmp_tcb/data" +#define CPU_SVN_PATH "/dev/attestation/cpu_svn" +#define TCB_MIGRATION_DONE_FLAG "/tmp_tcb/info/tcb_migration_done" +#define CURRENT_CPU_SVN_PATH "/tmp_tcb/info/current_cpu_svn" +#define OLD_CPU_SVN_PATH "/tmp_tcb/info/old_cpu_svn" +#define SVN_SIZE 16 + +typedef struct { + const char* path; + const char* content; +} sealed_file_t; + +sealed_file_t sealed_files[] = { + {SEALED_DATA_DIR "/helloworld.txt", "Hello World!\n"}, + {SEALED_DATA_DIR "/subdir/helloworld.txt", "Hello World!\n from a subdirectory"}, + {SEALED_DATA_DIR "/subdir1/subdir2/helloworld.txt", + "Hello World!\n from a nested subdirectory"}, +}; +int num_sealed_files = 3; + +static void print_hex(const unsigned char* data, size_t len) { + for (size_t i = 0; i < len; i++) { + printf("%02x", data[i]); + } +} + +static int file_exists(const char* path) { + printf("Checking existence of file: %s\n", path); + return access(path, F_OK) == 0; +} + +static int read_file_binary(const char* path, unsigned char* buffer, size_t size) { + printf("Reading from file: %s\n", path); + FILE* f = fopen(path, "rb"); + if (!f) { + perror("read_file_binary fopen"); + return -1; + } + size_t n = fread(buffer, 1, size, f); + fclose(f); + return (int)n; +} + +static int write_file_binary(const char* path, const unsigned char* data, size_t len) { + printf("Writing to file: %s\n", path); + FILE* f = fopen(path, "wb"); + if (!f) { + perror("write_file_binary fopen"); + return -1; + } + size_t n = fwrite(data, 1, len, f); + fclose(f); + return (int)n == (int)len ? 0 : -1; +} + +static int read_file_text(const char* path, char* buffer, size_t size) { + printf("Reading from file: %s\n", path); + FILE* f = fopen(path, "r"); + if (!f) { + perror("fopen"); + return -1; + } + int n = fread(buffer, 1, size - 1, f); + fclose(f); + if (n > 0) { + buffer[n] = '\0'; + } + return n; +} + +static int write_file_text(const char* path, const char* data) { + printf("Writing to file: %s\n", path); + FILE* f = fopen(path, "w"); + if (!f) { + perror("fopen"); + return -1; + } + size_t n = fwrite(data, 1, strlen(data), f); + fclose(f); + return (int)n == (int)strlen(data) ? 0 : -1; +} + +static int create_directories(const char* path) { + char tmp[512]; + printf("Creating directories for path: %s\n", path); + strncpy(tmp, path, sizeof(tmp) - 1); + tmp[sizeof(tmp) - 1] = '\0'; + for (char* p = tmp + 1; *p; p++) { + if (*p == '/') { + *p = '\0'; + mkdir(tmp, 0755); + *p = '/'; + } + } + return 0; +} + +int main() { + unsigned char cpu_svn[SVN_SIZE]; + unsigned char current_cpu_svn[SVN_SIZE]; + unsigned char old_cpu_svn[SVN_SIZE]; + printf("Starting TCB migration test\n"); + if (file_exists(TCB_MIGRATION_DONE_FLAG)) { + printf("TCB migration already done, testing results\n"); + + if (read_file_binary(CPU_SVN_PATH, cpu_svn, SVN_SIZE) < 0) { + printf("Error reading CPU SVN\n"); + return 1; + } + printf("CPU SVN: "); + print_hex(cpu_svn, SVN_SIZE); + printf("\n"); + + if (read_file_binary(CURRENT_CPU_SVN_PATH, current_cpu_svn, SVN_SIZE) < 0) { + printf("Error reading current CPU SVN\n"); + return 1; + } + printf("Current CPU SVN: "); + print_hex(current_cpu_svn, SVN_SIZE); + printf("\n"); + + if (memcmp(cpu_svn, current_cpu_svn, SVN_SIZE) != 0) { + printf("Error: CPU SVN does not match current CPU SVN\n"); + return 1; + } + + if (read_file_binary(OLD_CPU_SVN_PATH, old_cpu_svn, SVN_SIZE) < 0) { + printf("Error reading old CPU SVN\n"); + return 1; + } + printf("Old CPU SVN: "); + print_hex(old_cpu_svn, SVN_SIZE); + printf("\n"); + + if (memcmp(cpu_svn, old_cpu_svn, SVN_SIZE) == 0) { + printf("Error: CPU SVN matches old CPU SVN\n"); + return 1; + } + + printf("Sealed files:\n"); + for (int i = 0; i < num_sealed_files; i++) { + const char* filename = sealed_files[i].path; + const char* expected = sealed_files[i].content; + + printf("Reading sealed file: %s\n", filename); + + if (!file_exists(filename)) { + printf("Error: Sealed file %s does not exist\n", filename); + return 1; + } + + char buffer[512] = {0}; + read_file_text(filename, buffer, sizeof(buffer)); + + if (strcmp(buffer, expected) != 0) { + printf("%d Error: Sealed file %s content does not match expected content\n", i, + filename); + printf("Expected: %s\n", expected); + printf("Got: %s\n", buffer); + return 1; + } + printf("Sealed file %s content matches expected content\n", filename); + } + printf("TCB migration test successful\n"); + puts("TEST OK\n"); + + } else { + printf("Performing CPU SVN downgrade to enable old key\n"); + + if (read_file_binary(CPU_SVN_PATH, cpu_svn, SVN_SIZE) < 0) { + printf("Error reading CPU SVN\n"); + return 1; + } + printf("CPU SVN: "); + print_hex(cpu_svn, SVN_SIZE); + printf("\n"); + + memcpy(old_cpu_svn, cpu_svn, SVN_SIZE); + for (int i = 0; i < (int)SVN_SIZE - 1; i++) { + if (cpu_svn[i + 1] == 0x00 || i + 1 == (int)SVN_SIZE - 1) { + printf("Decreasing byte %d of cpu_svn from %d to %d\n", i, cpu_svn[i], + cpu_svn[i] - 1); + old_cpu_svn[i] = cpu_svn[i] - 1; + break; + } + } + + printf("Old CPU SVN: "); + print_hex(old_cpu_svn, SVN_SIZE); + printf("\n"); + + if (write_file_binary(CPU_SVN_PATH, old_cpu_svn, SVN_SIZE) < 0) { + printf("Error writing CPU SVN\n"); + return 1; + } + + if (write_file_binary(CURRENT_CPU_SVN_PATH, cpu_svn, SVN_SIZE) < 0) { + printf("Error writing current CPU SVN\n"); + return 1; + } + + if (write_file_binary(OLD_CPU_SVN_PATH, old_cpu_svn, SVN_SIZE) < 0) { + printf("Error writing old CPU SVN\n"); + return 1; + } + + for (int i = 0; i < num_sealed_files; i++) { + const char* filename = sealed_files[i].path; + const char* content = sealed_files[i].content; + + create_directories(filename); + + if (write_file_text(filename, content) < 0) { + printf("Error writing sealed file: %s\n", filename); + return 1; + } + printf("Wrote sealed file: %s using old key\n", filename); + } + + if (write_file_text(TCB_MIGRATION_DONE_FLAG, "done") < 0) { + printf("Error writing TCB migration flag\n"); + return 1; + } + + puts("TEST READY\n"); + printf("do \"cp %s %s/gramine.tcb_info\"\n", OLD_CPU_SVN_PATH, SEALED_DATA_DIR); + printf("then restart the application to test TCB migration\n"); + } + + return 0; +} \ No newline at end of file diff --git a/libos/test/regression/test_libos.py b/libos/test/regression/test_libos.py index ca7f169828..62421e6e07 100644 --- a/libos/test/regression/test_libos.py +++ b/libos/test/regression/test_libos.py @@ -1389,6 +1389,32 @@ def test_053_sealed_file_mrenclave_unlink(self): stdout, _ = self.run_binary(['sealed_file_mod', pf_path, 'unlink']) self.assertIn('UNLINK OK', stdout) + + @unittest.skipUnless(HAS_SGX, 'Sealed (protected) files are only available with SGX') + def test_054_sealed_file_tcb_migration(self): + pf_path = 'tmp_tcb/data/' + info_path = 'tmp_tcb/info/' + if os.path.exists(info_path): + for root, dirs, files in os.walk(info_path): + for f in files: + os.unlink(os.path.join(root, f)) + for d in dirs: + shutil.rmtree(os.path.join(root, d)) + if os.path.exists(pf_path): + for root, dirs, files in os.walk(pf_path): + for f in files: + os.unlink(os.path.join(root, f)) + for d in dirs: + shutil.rmtree(os.path.join(root, d)) + + stdout, _ = self.run_binary(['sealed_file_tcb_migration']) + self.assertIn('TEST READY', stdout) + assert(os.path.exists(os.path.join(info_path, 'old_cpu_svn'))) + shutil.copy(os.path.join(info_path, 'old_cpu_svn'), os.path.join(pf_path, 'gramine.tcb_info')) + assert(os.path.exists(os.path.join(pf_path, 'gramine.tcb_info'))) + stdout, _ = self.run_binary(['sealed_file_tcb_migration']) + self.assertIn('TEST OK', stdout) + def test_060_synthetic(self): stdout, _ = self.run_binary(['synthetic']) self.assertIn("TEST OK", stdout) diff --git a/libos/test/regression/tests.toml b/libos/test/regression/tests.toml index 69394eddfe..150567d409 100644 --- a/libos/test/regression/tests.toml +++ b/libos/test/regression/tests.toml @@ -111,6 +111,7 @@ manifests = [ "sched_set_get_affinity", "sealed_file", "sealed_file_mod", + "sealed_file_tcb_migration", "select", "send_handle", "shadow_pseudo_fs", diff --git a/libos/test/regression/tests_musl.toml b/libos/test/regression/tests_musl.toml index 7e72793d6a..db957cefc1 100644 --- a/libos/test/regression/tests_musl.toml +++ b/libos/test/regression/tests_musl.toml @@ -113,6 +113,7 @@ manifests = [ "sched_set_get_affinity", "sealed_file", "sealed_file_mod", + "sealed_file_tcb_migration", "select", "send_handle", "shadow_pseudo_fs", diff --git a/libos/test/regression/tmp_tcb/.gitkeep b/libos/test/regression/tmp_tcb/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libos/test/regression/tmp_tcb/data/.gitkeep b/libos/test/regression/tmp_tcb/data/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libos/test/regression/tmp_tcb/info/.gitkeep b/libos/test/regression/tmp_tcb/info/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pal/include/pal/pal.h b/pal/include/pal/pal.h index 0e2229f92a..da812f5b17 100644 --- a/pal/include/pal/pal.h +++ b/pal/include/pal/pal.h @@ -987,6 +987,54 @@ int PalAttestationQuote(const void* user_report_data, size_t user_report_data_si */ int PalGetSpecialKey(const char* name, void* key, size_t* key_size); +/*! + * \brief Get CPU SVN of the current CPU + * + * \param[out] cpu_svn On success, will be set to the CPU SVN. + * \param[in,out] cpu_svn_size Caller specifies maximum size for `cpu_svn`. On success, will + * contain actual size. + * + * Retrieve the value of a CPU SVN. Currently implemented for Linux-SGX PAL, which supports two + * such keys: `_sgx_mrenclave` and `_sgx_mrsigner` (see macros below). + * + * If a given key is not supported by the current PAL host, the function will return + * PAL_ERROR_NOTIMPLEMENTED. + */ +int PalGetCPUSVN(void* cpu_svn, size_t* cpu_svn_size); + +/*! + * \brief Set CPU SVN to be used in subsequent calls to PalGetSpecialKey + * + * \param cpu_svn CPU SVN to use. + * \param cpu_svn_size Size in bytes of `cpu_svn`. Must be exactly 16B in case of SGX PAL. + * + * Set the CPU SVN to be used in subsequent calls to PalGetSpecialKey. Currently implemented + * for Linux-SGX PAL. + * + * If a given key is not supported by the current PAL host, the function will return + * PAL_ERROR_NOTIMPLEMENTED. + */ +int PalSetCPUSVN(const void* cpu_svn, size_t cpu_svn_size); + +/*! + * \brief Get special key with a specific CPU SVN (specific to PAL host). + * + * \param cpu_svn CPU SVN to use. + * \param cpu_svn_size Size in bytes of `cpu_svn`. Must be exactly 16B in case of SGX PAL. + * \param name Key name. + * \param[out] key On success, will be set to retrieved key. + * \param[in,out] key_size Caller specifies maximum size for `key`. On success, will contain actual + * size. + * + * Retrieve the value of a special key. Currently implemented for Linux-SGX PAL, which supports two + * such keys: `_sgx_mrenclave` and `_sgx_mrsigner` (see macros below). + * + * If a given key is not supported by the current PAL host, the function will return + * PAL_ERROR_NOTIMPLEMENTED. + */ +int PalGetSpecialKeyForSVN(const void* cpu_svn, size_t cpu_svn_size, const char* name, void* key, + size_t* key_size); + #define PAL_KEY_NAME_SGX_MRENCLAVE "_sgx_mrenclave" #define PAL_KEY_NAME_SGX_MRSIGNER "_sgx_mrsigner" diff --git a/pal/include/pal_internal.h b/pal/include/pal_internal.h index a8695e5448..5762570cf4 100644 --- a/pal/include/pal_internal.h +++ b/pal/include/pal_internal.h @@ -244,6 +244,10 @@ int _PalAttestationReport(const void* user_report_data, size_t* user_report_data int _PalAttestationQuote(const void* user_report_data, size_t user_report_data_size, void* quote, size_t* quote_size); int _PalGetSpecialKey(const char* name, void* key, size_t* key_size); +int _PalGetSpecialKeyForSVN(const void* cpu_svn, size_t cpu_svn_size, const char* name, void* key, + size_t* key_size); +int _PalGetCPUSVN(void* cpu_svn, size_t* cpu_svn_size); +int _PalSetCPUSVN(const void* cpu_svn, size_t cpu_svn_size); #define INIT_FAIL(msg, ...) \ do { \ diff --git a/pal/src/host/linux-sgx/enclave_framework.c b/pal/src/host/linux-sgx/enclave_framework.c index 72adb4f32d..4641bbb0ee 100644 --- a/pal/src/host/linux-sgx/enclave_framework.c +++ b/pal/src/host/linux-sgx/enclave_framework.c @@ -396,6 +396,42 @@ int sgx_get_seal_key(uint16_t key_policy, sgx_key_128bit_t* out_seal_key) { return 0; } +int sgx_get_seal_key_with_svn(uint16_t key_policy, const void* cpu_svn, size_t cpu_svn_size, + sgx_key_128bit_t* out_seal_key) { + if (cpu_svn_size != sizeof(sgx_cpu_svn_t)) { + log_error("Invalid cpu_svn_size"); + return PAL_ERROR_INVAL; + } + assert(key_policy == SGX_KEYPOLICY_MRENCLAVE || key_policy == SGX_KEYPOLICY_MRSIGNER); + + /* The keyrequest struct dictates the key derivation material used to generate the sealing key. + * It includes MRENCLAVE/MRSIGNER key policy (to allow secret migration/sealing between + * instances of the same enclave or between different enclaves of the same author/signer), + * CPU/ISV/CONFIG SVNs (to prevent secret migration to older vulnerable versions of the + * enclave), ATTRIBUTES and MISCSELECT masks (to prevent secret migration from e.g. production + * enclave to debug enclave). Note that KEYID is zero, to generate the same sealing key in + * different instances of the same enclave/same signer. */ + __sgx_mem_aligned sgx_key_request_t key_request = {0}; + key_request.key_name = SGX_SEAL_KEY; + key_request.key_policy = key_policy; + + memcpy(&key_request.cpu_svn, cpu_svn, sizeof(sgx_cpu_svn_t)); + memcpy(&key_request.isv_svn, &g_pal_linuxsgx_state.enclave_info.isv_svn, sizeof(sgx_isv_svn_t)); + memcpy(&key_request.config_svn, &g_pal_linuxsgx_state.enclave_info.config_svn, + sizeof(sgx_config_svn_t)); + + key_request.attribute_mask.flags = g_seal_key_flags_mask; + key_request.attribute_mask.xfrm = g_seal_key_xfrm_mask; + key_request.misc_mask = g_seal_key_misc_mask; + + int ret = sgx_getkey(&key_request, out_seal_key); + if (ret) { + log_error("Failed to generate sealing key using SGX EGETKEY"); + return PAL_ERROR_DENIED; + } + return 0; +} + static int update_seal_key_mask(const char* mask_name, uint8_t* mask_ptr, size_t mask_size) { int ret; diff --git a/pal/src/host/linux-sgx/pal_linux.h b/pal/src/host/linux-sgx/pal_linux.h index 2c998ee267..7cbdaae429 100644 --- a/pal/src/host/linux-sgx/pal_linux.h +++ b/pal/src/host/linux-sgx/pal_linux.h @@ -148,6 +148,22 @@ int sgx_get_report(const sgx_target_info_t* target_info, const sgx_report_data_t */ int sgx_get_seal_key(uint16_t key_policy, sgx_key_128bit_t* seal_key); +/*! + * \brief Obtain an enclave/signer-bound key via EGETKEY(SGX_SEAL_KEY) for secret migration/sealing + * of files. + * + * \param key_policy Must be SGX_KEYPOLICY_MRENCLAVE or SGX_KEYPOLICY_MRSIGNER. Binds the + * sealing key to MRENCLAVE (only the same enclave can unseal secrets) or + * to MRSIGNER (all enclaves from the same signer can unseal secrets). + * \param cpu_svn CPU SVN to use for deriving the key; must be exactly 16 bytes. + * \param cpu_svn_size Size in bytes of `cpu_svn`; must be exactly 16 bytes. + * \param[out] seal_key Output buffer to store the sealing key. + * + * \returns 0 on success, negative error code otherwise. + */ + +int sgx_get_seal_key_with_svn(uint16_t key_policy, const void* cpu_svn, size_t cpu_svn_size, + sgx_key_128bit_t* seal_key); /*! * \brief Verify the peer enclave during SGX local attestation. * diff --git a/pal/src/host/linux-sgx/pal_misc.c b/pal/src/host/linux-sgx/pal_misc.c index 9ee161f9e9..5a699432f2 100644 --- a/pal/src/host/linux-sgx/pal_misc.c +++ b/pal/src/host/linux-sgx/pal_misc.c @@ -753,6 +753,47 @@ int _PalGetSpecialKey(const char* name, void* key, size_t* key_size) { return 0; } +int _PalGetSpecialKeyForSVN(const void* cpu_svn, size_t cpu_svn_size, const char* name, void* key, + size_t* key_size) { + sgx_key_128bit_t sgx_key; + + if (*key_size < sizeof(sgx_key)) + return PAL_ERROR_INVAL; + + int ret; + if (!strcmp(name, PAL_KEY_NAME_SGX_MRENCLAVE)) { + ret = sgx_get_seal_key_with_svn(SGX_KEYPOLICY_MRENCLAVE, cpu_svn, cpu_svn_size, &sgx_key); + } else if (!strcmp(name, PAL_KEY_NAME_SGX_MRSIGNER)) { + ret = sgx_get_seal_key_with_svn(SGX_KEYPOLICY_MRSIGNER, cpu_svn, cpu_svn_size, &sgx_key); + } else { + return PAL_ERROR_NOTIMPLEMENTED; + } + if (ret < 0) + return ret; + + memcpy(key, &sgx_key, sizeof(sgx_key)); + *key_size = sizeof(sgx_key); + return 0; +} + +int _PalGetCPUSVN(void* cpu_svn, size_t* cpu_svn_size) { + if (*cpu_svn_size != sizeof(sgx_cpu_svn_t)) + return PAL_ERROR_INVAL; + + sgx_cpu_svn_t svn = g_pal_linuxsgx_state.enclave_info.cpu_svn; + + memcpy(cpu_svn, &svn, sizeof(sgx_cpu_svn_t)); + *cpu_svn_size = sizeof(sgx_cpu_svn_t); + return 0; +} + +int _PalSetCPUSVN(const void* cpu_svn, size_t cpu_svn_size) { + if (cpu_svn_size != sizeof(sgx_cpu_svn_t)) + return PAL_ERROR_INVAL; + memcpy(&g_pal_linuxsgx_state.enclave_info.cpu_svn, cpu_svn, sizeof(sgx_cpu_svn_t)); + return 0; +} + ssize_t read_file_buffer(const char* filename, char* buf, size_t buf_size) { int fd; diff --git a/pal/src/host/linux/pal_misc.c b/pal/src/host/linux/pal_misc.c index 44da9ccd21..3986e6e8ae 100644 --- a/pal/src/host/linux/pal_misc.c +++ b/pal/src/host/linux/pal_misc.c @@ -80,6 +80,28 @@ int _PalGetSpecialKey(const char* name, void* key, size_t* key_size) { return PAL_ERROR_NOTIMPLEMENTED; } +int _PalGetSpecialKeyForSVN(const void* cpu_svn, size_t cpu_svn_size, const char* name, void* key, + size_t* key_size) { + __UNUSED(cpu_svn); + __UNUSED(cpu_svn_size); + __UNUSED(name); + __UNUSED(key); + __UNUSED(key_size); + return PAL_ERROR_NOTIMPLEMENTED; +} + +int _PalGetCPUSVN(void* cpu_svn, size_t* cpu_svn_size) { + __UNUSED(cpu_svn); + __UNUSED(cpu_svn_size); + return PAL_ERROR_NOTIMPLEMENTED; +} + +int _PalSetCPUSVN(const void* cpu_svn, size_t cpu_svn_size) { + __UNUSED(cpu_svn); + __UNUSED(cpu_svn_size); + return PAL_ERROR_NOTIMPLEMENTED; +} + int _PalValidateEntrypoint(const void* buf, size_t size) { __UNUSED(buf); __UNUSED(size); diff --git a/pal/src/host/skeleton/pal_misc.c b/pal/src/host/skeleton/pal_misc.c index 839bcd9a40..941785c619 100644 --- a/pal/src/host/skeleton/pal_misc.c +++ b/pal/src/host/skeleton/pal_misc.c @@ -59,6 +59,28 @@ int _PalGetSpecialKey(const char* name, void* key, size_t* key_size) { return PAL_ERROR_NOTIMPLEMENTED; } +int _PalGetSpecialKeyForSVN(const void* cpu_svn, size_t cpu_svn_size, const char* name, void* key, + size_t* key_size) { + __UNUSED(cpu_svn); + __UNUSED(cpu_svn_size); + __UNUSED(name); + __UNUSED(key); + __UNUSED(key_size); + return PAL_ERROR_NOTIMPLEMENTED; +} + +int _PalGetCPUSVN(void* cpu_svn, size_t* cpu_svn_size) { + __UNUSED(cpu_svn); + __UNUSED(cpu_svn_size); + return PAL_ERROR_NOTIMPLEMENTED; +} + +int _PalSetCPUSVN(const void* cpu_svn, size_t cpu_svn_size) { + __UNUSED(cpu_svn); + __UNUSED(cpu_svn_size); + return PAL_ERROR_NOTIMPLEMENTED; +} + int _PalValidateEntrypoint(const void* buf, size_t size) { __UNUSED(buf); __UNUSED(size); diff --git a/pal/src/pal_misc.c b/pal/src/pal_misc.c index 4aa3b9dac1..3489525300 100644 --- a/pal/src/pal_misc.c +++ b/pal/src/pal_misc.c @@ -52,6 +52,19 @@ int PalGetSpecialKey(const char* name, void* key, size_t* key_size) { return _PalGetSpecialKey(name, key, key_size); } +int PalGetSpecialKeyForSVN(const void* cpu_svn, size_t cpu_svn_size, const char* name, void* key, + size_t* key_size) { + return _PalGetSpecialKeyForSVN(cpu_svn, cpu_svn_size, name, key, key_size); +} + +int PalGetCPUSVN(void* cpu_svn, size_t* cpu_svn_size) { + return _PalGetCPUSVN(cpu_svn, cpu_svn_size); +} + +int PalSetCPUSVN(const void* cpu_svn, size_t cpu_svn_size) { + return _PalSetCPUSVN(cpu_svn, cpu_svn_size); +} + void PalGetLazyCommitPages(uintptr_t addr, size_t size, uint8_t* bitvector) { if (!addr || !IS_ALLOC_ALIGNED_PTR(addr) || !size || !IS_ALLOC_ALIGNED(size) || !bitvector) { BUG(); diff --git a/pal/src/pal_symbols b/pal/src/pal_symbols index dc6365ca30..6d9940442e 100644 --- a/pal/src/pal_symbols +++ b/pal/src/pal_symbols @@ -50,6 +50,9 @@ PalDebugDescribeLocation PalAttestationReport PalAttestationQuote PalGetSpecialKey +PalGetSpecialKeyForSVN +PalGetCPUSVN +PalSetCPUSVN PalDebugLog PalGetPalPublicState PalGetLazyCommitPages diff --git a/python/graminelibos/manifest_check.py b/python/graminelibos/manifest_check.py index a03bda779d..51e50618c8 100644 --- a/python/graminelibos/manifest_check.py +++ b/python/graminelibos/manifest_check.py @@ -31,6 +31,7 @@ Required('uri'): _uri, 'key_name': str, 'enable_recovery': bool, + 'allow_tcb_migration': bool, }, { Required('type'): 'tmpfs',