diff --git a/libos/test/regression/meson.build b/libos/test/regression/meson.build index 11c1f0156a..b8cd8ccbb9 100644 --- a/libos/test/regression/meson.build +++ b/libos/test/regression/meson.build @@ -84,6 +84,7 @@ tests = { 'c_args': '-fopenmp', 'link_args': '-fopenmp', }, + 'pf_rollback': {}, 'pipe': {}, 'pipe_nonblocking': {}, 'pipe_ocloexec': {}, diff --git a/libos/test/regression/pf_rollback.c b/libos/test/regression/pf_rollback.c new file mode 100644 index 0000000000..73ef3c771f --- /dev/null +++ b/libos/test/regression/pf_rollback.c @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: LGPL-3.0-or-later */ +/* Copyright (C) 2024 Intel Corporation + * Paweł Marczewski + * Michael Steiner + */ + +/* + * Tests for rollback protection of protected (encrypted) files + */ + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "rw_file.h" + +static const char message1[] = "first message\n"; +static const size_t message1_len = sizeof(message1) - 1; + +static const char message2[] = "second message\n"; +static const size_t message2_len = sizeof(message2) - 1; + +static_assert(sizeof(message1) != sizeof(message2), "the messages should have different lengths"); + +/* TODO: eventually remove below copy/paste/extract heap +static int create_file(const char* path, const char* str, size_t len) { + int fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0600); + if (fd < 0) + err(1, "open %s", path); + + ssize_t n = posix_fd_write(fd, str, len); + if (n < 0) + errx(1, "posix_fd_write %s", path); + if ((size_t)n != len) + errx(1, "written less bytes than expected into %s", path); + + if (rename(path, path) != 0) + err(1, "rename"); + + if (unlink(path) != 0) + err(1, "unlink %s", path); + + if (close(fd) != 0) + err(1, "close %s", path); + +} +*/ + +/* dummy functions which are gdb break-point targets */ +#pragma GCC push_options +#pragma GCC optimize("O0") +static void adversary_save_file(const char* path) {} +static void adversary_reset_file(const char* path) {} +static void adversary_delete_file(const char* path) {} +#pragma GCC pop_options + +static void test_test(const char* path1, const char* path2) { + adversary_save_file(path1); + adversary_reset_file(path1); + adversary_delete_file(path1); + adversary_delete_file(path2); +} + +int main(int argc, char* argv[]) { + setbuf(stdout, NULL); + setbuf(stderr, NULL); + + if (argc != 3) + errx(1, "Usage: %s ", argv[0]); + + const char* path1 = argv[1]; + const char* path2 = argv[2]; + + test_test(path1, path2); + printf("TEST OK\n"); + return 0; +} diff --git a/libos/test/regression/pf_rollback.gdb b/libos/test/regression/pf_rollback.gdb new file mode 100644 index 0000000000..f92f682624 --- /dev/null +++ b/libos/test/regression/pf_rollback.gdb @@ -0,0 +1,89 @@ +set breakpoint pending on +set pagination off +set backtrace past-main on + +# We want to check what happens in the child process after fork() +set follow-fork-mode child + +# Cannot detach after fork because of some bug in SGX version of GDB (GDB would segfault) +set detach-on-fork off + +break adversary_save_file +commands +python +# extract interesting info from context +test_function=gdb.selected_frame().older().name() +operation=gdb.selected_frame().name() +internal_path=gdb.selected_frame().read_var('path').string() +external_path=re.sub(r'/tmp_enc/pm_[^/]*/', './tmp_enc/', internal_path) +external_path_saved=external_path+"._saved_" + +# execute and report result for pytest digestion +try: + import shutil + shutil.copyfile(external_path, external_path_saved) + print(f"OK: {test_function} in {operation}({internal_path})") +except: + print(f"FAIL: {test_function} in {operation}({internal_path})") +end + +continue +end + +break adversary_reset_file +commands +python +# extract interesting info from context +test_function=gdb.selected_frame().older().name() +operation=gdb.selected_frame().name() +internal_path=gdb.selected_frame().read_var('path').string() +external_path=re.sub(r'/tmp_enc/pm_[^/]*/', './tmp_enc/', internal_path) +external_path_saved=external_path+"._saved_" + +# execute and report result for pytest digestion +try: + import shutil + shutil.move(external_path_saved, external_path) + print(f"OK: {test_function} in {operation}({internal_path})") +except: + print(f"FAIL: {test_function} in {operation}({internal_path})") +end + +continue +end + +break adversary_delete_file +commands +python +# extract interesting info from context +test_function=gdb.selected_frame().older().name() +operation=gdb.selected_frame().name() +internal_path=gdb.selected_frame().read_var('path').string() +external_path=re.sub(r'/tmp_enc/pm_[^/]*/', './tmp_enc/', internal_path) +external_path_saved=external_path+"._saved_" + +# execute and report result for pytest digestion +try: + import pathlib + pathlib.Path.unlink(external_path) + print(f"OK: {test_function} in {operation}({internal_path})") +except: + print(f"FAIL: {test_function} in {operation}({internal_path})") +end + +continue +end + +break die_or_inf_loop +commands + echo EXITING GDB WITH A GRAMINE ERROR\n + quit +end + +break exit +commands + echo EXITING GDB WITHOUT A GRAMINE ERROR\n + quit +end + +run diff --git a/libos/test/regression/pf_rollback.manifest.template b/libos/test/regression/pf_rollback.manifest.template new file mode 100644 index 0000000000..4298007eb8 --- /dev/null +++ b/libos/test/regression/pf_rollback.manifest.template @@ -0,0 +1,30 @@ +loader.entrypoint = "file:{{ gramine.libos }}" +libos.entrypoint = "{{ entrypoint }}" + +loader.env.LD_LIBRARY_PATH = "/lib:{{ arch_libdir }}:/usr/{{ arch_libdir }}" +loader.insecure__use_cmdline_argv = true + +fs.mounts = [ + { path = "/lib", uri = "file:{{ gramine.runtimedir(libc) }}" }, + { path = "/{{ entrypoint }}", uri = "file:{{ binary_dir }}/{{ entrypoint }}" }, + { path = "/bin", uri = "file:/bin" }, + + { type = "encrypted", protection_mode = "strict", path = "/tmp_enc/pm_strict", uri = "file:tmp_enc", key_name = "my_custom_key" }, + { type = "encrypted", protection_mode = "non-strict", path = "/tmp_enc/pm_non_strict", uri = "file:tmp_enc", key_name = "my_custom_key" }, + { type = "encrypted", protection_mode = "none", path = "/tmp_enc/pm_none", uri = "file:tmp_enc", key_name = "my_custom_key" }, +] + +sgx.max_threads = {{ '1' if env.get('EDMM', '0') == '1' else '16' }} +sgx.debug = true +sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }} + + +sgx.trusted_files = [ + "file:{{ gramine.libos }}", + "file:{{ gramine.runtimedir(libc) }}/", + "file:{{ binary_dir }}/{{ entrypoint }}", +] + +# See the `keys.c` test. +fs.insecure__keys.default = "ffeeddccbbaa99887766554433221100" +fs.insecure__keys.my_custom_key = "00112233445566778899aabbccddeeff" diff --git a/libos/test/regression/test_libos.py b/libos/test/regression/test_libos.py index 3f16eae33e..7dc79fbc55 100644 --- a/libos/test/regression/test_libos.py +++ b/libos/test/regression/test_libos.py @@ -1385,6 +1385,29 @@ def test_020_gdb_fork_and_access_file_bug(self): with open('fork_and_access_file_testfile', 'w') as f: f.write('fork_and_access_file_testfile') + def test_030_gdb_pf_rollback(self): + # To run this test manually, use: + # GDB=1 GDB_SCRIPT=pf_rollback.gdb gramine-[sgx|direct] pf_rollback + # + # This test checks rollback protection. + try: + file1='/tmp_enc/pm_strict/file1' + file2='/tmp_enc/pm_strict/file2' + stdout, _ = self.run_gdb(['pf_rollback', file1, file2], 'pf_rollback.gdb') + # TODO (MST): This test is not yet implemented. + # - loop for /tmp_enc/pm_strict, /tmp_enc/pm_non_strict, /tmp_enc/pm_none + # - define expected sequence for each test + self.assertIn('OK: test_test in adversary_save_file', stdout) + self.assertIn('OK: test_test in adversary_reset_file', stdout) + self.assertIn(f'OK: test_test in adversary_delete_file({file1})', stdout) + self.assertIn(f'OK: test_test in adversary_delete_file({file2})', stdout) + self.assertIn('EXITING GDB WITHOUT A GRAMINE ERROR', stdout) + self.assertNotIn('EXITING GDB WITH A GRAMINE ERROR', stdout) + finally: + # restore the trusted file contents (modified by the GDB script in this test) + with open('fork_and_access_file_testfile', 'w') as f: + f.write('fork_and_access_file_testfile') + class TC_80_Socket(RegressionTestCase): def test_000_getsockopt(self): stdout, _ = self.run_binary(['getsockopt']) diff --git a/libos/test/regression/tests.toml b/libos/test/regression/tests.toml index 1ed4da79f4..0108e6ef8f 100644 --- a/libos/test/regression/tests.toml +++ b/libos/test/regression/tests.toml @@ -83,6 +83,7 @@ manifests = [ "munmap", "open_opath", "openmp", + "pf_rollback", "pipe", "pipe_nonblocking", "pipe_ocloexec", diff --git a/libos/test/regression/tests_musl.toml b/libos/test/regression/tests_musl.toml index d8cbfac653..11fd1b8a0d 100644 --- a/libos/test/regression/tests_musl.toml +++ b/libos/test/regression/tests_musl.toml @@ -85,6 +85,7 @@ manifests = [ "munmap", "open_opath", "openmp", + "pf_rollback", "pipe", "pipe_nonblocking", "pipe_ocloexec",