diff --git a/common/src/protected_files/protected_files.c b/common/src/protected_files/protected_files.c index 02a06908a8..1b674f1b1a 100644 --- a/common/src/protected_files/protected_files.c +++ b/common/src/protected_files/protected_files.c @@ -791,6 +791,9 @@ static bool ipf_init_existing_file(pf_context_t* pf, const char* path) { if (PF_FAILURE(status)) { pf->last_error = status; DEBUG_PF("failed to decrypt metadata: %d", status); + if (status == PF_STATUS_MAC_MISMATCH) + // MAC could also mismatch if wrong key was provided but we err on side of safety ... + pf->file_status = PF_STATUS_CORRUPTED; return false; } @@ -818,6 +821,8 @@ static bool ipf_init_existing_file(pf_context_t* pf, const char* path) { &pf->metadata_decrypted.root_mht_node_mac); if (PF_FAILURE(status)) { pf->last_error = status; + if (status == PF_STATUS_MAC_MISMATCH) + pf->file_status = PF_STATUS_CORRUPTED; return false; } } @@ -1162,6 +1167,9 @@ pf_status_t pf_get_size(pf_context_t* pf, uint64_t* size) { if (!g_initialized) return PF_STATUS_UNINITIALIZED; + if (pf->file_status == PF_STATUS_CORRUPTED) + return pf->file_status; // Make corruption "sticky" + *size = pf->metadata_decrypted.file_size; return PF_STATUS_SUCCESS; } @@ -1173,6 +1181,9 @@ pf_status_t pf_set_size(pf_context_t* pf, uint64_t size) { if (!(pf->mode & PF_FILE_MODE_WRITE)) return PF_STATUS_INVALID_MODE; + if (pf->file_status == PF_STATUS_CORRUPTED) + return pf->file_status; // Make corruption "sticky" + if (size == pf->metadata_decrypted.file_size) return PF_STATUS_SUCCESS; @@ -1221,6 +1232,9 @@ pf_status_t pf_rename(pf_context_t* pf, const char* new_path, pf_mac_t* new_root if (!g_initialized) return PF_STATUS_UNINITIALIZED; + if (pf->file_status == PF_STATUS_CORRUPTED) + return pf->file_status; // Make corruption "sticky" + if (!(pf->mode & PF_FILE_MODE_WRITE)) return PF_STATUS_INVALID_MODE; @@ -1293,6 +1307,9 @@ pf_status_t pf_flush(pf_context_t* pf) { if (!g_initialized) return PF_STATUS_UNINITIALIZED; + if (pf->file_status == PF_STATUS_CORRUPTED) + return pf->file_status; // Make corruption "sticky" + if (!ipf_internal_flush(pf)) return pf->last_error; diff --git a/libos/include/libos_fs_encrypted.h b/libos/include/libos_fs_encrypted.h index db5f59534e..c5aaebc612 100644 --- a/libos/include/libos_fs_encrypted.h +++ b/libos/include/libos_fs_encrypted.h @@ -41,6 +41,11 @@ typedef enum { PF_FILE_STATE_DELETED = 2, // file was previously seen but then either unlinked or renamed } libos_encrypted_file_state_t; +inline static const char* file_state_to_string(libos_encrypted_file_state_t state) { + return (state == PF_FILE_STATE_ERROR ? "error" + : (state == PF_FILE_STATE_ACTIVE ? "active" : "deleted")); +} + /* * Map mapping file URIs to state providing information on files, in particular whether we have seen * them before and what the last seen root-hash is. This is necessary to provide rollback @@ -48,7 +53,7 @@ typedef enum { struct libos_encrypted_volume_state_map { char* norm_path; // assumptions: all paths canonicalized, symlinks are resolved & no hard links libos_encrypted_file_state_t state; - pf_mac_t last_seen_root_mac; + pf_mac_t last_seen_root_mac; // valid only if state == PF_FILE_STATE_ACTIVE UT_hash_handle hh; }; @@ -224,16 +229,17 @@ void encrypted_file_put(struct libos_encrypted_file* enc, bool fs_reachable); /* * \brief Flush pending writes to an encrypted file. */ -int encrypted_file_flush(struct libos_encrypted_file* enc); +int encrypted_file_flush(struct libos_encrypted_file* enc, bool fs_reachable); int encrypted_file_read(struct libos_encrypted_file* enc, void* buf, size_t buf_size, - file_off_t offset, size_t* out_count); + file_off_t offset, size_t* out_count, bool fs_reachable); int encrypted_file_write(struct libos_encrypted_file* enc, const void* buf, size_t buf_size, - file_off_t offset, size_t* out_count); + file_off_t offset, size_t* out_count, bool fs_reachable); int encrypted_file_rename(struct libos_encrypted_file* enc, const char* new_uri); int encrypted_file_unlink(struct libos_encrypted_file* enc); -int encrypted_file_get_size(struct libos_encrypted_file* enc, file_off_t* out_size); -int encrypted_file_set_size(struct libos_encrypted_file* enc, file_off_t size); +int encrypted_file_get_size(struct libos_encrypted_file* enc, file_off_t* out_size, + bool fs_reachable); +int encrypted_file_set_size(struct libos_encrypted_file* enc, file_off_t size, bool fs_reachable); int parse_pf_key(const char* key_str, pf_key_t* pf_key); diff --git a/libos/src/fs/chroot/encrypted.c b/libos/src/fs/chroot/encrypted.c index aa8090b7be..df98c5d024 100644 --- a/libos/src/fs/chroot/encrypted.c +++ b/libos/src/fs/chroot/encrypted.c @@ -207,7 +207,7 @@ static int chroot_encrypted_lookup(struct libos_dentry* dent) { goto out; } } else { - ret = encrypted_file_get_size(enc, &size); + ret = encrypted_file_get_size(enc, &size, true); encrypted_file_put(enc, true); if (ret < 0) { @@ -468,7 +468,7 @@ static int chroot_encrypted_flush(struct libos_handle* hdl) { /* This will write changes from `enc` to host file */ lock(&hdl->inode->lock); - ret = encrypted_file_flush(enc); + ret = encrypted_file_flush(enc, hdl->inode == hdl->dentry->inode); unlock(&hdl->inode->lock); return ret; } @@ -502,7 +502,8 @@ static ssize_t chroot_encrypted_read(struct libos_handle* hdl, void* buf, size_t size_t actual_count; lock(&hdl->inode->lock); - int ret = encrypted_file_read(enc, buf, count, *pos, &actual_count); + int ret = + encrypted_file_read(enc, buf, count, *pos, &actual_count, hdl->inode == hdl->dentry->inode); unlock(&hdl->inode->lock); if (ret < 0) @@ -527,7 +528,8 @@ static ssize_t chroot_encrypted_write(struct libos_handle* hdl, const void* buf, lock(&hdl->inode->lock); - int ret = encrypted_file_write(enc, buf, count, *pos, &actual_count); + int ret = encrypted_file_write(enc, buf, count, *pos, &actual_count, + hdl->inode == hdl->dentry->inode); if (ret < 0) { unlock(&hdl->inode->lock); return ret; @@ -557,7 +559,7 @@ static int chroot_encrypted_truncate(struct libos_handle* hdl, file_off_t size) assert(enc); lock(&hdl->inode->lock); - ret = encrypted_file_set_size(enc, size); + ret = encrypted_file_set_size(enc, size, hdl->inode == hdl->dentry->inode); if (ret < 0) { unlock(&hdl->inode->lock); return ret; diff --git a/libos/src/fs/libos_fs_encrypted.c b/libos/src/fs/libos_fs_encrypted.c index d6dc5d52b4..4cbc835d3c 100644 --- a/libos/src/fs/libos_fs_encrypted.c +++ b/libos/src/fs/libos_fs_encrypted.c @@ -178,6 +178,62 @@ static int uri_to_normalized_path(const char* uri, char** out_norm_path) { return 0; } +static void update_mac_in_file_state_map(struct libos_encrypted_file* enc, bool fs_reachable, + const pf_mac_t* closing_root_mac) { + char* norm_path = NULL; + int ret = uri_to_normalized_path(enc->uri, &norm_path); + if (ret < 0) { + log_error("Could not normalize uri %s while updating file state map (ret=%d)", enc->uri, + ret); + } else { + log_debug("map update of %sreachable file '%s' closed with MAC=" MAC_PRINTF_PATTERN, + (fs_reachable ? "" : "un"), norm_path, + MAC_PRINTF_ARGS(*closing_root_mac)); // TODO (MST): remove me eventually? + if (fs_reachable) { + /* note: we only update if reachable in fileystem to prevent file-handles made + * unreachable via unlink or rename to modify state. */ + lock(&(enc->volume->files_state_map_lock)); + struct libos_encrypted_volume_state_map* file_state = NULL; + + HASH_FIND_STR(enc->volume->files_state_map, norm_path, file_state); + assert(file_state != NULL); + if (file_state->state == PF_FILE_STATE_ACTIVE) { + /* note: we do not touch it if earlier we determined this file is in inconsistent + * error state. */ + memcpy(file_state->last_seen_root_mac, *closing_root_mac, sizeof(pf_mac_t)); + } + unlock(&(enc->volume->files_state_map_lock)); + free(norm_path); + } + } +} + +static void update_state_in_file_state_map(struct libos_encrypted_file* enc, bool fs_reachable, + libos_encrypted_file_state_t state) { + char* norm_path = NULL; + int ret = uri_to_normalized_path(enc->uri, &norm_path); + if (ret < 0) { + log_error("Could not normalize uri %s while updating file state map (ret=%d)", enc->uri, + ret); + } else { + log_debug("map update of %sreachable file '%s' to state %s", (fs_reachable ? "" : "un"), + norm_path, + file_state_to_string(state)); // TODO (MST): remove me eventually? + if (fs_reachable) { + /* note: we only update if reachable in fileystem to prevent file-handles made + * unreachable via unlink or rename to modify state. */ + lock(&(enc->volume->files_state_map_lock)); + struct libos_encrypted_volume_state_map* file_state = NULL; + + HASH_FIND_STR(enc->volume->files_state_map, norm_path, file_state); + assert(file_state != NULL); + file_state->state = state; + unlock(&(enc->volume->files_state_map_lock)); + free(norm_path); + } + } +} + /* * The `pal_handle` parameter is used if this is a checkpointed file, and we have received the PAL * handle from the parent process. Note that in this case, it would not be safe to attempt opening @@ -230,6 +286,7 @@ static int encrypted_file_internal_open(struct libos_encrypted_file* enc, PAL_HA ret = -EACCES; goto out; } + /* rollback protection */ struct libos_encrypted_volume_state_map* file_state = NULL; log_debug("file '%s' opened with MAC=" MAC_PRINTF_PATTERN, norm_path, @@ -240,7 +297,8 @@ static int encrypted_file_internal_open(struct libos_encrypted_file* enc, PAL_HA /* - check current state */ if (create) { if (file_state && (file_state->state != PF_FILE_STATE_DELETED)) { - log_error("file '%s' already exists or is in error state", norm_path); + log_error("newly created file '%s' is in state %s", norm_path, + file_state_to_string(file_state->state)); if (enc->volume->protection_mode != PF_ENCLAVE_LIFE_RB_PROTECTION_NONE) { pf_set_corrupted(pf); ret = -EEXIST; @@ -252,7 +310,7 @@ static int encrypted_file_internal_open(struct libos_encrypted_file* enc, PAL_HA if ((file_state->state == PF_FILE_STATE_ERROR) || (file_state->state == PF_FILE_STATE_DELETED)) { log_error("file '%s' was seen before but in %s state", norm_path, - file_state->state == PF_FILE_STATE_DELETED ? "deleted" : "error"); + file_state_to_string(file_state->state)); if (enc->volume->protection_mode != PF_ENCLAVE_LIFE_RB_PROTECTION_NONE) { pf_set_corrupted(pf); ret = -EACCES; @@ -337,35 +395,12 @@ static void encrypted_file_internal_close(struct libos_encrypted_file* enc, bool assert(enc->pf); pf_mac_t closing_root_mac; pf_status_t pfs = pf_close(enc->pf, &closing_root_mac); - char* norm_path = NULL; - int ret = uri_to_normalized_path(enc->uri, &norm_path); - if (ret < 0) { - log_error("Could not normalize uri %s while closing file (ret=%d)", enc->uri, ret); + if (PF_FAILURE(pfs)) { + log_warning("pf_close failed: %s", pf_strerror(pfs)); + update_state_in_file_state_map(enc, fs_reachable, PF_FILE_STATE_ERROR); } else { - log_debug("%sreachable file '%s' closed with MAC=" MAC_PRINTF_PATTERN, - (fs_reachable ? "" : "un"), norm_path, - MAC_PRINTF_ARGS(closing_root_mac)); // TODO (MST): remove me eventually? - lock(&(enc->volume->files_state_map_lock)); - struct libos_encrypted_volume_state_map* file_state = NULL; - - HASH_FIND_STR(enc->volume->files_state_map, norm_path, file_state); - assert(file_state != NULL); - if (PF_FAILURE(pfs)) { - log_warning("pf_close failed: %s", pf_strerror(pfs)); - file_state->state = PF_FILE_STATE_ERROR; - pf_set_corrupted(enc->pf); - } else { - if (fs_reachable && (file_state->state == PF_FILE_STATE_ACTIVE)) { - /* note: we only update if reachable in fileystem to prevent file-handles made - * unreachable via unlink or rename to modify state. We also do not touch it if - * earlier we determined this file is in inconsistent error state. */ - memcpy(file_state->last_seen_root_mac, closing_root_mac, sizeof(pf_mac_t)); - } - } - unlock(&(enc->volume->files_state_map_lock)); - free(norm_path); + update_mac_in_file_state_map(enc, fs_reachable, &closing_root_mac); } - enc->pf = NULL; PalObjectDestroy(enc->pal_handle); enc->pal_handle = NULL; @@ -721,19 +756,21 @@ void encrypted_file_put(struct libos_encrypted_file* enc, bool fs_reachable) { } } -int encrypted_file_flush(struct libos_encrypted_file* enc) { +int encrypted_file_flush(struct libos_encrypted_file* enc, bool fs_reachable) { assert(enc->pf); pf_status_t pfs = pf_flush(enc->pf); if (PF_FAILURE(pfs)) { log_warning("pf_flush failed: %s", pf_strerror(pfs)); + if (pfs == PF_STATUS_CORRUPTED) + update_state_in_file_state_map(enc, fs_reachable, PF_FILE_STATE_ERROR); return -EACCES; } return 0; } int encrypted_file_read(struct libos_encrypted_file* enc, void* buf, size_t buf_size, - file_off_t offset, size_t* out_count) { + file_off_t offset, size_t* out_count, bool fs_reachable) { assert(enc->pf); if (offset < 0) @@ -745,6 +782,8 @@ int encrypted_file_read(struct libos_encrypted_file* enc, void* buf, size_t buf_ pf_status_t pfs = pf_read(enc->pf, offset, buf_size, buf, &count); if (PF_FAILURE(pfs)) { log_warning("pf_read failed: %s", pf_strerror(pfs)); + if (pfs == PF_STATUS_CORRUPTED) + update_state_in_file_state_map(enc, fs_reachable, PF_FILE_STATE_ERROR); return -EACCES; } *out_count = count; @@ -752,7 +791,7 @@ int encrypted_file_read(struct libos_encrypted_file* enc, void* buf, size_t buf_ } int encrypted_file_write(struct libos_encrypted_file* enc, const void* buf, size_t buf_size, - file_off_t offset, size_t* out_count) { + file_off_t offset, size_t* out_count, bool fs_reachable) { assert(enc->pf); if (offset < 0) @@ -763,6 +802,8 @@ int encrypted_file_write(struct libos_encrypted_file* enc, const void* buf, size pf_status_t pfs = pf_write(enc->pf, offset, buf_size, buf); if (PF_FAILURE(pfs)) { log_warning("pf_write failed: %s", pf_strerror(pfs)); + if (pfs == PF_STATUS_CORRUPTED) + update_state_in_file_state_map(enc, fs_reachable, PF_FILE_STATE_ERROR); return -EACCES; } /* We never write less than `buf_size` */ @@ -770,13 +811,16 @@ int encrypted_file_write(struct libos_encrypted_file* enc, const void* buf, size return 0; } -int encrypted_file_get_size(struct libos_encrypted_file* enc, file_off_t* out_size) { +int encrypted_file_get_size(struct libos_encrypted_file* enc, file_off_t* out_size, + bool fs_reachable) { assert(enc->pf); uint64_t size; pf_status_t pfs = pf_get_size(enc->pf, &size); if (PF_FAILURE(pfs)) { log_warning("pf_get_size failed: %s", pf_strerror(pfs)); + if (pfs == PF_STATUS_CORRUPTED) + update_state_in_file_state_map(enc, fs_reachable, PF_FILE_STATE_ERROR); return -EACCES; } if (OVERFLOWS(file_off_t, size)) @@ -785,7 +829,7 @@ int encrypted_file_get_size(struct libos_encrypted_file* enc, file_off_t* out_si return 0; } -int encrypted_file_set_size(struct libos_encrypted_file* enc, file_off_t size) { +int encrypted_file_set_size(struct libos_encrypted_file* enc, file_off_t size, bool fs_reachable) { assert(enc->pf); if (size < 0) @@ -796,6 +840,8 @@ int encrypted_file_set_size(struct libos_encrypted_file* enc, file_off_t size) { pf_status_t pfs = pf_set_size(enc->pf, size); if (PF_FAILURE(pfs)) { log_warning("pf_set_size failed: %s", pf_strerror(pfs)); + if (pfs == PF_STATUS_CORRUPTED) + update_state_in_file_state_map(enc, fs_reachable, PF_FILE_STATE_ERROR); return -EACCES; } return 0; @@ -822,6 +868,8 @@ int encrypted_file_rename(struct libos_encrypted_file* enc, const char* new_uri) pf_status_t pfs = pf_rename(enc->pf, new_norm_path, &new_root_mac); if (PF_FAILURE(pfs)) { log_warning("pf_rename failed: %s", pf_strerror(pfs)); + if (pfs == PF_STATUS_CORRUPTED) + update_state_in_file_state_map(enc, /* fs_reachable */ true, PF_FILE_STATE_ERROR); ret = -EACCES; goto out; } @@ -835,6 +883,8 @@ int encrypted_file_rename(struct libos_encrypted_file* enc, const char* new_uri) if (PF_FAILURE(pfs)) { log_warning("pf_rename (during cleanup) failed, the file might be unusable: %s", pf_strerror(pfs)); + if (pfs == PF_STATUS_CORRUPTED) + update_state_in_file_state_map(enc, /* fs_reachable */ true, PF_FILE_STATE_ERROR); } old_norm_path = NULL; // don't free it later ... ret = pal_to_unix_errno(ret); @@ -896,22 +946,7 @@ int encrypted_file_rename(struct libos_encrypted_file* enc, const char* new_uri) } int encrypted_file_unlink(struct libos_encrypted_file* enc) { - char* norm_path = NULL; - int ret = uri_to_normalized_path(enc->uri, &norm_path); - if (ret < 0) - return ret; - - lock(&(enc->volume->files_state_map_lock)); - struct libos_encrypted_volume_state_map* file_state = NULL; - HASH_FIND_STR(enc->volume->files_state_map, norm_path, file_state); - assert(file_state != NULL); - pf_mac_t root_mac_before_unlink; - memcpy(root_mac_before_unlink, file_state->last_seen_root_mac, sizeof(pf_mac_t)); - file_state->state = PF_FILE_STATE_DELETED; - memset(file_state->last_seen_root_mac, 0, sizeof(pf_mac_t)); - unlock(&(enc->volume->files_state_map_lock)); - log_debug("file '%s' unlinked, previously with MAC=" MAC_PRINTF_PATTERN, norm_path, - MAC_PRINTF_ARGS(root_mac_before_unlink)); // TODO (MST): remove me eventually? + update_state_in_file_state_map(enc, /* fs_reachable */ true, PF_FILE_STATE_DELETED); return 0; } @@ -1072,7 +1107,7 @@ BEGIN_CP_FUNC(encrypted_file) { struct libos_encrypted_file* new_enc = NULL; if (enc->pf) { - int ret = encrypted_file_flush(enc); + int ret = encrypted_file_flush(enc, true); if (ret < 0) return ret; }