diff --git a/Documentation/devel/features.md b/Documentation/devel/features.md index 265cd9b427..af9e77c1db 100644 --- a/Documentation/devel/features.md +++ b/Documentation/devel/features.md @@ -389,10 +389,10 @@ The below list is generated from the [syscall table of Linux - ☑ `fdatasync()` [9a](#file-system-operations) -- ▣ `truncate()` +- ☑ `truncate()` [9a](#file-system-operations) -- ▣ `ftruncate()` +- ☑ `ftruncate()` [9a](#file-system-operations) - ☑ `getdents()` @@ -2087,9 +2087,7 @@ Gramine supports file flushes (via `fsync()` and `fdatasync()`). However, flushi metadata (`sync()` and `syncfs()`) is not supported. Similarly, `sync_file_range()` system call is currently not supported. -Gramine supports file truncation (via `truncate()` and `ftruncate()`). There is one exception -currently: shrinking encrypted files to arbitrary size is not supported (only shrink-to-zero is -supported). +Gramine supports file truncation (via `truncate()` and `ftruncate()`). Gramine has very limited support of `fallocate()` system call. Only mode 0 is supported ("allocating disk space"). The emulation of this mode simply extends the file size if applicable, otherwise does @@ -2164,8 +2162,8 @@ Gramine currently does *not* support changing file access/modification times, vi - ▣ `ppoll()`: dummy - ☑ `fsync()` - ☑ `fdatasync()` -- ▣ `truncate()`: see note above -- ▣ `ftruncate()`: see note above +- ☑ `truncate()` +- ☑ `ftruncate()` - ▣ `fallocate()`: dummy - ▣ `fadvise64()`: dummy diff --git a/common/src/protected_files/README.rst b/common/src/protected_files/README.rst index 483fe96d07..64537a0fe1 100644 --- a/common/src/protected_files/README.rst +++ b/common/src/protected_files/README.rst @@ -66,7 +66,6 @@ Some tests in ``libos/test/regression`` also work with encrypted files. TODO ==== -- Shrinking protected files via truncate(2) to arbitrary size is not yet implemented. - The recovery file feature is disabled, this needs to be discussed if it's needed in Gramine. - Tests for invalid/malformed/corrupted files need to be ported to the new diff --git a/common/src/protected_files/protected_files.c b/common/src/protected_files/protected_files.c index 96f4e0f9a9..40ad501c75 100644 --- a/common/src/protected_files/protected_files.c +++ b/common/src/protected_files/protected_files.c @@ -1123,8 +1123,17 @@ static size_t ipf_read(pf_context_t* pf, void* ptr, uint64_t offset, size_t size return data_attempted_to_read - data_left_to_read; } -static bool ipf_close(pf_context_t* pf) { +static void ipf_delete_cache(pf_context_t* pf) { void* data; + while ((data = lruc_get_last(pf->cache)) != NULL) { + file_node_t* file_node = (file_node_t*)data; + erase_memory(&file_node->decrypted, sizeof(file_node->decrypted)); + free(file_node); + lruc_remove_last(pf->cache); + } +} + +static bool ipf_close(pf_context_t* pf) { bool retval = true; if (pf->file_status != PF_STATUS_SUCCESS) { @@ -1140,12 +1149,7 @@ static bool ipf_close(pf_context_t* pf) { // omeg: fs close is done by Gramine handler pf->file_status = PF_STATUS_UNINITIALIZED; - while ((data = lruc_get_last(pf->cache)) != NULL) { - file_node_t* file_node = (file_node_t*)data; - erase_memory(&file_node->decrypted, sizeof(file_node->decrypted)); - free(file_node); - lruc_remove_last(pf->cache); - } + ipf_delete_cache(pf); // scrub first MD_USER_DATA_SIZE of file data and the gmac_key erase_memory(&pf->encrypted_part_plain, sizeof(pf->encrypted_part_plain)); @@ -1211,7 +1215,6 @@ pf_status_t pf_get_size(pf_context_t* pf, uint64_t* size) { return PF_STATUS_SUCCESS; } -// TODO: File truncation to arbitrary size. pf_status_t pf_set_size(pf_context_t* pf, uint64_t size) { if (!g_initialized) return PF_STATUS_UNINITIALIZED; @@ -1232,40 +1235,36 @@ pf_status_t pf_set_size(pf_context_t* pf, uint64_t size) { return PF_STATUS_SUCCESS; } - if (size == 0) { - // Shrink the file to zero. - void* data; - char path[PATH_MAX_SIZE]; - size_t path_len; - pf_status_t status = g_cb_truncate(pf->file, 0); - if (PF_FAILURE(status)) - return status; - - path_len = strlen(pf->encrypted_part_plain.path); - memcpy(path, pf->encrypted_part_plain.path, path_len); - erase_memory(&pf->encrypted_part_plain, sizeof(pf->encrypted_part_plain)); - memcpy(pf->encrypted_part_plain.path, path, path_len); - - memset(&pf->file_metadata, 0, sizeof(pf->file_metadata)); - pf->file_metadata.plain_part.file_id = PF_FILE_ID; - pf->file_metadata.plain_part.major_version = PF_MAJOR_VERSION; - pf->file_metadata.plain_part.minor_version = PF_MINOR_VERSION; - - ipf_init_root_mht(&pf->root_mht); + // Truncation. - pf->need_writing = true; + // The structure of the protected file is such that we can simply truncate + // the file after the last data block belonging to still-used data. + // Some MHT entries will be left with dangling data describing the truncated + // nodes, but this is not a problem since they will be unused, and will be + // overwritten with new data when the relevant nodes get allocated again. - while ((data = lruc_get_last(pf->cache)) != NULL) { - file_node_t* file_node = (file_node_t*)data; - erase_memory(&file_node->decrypted, sizeof(file_node->decrypted)); - free(file_node); - lruc_remove_last(pf->cache); - } - - return PF_STATUS_SUCCESS; - } + // First, ensure any nodes that will be truncated are not in cache. We do it + // by simply flushing and then emptying the entire cache. + if (!ipf_internal_flush(pf)) + return pf->last_error; + ipf_delete_cache(pf); - return PF_STATUS_NOT_IMPLEMENTED; + // Calculate new file size. + uint64_t new_file_size; + if (size <= MD_USER_DATA_SIZE) { + new_file_size = PF_NODE_SIZE; + } else { + uint64_t physical_node_number; + get_node_numbers(size - 1, NULL, NULL, NULL, &physical_node_number); + new_file_size = (physical_node_number + 1) * PF_NODE_SIZE; + } + pf_status_t status = g_cb_truncate(pf->file, new_file_size); + if (PF_FAILURE(status)) + return status; + // If successfully truncated, update our bookkeeping. + pf->encrypted_part_plain.size = size; + pf->need_writing = true; + return PF_STATUS_SUCCESS; } pf_status_t pf_rename(pf_context_t* pf, const char* new_path) { diff --git a/common/src/protected_files/protected_files.h b/common/src/protected_files/protected_files.h index 793a4864a5..4354f3e6fe 100644 --- a/common/src/protected_files/protected_files.h +++ b/common/src/protected_files/protected_files.h @@ -281,8 +281,7 @@ pf_status_t pf_get_size(pf_context_t* pf, uint64_t* size); * * \returns PF status. * - * If the file is extended, added bytes are zero. Shrinking to arbitrary size is not implemented - * yet (TODO). + * If the file is extended, added bytes are zero. */ pf_status_t pf_set_size(pf_context_t* pf, uint64_t size); diff --git a/libos/src/fs/chroot/encrypted.c b/libos/src/fs/chroot/encrypted.c index 94a03be134..37af2f9edc 100644 --- a/libos/src/fs/chroot/encrypted.c +++ b/libos/src/fs/chroot/encrypted.c @@ -31,8 +31,6 @@ * * TODO: * - * - truncate - The truncate functionality does not support shrinking to arbitrary size. - * It has to be added to the `protected_files` module. * - flush all files on process exit */ diff --git a/libos/test/fs/seek_tell_truncate.c b/libos/test/fs/seek_tell_truncate.c index 7b653fd7cf..6b8262218b 100644 --- a/libos/test/fs/seek_tell_truncate.c +++ b/libos/test/fs/seek_tell_truncate.c @@ -2,18 +2,19 @@ #define CHUNK_SIZE 512 -static void setup_file(const char* path, size_t size) { +static void setup_file(const char* path, size_t size, void* check_buf, size_t check_size) { int fd = open_output_fd(path, /*rdwr=*/false); void* buf = alloc_buffer(size); fill_random(buf, size); write_fd(path, fd, buf, size); + memcpy(check_buf, buf, size < check_size ? size : check_size); free(buf); close_fd(path, fd); } -static void seek_truncate(const char* path, size_t file_pos, size_t file_truncate) { +static void seek_truncate(const char* path, size_t file_pos, size_t file_truncate, void* check_buf) { int fd = open_output_fd(path, /*rdwr=*/false); seek_fd(path, fd, file_pos, SEEK_SET); @@ -37,6 +38,7 @@ static void seek_truncate(const char* path, size_t file_pos, size_t file_truncat void* buf = alloc_buffer(CHUNK_SIZE); fill_random(buf, CHUNK_SIZE); write_fd(path, fd, buf, CHUNK_SIZE); + memcpy(check_buf + pos, buf, CHUNK_SIZE); free(buf); size_t next_file_pos = file_pos + CHUNK_SIZE; @@ -49,6 +51,19 @@ static void seek_truncate(const char* path, size_t file_pos, size_t file_truncat close_fd(path, fd); } +static void check_contents(const char* path, void* data, size_t size) { + int fd = open_input_fd(path); + + void* buf = alloc_buffer(size); + read_fd(path, fd, buf, size); + if (memcmp(buf, data, size)) { + fatal_error("truncated data mismatch"); + } + free(buf); + + close_fd(path, fd); +} + int main(int argc, char* argv[]) { if (argc < 5) fatal_error("Usage: %s \n", argv[0]); @@ -58,8 +73,16 @@ int main(int argc, char* argv[]) { size_t file_pos = strtoul(argv[3], NULL, 10); size_t file_truncate = strtoul(argv[4], NULL, 10); - setup_file(argv[1], file_size); - seek_truncate(argv[1], file_pos, file_truncate); + + size_t check_size = file_pos + CHUNK_SIZE; + if (file_truncate > check_size) + check_size = file_truncate; + void* check_buf = alloc_buffer(check_size); + memset(check_buf, 0, check_size); + + setup_file(argv[1], file_size, check_buf, file_truncate); + seek_truncate(argv[1], file_pos, file_truncate, check_buf); + check_contents(argv[1], check_buf, check_size); printf("Test passed\n"); diff --git a/libos/test/fs/test_enc.py b/libos/test/fs/test_enc.py index ee90890a6d..6eccc02e19 100644 --- a/libos/test/fs/test_enc.py +++ b/libos/test/fs/test_enc.py @@ -159,23 +159,6 @@ def verify_truncate_test(self, path_1, path_2, size_out): self.verify_size(path_1, size_out) self.verify_size(path_2, size_out) - # overrides TC_00_FileSystem to change input dir (from plaintext to encrypted) and - # because file truncation from greater to small size is not yet implemented - def test_140_file_truncate(self): - enc_path = self.ENCRYPTED_FILES[-1] # existing file - path_1 = os.path.join(self.OUTPUT_DIR, 'test_140a') # writable files - path_2 = os.path.join(self.OUTPUT_DIR, 'test_140b') # writable files - self.copy_input(enc_path, path_1) # encrypt - self.copy_input(enc_path, path_2) # encrypt - - self.verify_truncate_test(path_1, path_2, 0) - self.verify_truncate_test(path_1, path_2, 200) - self.verify_truncate_test(path_1, path_2, 0) - self.verify_truncate_test(path_1, path_2, 1000) - self.verify_truncate_test(path_1, path_2, 1000) - self.verify_truncate_test(path_1, path_2, 0) - self.verify_truncate_test(path_1, path_2, 0) - def test_150_file_rename(self): path1 = os.path.join(self.OUTPUT_DIR, 'test_150a') path2 = os.path.join(self.OUTPUT_DIR, 'test_150b') diff --git a/libos/test/fs/test_fs.py b/libos/test/fs/test_fs.py index 99f0dd599c..9aa8e77655 100644 --- a/libos/test/fs/test_fs.py +++ b/libos/test/fs/test_fs.py @@ -262,7 +262,7 @@ def test_141_file_seek_tell_truncate(self): self.verify_seek_tell_truncate(f"{file_path}c", 512, 64, 65536) self.verify_seek_tell_truncate(f"{file_path}d", 512, 512, 0) self.verify_seek_tell_truncate(f"{file_path}e", 512, 256, 0) - # XXX: we do not support shrinking files to arbitrary sizes in protected files + self.verify_seek_tell_truncate(f"{file_path}f", 31337, 20000, 7331) def verify_copy_content(self, input_path, output_path): self.assertTrue(filecmp.cmp(input_path, output_path, shallow=False))