Skip to content

Commit

Permalink
[common/protected_files] Add support for arbitrary truncation
Browse files Browse the repository at this point in the history
Signed-off-by: Marcelina Kościelnicka <mwk@0x04.net>
  • Loading branch information
mwkmwkmwk committed Apr 5, 2024
1 parent 4dae6b0 commit fcdd1d5
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 73 deletions.
12 changes: 5 additions & 7 deletions Documentation/devel/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,10 +389,10 @@ The below list is generated from the [syscall table of Linux
-`fdatasync()`
<sup>[9a](#file-system-operations)</sup>

- `truncate()`
- `truncate()`
<sup>[9a](#file-system-operations)</sup>

- `ftruncate()`
- `ftruncate()`
<sup>[9a](#file-system-operations)</sup>

-`getdents()`
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
1 change: 0 additions & 1 deletion common/src/protected_files/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
77 changes: 38 additions & 39 deletions common/src/protected_files/protected_files.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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));
Expand Down Expand Up @@ -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;
Expand All @@ -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) {
Expand Down
3 changes: 1 addition & 2 deletions common/src/protected_files/protected_files.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
2 changes: 0 additions & 2 deletions libos/src/fs/chroot/encrypted.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/

Expand Down
31 changes: 27 additions & 4 deletions libos/test/fs/seek_tell_truncate.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
Expand All @@ -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 <output> <size> <position> <truncate>\n", argv[0]);
Expand All @@ -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");

Expand Down
17 changes: 0 additions & 17 deletions libos/test/fs/test_enc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
2 changes: 1 addition & 1 deletion libos/test/fs/test_fs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down

0 comments on commit fcdd1d5

Please sign in to comment.