diff --git a/Jenkinsfiles/ubuntu-16.04.dockerfile b/Jenkinsfiles/ubuntu-16.04.dockerfile index 01638fe8..e2d9076f 100644 --- a/Jenkinsfiles/ubuntu-16.04.dockerfile +++ b/Jenkinsfiles/ubuntu-16.04.dockerfile @@ -42,6 +42,14 @@ RUN apt-get update \ zlib1g-dev \ && /usr/bin/pip3 install protobuf \ +# Install OpenSSL 1.1 from source, 16.04 doesn't have it in official repositories + && wget https://www.openssl.org/source/openssl-1.1.1d.tar.gz \ + && tar -xf openssl-1.1.1d.tar.gz \ + && cd openssl-1.1.1d \ + && ./config --prefix=/usr --openssldir=/usr shared \ + && make \ + && make install \ + # Add the user UID:1001, GID:1001, home at /leeroy && groupadd -r leeroy -g 1001 \ && useradd -u 1001 -r -g leeroy -m -d /leeroy -c "Leeroy Jenkins" leeroy \ diff --git a/Jenkinsfiles/ubuntu-18.04.dockerfile b/Jenkinsfiles/ubuntu-18.04.dockerfile index 2b0345b3..d4a390ae 100644 --- a/Jenkinsfiles/ubuntu-18.04.dockerfile +++ b/Jenkinsfiles/ubuntu-18.04.dockerfile @@ -22,6 +22,7 @@ RUN apt-get update && env DEBIAN_FRONTEND=noninteractive apt-get install -y \ libpcre2-dev \ libpcre3-dev \ libprotobuf-c-dev \ + libssl-dev \ libxml2-dev \ linux-headers-4.15.0-20-generic \ net-tools \ diff --git a/LibOS/shim/test/fs/Makefile b/LibOS/shim/test/fs/Makefile index 147c77d1..c5273f70 100644 --- a/LibOS/shim/test/fs/Makefile +++ b/LibOS/shim/test/fs/Makefile @@ -27,16 +27,25 @@ $(execs) endif export PAL_LOADER = $(RUNTIME)/pal-$(PAL_HOST) +export PAL_TOOLS = ../../../../Pal/src/host/$(PAL_HOST)/tools export PYTHONPATH = ../../../../Scripts +.PHONY: fs-test +fs-test: $(target) + $(RM) fs-test.xml + $(MAKE) fs-test.xml + .PHONY: test test: $(target) - $(RM) fs-test.xml - $(MAKE) fs-test.xml + $(RM) pf-test.xml + $(MAKE) pf-test.xml fs-test.xml: python3 -m pytest --junit-xml $@ -v test_fs.py +pf-test.xml: + python3 -m pytest --junit-xml $@ -v test_pf.py + .PHONY: clean-tmp clean-tmp: rm -rf *.tmp *.cached *.manifest.sgx *~ *.sig *.token *.o __pycache__ .pytest_cache .cache *.xml diff --git a/LibOS/shim/test/fs/README.md b/LibOS/shim/test/fs/README.md index 4c2cfbd0..3e52a5a2 100644 --- a/LibOS/shim/test/fs/README.md +++ b/LibOS/shim/test/fs/README.md @@ -14,4 +14,5 @@ These tests perform common FS operations in various ways to exercise the Graphen How to execute -------------- -Run `make test`. +Run `make test` (tests both regular files and protected files). +Run `make fs-test` to only test regular files. diff --git a/LibOS/shim/test/fs/manifest.template b/LibOS/shim/test/fs/manifest.template index 7a80c6af..e3ee32f2 100644 --- a/LibOS/shim/test/fs/manifest.template +++ b/LibOS/shim/test/fs/manifest.template @@ -31,3 +31,7 @@ sgx.trusted_files.libpthread = file:../../../../Runtime/libpthread.so.0 sgx.trusted_files.libgcc_s = file:/lib/x86_64-linux-gnu/libgcc_s.so.1 sgx.allowed_files.tmp_dir = file:tmp/ + +sgx.protected_files_key = ffeeddccbbaa99887766554433221100 +sgx.protected_files.input = file:tmp/pf_input +sgx.protected_files.output = file:tmp/pf_output diff --git a/LibOS/shim/test/fs/test_pf.py b/LibOS/shim/test/fs/test_pf.py new file mode 100644 index 00000000..98baedc1 --- /dev/null +++ b/LibOS/shim/test/fs/test_pf.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 + +import filecmp +import os +import shutil +import subprocess +import sys +import unittest + +from test_fs import ( + TC_00_FileSystem, +) + +from regression import ( + HAS_SGX, +) + +@unittest.skipUnless(HAS_SGX, 'Protected files require SGX support') +class TC_50_ProtectedFiles(TC_00_FileSystem): + @classmethod + def setUpClass(c): + c.PF_CRYPT = os.path.join(os.environ.get('PAL_TOOLS'), 'pf_crypt') + c.PF_TAMPER = os.path.join(os.environ.get('PAL_TOOLS'), 'pf_tamper') + c.WRAP_KEY = os.path.join(c.TEST_DIR, 'wrap-key') + # CONST_WRAP_KEY must match the one in manifest + c.CONST_WRAP_KEY = [0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00] + c.ENCRYPTED_DIR = os.path.join(c.TEST_DIR, 'pf_input') + c.ENCRYPTED_FILES = [os.path.join(c.ENCRYPTED_DIR, str(v)) for v in c.FILE_SIZES] + + super().setUpClass() + if not os.path.exists(c.ENCRYPTED_DIR): + os.mkdir(c.ENCRYPTED_DIR) + c.OUTPUT_DIR = os.path.join(c.TEST_DIR, 'pf_output') + c.OUTPUT_FILES = [os.path.join(c.OUTPUT_DIR, str(x)) for x in c.FILE_SIZES] + # create encrypted files + c.set_default_key(c) + for i in c.INDEXES: + cmd = [c.PF_CRYPT, 'e', '-w', c.WRAP_KEY, '-i', c.INPUT_FILES[i], '-o', c.ENCRYPTED_FILES[i], '-p', c.ENCRYPTED_DIR] + c.run_native_binary(c, cmd) + + def set_default_key(self): + with open(self.WRAP_KEY, 'wb') as file: + file.write(bytes(self.CONST_WRAP_KEY)) + + # override to encrypt the file + def copy_input(self, input, output): + self.encrypt_file(input, output, self.OUTPUT_DIR) + + def encrypt_file(self, input, output, prefix): + cmd = [self.PF_CRYPT, 'e', '-w', self.WRAP_KEY, '-i', input, '-o', output, '-p', prefix] + stdout, stderr = self.run_native_binary(cmd) + return (stdout, stderr) + + def decrypt_file(self, input, output): + cmd = [self.PF_CRYPT, 'd', '-w', self.WRAP_KEY, '-i', input, '-o', output] + stdout, stderr = self.run_native_binary(cmd) + return (stdout, stderr) + + def test_000_gen_key(self): + # test random key generation + cmd = [self.PF_CRYPT, 'g', '-w', self.WRAP_KEY] + stdout, stderr = self.run_native_binary(cmd) + self.assertIn('Wrap key saved to: ' + self.WRAP_KEY, stdout) + self.assertEqual(os.path.getsize(self.WRAP_KEY), 16) + # change key to the hardcoded one for remaining tests + self.set_default_key() + + def test_010_encrypt_decrypt(self): + for i in self.INDEXES: + stdout, stderr = self.encrypt_file(self.INPUT_FILES[i], self.OUTPUT_FILES[i], '/'+self.OUTPUT_DIR) + self.assertFalse(filecmp.cmp(self.INPUT_FILES[i], self.OUTPUT_FILES[i], shallow=False)) + dp = os.path.join(self.OUTPUT_DIR, os.path.basename(self.OUTPUT_FILES[i]) + '.decrypted') + stdout, stderr = self.decrypt_file(self.OUTPUT_FILES[i], dp) + self.assertTrue(filecmp.cmp(self.INPUT_FILES[i], dp, shallow=False)) + + # override to change input dir (from plaintext to encrypted) + def test_100_open_close(self): + input_path = self.ENCRYPTED_FILES[-1] # existing file + output_path = os.path.join(self.OUTPUT_DIR, 'test_100') # new file + stdout, stderr = self.run_binary(['open_close', input_path, output_path]) + self.verify_open_close(stdout, stderr, input_path, output_path) + + # override to change input dir (from plaintext to encrypted) + def test_115_seek_tell(self): + plaintext_path = self.INPUT_FILES[-1] + input_path = self.ENCRYPTED_FILES[-1] # existing file + output_path_1 = os.path.join(self.OUTPUT_DIR, 'test_115a') # writable files + output_path_2 = os.path.join(self.OUTPUT_DIR, 'test_115b') + self.copy_input(plaintext_path, output_path_1) # encrypt + self.copy_input(plaintext_path, output_path_2) + stdout, stderr = self.run_binary(['seek_tell', input_path, output_path_1, output_path_2]) + self.verify_seek_tell(stdout, stderr, input_path, output_path_1, output_path_2, self.FILE_SIZES[-1]) + + # override to change input dir (from plaintext to encrypted) + def test_130_file_stat(self): + for i in self.INDEXES: + input_path = self.ENCRYPTED_FILES[i] + output_path = self.OUTPUT_FILES[i] + size = str(self.FILE_SIZES[i]) + self.copy_input(self.INPUT_FILES[i], output_path) + stdout, stderr = self.run_binary(['stat', input_path, output_path]) + self.verify_stat(stdout, stderr, input_path, output_path, size) + + # override to decrypt output + def verify_size(self, file, size): + dp = os.path.join(self.OUTPUT_DIR, os.path.basename(file) + '.decrypted') + self.decrypt_file(file, dp) + self.assertEqual(os.stat(dp).st_size, size) + + # override to decrypt output + def verify_copy_content(self, input, output): + dp = os.path.join(self.OUTPUT_DIR, os.path.basename(output) + '.decrypted') + self.decrypt_file(output, dp) + self.assertTrue(filecmp.cmp(input, dp, shallow=False)) + + # override to change input dir (from plaintext to encrypted) + def do_copy_test(self, exec, timeout): + stdout, stderr = self.run_binary([exec, self.ENCRYPTED_DIR, self.OUTPUT_DIR], timeout=timeout) + self.verify_copy(stdout, stderr, self.ENCRYPTED_DIR, exec) + + # override copy_dir_mmap* to not skip them on SGX + def test_203_copy_dir_mmap_whole(self): + self.do_copy_test('copy_mmap_whole', 30) + + def test_204_copy_dir_mmap_seq(self): + self.do_copy_test('copy_mmap_seq', 60) + + def test_205_copy_dir_mmap_rev(self): + self.do_copy_test('copy_mmap_rev', 60) + + def test_210_copy_dir_mounted(self): + exec = 'copy_whole' + stdout, stderr = self.run_binary([exec, '/mounted/pf_input', '/mounted/pf_output'], timeout=30) + self.verify_copy(stdout, stderr, '/mounted/pf_input', exec) + + def corrupt_file(self, input, output): + cmd = [self.PF_TAMPER, '-w', self.WRAP_KEY, '-i', input, '-o', output] + stdout, stderr = self.run_native_binary(cmd) + return (stdout, stderr) + + # invalid/corrupted files + def test_500_invalid(self): + INVALID_DIR = os.path.join(self.TEST_DIR, 'pf_invalid') + # files below should work normally (benign modifications) + SHOULD_PASS = ['chunk_padding_1_fixed', 'chunk_padding_2_fixed', 'chunk_data_3', 'chunk_data_3_fixed', 'chunk_data_4', 'chunk_data_4_fixed'] + if not os.path.exists(INVALID_DIR): + os.mkdir(INVALID_DIR) + # prepare valid encrypted file (largest one for maximum possible corruptions) + original_input = self.OUTPUT_FILES[-1] + # target prefix is INVALID_DIR + self.encrypt_file(self.INPUT_FILES[-1], original_input, INVALID_DIR) + # generate invalid files based on the above + self.corrupt_file(original_input, INVALID_DIR) + # try to decrypt invalid files + for name in os.listdir(INVALID_DIR): + invalid = os.path.join(INVALID_DIR, name) + output = os.path.join(self.OUTPUT_DIR, name) + input = os.path.join(INVALID_DIR, os.path.basename(original_input)) + # copy the file so it has the original file name (for allowed path check) + shutil.copy(invalid, input) + should_pass = any(s in name for s in SHOULD_PASS) + + try: + self.run_native_binary([self.PF_CRYPT, 'd', '-V', '-w', self.WRAP_KEY, '-i', input, '-o', output]) + except subprocess.CalledProcessError as e: + if should_pass: + self.assertEqual(e.returncode, 0) + else: + self.assertNotEqual(e.returncode, 0) + else: + if not should_pass: + print('[!] Fail: successfully decrypted file: ' + name) + self.fail() diff --git a/Pal/lib/Makefile b/Pal/lib/Makefile index c4412f36..5dc00efc 100644 --- a/Pal/lib/Makefile +++ b/Pal/lib/Makefile @@ -34,7 +34,7 @@ CRYPTO_PROVIDER ?= mbedtls # symbols. ifeq ($(CRYPTO_PROVIDER),mbedtls) subdirs += crypto/mbedtls/library -crypto_mbedtls_library_objs = $(addsuffix .o,aes aesni asn1parse base64 bignum cipher cipher_wrap cmac dhm md md_wrap oid rsa rsa_internal sha256 platform_util) +crypto_mbedtls_library_objs = $(addsuffix .o,aes aesni asn1parse base64 bignum cipher cipher_wrap cmac dhm gcm md md_wrap oid rsa rsa_internal sha256 platform_util) endif MBEDTLS_VERSION ?= 2.16.3 diff --git a/Pal/lib/crypto/adapters/mbedtls_adapter.c b/Pal/lib/crypto/adapters/mbedtls_adapter.c index a675c2e0..0dcd940a 100644 --- a/Pal/lib/crypto/adapters/mbedtls_adapter.c +++ b/Pal/lib/crypto/adapters/mbedtls_adapter.c @@ -26,6 +26,7 @@ #include "assert.h" #include "mbedtls/aes.h" #include "mbedtls/cmac.h" +#include "mbedtls/gcm.h" #include "mbedtls/sha256.h" #include "mbedtls/rsa.h" @@ -149,6 +150,70 @@ int lib_SHA256Final(LIB_SHA256_CONTEXT *context, uint8_t *output) return 0; } +/* GCM encrypt, iv is assumed to be 12 bytes. + * input_len doesn't have to be a multiple of 16. + * Additional authenticated data (aad) may be NULL if absent. + * Output len is the same as input_len. */ +int lib_AESGCMEncrypt(const uint8_t* key, uint64_t key_len, const uint8_t* iv, const uint8_t* input, + uint64_t input_len, const uint8_t* aad, uint64_t aad_len, uint8_t* output, + uint8_t* tag, uint64_t tag_len) { + int ret = -PAL_ERROR_INVAL; + + mbedtls_gcm_context gcm; + mbedtls_gcm_init(&gcm); + + if (key_len != 16 && key_len != 24 && key_len != 32) + goto out; + + ret = mbedtls_gcm_setkey(&gcm, MBEDTLS_CIPHER_ID_AES, key, key_len * 8); + ret = mbedtls_to_pal_error(ret); + if (ret != 0) + goto out; + + ret = mbedtls_gcm_crypt_and_tag(&gcm, MBEDTLS_GCM_ENCRYPT, input_len, iv, 12, aad, aad_len, + input, output, tag_len, tag); + ret = mbedtls_to_pal_error(ret); + if (ret != 0) + goto out; + + ret = 0; +out: + mbedtls_gcm_free(&gcm); + return ret; + } + +/* GCM decrypt, iv is assumed to be 12 bytes. + * input_len doesn't have to be a multiple of 16. + * Additional authenticated data (aad) may be NULL if absent. + * Output len is the same as input_len. */ +int lib_AESGCMDecrypt(const uint8_t* key, uint64_t key_len, const uint8_t* iv, const uint8_t* input, + uint64_t input_len, const uint8_t* aad, uint64_t aad_len, uint8_t* output, + const uint8_t* tag, uint64_t tag_len) { + int ret = -PAL_ERROR_INVAL; + + mbedtls_gcm_context gcm; + mbedtls_gcm_init(&gcm); + + if (key_len != 16 && key_len != 24 && key_len != 32) + goto out; + + ret = mbedtls_gcm_setkey(&gcm, MBEDTLS_CIPHER_ID_AES, key, key_len * 8); + ret = mbedtls_to_pal_error(ret); + if (ret != 0) + goto out; + + ret = mbedtls_gcm_auth_decrypt(&gcm, input_len, iv, 12, aad, aad_len, tag, tag_len, input, + output); + ret = mbedtls_to_pal_error(ret); + if (ret != 0) + goto out; + + ret = 0; +out: + mbedtls_gcm_free(&gcm); + return ret; + } + int lib_AESCMAC(const uint8_t *key, uint64_t key_len, const uint8_t *input, uint64_t input_len, uint8_t *mac, uint64_t mac_len) { mbedtls_cipher_type_t cipher; diff --git a/Pal/lib/crypto/config.h b/Pal/lib/crypto/config.h index 59788780..aafd8d1a 100644 --- a/Pal/lib/crypto/config.h +++ b/Pal/lib/crypto/config.h @@ -35,6 +35,7 @@ #define MBEDTLS_PLATFORM_C #define MBEDTLS_RSA_C #define MBEDTLS_SHA256_C +#define MBEDTLS_GCM_C #define MBEDTLS_BASE64_C #define MBEDTLS_ASN1_PARSE_C diff --git a/Pal/lib/pal_crypto.h b/Pal/lib/pal_crypto.h index c6654afe..79aa7226 100644 --- a/Pal/lib/pal_crypto.h +++ b/Pal/lib/pal_crypto.h @@ -74,6 +74,20 @@ void lib_DhFinal(LIB_DH_CONTEXT *context); /* AES-CMAC */ int lib_AESCMAC(const uint8_t *key, uint64_t key_len, const uint8_t *input, uint64_t input_len, uint8_t *mac, uint64_t mac_len); +/* GCM encrypt, iv is assumed to be 12 bytes (and is changed by this call). + * input_len doesn't have to be a multiple of 16. + * Additional authenticated data (aad) may be NULL if absent. + * Output len is the same as input_len. */ +int lib_AESGCMEncrypt(const uint8_t* key, uint64_t key_len, const uint8_t* iv, const uint8_t* input, + uint64_t input_len, const uint8_t* aad, uint64_t aad_len, uint8_t* output, + uint8_t* tag, uint64_t tag_len); +/* GCM decrypt, iv is assumed to be 12 bytes (and is changed by this call). + * input_len doesn't have to be a multiple of 16. + * Additional authenticated data (aad) may be NULL if absent. + * Output len is the same as input_len. */ +int lib_AESGCMDecrypt(const uint8_t* key, uint64_t key_len, const uint8_t* iv, const uint8_t* input, + uint64_t input_len, const uint8_t* aad, uint64_t aad_len, uint8_t* output, + const uint8_t* tag, uint64_t tag_len); /* note: 'lib_AESCMAC' is the combination of 'lib_AESCMACInit', * 'lib_AESCMACUpdate', and 'lib_AESCMACFinish'. */ diff --git a/Pal/src/host/Linux-SGX/Makefile b/Pal/src/host/Linux-SGX/Makefile index 33044012..130b1185 100644 --- a/Pal/src/host/Linux-SGX/Makefile +++ b/Pal/src/host/Linux-SGX/Makefile @@ -13,9 +13,10 @@ defs = -DIN_PAL -DPAL_DIR=$(PAL_DIR) -DRUNTIME_DIR=$(RUNTIME_DIR) CFLAGS += $(defs) ASFLAGS += $(defs) enclave-objs = $(addprefix db_,files devices pipes eventfd sockets streams memory \ - threading mutex events process object main rtld \ - exception misc spinlock) \ - $(addprefix enclave_,ocalls ecalls framework platform pages untrusted) + threading mutex events process object main rtld \ + exception misc spinlock) \ + $(addprefix enclave_,ocalls ecalls framework pf platform pages untrusted) \ + protected_files enclave-asm-objs = enclave_entry urts-objs = $(addprefix sgx_,enclave framework platform main rtld thread process exception graphene) \ quote/aesm.pb-c clone-x86_64 @@ -23,7 +24,7 @@ urts-asm-objs = sgx_entry graphene_lib = .lib/graphene-lib.a .PHONY: all -all: sgx-driver/isgx_version.h $(host_files) +all: sgx-driver/isgx_version.h $(host_files) tools ifeq ($(DEBUG),1) CC += -gdwarf-2 -g3 @@ -32,6 +33,8 @@ ASFLAGS += -DDEBUG export DEBUG endif +sgx_framework.c: sgx-driver/isgx_version.h + ../../host_endian.h: host_endian.h $(MAKE) -C ../../ $< @@ -88,6 +91,10 @@ enclave_entry.o sgx_entry.o: asm-offsets.h sgx-driver/isgx_version.h: $(MAKE) -C sgx-driver $(notdir $@) +.PHONY: tools +tools: + @$(MAKE) -C tools + ifeq ($(filter clean,$(MAKECMDGOALS)),) include $(wildcard *.d) $(wildcard debugger/*.d) endif @@ -104,6 +111,7 @@ clean: rm -rf *.o *.e *.i *.s $(host_files) $(CLEAN_FILES) *.d debugger/*.d signer/*.pyc __pycache__ \ signer/__pycache__ $(MAKE) -C sgx-driver $@ + $(MAKE) -C tools clean .PHONY: test test: diff --git a/Pal/src/host/Linux-SGX/db_files.c b/Pal/src/host/Linux-SGX/db_files.c index 7ba4637e..5f878711 100644 --- a/Pal/src/host/Linux-SGX/db_files.c +++ b/Pal/src/host/Linux-SGX/db_files.c @@ -21,10 +21,7 @@ * "file:" or "dir:". */ -#include - #include "api.h" -#include "assert.h" #include "pal.h" #include "pal_debug.h" #include "pal_defs.h" @@ -39,6 +36,7 @@ typedef __kernel_pid_t pid_t; #include #include #include +#include #include "enclave_pages.h" @@ -47,6 +45,9 @@ static int file_open(PAL_HANDLE* handle, const char* type, const char* uri, int int create, int options) { if (strcmp_static(type, URI_TYPE_FILE)) return -PAL_ERROR_INVAL; + + struct protected_file* pf = NULL; + /* try to do the real open */ int fd = ocall_open(uri, access | create | options, share); @@ -56,10 +57,13 @@ static int file_open(PAL_HANDLE* handle, const char* type, const char* uri, int /* if try_create_path succeeded, prepare for the file handle */ size_t len = strlen(uri) + 1; PAL_HANDLE hdl = malloc(HANDLE_SIZE(file) + len); + if (!hdl) + return -PAL_ERROR_NOMEM; + SET_HANDLE_TYPE(hdl, file); HANDLE_HDR(hdl)->flags |= RFD(0) | WFD(0) | WRITABLE(0); - hdl->file.fd = fd; - char* path = (void*)hdl + HANDLE_SIZE(file); + hdl->file.fd = fd; + char* path = (void*)hdl + HANDLE_SIZE(file); int ret; if ((ret = get_norm_path(uri, path, &len)) < 0) { SGX_DBG(DBG_E, "Could not normalize path (%s): %s\n", uri, pal_strerror(ret)); @@ -68,38 +72,116 @@ static int file_open(PAL_HANDLE* handle, const char* type, const char* uri, int } hdl->file.realpath = (PAL_STR)path; - sgx_stub_t* stubs; - uint64_t total; - ret = load_trusted_file(hdl, &stubs, &total, create); - if (ret < 0) { - SGX_DBG(DBG_E, - "Accessing file:%s is denied. (%s) " - "This file is not trusted or allowed.\n", - hdl->file.realpath, pal_strerror(ret)); - free(hdl); - return ret; - } + SGX_DBG(DBG_D, "file_open: fd %d, uri %s [%s]\n", fd, uri, path); + pf = get_protected_file(path); + if (pf) { + pf_file_mode_t pf_mode = 0; + if ((access & O_RDWR) == O_RDWR) /* 2 */ + pf_mode = PF_FILE_MODE_READ | PF_FILE_MODE_WRITE; + else if ((access & O_WRONLY) == O_WRONLY) /* 1 */ + pf_mode = PF_FILE_MODE_WRITE; + else /* O_RDONLY == 0 */ + pf_mode = PF_FILE_MODE_READ; - hdl->file.stubs = (PAL_PTR)stubs; - hdl->file.total = total; - hdl->file.offset = 0; - - if (hdl->file.stubs && hdl->file.total) { - /* case of trusted file: mmap the whole file in untrusted memory for future reads/writes */ - ret = ocall_mmap_untrusted(hdl->file.fd, 0, hdl->file.total, PROT_READ, &hdl->file.umem); + /* get real file size */ + struct stat st; + ret = ocall_fstat(fd, &st); if (IS_ERR(ret)) { - /* note that we don't free stubs because they are re-used in same trusted file */ + SGX_DBG(DBG_E, "file_open(%s): fstat failed: %d\n", path, ret); + ret = unix_to_pal_error(ERRNO(ret)); + goto out; + } + + ret = -PAL_ERROR_DENIED; + pf = load_protected_file(path, (int*)&hdl->file.fd, st.st_size, pf_mode, create, pf); + if (pf) { + bool allowed = false; + pf_status_t pfs = pf_check_path(pf->context, path, &allowed); + if (!allowed || PF_FAILURE(pfs)) { + SGX_DBG(DBG_E, "file_open(%s): path doesn't match PF's allowed paths\n", path); + goto out; + } + + if (pf->refcount == INT64_MAX) { + SGX_DBG(DBG_E, "file_open(%s): maximum refcount exceeded\n", path); + goto out; + } + + pf->refcount++; + } else { + SGX_DBG(DBG_E, "load_protected_file(%s, %d) failed\n", path, hdl->file.fd); + goto out; + } + + hdl->file.offset = 0; + } else { + sgx_stub_t* stubs; + uint64_t total; + ret = load_trusted_file(hdl, &stubs, &total, create); + if (ret < 0) { + SGX_DBG(DBG_E, "Accessing file:%s is denied. (%s) " + "This file is not trusted or allowed.\n", + hdl->file.realpath, pal_strerror(ret)); free(hdl); - return unix_to_pal_error(ERRNO(ret)); + return ret; + } + + hdl->file.stubs = (PAL_PTR)stubs; + hdl->file.total = total; + hdl->file.offset = 0; + + if (hdl->file.stubs && hdl->file.total) { + /* case of trusted file: mmap the whole file in untrusted memory for future reads/writes */ + ret = ocall_mmap_untrusted(hdl->file.fd, 0, hdl->file.total, PROT_READ, &hdl->file.umem); + if (IS_ERR(ret)) { + /* note that we don't free stubs because they are re-used in same trusted file */ + free(hdl); + return unix_to_pal_error(ERRNO(ret)); + } } } *handle = hdl; - return 0; + ret = 0; + +out: + if (ret != 0) { + if (pf) + unload_protected_file(pf); + + free(hdl); + ocall_close(fd); + } + return ret; +} + + +static int64_t pf_file_read(struct protected_file* pf, PAL_HANDLE handle, uint64_t offset, + uint64_t count, void* buffer) { + int fd = handle->file.fd; + + if (!pf->context) { + SGX_DBG(DBG_E, "pf_file_read: PF fd %d not initialized\n", fd); + return -PAL_ERROR_BADHANDLE; + } + + pf_status_t pfs = pf_read(pf->context, offset, count, buffer); + + if (PF_FAILURE(pfs)) { + SGX_DBG(DBG_E, "pf_file_read(PF fd %d): pf_read failed: %d\n", fd, pfs); + return -PAL_ERROR_DENIED; + } + + return count; } /* 'read' operation for file streams. */ static int64_t file_read(PAL_HANDLE handle, uint64_t offset, uint64_t count, void* buffer) { + struct protected_file* pf = find_protected_file_handle(handle); + + if (pf) + return pf_file_read(pf, handle, offset, count, buffer); + int64_t ret; sgx_stub_t* stubs = (sgx_stub_t*)handle->file.stubs; @@ -140,8 +222,33 @@ static int64_t file_read(PAL_HANDLE handle, uint64_t offset, uint64_t count, voi return end - offset; } + +static int64_t pf_file_write(struct protected_file* pf, PAL_HANDLE handle, uint64_t offset, + uint64_t count, const void* buffer) { + int fd = handle->file.fd; + + if (!pf->context) { + SGX_DBG(DBG_E, "pf_file_write: PF fd %d not initialized\n", fd); + return -PAL_ERROR_BADHANDLE; + } + + pf_status_t pf_ret = pf_write(pf->context, offset, count, buffer); + + if (PF_FAILURE(pf_ret)) { + SGX_DBG(DBG_E, "file_write(PF fd %d): pf_write failed: %d\n", fd, pf_ret); + return -PAL_ERROR_DENIED; + } + + return count; +} + /* 'write' operation for file streams. */ static int64_t file_write(PAL_HANDLE handle, uint64_t offset, uint64_t count, const void* buffer) { + struct protected_file *pf = find_protected_file_handle(handle); + + if (pf) + return pf_file_write(pf, handle, offset, count, buffer); + int64_t ret; sgx_stub_t* stubs = (sgx_stub_t*)handle->file.stubs; @@ -167,10 +274,32 @@ static int64_t file_write(PAL_HANDLE handle, uint64_t offset, uint64_t count, co return -PAL_ERROR_DENIED; } +static int pf_file_close(struct protected_file* pf, PAL_HANDLE handle) { + int fd = handle->file.fd; + + if (pf->refcount == 0) { + SGX_DBG(DBG_E, "pf_file_close(PF fd %d) refcount == 0\n", fd); + return -PAL_ERROR_INVAL; + } + + pf->refcount--; + if (pf->refcount == 0) + return unload_protected_file(pf); + + return 0; +} + /* 'close' operation for file streams. In this case, it will only close the file without deleting it. */ static int file_close(PAL_HANDLE handle) { int fd = handle->file.fd; + struct protected_file* pf = find_protected_file_handle(handle); + + if (pf) { + int ret = pf_file_close(pf, handle); + if (ret < 0) + return ret; + } if (handle->file.stubs && handle->file.total) { /* case of trusted file: the whole file was mmapped in untrusted memory */ @@ -196,8 +325,81 @@ static int file_delete(PAL_HANDLE handle, int access) { return IS_ERR(ret) ? unix_to_pal_error(ERRNO(ret)) : ret; } +static int pf_file_map(struct protected_file* pf, PAL_HANDLE handle, void** addr, int prot, + uint64_t offset, uint64_t size) { + int fd = handle->file.fd; + if ((prot & PAL_PROT_READ) && (prot & PAL_PROT_WRITE)) { + SGX_DBG(DBG_E, "file_map(PF fd %d): trying to map with R+W access\n", fd); + return -PAL_ERROR_NOTSUPPORT; + } + + if (!pf->context) { + SGX_DBG(DBG_E, "file_map(PF fd %d): PF not initialized\n", fd); + return -PAL_ERROR_BADHANDLE; + } + + void* buf = NULL; + if (!*addr) { + buf = malloc(size); + if (!buf) + return -PAL_ERROR_NOMEM; + } + + uint64_t pf_size; + __attribute__((unused)) pf_status_t pfs = pf_get_size(pf->context, &pf_size); + assert(PF_SUCCESS(pfs)); + + if ((prot & PAL_PROT_WRITE) || !*addr) { + struct pf_allocation* pfa = malloc(sizeof(struct pf_allocation)); + memset(pfa, 0, sizeof(*pfa)); + + if (prot & PAL_PROT_WRITE) { + pfa->size = size; /* size > 0 marks pfa for writing to the PF */ + pfa->offset = offset; + } + + if (!*addr) { /* buffer was allocated by us */ + pfa->free = true; + *addr = buf; + } else { + pfa->free = false; + } + + pfa->mem = *addr; + LISTP_ADD_TAIL(pfa, &pf->allocation_list, list); + } + + if (prot & PAL_PROT_READ) { + /* we don't check this on writes since file size may be extended then */ + if (offset >= pf_size) { + SGX_DBG(DBG_E, "file_map(PF fd %d): offset (%lu) >= file size (%lu)\n", + fd, offset, pf_size); + return -PAL_ERROR_INVAL; + } + + memset(*addr, 0, size); + uint64_t copy_size = size; + if (size > pf_size - offset) + copy_size = pf_size - offset; + + pf_status_t pf_ret = pf_read(pf->context, offset, copy_size, *addr); + if (PF_FAILURE(pf_ret)) { + SGX_DBG(DBG_E, "file_map(PF fd %d): pf_read failed: %d\n", fd, pf_ret); + return -PAL_ERROR_DENIED; + } + } + + /* Writes will be flushed to the PF on close. */ + return 0; +} + /* 'map' operation for file stream. */ static int file_map(PAL_HANDLE handle, void** addr, int prot, uint64_t offset, uint64_t size) { + struct protected_file* pf = find_protected_file_handle(handle); + + if (pf) + return pf_file_map(pf, handle, addr, prot, offset, size); + sgx_stub_t* stubs = (sgx_stub_t*)handle->file.stubs; uint64_t total = handle->file.total; void* mem = *addr; @@ -264,8 +466,27 @@ static int file_map(PAL_HANDLE handle, void** addr, int prot, uint64_t offset, u return 0; } +static int64_t pf_file_setlength(struct protected_file *pf, PAL_HANDLE handle, uint64_t length) { + int fd = handle->file.fd; + + pf_status_t pfs = pf_set_size(pf->context, length); + if (PF_FAILURE(pfs)) { + SGX_DBG(DBG_E, "file_setlength(PF fd %d, %lu): pf_set_size returned %d\n", + fd, length, pfs); + uint64_t size; + pfs = pf_get_size(pf->context, &size); + assert(PF_SUCCESS(pfs)); + return size; + } + return length; +} + /* 'setlength' operation for file stream. */ static int64_t file_setlength(PAL_HANDLE handle, uint64_t length) { + struct protected_file *pf = find_protected_file_handle(handle); + if (pf) + return pf_file_setlength(pf, handle, length); + int ret = ocall_ftruncate(handle->file.fd, length); if (IS_ERR(ret)) return unix_to_pal_error(ERRNO(ret)); @@ -307,10 +528,39 @@ static inline void file_attrcopy(PAL_STREAM_ATTR* attr, struct stat* stat) { attr->pending_size = stat->st_size; } +static int pf_file_attrquery(struct protected_file* pf, int fd, const char* path, size_t real_size, + PAL_STREAM_ATTR* attr) { + pf = load_protected_file(path, (pf_handle_t)&fd, real_size, PAL_PROT_READ, false, pf); + if (!pf) { + SGX_DBG(DBG_E, "pf_file_attrquery: load_protected_file(%s, %d) failed\n", path, fd); + /* The call above will fail for PFs that were tampered with or have a wrong path. + * glibc kills the process if this fails during directory enumeration, but that + * should be fine given the scenario. + */ + ocall_close(fd); + return -PAL_ERROR_DENIED; + } + + uint64_t size; + __attribute__((unused)) pf_status_t pfs = pf_get_size(pf->context, &size); + assert(PF_SUCCESS(pfs)); + attr->pending_size = size; + + if (fd == *(int*)pf->context->handle) { /* this is a PF opened just for us, close it */ + pfs = pf_close(pf->context); + pf->context = NULL; + assert(PF_SUCCESS(pfs)); + } + + ocall_close(fd); + return 0; +} + /* 'attrquery' operation for file streams */ static int file_attrquery(const char* type, const char* uri, PAL_STREAM_ATTR* attr) { if (strcmp_static(type, URI_TYPE_FILE) && strcmp_static(type, URI_TYPE_DIR)) return -PAL_ERROR_INVAL; + /* try to do the real open */ int fd = ocall_open(uri, 0, 0); if (IS_ERR(fd)) @@ -318,13 +568,30 @@ static int file_attrquery(const char* type, const char* uri, PAL_STREAM_ATTR* at struct stat stat_buf; int ret = ocall_fstat(fd, &stat_buf); - ocall_close(fd); /* if it failed, return the right error code */ - if (IS_ERR(ret)) + if (IS_ERR(ret)) { + ocall_close(fd); return unix_to_pal_error(ERRNO(ret)); + } file_attrcopy(attr, &stat_buf); + + char path[URI_MAX]; + size_t len = URI_MAX; + ret = get_norm_path(uri, path, &len); + if (ret < 0) { + SGX_DBG(DBG_E, "Could not normalize path (%s): %s\n", uri, pal_strerror(ret)); + ocall_close(fd); + return ret; + } + + /* For protected files return the data size, not real FS size */ + struct protected_file* pf = get_protected_file(path); + if (pf && attr->handle_type != pal_type_dir) + return pf_file_attrquery(pf, fd, path, stat_buf.st_size, attr); + + ocall_close(fd); return 0; } @@ -338,6 +605,17 @@ static int file_attrquerybyhdl(PAL_HANDLE handle, PAL_STREAM_ATTR* attr) { return unix_to_pal_error(ERRNO(ret)); file_attrcopy(attr, &stat_buf); + + if (attr->handle_type != pal_type_dir) { + /* For protected files return the data size, not real FS size */ + struct protected_file* pf = find_protected_file_handle(handle); + if (pf) { + uint64_t size; + __attribute__((unused)) pf_status_t pfs = pf_get_size(pf->context, &size); + assert(PF_SUCCESS(pfs)); + attr->pending_size = size; + } + } return 0; } diff --git a/Pal/src/host/Linux-SGX/db_main.c b/Pal/src/host/Linux-SGX/db_main.c index 4ce2cdea..bb9c73b7 100644 --- a/Pal/src/host/Linux-SGX/db_main.c +++ b/Pal/src/host/Linux-SGX/db_main.c @@ -30,6 +30,7 @@ #include "pal_error.h" #include "pal_security.h" #include "api.h" +#include "protected_files.h" #include #include @@ -413,6 +414,11 @@ void pal_linux_main(char * uptr_args, uint64_t args_size, ocall_exit(rv, true); } + if ((rv = init_protected_files()) < 0) { + SGX_DBG(DBG_E, "Failed to initialize protected files: %d\n", rv); + ocall_exit(rv, true); + } + #if PRINT_ENCLAVE_STAT == 1 printf(" >>>>>>>> " "Enclave loading time = %10ld milliseconds\n", diff --git a/Pal/src/host/Linux-SGX/enclave_framework.c b/Pal/src/host/Linux-SGX/enclave_framework.c index 2bc18819..80a66fc0 100644 --- a/Pal/src/host/Linux-SGX/enclave_framework.c +++ b/Pal/src/host/Linux-SGX/enclave_framework.c @@ -799,7 +799,6 @@ int init_trusted_files (void) { goto out; } - nuris = get_config_entries(store, "sgx.trusted_files", cfgbuf, cfgsize); if (nuris <= 0) goto no_trusted; diff --git a/Pal/src/host/Linux-SGX/enclave_pf.c b/Pal/src/host/Linux-SGX/enclave_pf.c new file mode 100644 index 00000000..b2287859 --- /dev/null +++ b/Pal/src/host/Linux-SGX/enclave_pf.c @@ -0,0 +1,557 @@ +/* Copyright (C) 2018,2019 Invisible Things Lab + Rafal Wojdyla + + This file is part of Graphene Library OS. + + Graphene Library OS is free software: you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public License + as published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + Graphene Library OS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +#include +#include +#include +#include + +/* +At startup, protected file paths are read from the manifest and the specified files +or directories registered. For supported I/O operations, handlers (in db_files.c) +check if the file is a PF to perform the required operation transparently. + +Since PF's "logical" size is different than the real FS size (and to avoid potential +infinite recursion in FS handlers) we don't use PAL file APIs here, but raw OCALLs. +*/ + +/* Callbacks for protected files handling */ +static void* cb_malloc(size_t size) { + void* address = malloc(size); + if (address) + memset(address, 0, size); + return address; +} + +static int pal_prot(pf_file_mode_t mode) { + int prot = 0; + if (mode & PF_FILE_MODE_READ) + prot |= PROT_READ; + if (mode & PF_FILE_MODE_WRITE) + prot |= PROT_WRITE; + return prot; +} + +static pf_status_t cb_map(pf_handle_t handle, pf_file_mode_t mode, size_t offset, size_t size, + void** address) { + int fd = *(int*)handle; + int ret = ocall_mmap_untrusted(fd, offset, size, pal_prot(mode), address); + if (IS_ERR(ret)) { + SGX_DBG(DBG_E, "cb_map(%d, %d, %lu, %lu): ocall failed: %d\n", fd, mode, offset, size, ret); + return PF_STATUS_CALLBACK_FAILED; + } + return PF_STATUS_SUCCESS; +} + +static pf_status_t cb_unmap(void* address, size_t size) { + int ret = ocall_munmap_untrusted(address, size); + if (IS_ERR(ret)) { + SGX_DBG(DBG_E, "cb_unmap(%p, %lu): ocall failed: %d\n", address, size, ret); + return PF_STATUS_CALLBACK_FAILED; + } + return PF_STATUS_SUCCESS; +} + +static pf_status_t cb_truncate(pf_handle_t handle, size_t size) { + int fd = *(int*)handle; + int ret = ocall_ftruncate(fd, size); + if (IS_ERR(ret)) { + SGX_DBG(DBG_E, "cb_truncate(%d, %lu): ocall failed: %d\n", fd, size, ret); + return PF_STATUS_CALLBACK_FAILED; + } + return PF_STATUS_SUCCESS; +} + +static pf_status_t cb_flush(__attribute__((unused)) pf_handle_t handle) { + return PF_STATUS_NOT_IMPLEMENTED; +} + +#ifdef DEBUG +static void cb_debug(const char* msg) { + SGX_DBG(DBG_D, "%s", msg); +} +#endif + +static pf_status_t cb_crypto_aes_gcm_encrypt(const uint8_t* key, size_t key_size, const uint8_t* iv, + size_t iv_size, const void* aad, size_t aad_size, + const void* input, size_t input_size, void* output, + uint8_t* mac, size_t mac_size) { + if (iv_size != PF_IV_SIZE) + return PF_STATUS_INVALID_PARAMETER; + + int ret = lib_AESGCMEncrypt(key, key_size, iv, input, input_size, aad, aad_size, output, mac, + mac_size); + if (ret != 0) { + SGX_DBG(DBG_E, "lib_AESGCMEncrypt failed: %d\n", ret); + return PF_STATUS_CALLBACK_FAILED; + } + return PF_STATUS_SUCCESS; +} + +static pf_status_t cb_crypto_aes_gcm_decrypt(const uint8_t* key, size_t key_size, const uint8_t* iv, + size_t iv_size, const void* aad, size_t aad_size, + const void* input, size_t input_size, void* output, + const uint8_t* mac, size_t mac_size) { + if (iv_size != PF_IV_SIZE) + return PF_STATUS_INVALID_PARAMETER; + + int ret = lib_AESGCMDecrypt(key, key_size, iv, input, input_size, aad, aad_size, output, mac, + mac_size); + if (ret != 0) { + SGX_DBG(DBG_E, "lib_AESGCMDecrypt failed: %d\n", ret); + return PF_STATUS_CALLBACK_FAILED; + } + return PF_STATUS_SUCCESS; +} + +static pf_status_t cb_crypto_random(uint8_t* buffer, size_t size) { + int ret = _DkRandomBitsRead(buffer, size); + if (ret < 0) { + SGX_DBG(DBG_E, "_DkRandomBitsRead failed: %d\n", ret); + return PF_STATUS_CALLBACK_FAILED; + } + return PF_STATUS_SUCCESS; +} + +/* Wrap key for protected files. + TODO: In the future, this key should be provisioned after local/remote attestation. */ +static uint8_t g_pf_wrap_key[PF_WRAP_KEY_SIZE] = {0}; + +static LISTP_TYPE(protected_file) protected_file_list = LISTP_INIT; +static LISTP_TYPE(protected_file) protected_dir_list = LISTP_INIT; +static struct spinlock protected_file_lock = LOCK_INIT; + +#define FILE_URI_PREFIX "file:" +#define FILE_URI_PREFIX_LEN strlen(FILE_URI_PREFIX) + +/* Exact match of path in protected_file_list */ +struct protected_file* find_protected_file(const char* path) { + struct protected_file* pf = NULL; + struct protected_file* tmp = NULL; + size_t len = strlen(path); + + _DkSpinLock(&protected_file_lock); + LISTP_FOR_EACH_ENTRY(tmp, &protected_file_list, list) { + /* files: must be exactly the same URI */ + if (tmp->path_len == len && !memcmp(tmp->path, path, len + 1)) { + pf = tmp; + break; + } + } + + _DkSpinUnlock(&protected_file_lock); + return pf; +} + +/* Find registered pf directory starting with the given path */ +struct protected_file* find_protected_dir(const char* path) { + struct protected_file* pf = NULL; + struct protected_file* tmp = NULL; + size_t len = strlen(path); + + _DkSpinLock(&protected_file_lock); + LISTP_FOR_EACH_ENTRY(tmp, &protected_dir_list, list) { + if (tmp->path_len <= len && + !memcmp(tmp->path, path, tmp->path_len) && + (!path[tmp->path_len] || path[tmp->path_len] == '/')) { + pf = tmp; + break; + } + } + + _DkSpinUnlock(&protected_file_lock); + return pf; +} + +/* Find PF by handle */ +struct protected_file* find_protected_file_handle(PAL_HANDLE handle) { + char uri[URI_MAX]; + int uri_len; + + uri_len = _DkStreamGetName(handle, uri, URI_MAX); + if (uri_len < 0) + return NULL; + + /* uri is prefixed by "file:", we need path */ + return find_protected_file(uri + FILE_URI_PREFIX_LEN); +} + +static int register_protected_path(const char* path, struct protected_file** new_pf); + +/* Return a registered PF that matches specified path + (or the path is contained in a registered PF directory) */ +struct protected_file* get_protected_file(const char* path) { + struct protected_file* pf = find_protected_file(path); + if (pf) + goto out; + + pf = find_protected_dir(path); + if (pf) { + /* path not registered but matches registered dir */ + SGX_DBG(DBG_D, "is_pf: registering new PF '%s' in dir '%s'\n", path, pf->path); + __attribute__((unused)) int ret = register_protected_path(path, &pf); + assert(ret == 0); + /* return newly registered PF */ + } + +out: + SGX_DBG(DBG_D, "get_pf(%s) = %p\n", path, pf); + return pf; +} + +#define S_ISDIR(m) ((m & 0170000) == 0040000) + +static int is_directory(const char* path, bool* is_dir) { + int fd = -1; + struct stat st; + + *is_dir = false; + int ret = ocall_open(path, O_RDONLY, 0); + if (IS_ERR(ret)) { + SGX_DBG(DBG_E, "is_directory(%s): open failed: %d\n", path, ret); + goto out; + } + + fd = ret; + ret = ocall_fstat(fd, &st); + if (IS_ERR(ret)) { + SGX_DBG(DBG_E, "is_directory(%s): fstat failed: %d\n", path, ret); + goto out; + } + + if (S_ISDIR(st.st_mode)) + *is_dir = true; + +out: + if (fd >= 0) { + int rv; + if ((rv = ocall_close(fd)) < 0) { + SGX_DBG(DBG_E, "is_directory(%s): close failed: %d\n", path, rv); + } + } + + return unix_to_pal_error(ERRNO(ret)); +} + +/* Register all files from the given directory recursively */ +static int register_protected_dir(const char* path) { + int fd = -1; + int ret = -PAL_ERROR_NOMEM; + size_t bufsize = 1024; + void* buf = malloc(bufsize); + + if (!buf) + return -PAL_ERROR_NOMEM; + + ret = ocall_open(path, O_RDONLY | O_DIRECTORY, 0); + if (IS_ERR(ret)) { + SGX_DBG(DBG_E, "register_protected_dir: opening %s failed: %d\n", path, ret); + goto out; + } + fd = ret; + + size_t path_size = strlen(path) + 1; + int returned; + do { + returned = ocall_getdents(fd, buf, bufsize); + if (IS_ERR(returned)) { + ret = returned; + SGX_DBG(DBG_E, "register_protected_dir: reading %s failed: %d\n", path, ret); + goto out; + } + + int pos = 0; + struct linux_dirent64* dir; + + while (pos < returned) { + dir = (struct linux_dirent64*)((char*)buf + pos); + + if (!strcmp_static(dir->d_name, ".") || !strcmp_static(dir->d_name, "..")) + goto next; + + /* register file */ + size_t sub_path_size = strlen(dir->d_name) + 1 + path_size + FILE_URI_PREFIX_LEN; + char* sub_path = (char*)malloc(sub_path_size); + ret = -PAL_ERROR_NOMEM; + if (!sub_path) + goto out; + + snprintf(sub_path, sub_path_size, FILE_URI_PREFIX "%s/%s", path, dir->d_name); + ret = register_protected_path(sub_path, NULL); + if (ret != 0) { + free(sub_path); + goto out; + } + free(sub_path); +next: + pos += dir->d_reclen; + } + } while (returned != 0); + + ret = 0; +out: + if (fd >= 0) + ocall_close(fd); + free(buf); + return ret; +} + +/* Register a single PF (if it's a directory, recursively) */ +static int register_protected_path(const char* path, struct protected_file** new_pf) { + char normpath[URI_MAX]; + + size_t len = URI_MAX; + int ret = get_norm_path(path, normpath, &len); + if (ret < 0) { + SGX_DBG(DBG_E, "Couldn't normalize path (%s): %s\n", path, pal_strerror(ret)); + return ret; + } + + /* discard the "file:" prefix */ + if (strstartswith_static(normpath, FILE_URI_PREFIX)) + path = normpath + FILE_URI_PREFIX_LEN; + else + path = normpath; + + struct protected_file* new; + + if (find_protected_file(path)) { + SGX_DBG(DBG_D, "register_protected_path: file %s already registered\n", path); + return 0; + } + + new = malloc(sizeof(struct protected_file)); + if (!new) + return -PAL_ERROR_NOMEM; + + INIT_LIST_HEAD(new, list); + + memset(new, 0, sizeof(struct protected_file)); + + new->path_len = strlen(path); + memcpy(new->path, path, new->path_len + 1); + INIT_LISTP(&new->allocation_list); + new->refcount = 0; + + bool is_dir; + ret = is_directory(path, &is_dir); + if (ret != 0) { + free(new); + return ret; + } + SGX_DBG(DBG_D, "register_protected_path: [%s] %s\n", is_dir ? "dir" : "file", path); + + if (is_dir) + register_protected_dir(path); + + _DkSpinLock(&protected_file_lock); + + if (is_dir) { + LISTP_ADD_TAIL(new, &protected_dir_list, list); + } else { + LISTP_ADD_TAIL(new, &protected_file_list, list); + } + + _DkSpinUnlock(&protected_file_lock); + + if (new_pf) + *new_pf = new; + + return 0; +} + +/* Read PF paths from manifest and register them */ +static int register_protected_files(const char* key_prefix) { + char* cfgbuf = NULL; + int ret = -1; + ssize_t cfgsize = get_config_entries_size(pal_state.root_config, key_prefix); + if (cfgsize <= 0) + goto out; + + cfgbuf = (char*)malloc(cfgsize); + int nuris = get_config_entries(pal_state.root_config, key_prefix, cfgbuf, cfgsize); + if (nuris == -PAL_ERROR_INVAL) + nuris = 0; + + if (nuris >= 0) { + char key[CONFIG_MAX], uri[CONFIG_MAX]; + char* k = cfgbuf; + + for (int i = 0 ; i < nuris ; i++) { + int len = strlen(k); + snprintf(key, CONFIG_MAX, "%s.%s", key_prefix, k); + k += len + 1; + len = get_config(pal_state.root_config, key, uri, CONFIG_MAX); + if (len > 0) { + if (!strstartswith_static(uri, FILE_URI_PREFIX)) { + SGX_DBG(DBG_E, "Invalid URI [%s]: Protected files must start with 'file:'\n", uri); + } else { + register_protected_path(uri, NULL); + } + } + } + } else { + ret = nuris; + goto out; + } + + ret = 0; +out: + free(cfgbuf); + return ret; +} + +/* Initialize the PF library, register PFs from the manifest */ +int init_protected_files() { + pf_set_callbacks(cb_malloc, free, cb_map, cb_unmap, cb_truncate, cb_flush, +#ifdef DEBUG + cb_debug +#else + NULL +#endif + ); + + pf_set_crypto_callbacks(cb_crypto_aes_gcm_encrypt, cb_crypto_aes_gcm_decrypt, cb_crypto_random); + + /* TODO: development only: get SECRET WRAP KEY FOR PROTECTED FILES from manifest + In the future, this key should be provisioned after local/remote attestation. */ + + char key_hex[PF_WRAP_KEY_SIZE * 2 + 1]; + ssize_t len = get_config(pal_state.root_config, "sgx.protected_files_key", key_hex, + sizeof(key_hex)); + if (len <= 0) { + SGX_DBG(DBG_E, "*** No protected files wrap key specified in the manifest. " + "Protected files will not be available. ***\n"); + return 0; + } + + if (len != sizeof(key_hex) - 1) { + SGX_DBG(DBG_E, "Malformed sgx.protected_files_key value in the manifest\n"); + return -PAL_ERROR_INVAL; + } + + memset(g_pf_wrap_key, 0, sizeof(g_pf_wrap_key)); + for (ssize_t i = 0; i < len; i++) { + int8_t val = hex2dec(key_hex[i]); + if (val < 0) { + SGX_DBG(DBG_E, "Malformed sgx.protected_files_key value in the manifest\n"); + return -PAL_ERROR_INVAL; + } + g_pf_wrap_key[i/2] = g_pf_wrap_key[i/2] * 16 + (uint8_t)val; + } + + if (register_protected_files("sgx.protected_files") < 0) + SGX_DBG(DBG_E, "sgx.protected_files key not found in manifest, " + "protected files will not be available\n"); + + return 0; +} + +/* Open/create a PF */ +static int open_protected_file(const char* path, struct protected_file* pf, pf_handle_t handle, + size_t size, pf_file_mode_t mode, bool create) { + pf_status_t pfs; + + if (!create) { + pfs = pf_open(handle, size, mode, g_pf_wrap_key, &pf->context); + } else { + char name[URI_MAX]; + char prefix[URI_MAX]; + size_t len = URI_MAX; + int ret = get_base_name(path, name, &len); + if (ret < 0) { + SGX_DBG(DBG_E, "Couldn't normalize path (%s): %s\n", path, pal_strerror(ret)); + return ret; + } + + memcpy(prefix, path, strlen(path) - len); + prefix[strlen(path) - len] = 0; + + pfs = pf_create(handle, prefix, name, g_pf_wrap_key, &pf->context); + } + + if (PF_FAILURE(pfs)) { + SGX_DBG(DBG_E, "pf_open/pf_create(%d) failed: %d\n", *(int*)handle, pfs); + return -PAL_ERROR_DENIED; + } + return 0; +} + +/* Prepare a PF for I/O + This function registers the PF if path is in a registered PF directory, then + calls the appropriate PF function to open/create it (if allowed) */ +struct protected_file* load_protected_file(const char* path, int* fd, size_t size, + pf_file_mode_t mode, bool create, + struct protected_file* pf) { + SGX_DBG(DBG_D, "load_protected_file: %s, fd %d, size %lu, mode %d, create %d, pf %p\n", + path, *fd, size, mode, create, pf); + + if (!pf) + pf = get_protected_file(path); + + if (pf) { + if (!pf->context) { + SGX_DBG(DBG_D, "load_protected_file: %s, fd %d: opening new PF %p\n", path, *fd, pf); + int ret = open_protected_file(path, pf, (pf_handle_t)fd, size, mode, create); + if (ret != 0) + return NULL; + } else { + SGX_DBG(DBG_D, "load_protected_file: %s, fd %d: returning old PF %p\n", path, *fd, pf); + } + } + + return pf; +} + +/* Cleanup/flush write buffers */ +int unload_protected_file(struct protected_file* pf) { + struct pf_allocation* pfa; + struct pf_allocation* tmp; + __attribute__((unused)) pf_status_t pfs; + + LISTP_FOR_EACH_ENTRY_SAFE(pfa, tmp, &pf->allocation_list, list) { + size_t size = pfa->size; + size_t pf_size; + pf_status_t pfs = pf_get_size(pf->context, &pf_size); + assert(PF_SUCCESS(pfs)); + + if (size > 0) { /* 'write' pfa, flush it */ + if (size > pf_size) + size = pf_size; + + if (size > 0) { + pfs = pf_write(pf->context, pfa->offset, size, pfa->mem); + if (PF_FAILURE(pfs)) { + SGX_DBG(DBG_E, "unload_protected_file: pf_write failed: %d\n", pfs); + return -PAL_ERROR_INVAL; + } + } + } + + if (pfa->free) + free(pfa->mem); + + LISTP_DEL(pfa, &pf->allocation_list, list); + } + + pfs = pf_close(pf->context); + assert(PF_SUCCESS(pfs)); + + pf->context = NULL; + return 0; +} diff --git a/Pal/src/host/Linux-SGX/pal_linux.h b/Pal/src/host/Linux-SGX/pal_linux.h index df23362e..8372b748 100644 --- a/Pal/src/host/Linux-SGX/pal_linux.h +++ b/Pal/src/host/Linux-SGX/pal_linux.h @@ -28,6 +28,7 @@ #include "sgx_api.h" #include "sgx_attest.h" #include "enclave_ocalls.h" +#include "protected_files.h" #include @@ -145,6 +146,59 @@ int copy_and_verify_trusted_file (const char * path, const void * umem, int init_trusted_children (void); int register_trusted_child (const char * uri, const char * mr_enclave_str); +/* Used to track map allocations for protected files */ +DEFINE_LIST(pf_allocation); +struct pf_allocation { + LIST_TYPE(pf_allocation) list; + void* mem; /* buffer address */ + bool free; /* whether to free the buffer */ + uint64_t size; /* allocation size */ + uint64_t offset; /* needed for write buffers when flushing to the PF */ +}; +DEFINE_LISTP(pf_allocation); + +/* Data of a protected file */ +DEFINE_LIST(protected_file); +struct protected_file { + LIST_TYPE(protected_file) list; + size_t path_len; + char path[URI_MAX]; + pf_context_t* context; /* NULL until PF is opened */ + int64_t refcount; /* used for deciding when to call unload_protected_file() */ + LISTP_TYPE(pf_allocation) allocation_list; +}; +DEFINE_LISTP(protected_file); + +/* Initialize the PF library, register PFs from the manifest */ +int init_protected_files(); + +/* Return a registered PF that matches specified path + (or the path is contained in a registered PF directory) */ +struct protected_file* get_protected_file(const char* path); + +/* Load and initialize a PF (must be called before any I/O operations) + * + * path: normalized host path + * fd: pointer to an opened file descriptor (must point to a valid value for the whole time PF + * is being accessed) + * size: underlying file size (in bytes) + * mode: access mode + * create: if true, the PF is being created/truncated + * pf: (optional) PF pointer if already known + */ +struct protected_file* load_protected_file(const char* path, int* fd, size_t size, + pf_file_mode_t mode, bool create, + struct protected_file* pf); + +/* Cleanup: flush mmap'd writes to the PF, deallocate buffers etc */ +int unload_protected_file(struct protected_file* pf); + +/* Find registered PF by path (exact match) */ +struct protected_file* find_protected_file(const char* path); + +/* Find protected file by handle (uses handle's path to call find_protected_file) */ +struct protected_file* find_protected_file_handle(PAL_HANDLE handle); + /* exchange and establish a 256-bit session key */ int _DkStreamKeyExchange(PAL_HANDLE stream, PAL_SESSION_KEY* key); diff --git a/Pal/src/host/Linux-SGX/pal_linux_error.h b/Pal/src/host/Linux-SGX/pal_linux_error.h index fb6b86b6..5bbd943f 100644 --- a/Pal/src/host/Linux-SGX/pal_linux_error.h +++ b/Pal/src/host/Linux-SGX/pal_linux_error.h @@ -8,6 +8,8 @@ static inline __attribute__((unused)) int unix_to_pal_error(int unix_errno) { switch (unix_errno) { + case 0: + return 0; case ENOENT: return -PAL_ERROR_STREAMNOTEXIST; case EINTR: diff --git a/Pal/src/host/Linux-SGX/protected_files.c b/Pal/src/host/Linux-SGX/protected_files.c new file mode 100644 index 00000000..06b66f2b --- /dev/null +++ b/Pal/src/host/Linux-SGX/protected_files.c @@ -0,0 +1,809 @@ +/* Copyright (C) 2018,2019 Invisible Things Lab + Rafal Wojdyla + + This file is part of Graphene Library OS. + + Graphene Library OS is free software: you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public License + as published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + Graphene Library OS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +#include +#include "protected_files.h" + +/* We can't include stdio.h in Graphene */ +int snprintf(char* str, size_t size, const char* format, ...); + +/* Callbacks */ +static pf_malloc_f cb_malloc = NULL; +static pf_free_f cb_free = NULL; +static pf_map_f cb_map = NULL; +static pf_unmap_f cb_unmap = NULL; +static pf_truncate_f cb_truncate = NULL; +static pf_flush_f cb_flush = NULL; +static pf_debug_f cb_debug = NULL; + +static pf_crypto_aes_gcm_encrypt_f cb_crypto_aes_gcm_encrypt = NULL; +static pf_crypto_aes_gcm_decrypt_f cb_crypto_aes_gcm_decrypt = NULL; +static pf_crypto_random_f cb_crypto_random = NULL; + +/* Debug print without function name prefix. Implicit param: pf (context pointer). */ +#define __DEBUG_PF(format, ...) \ + do { \ + if (cb_debug) { \ + snprintf(pf->debug_buffer, PF_DEBUG_PRINT_SIZE_MAX, format, ##__VA_ARGS__); \ + cb_debug(pf->debug_buffer); \ + } \ + } while(0) + +/* Debug print with function name prefix. Implicit param: pf (context pointer). */ +#define DEBUG_PF(format, ...) \ + do { \ + if (cb_debug) { \ + snprintf(pf->debug_buffer, PF_DEBUG_PRINT_SIZE_MAX, "%s: " format, __FUNCTION__, ##__VA_ARGS__); \ + cb_debug(pf->debug_buffer); \ + } \ + } while(0) + +/* Debug print buffer as hex byte values. */ +void __hexdump(const void* data, size_t size) { + if (!cb_debug) + return; + + const char* digits = "0123456789abcdef"; + uint8_t* ptr = (uint8_t*)data; + char b[3]; + + for (size_t i = 0; i < size; i++) { + b[0] = digits[ptr[i] / 16]; + b[1] = digits[ptr[i] % 16]; + b[2] = 0; + cb_debug(b); + } +} + +#define HEXDUMP(x) __hexdump((void*)&(x), sizeof(x)) + +/* nl suffix: add new line at the end */ +#define __HEXDUMPNL(data, size) { if (cb_debug) { __hexdump(data, size); __DEBUG_PF("\n"); } } +#define HEXDUMPNL(x) __HEXDUMPNL((void*)&(x), sizeof(x)) + +void pf_set_callbacks(pf_malloc_f malloc_f, pf_free_f free_f, pf_map_f map_f, pf_unmap_f unmap_f, + pf_truncate_f truncate_f, pf_flush_f flush_f, pf_debug_f debug_f) { + cb_malloc = malloc_f; + cb_free = free_f; + cb_map = map_f; + cb_unmap = unmap_f; + cb_truncate = truncate_f; + cb_flush = flush_f; + cb_debug = debug_f; +} + +void pf_set_crypto_callbacks(pf_crypto_aes_gcm_encrypt_f crypto_aes_gcm_encrypt_f, + pf_crypto_aes_gcm_decrypt_f crypto_aes_gcm_decrypt_f, + pf_crypto_random_f crypto_random_f) { + cb_crypto_aes_gcm_encrypt = crypto_aes_gcm_encrypt_f; + cb_crypto_aes_gcm_decrypt = crypto_aes_gcm_decrypt_f; + cb_crypto_random = crypto_random_f; +} + +static pf_status_t check_callbacks() { + return (cb_malloc != NULL && + cb_free != NULL && + cb_map != NULL && + cb_unmap != NULL && + cb_truncate != NULL && + cb_flush != NULL && + cb_crypto_aes_gcm_encrypt != NULL && + cb_crypto_aes_gcm_decrypt != NULL && + cb_crypto_random != NULL) ? PF_STATUS_SUCCESS : PF_STATUS_UNINITIALIZED; +} + +/* All internal functions assume that callbacks are initialized and parameters are validated. */ + +static bool has_mode(pf_context_t* pf, pf_file_mode_t mode) { + return ((pf->mode & mode) == mode); +} + +/* (Public) Check access mode */ +pf_status_t pf_has_mode(pf_context_t* pf, pf_file_mode_t mode, bool* result) { + assert(pf && pf->header); + pf_status_t pfs = check_callbacks(); + if (PF_FAILURE(pfs)) + goto out; + + pfs = PF_STATUS_SUCCESS; +out: + if (PF_SUCCESS(pfs)) + *result = has_mode(pf, mode); + return pfs; +} + +/* (Public) Get data size */ +pf_status_t pf_get_size(pf_context_t* pf, uint64_t* size) { + assert(pf->header); + pf_status_t pfs = check_callbacks(); + if (PF_FAILURE(pfs)) + goto out; + + pfs = PF_STATUS_SUCCESS; +out: + if (PF_SUCCESS(pfs)) + *size = pf->header->data_size; + else + *size = 0; + return pfs; +} + +/* (Public) Check if a path is allowed */ +pf_status_t pf_check_path(pf_context_t* pf, const char* path, bool* result) { + assert(pf && pf->header); + pf_status_t pfs = check_callbacks(); + if (PF_FAILURE(pfs)) + goto out; + + /* TODO: multiple paths */ + /* allowed_paths should contain at least one NULL-terminated string */ + pfs = PF_STATUS_BAD_HEADER; + if (pf->header->allowed_paths_size < 1) + goto out; + + pfs = PF_STATUS_SUCCESS; + *result = true; + + if (strlen(path) != pf->header->allowed_paths_size - 1) + *result = false; + + if (memcmp(path, pf->header->allowed_paths, strlen(path)) != 0) + *result = false; + +out: + return pfs; +} + +/* (Internal) Map header and verify its integrity */ +static pf_status_t map_header(pf_context_t* pf, size_t underlying_size) { + pf_status_t status = PF_STATUS_BAD_HEADER; + + DEBUG_PF("pf %p, underlying size %lu\n", pf, underlying_size); + + if (underlying_size < PF_HEADER_SIZE) + goto out; + + status = cb_map(pf->handle, pf->mode, 0, PF_HEADER_SIZE, (void**)&pf->header); + if (PF_FAILURE(status)) + goto out; + + pf_header_t* hdr = pf->header; + DEBUG_PF("version %u, data size %lu, iv ", hdr->version, hdr->data_size); + HEXDUMPNL(hdr->header_iv); + + status = PF_STATUS_BAD_VERSION; + if (hdr->version != PF_FORMAT_VERSION) + goto out; + + status = PF_STATUS_BAD_HEADER; + if (hdr->data_size > 0) { + if (underlying_size != PF_CHUNK_OFFSET(PF_CHUNKS_COUNT(hdr->data_size))) { + DEBUG_PF("invalid underlying size, expected %lu\n", + PF_CHUNK_OFFSET(PF_CHUNKS_COUNT(hdr->data_size))); + goto out; + } + } else { + /* empty file = no chunks */ + if (underlying_size != PF_HEADER_SIZE) { + DEBUG_PF("invalid underlying size, expected %u\n", PF_HEADER_SIZE); + goto out; + } + } + + status = PF_STATUS_PATH_TOO_LONG; + if (hdr->allowed_paths_size > PF_HEADER_ALLOWED_PATHS_SIZE) { + DEBUG_PF("invalid allowed_paths_size %u\n", hdr->allowed_paths_size); + goto out; + } + + /* Check header integrity */ + uint8_t tag[PF_MAC_SIZE]; + + status = cb_crypto_aes_gcm_encrypt(pf->key, PF_WRAP_KEY_SIZE, hdr->header_iv, PF_IV_SIZE, + hdr, PF_HEADER_SIZE - PF_MAC_SIZE, /* aad */ + NULL, 0, /* no data to encrypt */ + NULL, /* no output, calc MAC only */ + tag, PF_MAC_SIZE); + + if (PF_FAILURE(status)) { + DEBUG_PF("calculating header MAC failed: %d\n", status); + goto out; + } + + status = PF_STATUS_MAC_MISMATCH; + if (memcmp(tag, hdr->header_mac, PF_MAC_SIZE) != 0) { + DEBUG_PF("MAC mismatch: "); + HEXDUMP(tag); + __DEBUG_PF(" vs expected "); + __HEXDUMPNL(hdr->header_mac, PF_MAC_SIZE); + goto out; + } + + status = PF_STATUS_SUCCESS; + +out: + if (pf->header && PF_FAILURE(status)) { + if (PF_FAILURE(cb_unmap(pf->header, PF_HEADER_SIZE))) + DEBUG_PF("(failure path) header unmap failed\n"); + pf->header = NULL; + } + + return status; +} + +/* (Internal) Create header for a new PF (erases any data in the file) */ +static pf_status_t create_header(pf_context_t* pf, const char* prefix, const char* file_name) { + pf_status_t status; + bool trailing_slash = prefix[strlen(prefix) - 1] == '/'; + + status = cb_truncate(pf->handle, PF_HEADER_SIZE); + if (PF_FAILURE(status)) + goto out; + + status = cb_map(pf->handle, pf->mode, 0, PF_HEADER_SIZE, (void**)&pf->header); + if (PF_FAILURE(status)) + goto out; + + pf_header_t* hdr = pf->header; + memset(hdr, 0, PF_HEADER_SIZE); + hdr->version = PF_FORMAT_VERSION; + + status = cb_crypto_random(hdr->header_iv, PF_IV_SIZE); + if (PF_FAILURE(status)) + goto out; + + /* TODO: multiple allowed paths support */ + hdr->allowed_paths_size = strlen(file_name) + + 1 /* slash */ + + strlen(prefix) + + 1; /* path NULL-terminator */ + + /* Trailing slash will be stripped */ + if (trailing_slash) + hdr->allowed_paths_size--; + + status = PF_STATUS_PATH_TOO_LONG; + if (hdr->allowed_paths_size > PF_HEADER_ALLOWED_PATHS_SIZE) + goto out; + + DEBUG_PF("allowed_paths_size: %u\n", hdr->allowed_paths_size); + + /* TODO: multiple allowed paths support */ + if (trailing_slash) { + snprintf(hdr->allowed_paths, hdr->allowed_paths_size, "%s%s", prefix, file_name); + } else { + snprintf(hdr->allowed_paths, hdr->allowed_paths_size, "%s/%s", prefix, file_name); + } + + status = PF_STATUS_SUCCESS; +out: + if (PF_FAILURE(status)) { + if (pf->header) { + if (PF_FAILURE(cb_unmap(pf->header, PF_HEADER_SIZE))) { + DEBUG_PF("(failure path) header unmap failed\n"); + } + pf->header = NULL; + } + } + + return status; +} + +/* (Internal) Update header MAC for a writable PF and set underlying file size */ +static pf_status_t update_header(pf_context_t* pf, size_t data_size) { + assert(pf && pf->header); + pf_status_t status; + pf_header_t* hdr = pf->header; + + DEBUG_PF("pf %p, data size %lu->%lu\n", pf, hdr->data_size, data_size); + + hdr->data_size = data_size; + + /* Regenerate IV */ + status = cb_crypto_random(hdr->header_iv, PF_IV_SIZE); + if (PF_FAILURE(status)) + goto out; + + /* Calculate header MAC */ + status = cb_crypto_aes_gcm_encrypt(pf->key, PF_WRAP_KEY_SIZE, hdr->header_iv, PF_IV_SIZE, + hdr, PF_HEADER_SIZE - PF_MAC_SIZE, + NULL, 0, /* no data to encrypt */ + NULL, /* no output, calc MAC only */ + hdr->header_mac, PF_MAC_SIZE); + + if (PF_SUCCESS(status)) { + DEBUG_PF("data size %lu, iv ", data_size); + HEXDUMP(hdr->header_iv); + __DEBUG_PF(", mac "); + __HEXDUMPNL(hdr->header_mac, PF_MAC_SIZE); + + /* Set the underlying file size */ + size_t size; + if (data_size > 0) + size = PF_CHUNK_OFFSET(PF_CHUNK_NUMBER(data_size - 1) + 1); + else + size = PF_HEADER_SIZE; + + status = cb_truncate(pf->handle, size); + if (PF_FAILURE(status)) + goto out; + + DEBUG_PF("underlying file size: %lu\n", size); + } + +out: + return status; +} + +pf_status_t open_common(pf_context_t** pf, pf_handle_t handle, pf_file_mode_t mode, + const uint8_t key[PF_WRAP_KEY_SIZE]) { + *pf = NULL; + pf_status_t status = check_callbacks(); + if (PF_FAILURE(status)) + goto out; + + status = PF_STATUS_NO_MEMORY; + *pf = cb_malloc(sizeof(**pf)); + if (!*pf) + goto out; + + if (cb_debug) { + (*pf)->debug_buffer = cb_malloc(PF_DEBUG_PRINT_SIZE_MAX); + if (!(*pf)->debug_buffer) + goto out; + } + + (*pf)->plaintext = cb_malloc(PF_CHUNK_SIZE); + if (!(*pf)->plaintext) + goto out; + + (*pf)->encrypted = cb_malloc(PF_CHUNK_SIZE); + if (!(*pf)->encrypted) + goto out; + + (*pf)->handle = handle; + (*pf)->mode = mode; + memcpy(&(*pf)->key, key, PF_WRAP_KEY_SIZE); + + status = PF_STATUS_SUCCESS; + +out: + return status; +} + +void free_context(pf_context_t* pf) { + if (pf) { + cb_free(pf->plaintext); + cb_free(pf->encrypted); + cb_free(pf->debug_buffer); + cb_free(pf); + } +} + +void open_cleanup(pf_context_t* pf, pf_status_t status, pf_context_t** context) { + if (PF_FAILURE(status)) { + free_context(pf); + *context = NULL; + } else { + *context = pf; + } +} + +/* (Public) Open an existing PF */ +pf_status_t pf_open(pf_handle_t handle, size_t underlying_size, pf_file_mode_t mode, + const uint8_t key[PF_WRAP_KEY_SIZE], pf_context_t** context) { + pf_context_t* pf = NULL; + pf_status_t status = open_common(&pf, handle, mode, key); + if (PF_FAILURE(status)) + goto out; + + DEBUG_PF("handle %p, context %p, mode %d\n", handle, pf, mode); + + status = map_header(pf, underlying_size); +out: + open_cleanup(pf, status, context); + return status; +} + +/* (Public) Create a new PF (R+W) */ +pf_status_t pf_create(pf_handle_t handle, const char* prefix, const char* file_name, + const uint8_t key[PF_WRAP_KEY_SIZE], pf_context_t** context) { + pf_context_t* pf = NULL; + pf_file_mode_t mode = PF_FILE_MODE_READ | PF_FILE_MODE_WRITE; + pf_status_t status = open_common(&pf, handle, mode, key); + if (PF_FAILURE(status)) + goto out; + + DEBUG_PF("handle %p, prefix %s, name %s, context %p\n", handle, prefix, file_name, pf); + + status = create_header(pf, prefix, file_name); + if (PF_FAILURE(status)) + goto out; + + /* update header for 0 size */ + status = update_header(pf, 0); + +out: + open_cleanup(pf, status, context); + return status; +} + +/* (Public) Close a PF */ +pf_status_t pf_close(pf_context_t* pf) { + assert(pf && pf->header); + pf_status_t status = check_callbacks(); + if (PF_FAILURE(status)) + goto out; + + DEBUG_PF("pf %p, mode %d\n", pf, pf->mode); + + status = cb_unmap(pf->header, PF_HEADER_SIZE); + if (PF_FAILURE(status)) { + DEBUG_PF("failed to unmap header: %d\n", status); + goto out; + } + + free_context(pf); + status = PF_STATUS_SUCCESS; + +out: + return status; +} + +/* (Public) Decrypt a single chunk */ +pf_status_t pf_decrypt_chunk(pf_context_t* pf, uint64_t chunk_number, const pf_chunk_t* chunk, + uint32_t chunk_size, void* output) { + assert(pf && pf->header); + pf_status_t status = check_callbacks(); + if (PF_FAILURE(status)) + goto out; + + DEBUG_PF("chunk #%lu: idx 0x%lx, size %u, iv ", chunk_number, chunk->chunk_number, chunk_size); + HEXDUMP(chunk->chunk_iv); + __DEBUG_PF(", mac "); + __HEXDUMPNL(chunk->chunk_mac, PF_MAC_SIZE); + + status = PF_STATUS_BAD_CHUNK; + /* Verify chunk metadata */ + if (chunk->chunk_number != chunk_number) { + DEBUG_PF("chunk #%lu: invalid chunk number 0x%lx\n", chunk_number, chunk->chunk_number); + goto out; + } + + /* Decrypt data */ + status = cb_crypto_aes_gcm_decrypt(pf->key, PF_WRAP_KEY_SIZE, chunk->chunk_iv, PF_IV_SIZE, + chunk, PF_CHUNK_HEADER_SIZE, /* AAD: chunk header */ + chunk->chunk_data, chunk_size, /* input */ + (uint8_t*)output, /* output */ + chunk->chunk_mac, PF_MAC_SIZE); /* mac */ + + if (PF_FAILURE(status)) { + DEBUG_PF("chunk #%lu: decryption failed: %d\n", chunk_number, status); + goto out; + } + + status = PF_STATUS_SUCCESS; +out: + return status; +} + +/* (Public) Read from a PF */ +pf_status_t pf_read(pf_context_t* pf, uint64_t offset, size_t size, void* output) { + pf_chunk_t* chunk = NULL; + assert(pf && pf->header); + pf_status_t status = check_callbacks(); + if (PF_FAILURE(status)) + goto out; + + DEBUG_PF("pf %p, offset %lu, size %lu\n", pf, offset, size); + + status = PF_STATUS_INVALID_PARAMETER; + if (offset + size > pf->header->data_size) { + DEBUG_PF("offset + size (%lu) >= file size (%lu)\n", offset + size, pf->header->data_size); + size = pf->header->data_size - offset; + } + + if (offset + size <= offset) + goto out; + + uint64_t first_chunk = PF_CHUNK_NUMBER(offset); + uint32_t offset_in_chunk = offset % PF_CHUNK_DATA_MAX; + uint64_t last_chunk = PF_CHUNK_NUMBER(offset + size - 1); + uint64_t output_offset = 0; + uint64_t chunk_nr; + + DEBUG_PF("handle %p, chunks: %lu - %lu, 1st offset %u\n", + pf->handle, first_chunk, last_chunk, offset_in_chunk); + + for (chunk_nr = first_chunk; chunk_nr <= last_chunk; chunk_nr++) { + status = cb_map(pf->handle, PF_FILE_MODE_READ, PF_CHUNK_OFFSET(chunk_nr), PF_CHUNK_SIZE, + (void**)&chunk); + + if (PF_SUCCESS(status)) { + uint64_t chunk_size = PF_CHUNK_DATA_SIZE(pf->header->data_size, chunk_nr); + uint32_t read_size = size - output_offset; + if (read_size > chunk_size - offset_in_chunk) + read_size = chunk_size - offset_in_chunk; + + status = pf_decrypt_chunk(pf, chunk_nr, chunk, chunk_size, pf->plaintext); + if (PF_SUCCESS(status)) { + memcpy((uint8_t*)output + output_offset, (uint8_t*)pf->plaintext + offset_in_chunk, read_size); + output_offset += read_size; + offset_in_chunk = 0; + status = PF_STATUS_SUCCESS; + } + } + + if (chunk) { + if (PF_FAILURE(cb_unmap(chunk, PF_CHUNK_SIZE))) + DEBUG_PF("chunk unmap failed\n"); + } + + if (PF_FAILURE(status)) + break; + } + +out: + return status; +} + +/* (Public) Encrypt a single chunk */ +pf_status_t pf_encrypt_chunk(pf_context_t* pf, uint64_t chunk_number, const void* input, + uint32_t chunk_size, pf_chunk_t* output) { + assert(pf && pf->header); + pf_status_t status = check_callbacks(); + if (PF_FAILURE(status)) + goto out; + + assert(chunk_size <= PF_CHUNK_DATA_MAX); + output->chunk_number = chunk_number; + + /* Generate IV for the chunk */ + status = cb_crypto_random(output->chunk_iv, PF_IV_SIZE); + if (PF_FAILURE(status)) + goto out; + + DEBUG_PF("pf %p, #%lu: size %u, iv ", pf, chunk_number, chunk_size); + HEXDUMPNL(output->chunk_iv); + + /* Encrypt data */ + status = cb_crypto_aes_gcm_encrypt(pf->key, PF_WRAP_KEY_SIZE, output->chunk_iv, PF_IV_SIZE, + output, PF_CHUNK_HEADER_SIZE, /* AAD: chunk header */ + input, chunk_size, /* input */ + output->chunk_data, /* output */ + output->chunk_mac, PF_MAC_SIZE); /* mac */ + + if (status == PF_STATUS_SUCCESS) { + DEBUG_PF("mac "); + __HEXDUMPNL(output->chunk_mac, PF_MAC_SIZE); + } + +out: + return status; +} + +static bool is_chunk_uninitialized(const pf_chunk_t* chunk) { + static const uint8_t zero[PF_IV_SIZE] = {0}; + return memcmp(chunk->chunk_iv, zero, sizeof(zero)) == 0; +} + +static pf_status_t write_internal(pf_context_t* pf, uint64_t offset, size_t size, const void* input, + size_t previous_size) { + pf_status_t status; + uint64_t first_chunk = PF_CHUNK_NUMBER(offset); + uint32_t offset_in_chunk = offset % PF_CHUNK_DATA_MAX; + uint64_t last_chunk = PF_CHUNK_NUMBER(offset + size - 1); + uint64_t input_offset = 0; + pf_chunk_t* chunk = NULL; /* current chunk in the file */ + + DEBUG_PF("pf %p, offset %lu, size %lu/%lu, handle %p, chunks: %lu - %lu, 1st offset %u\n", + pf, offset, size, previous_size, pf->handle, first_chunk, last_chunk, offset_in_chunk); + + for (uint64_t chunk_nr = first_chunk; chunk_nr <= last_chunk; chunk_nr++) { + /* Read existing chunk (may be uninitialized if the file was extended) */ + status = cb_map(pf->handle, PF_FILE_MODE_READ | PF_FILE_MODE_WRITE, + PF_CHUNK_OFFSET(chunk_nr), PF_CHUNK_SIZE, (void**)&chunk); + + if (PF_FAILURE(status)) + goto out; + + uint64_t plaintext_size = PF_CHUNK_DATA_SIZE(previous_size, chunk_nr); + uint32_t chunk_data_size = size - input_offset + offset_in_chunk; + if (chunk_data_size > PF_CHUNK_DATA_MAX) + chunk_data_size = PF_CHUNK_DATA_MAX; + + /* Size of data being encrypted: might not equal to chunk_data_size + if the write offset is not at the start of the chunk */ + uint32_t encrypt_size = chunk_data_size; + + /* prepare data to encrypt */ + if (is_chunk_uninitialized(chunk)) { + /* No existing data in chunk - just encrypt new data, + make sure to account for writes that skip some bytes (need zeros at the start) */ + if (input) { + memcpy(pf->plaintext->chunk_data + offset_in_chunk, + (uint8_t*)input + input_offset, + chunk_data_size - offset_in_chunk); + } else { + memset(pf->plaintext->chunk_data + offset_in_chunk, + 0, + chunk_data_size - offset_in_chunk); + } + } else { + /* There is some data in the target chunk - we need to decrypt it, + overlay new data onto it and then encrypt again. */ + + memcpy(pf->plaintext, chunk, PF_CHUNK_HEADER_SIZE); + status = pf_decrypt_chunk(pf, chunk_nr, chunk, plaintext_size, + pf->plaintext->chunk_data); + if (PF_FAILURE(status)) { + DEBUG_PF("pf_decrypt_chunk failed: %d\n", status); + goto out; + } + + if (input) { + /* copy new data */ + memcpy(pf->plaintext->chunk_data + offset_in_chunk, + (uint8_t*)input + input_offset, + chunk_data_size - offset_in_chunk); + } else { + memset(pf->plaintext->chunk_data + offset_in_chunk, + 0, + chunk_data_size - offset_in_chunk); + } + + if (chunk_data_size < plaintext_size) + encrypt_size = plaintext_size; + } + + status = pf_encrypt_chunk(pf, chunk_nr, pf->plaintext->chunk_data, encrypt_size, + pf->encrypted); + + if (PF_FAILURE(status)) { + DEBUG_PF("pf_encrypt_chunk failed: %d\n", status); + goto out; + } + + /* write encrypted chunk to underlying file */ + memcpy(chunk, pf->encrypted, PF_CHUNK_SIZE); + + if (chunk) { + status = cb_unmap(chunk, PF_CHUNK_SIZE); + if (PF_FAILURE(status)) { + DEBUG_PF("failed to unmap chunk\n"); + goto out; + } + } + chunk = NULL; + input_offset += chunk_data_size - offset_in_chunk; + offset_in_chunk = 0; /* all remaining chunks are filled from the beginning */ + } + + status = PF_STATUS_SUCCESS; +out: + return status; +} + +/* (Public) Write to a PF (if input is NULL, write zeros) */ +pf_status_t pf_write(pf_context_t* pf, uint64_t offset, size_t size, const void* input) { + assert(pf && pf->header); + pf_status_t status = check_callbacks(); + if (PF_FAILURE(status)) + goto out; + + status = PF_STATUS_INVALID_CONTEXT; + if (!pf->header) + goto out; + + if (!has_mode(pf, PF_FILE_MODE_WRITE)) + return PF_STATUS_INVALID_MODE; + + if (size == 0) + return PF_STATUS_SUCCESS; + + uint64_t data_size = pf->header->data_size; /* file data size before this write operation */ + DEBUG_PF("pf %p, offset %lu, size %lu, file size %lu\n", pf, offset, size, data_size); + + /* Update file size if the write exceeds current size */ + if (offset + size > data_size) { + status = update_header(pf, offset + size); + if (PF_FAILURE(status)) + goto out; + + if (offset - data_size > 0) { + /* Write zeros to the extended portion of the file */ + status = write_internal(pf, data_size, offset - data_size, NULL, data_size); + if (PF_FAILURE(status)) + goto out; + + data_size = offset; + } + } + + status = write_internal(pf, offset, size, input, data_size); + +out: + return status; +} + +/* (Public) Set PF data size */ +pf_status_t pf_set_size(pf_context_t* pf, size_t size) { + pf_chunk_t* chunk = NULL; + assert(pf && pf->header); + pf_status_t status = check_callbacks(); + if (PF_FAILURE(status)) + goto out; + + status = PF_STATUS_INVALID_CONTEXT; + if (!pf->header) + goto out; + + status = PF_STATUS_INVALID_MODE; + if (!has_mode(pf, PF_FILE_MODE_WRITE)) + goto out; + + uint64_t old_size = pf->header->data_size; + DEBUG_PF("pf %p, size %lu->%lu\n", pf, old_size, size); + + if (size > old_size) /* extend the file with zeros */ + status = pf_write(pf, old_size, size - old_size, NULL); + else { + uint64_t old_last_chunk = PF_CHUNKS_COUNT(old_size) - 1; + uint32_t old_last_chunk_size = PF_CHUNK_DATA_SIZE(old_size, old_last_chunk); + uint64_t last_chunk = PF_CHUNKS_COUNT(size) - 1; + uint32_t last_chunk_size = PF_CHUNK_DATA_SIZE(size, last_chunk); + + /* update header and possibly truncate file */ + status = update_header(pf, size); + + /* truncation between chunks -> no chunk update needed */ + if (size > 0 && last_chunk == old_last_chunk) { + /* update last chunk */ + status = cb_map(pf->handle, PF_FILE_MODE_READ | PF_FILE_MODE_WRITE, + PF_CHUNK_OFFSET(last_chunk), PF_CHUNK_SIZE, (void**)&chunk); + if (PF_FAILURE(status)) + goto out; + + /* decrypt old data */ + status = pf_decrypt_chunk(pf, last_chunk, chunk, old_last_chunk_size, pf->plaintext); + if (PF_FAILURE(status)) { + DEBUG_PF("pf_decrypt_chunk failed: %d\n", status); + goto out; + } + + /* zero unused part of the chunk (and truncated data) */ + memset(&chunk->chunk_data[last_chunk_size], 0, PF_CHUNK_DATA_MAX - last_chunk_size); + + /* encrypt the data, truncating it */ + status = pf_encrypt_chunk(pf, last_chunk, pf->plaintext, last_chunk_size, chunk); + if (PF_FAILURE(status)) { + DEBUG_PF("pf_encrypt_chunk failed: %d\n", status); + goto out; + } + } + } +out: + if (chunk) { + status = cb_unmap(chunk, PF_CHUNK_SIZE); + if (PF_FAILURE(status)) { + DEBUG_PF("failed to unmap chunk\n"); + return status; + } + } + return status; +} diff --git a/Pal/src/host/Linux-SGX/protected_files.h b/Pal/src/host/Linux-SGX/protected_files.h new file mode 100644 index 00000000..38a18fba --- /dev/null +++ b/Pal/src/host/Linux-SGX/protected_files.h @@ -0,0 +1,434 @@ +/* Copyright (C) 2018,2019 Invisible Things Lab + Rafal Wojdyla + + This file is part of Graphene Library OS. + + Graphene Library OS is free software: you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public License + as published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + Graphene Library OS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +#ifndef PROTECTED_FILES_H +#define PROTECTED_FILES_H + +#include +#include +#include +#include + +/* +Protected file (PF) format requirements: +- Confidentiality (encryption). A party without the wrap key should not be able to + read data contained within. +- Integrity (tamper detection). Unauthorized PF modifications should be detected and such + file should not be usable. +- Path/file swap protection. A PF should only be accessible when located in a path(s) + explicitely defined in the PF metadata. +- Ease of use for transparent I/O operations in Graphene. Graphene payload doesn't need to + know anything about internal PF implementation (or that an accessed file is a PF at all). +- (Possibly in the future) Streamed data support. + +PF consist of a header with some global metadata followed by zero or more data chunks. +Each chunk is encrypted separately to reduce performance impact of crypto operations. + +AES-GCM is used for authenticated encryption and decryption. + +Implementation is designed to be pretty modular and as environment-independent as possible +since it's used by the Graphene enclave and by non-Graphene (native) tools. + +TODO: +- Secure provisioning of the wrap key. +- Currently only one allowed path is supported. Multiple allowed paths will allow for + (sym)link support in the future. +- Thorough cryptographic review is needed. One known weakness is a chunk swap attack: + same-numbered chunks can be swapped between files without detection. This will be + fixed in the next iteration. +- Possible performance optimizations (in the PF implementation and Graphene handlers). +- Tests with invalid/corrupted/tampered contents of PFs (not ported from the original + implementation yet). +- Convert into a library if needed. +*/ + +/*! File format version */ +#define PF_FORMAT_VERSION 4 + +/*! Size of the AES-GCM encryption key */ +#define PF_WRAP_KEY_SIZE 16 + +/*! Size of IV for AES-GCM */ +#define PF_IV_SIZE 12 + +/*! Size of MAC fields */ +#define PF_MAC_SIZE 16 + +/*! Total size of a chunk */ +#define PF_CHUNK_SIZE (4 * 0x1000) + +/*! File offset for the first chunk, page aligned for easy mmap-ing */ +#define PF_CHUNKS_OFFSET 0x1000 + +/*! Header size (constant) */ +#define PF_HEADER_SIZE PF_CHUNKS_OFFSET + +/*! Maximum size for allowed paths */ +#define PF_HEADER_ALLOWED_PATHS_SIZE (PF_HEADER_SIZE -4 -8 -PF_IV_SIZE -4 -PF_MAC_SIZE) + +/*! Maximum number of data bytes in a chunk */ +#define PF_CHUNK_DATA_MAX (PF_CHUNK_SIZE -8 -PF_IV_SIZE -12 -PF_MAC_SIZE) + +/*! Protected file header */ +typedef struct __attribute__((packed)) _pf_header_t { + uint32_t version; //!< File format version + uint64_t data_size; //!< Original file size + uint8_t header_iv[PF_IV_SIZE]; //!< AES-GCM IV + uint32_t allowed_paths_size; //!< Size of allowed paths that follow (including NULL terminators) + char allowed_paths[PF_HEADER_ALLOWED_PATHS_SIZE]; //!< C-string paths, padded with zeros + uint8_t header_mac[PF_MAC_SIZE]; //!< AES-GCM tag of header up to this field +} pf_header_t; + +static_assert(sizeof(pf_header_t) == PF_HEADER_SIZE, "incorrect struct size"); + +/*! Protected file chunk, each is individually encrypted */ +typedef struct __attribute__((packed)) _pf_chunk_t { + uint64_t chunk_number; //!< Sequential in a file, starting from 0 + uint8_t chunk_iv[PF_IV_SIZE]; //!< AES-GCM IV + uint8_t padding[12]; //!< Unused + uint8_t chunk_data[PF_CHUNK_DATA_MAX]; //!< Use PF_CHUNK_DATA_SIZE for actual data size + uint8_t chunk_mac[PF_MAC_SIZE]; //!< AES-GCM tag for chunk_data, fields before are used as aad +} pf_chunk_t; + +static_assert(sizeof(pf_chunk_t) == PF_CHUNK_SIZE, "incorrect struct size"); + +/*! Size of chunk metadata/header */ +#define PF_CHUNK_HEADER_SIZE (offsetof(pf_chunk_t, chunk_data)) +/*! Number of a chunk containing given data offset */ +#define PF_CHUNK_NUMBER(offset) ((offset) / PF_CHUNK_DATA_MAX) +/*! Number of chunks needed for the given data size */ +#define PF_CHUNKS_COUNT(size) ((size) > 0 ? PF_CHUNK_NUMBER((size) - 1) + 1 : 0) +/*! Offset of a given chunk relative to the start of the file */ +#define PF_CHUNK_OFFSET(chunk_nr) (PF_CHUNKS_OFFSET + (chunk_nr) * PF_CHUNK_SIZE) +/*! Size of chunk data */ +#define PF_CHUNK_DATA_SIZE(size, chunk_nr) (((chunk_nr) < (PF_CHUNKS_COUNT(size) - 1)) ? PF_CHUNK_DATA_MAX : (size % PF_CHUNK_DATA_MAX)) + +/*! Return values for PF functions */ +typedef enum _pf_status_t { + PF_STATUS_SUCCESS = 0, + PF_STATUS_UNKNOWN_ERROR = -1, + PF_STATUS_UNINITIALIZED = -2, + PF_STATUS_INVALID_PARAMETER = -3, + PF_STATUS_INVALID_MODE = -4, + PF_STATUS_INVALID_CONTEXT = -5, + PF_STATUS_NO_MEMORY = -6, + PF_STATUS_BAD_VERSION = -7, + PF_STATUS_BAD_HEADER = -8, + PF_STATUS_BAD_CHUNK = -9, + PF_STATUS_MAC_MISMATCH = -10, + PF_STATUS_NOT_IMPLEMENTED = -11, + PF_STATUS_CALLBACK_FAILED = -12, + PF_STATUS_PATH_TOO_LONG = -13, +} pf_status_t; + +#define PF_SUCCESS(status) ((status) == PF_STATUS_SUCCESS) +#define PF_FAILURE(status) ((status) != PF_STATUS_SUCCESS) + +/*! PF open/map modes */ +typedef enum _pf_file_mode_t { + PF_FILE_MODE_READ = 1, + PF_FILE_MODE_WRITE = 2, +} pf_file_mode_t; + +/*! Opaque file handle type, interpreted by callbacks as necessary */ +typedef void* pf_handle_t; + +/*! + * \brief Allocate memory callback + * + * \param [in] size Size to allocate + * \return Allocated address or NULL if failed + * + * \details Must zero the allocated buffer + */ +typedef void* (*pf_malloc_f)(size_t size); + +/*! + * \brief Free memory callback + * + * \param [in] address Address to free + * + * \details Must accept NULL pointers + */ +typedef void (*pf_free_f)(void* address); + +/*! + * \brief File map callback + * + * \param [in] handle File handle + * \param [in] mode Access mode + * \param [in] offset Starting offset of the region to map + * \param [in] size Size of the region to map + * \param [out] address Mapped address + * \return PF status + */ +typedef pf_status_t (*pf_map_f)(pf_handle_t handle, pf_file_mode_t mode, size_t offset, size_t size, + void** address); + +/*! + * \brief File unmap callback + * + * \param [in] address Address to unmap + * \param [in] size Size of mapped region + * \return PF status + */ +typedef pf_status_t (*pf_unmap_f)(void* address, size_t size); + +/*! + * \brief File truncate callback + * + * \param [in] handle File handle + * \param [in] size Target file size + * \return PF status + */ +typedef pf_status_t (*pf_truncate_f)(pf_handle_t handle, size_t size); + +/*! + * \brief File flush callback + * + * \param [in] handle File handle + * \return PF status + */ +typedef pf_status_t (*pf_flush_f)(pf_handle_t handle); + +/*! + * \brief Debug print callback + * + * \param [in] msg Message to print + */ +typedef void (*pf_debug_f)(const char* msg); + +/*! + * \brief AES-GCM encrypt callback + * + * \param [in] key AES-GCM key + * \param [in] key_size Size of \a key in bytes + * \param [in] iv Initialization vector + * \param [in] iv_size Size of \a iv in bytes + * \param [in] aad (optional) Additional authenticated data + * \param [in] aad_size Size of \a aad in bytes + * \param [in] input Plaintext data + * \param [in] input_size Size of \a input in bytes + * \param [out] output Buffer for encrypted data (size: \a input_size) + * \param [out] mac MAC computed for \a input and \a aad + * \param [in] mac_size Size of \a mac in bytes + * \return PF status + */ +typedef pf_status_t (*pf_crypto_aes_gcm_encrypt_f)(const uint8_t* key, size_t key_size, + const uint8_t* iv, size_t iv_size, + const void* aad, size_t aad_size, + const void* input, size_t input_size, + void* output, uint8_t* mac, size_t mac_size); + +/*! + * \brief AES-GCM decrypt callback + * + * \param [in] key AES-GCM key + * \param [in] key_size Size of \a key in bytes + * \param [in] iv Initialization vector + * \param [in] iv_size Size of \a iv in bytes + * \param [in] aad (optional) Additional authenticated data + * \param [in] aad_size Size of \a aad in bytes + * \param [in] input Encrypted data + * \param [in] input_size Size of \a input in bytes + * \param [out] output Buffer for decrypted data (size: \a input_size) + * \param [in] mac Expected MAC + * \param [in] mac_size Size of \a mac in bytes + * \return PF status + */ +typedef pf_status_t (*pf_crypto_aes_gcm_decrypt_f)(const uint8_t* key, size_t key_size, + const uint8_t* iv, size_t iv_size, + const void* aad, size_t aad_size, + const void* input, size_t input_size, + void* output, const uint8_t* mac, + size_t mac_size); + +/*! + * \brief Cryptographic random number generator callback + * + * \param [out] buffer Buffer to fill with random bytes + * \param [in] size Size of \a buffer in bytes + * \return PF status + */ +typedef pf_status_t (*pf_crypto_random_f)(uint8_t* buffer, size_t size); + +#define PF_DEBUG_PRINT_SIZE_MAX 4096 + +/*! Context holding information for an opened protected file */ +typedef struct _pf_context_t { + pf_handle_t handle; //!< Underlying file handle + pf_header_t* header; //!< PF header mapped in memory + pf_file_mode_t mode; //!< Access mode + uint8_t key[PF_WRAP_KEY_SIZE]; //!< Wrap key + char* debug_buffer; //!< Buffer for debug output + pf_chunk_t* plaintext; //!< Temporary chunk buffer + pf_chunk_t* encrypted; //!< Temporary chunk buffer +} pf_context_t; + +/*! + * \brief Initialize I/O callbacks + * + * \param [in] malloc_f Allocate memory callback + * \param [in] free_f Free memory callback + * \param [in] map_f File map callback + * \param [in] unmap_f File unmap callback + * \param [in] truncate_f File truncate callback + * \param [in] flush_f File flush callback + * \param [in] debug_f (optional) Debug print callback + * + * \details Must be called before any actual APIs + */ +void pf_set_callbacks(pf_malloc_f malloc_f, pf_free_f free_f, pf_map_f map_f, pf_unmap_f unmap_f, + pf_truncate_f truncate_f, pf_flush_f flush_f, pf_debug_f debug_f); + +/*! + * \brief Initialize cryptographic callbacks + * + * \param [in] crypto_aes_gcm_encrypt_f AES-GCM encrypt callback + * \param [in] crypto_aes_gcm_decrypt_f AES-GCM decrypt callback + * \param [in] crypto_random_f Cryptographic random number generator callback + * + * \details Must be called before any actual APIs + */ +void pf_set_crypto_callbacks(pf_crypto_aes_gcm_encrypt_f crypto_aes_gcm_encrypt_f, + pf_crypto_aes_gcm_decrypt_f crypto_aes_gcm_decrypt_f, + pf_crypto_random_f crypto_random_f); + +/*! + * \brief Open an existing protected file + * + * \param [in] handle Opened underlying file handle + * \param [in] underlying_size Underlying file size + * \param [in] mode Access mode + * \param [in] key Wrap key + * \param [out] context PF context for later calls + * \return PF status + */ +pf_status_t pf_open(pf_handle_t handle, size_t underlying_size, pf_file_mode_t mode, + const uint8_t key[PF_WRAP_KEY_SIZE], pf_context_t** context); + +/*! + * \brief Create a new protected file + * + * \param [in] handle Opened underlying file handle + * \param [in] prefix Path prefix for allowed file name + * \param [in] file_name Allowed file name + * \param [in] key Wrap key + * \param [out] context PF context for later calls + * \return PF status + */ +pf_status_t pf_create(pf_handle_t handle, const char* prefix, const char* file_name, + const uint8_t key[PF_WRAP_KEY_SIZE], pf_context_t** context); + +/*! + * \brief Close a protected file + * + * \param [in] pf PF context + * \return PF status + * + * \details Any writable mmap buffers are written to the PF by this function + */ +pf_status_t pf_close(pf_context_t* pf); + +/*! + * \brief Read from a protected file + * + * \param [in] pf PF context + * \param [in] offset Data offset to read from + * \param [in] size Number of bytes to read + * \param [out] output Destination buffer + * \return PF status + */ +pf_status_t pf_read(pf_context_t* pf, uint64_t offset, size_t size, void* output); + +/*! + * \brief Write to a protected file + * + * \param [in] pf PF context + * \param [in] offset Data offset to write to + * \param [in] size Number of bytes to write + * \param [in] input Source buffer + * \return PF status + */ +pf_status_t pf_write(pf_context_t* pf, uint64_t offset, size_t size, const void* input); + +/*! + * \brief Decrypt a single chunk + * + * \param [in] pf PF context + * \param [in] chunk_number Expected chunk number + * \param [in] chunk Encrypted chunk with metadata (pf_chunk_t) + * \param [in] chunk_size Size of \a output + * \param [out] output Decrypted chunk data + * \return PF status + */ +pf_status_t pf_decrypt_chunk(pf_context_t* pf, uint64_t chunk_number, const pf_chunk_t* chunk, + uint32_t chunk_size, void* output); + +/*! + * \brief Encrypt a single chunk + * + * \param [in] pf PF context + * \param [in] chunk_number Chunk number + * \param [in] input Chunk data to encrypt + * \param [in] chunk_size Size of \a input + * \param [out] output Output encrypted chunk, size PF_CHUNK_SIZE + * \return PF status + */ +pf_status_t pf_encrypt_chunk(pf_context_t* pf, uint64_t chunk_number, const void* input, + uint32_t chunk_size, pf_chunk_t* output); + +/*! + * \brief Check whether a PF was opened with specified access mode + * + * \param [in] pf PF context + * \param [in] mode Access mode to check for + * \param [out] result True if the PF was opened with specified access mode + * \return PF status + */ +pf_status_t pf_has_mode(pf_context_t* pf, pf_file_mode_t mode, bool* result); + +/*! + * \brief Check whether the specified path is in allowed paths for a PF + * + * \param [in] pf PF context + * \param [in] path Path to check + * \param [out] result True if \a path is in allowed paths for this PF + * \return PF status + */ +pf_status_t pf_check_path(pf_context_t* pf, const char* path, bool* result); + +/*! + * \brief Get data size of a PF + * + * \param [in] pf PF context + * \param [out] size Data size of \a pf + * \return PF status + */ +pf_status_t pf_get_size(pf_context_t* pf, uint64_t* size); + +/*! + * \brief Set data size of a PF + * + * \param [in] pf PF context + * \param [in] size Data size to set + * \return PF status + */ +pf_status_t pf_set_size(pf_context_t* pf, size_t size); + +#endif /* PROTECTED_FILES_H */ diff --git a/Pal/src/host/Linux-SGX/tools/.gitignore b/Pal/src/host/Linux-SGX/tools/.gitignore new file mode 100644 index 00000000..3ad84aa6 --- /dev/null +++ b/Pal/src/host/Linux-SGX/tools/.gitignore @@ -0,0 +1,2 @@ +pf_crypt +pf_tamper diff --git a/Pal/src/host/Linux-SGX/tools/Makefile b/Pal/src/host/Linux-SGX/tools/Makefile new file mode 100644 index 00000000..33ee13af --- /dev/null +++ b/Pal/src/host/Linux-SGX/tools/Makefile @@ -0,0 +1,40 @@ +include ../../../../../Makefile.configs + +CFLAGS := -Wall -Wextra -O2 -maes -std=c11 \ + -fno-omit-frame-pointer \ + -D_POSIX_C_SOURCE=200809L + +ifeq ($(DEBUG),1) +CC += -gdwarf-2 -g3 +CFLAGS += -DDEBUG +export DEBUG +endif + +ifeq ($(WERROR),1) +CFLAGS += -Werror +endif + +headers = $(wildcard *.h) ../protected_files.h +objs = util pf_util pf_crypt +bins = pf_crypt pf_tamper + +.PHONY: all +all: $(bins) + +protected_files.o: ../protected_files.c + $(CC) $(CFLAGS) $(CFLAGS-$@) -c -o $@ $< + +$(addsuffix .o,$(objs)): %.o: %.c $(headers) + $(CC) $(CFLAGS) $(CFLAGS-$@) -c -o $@ $< + +pf_crypt: pf_crypt.o util.o pf_util.o protected_files.o + @echo [ host/Linux-SGX/tools/$@ ] + @$(CC) $(CFLAGS) -Wl,-z,relro,-z,now $^ -lc -lcrypto -o $@ + +pf_tamper: pf_tamper.o util.o pf_util.o protected_files.o + @echo [ host/Linux-SGX/tools/$@ ] + @$(CC) $(CFLAGS) -Wl,-z,relro,-z,now $^ -lc -lcrypto -o $@ + +clean: + @rm -f *.o + @rm -f $(bins) diff --git a/Pal/src/host/Linux-SGX/tools/pf_crypt.c b/Pal/src/host/Linux-SGX/tools/pf_crypt.c new file mode 100644 index 00000000..7d21b5ce --- /dev/null +++ b/Pal/src/host/Linux-SGX/tools/pf_crypt.c @@ -0,0 +1,147 @@ +/* Copyright (C) 2018,2019 Invisible Things Lab + Rafal Wojdyla + + This file is part of Graphene Library OS. + + Graphene Library OS is free software: you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public License + as published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + Graphene Library OS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +#include +#include + +#include "pf_util.h" +#include "util.h" + +/* Command line options */ +struct option g_options[] = { + { "input", required_argument, 0, 'i' }, + { "output", required_argument, 0, 'o' }, + { "prefix", required_argument, 0, 'p' }, + { "wrap-key", required_argument, 0, 'w' }, + { "verify", no_argument, 0, 'V' }, + { "verbose", no_argument, 0, 'v' }, + { "help", no_argument, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +void usage() { + INFO("\nUsage: pf_crypt mode [options]\n"); + INFO("Available modes:\n"); + INFO(" gen-key Generate and save wrap key to file\n"); + INFO(" encrypt Encrypt plaintext files\n"); + INFO(" decrypt Decrypt encrypted files\n"); + INFO("\nAvailable general options:\n"); + INFO(" --help, -h Display this help\n"); + INFO(" --verbose, -v Verbose output\n"); + INFO("\nAvailable gen-key options:\n"); + INFO(" --wrap-key, -w PATH Path to wrap key file\n"); + INFO("\nAvailable encrypt options:\n"); + INFO(" --input, -i PATH Single file or directory with input files to convert\n"); + INFO(" --output, -o PATH Single file or directory to write output files to\n"); + INFO(" --prefix, -p PATH Path prefix for protected files that the payload manifest expects\n"); + INFO(" --wrap-key, -w PATH Path to wrap key file, must exist\n"); + INFO("\nAvailable decrypt options:\n"); + INFO(" --input, -i PATH Single file or directory with input files to convert\n"); + INFO(" --output, -o PATH Single file or directory to write output files to\n"); + INFO(" --wrap-key, -w PATH Path to wrap key file, must exist\n"); + INFO(" --verify, -V (optional) Verify that input path matches PF's allowed paths\n"); +} + +int main(int argc, char *argv[]) { + int ret = -1; + int this_option = 0; + char* input_path = NULL; + char* output_path = NULL; + char* wrap_key_path = NULL; + char* prefix = NULL; + char* mode = NULL; + bool verify = false; + + while (true) { + this_option = getopt_long(argc, argv, "i:o:p:w:Vvh", g_options, NULL); + if (this_option == -1) + break; + + switch (this_option) { + case 'i': + input_path = optarg; + break; + case 'o': + output_path = optarg; + break; + case 'p': + prefix = optarg; + break; + case 'w': + wrap_key_path = optarg; + break; + case 'v': + set_verbose(true); + break; + case 'V': + verify = true; + break; + case 'h': + usage(); + exit(0); + default: + ERROR("Unknown option: %c\n", this_option); + usage(); + } + } + + if (optind >= argc) { + ERROR("Mode not specified\n"); + usage(); + goto out; + } + + if (!wrap_key_path) { + ERROR("Wrap key path not specified\n"); + goto out; + } + + mode = argv[optind]; + pf_init(); + + switch (mode[0]) { + case 'g': /* gen-key */ + ret = pf_generate_wrap_key(wrap_key_path); + break; + + case 'e': /* encrypt */ + if (!input_path || !output_path || !prefix) { + ERROR("Input/output path or prefix not specified\n"); + usage(); + goto out; + } + ret = pf_encrypt_files(input_path, output_path, prefix, wrap_key_path); + break; + + case 'd': /* decrypt */ + if (!input_path || !output_path) { + ERROR("Input or output path not specified\n"); + usage(); + goto out; + } + ret = pf_decrypt_files(input_path, output_path, verify, wrap_key_path); + break; + + default: + usage(); + goto out; + } + +out: + return ret; +} diff --git a/Pal/src/host/Linux-SGX/tools/pf_tamper.c b/Pal/src/host/Linux-SGX/tools/pf_tamper.c new file mode 100644 index 00000000..b2bbca53 --- /dev/null +++ b/Pal/src/host/Linux-SGX/tools/pf_tamper.c @@ -0,0 +1,420 @@ +/* Copyright (C) 2019 Invisible Things Lab + Rafal Wojdyla + + This file is part of Graphene Library OS. + + Graphene Library OS is free software: you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public License + as published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + Graphene Library OS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +#include +#include +#include +#include +#include +#include + +#include "pf_util.h" +#include "util.h" + +/* Tamper with a PF in various ways for testing purposes. + Wrap key is needed, some modifications change the key and/or (meta)data + to create invalid MACs etc. */ + +/* Command line options */ +struct option g_options[] = { + { "input", required_argument, 0, 'i' }, + { "output", required_argument, 0, 'o' }, + { "wrap-key", required_argument, 0, 'w' }, + { "verbose", no_argument, 0, 'v' }, + { "help", no_argument, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +void usage() { + INFO("\nUsage: pf_tamper [options]\n"); + INFO("To enable all modifications, the PF should contain at least 3 chunks\n"); + INFO("and the last one should not be full.\n"); + INFO("\nAvailable options:\n"); + INFO(" --help, -h Display this help\n"); + INFO(" --verbose, -v Enable verbose output\n"); + INFO(" --wrap-key, -w PATH Path to wrap key file\n"); + INFO(" --input, -i PATH Source file to be tampered with (must be a valid PF)\n"); + INFO(" --output, -o PATH Directory where modified files will be written to\n"); +} + +int truncate_pf(const char* input_name, size_t input_size, const void* input, + const char* output_dir, char* output_path, size_t output_path_size, + const char* suffix, const char* msg, bool extend, size_t size) { + + int ret; + if (extend || input_size > size) { + snprintf(output_path, output_path_size, "%s/%s.%s", output_dir, input_name, suffix); + INFO("[*] %s: %s\n", msg, output_path); + if (input_size >= size) { + ret = write_file(output_path, size, input); + } else { + ret = write_file(output_path, input_size, input); + if (ret < 0) + return ret; + ret = truncate(output_path, size); + } + if (ret < 0) + return ret; + } + return 0; +} + +#define TRUNCATE(suffix, msg, extend, size) \ +{ \ + ret = truncate_pf(input_name, input_size, input, output_dir, output_path, output_path_size, \ + suffix, msg, extend, size); \ + if (ret < 0) \ + goto out; \ +} + +#define FIELD_SIZEOF(t, f) (sizeof(((t*)0)->f)) + +int tamper_truncate(const char* input_name, size_t input_size, const void* input, + const char* output_dir, char* output_path, size_t output_path_size) { + int ret = -1; + + snprintf(output_path, output_path_size, "%s/%s.trunc_zero", output_dir, input_name); + INFO("[*] Zero-size file: %s\n", output_path); + ret = write_file(output_path, 0, input); + if (ret < 0) + goto out; + + TRUNCATE("trunc_header", "Truncated header", false, PF_HEADER_SIZE / 2); + + TRUNCATE("trunc_chunk_metadata", "Truncated chunk (metadata)", false, + PF_CHUNKS_OFFSET + offsetof(pf_chunk_t, chunk_iv) + FIELD_SIZEOF(pf_chunk_t, chunk_iv) / 2); + + TRUNCATE("trunc_chunk_data", "Truncated chunk (data)", false, + PF_CHUNKS_OFFSET + offsetof(pf_chunk_t, chunk_data) + 10); + + TRUNCATE("trunc_chunks", "Truncated between chunks", false, PF_CHUNK_OFFSET(1)); + + TRUNCATE("trunc_extend_1", "Extended (+1)", true, input_size + 1); + + TRUNCATE("trunc_extend_2", "Extended (+chunk)", true, input_size + PF_CHUNK_SIZE); + + ret = 0; +out: + return ret; +} + +void* open_output(const char* path, size_t size, const void* input) { + void* mem = MAP_FAILED; + int fd = open(path, O_RDWR|O_CREAT, 0664); + if (fd < 0) { + ERROR("Failed to open output file '%s': %s\n", path, strerror(errno)); + goto out; + } + + if (ftruncate(fd, size) < 0) { + ERROR("Failed to ftruncate output file '%s': %s\n", path, strerror(errno)); + goto out; + } + + mem = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); + if (mem == MAP_FAILED) { + ERROR("Failed to mmap output file '%s': %s\n", path, strerror(errno)); + goto out; + } + + memcpy(mem, input, size); + +out: + if (fd >= 0) + close(fd); + return mem; +} + +/* copy input PF and apply some modifications */ +#define __BREAK_PF(file_suffix, msg, ...) \ +{ \ + snprintf(output_path, output_path_size, "%s/%s." file_suffix, output_dir, input_name); \ + INFO("[*] " msg ": %s\n", output_path); \ + output = open_output(output_path, size, input); \ + if (output == MAP_FAILED) \ + goto out; \ + __VA_ARGS__ \ + munmap(output, size); \ +} + +/* if fix is true, also create a file with correct header's MAC */ +#define BREAK_HEADER(file_suffix, msg, fix, ...) \ +{ \ + __BREAK_PF(file_suffix, "Header " msg, __VA_ARGS__); \ + if (fix) { \ + __BREAK_PF(file_suffix "_fixed", "Header (fixed) " msg, __VA_ARGS__ { \ + openssl_crypto_aes_gcm_encrypt(key, PF_WRAP_KEY_SIZE, output->header_iv, PF_IV_SIZE, \ + output, PF_HEADER_SIZE-PF_MAC_SIZE, NULL, 0, NULL, \ + output->header_mac, PF_MAC_SIZE); \ + }); \ + } \ +} + +int tamper_header(const char* input_name, size_t size, const void* input, const uint8_t* key, + const char* output_dir, char* output_path, size_t output_path_size) { + int ret = -1; + pf_header_t* output = MAP_FAILED; + + BREAK_HEADER("header_version_1", "invalid version (0)", true, + {output->version = 0;}); + + BREAK_HEADER("header_version_2", "invalid version (max)", true, + {output->version = UINT32_MAX;}); + + BREAK_HEADER("header_size_1", "invalid size (0)", false, + {output->data_size = 0;}); + + BREAK_HEADER("header_size_2", "invalid size (x-1)", true, + {output->data_size--;}); + + BREAK_HEADER("header_size_3", "invalid size (x+1)", true, + {output->data_size++;}); + + BREAK_HEADER("header_size_4", "invalid size (max)", true, + {output->data_size = UINT64_MAX;}); + + BREAK_HEADER("header_iv", "invalid IV", false, + {output->header_iv[0] ^= 1;}); + + BREAK_HEADER("header_aps_1", "invalid allowed_paths_size (0)", true, + {output->allowed_paths_size = 0;}); + + BREAK_HEADER("header_mac", "invalid MAC", false, + {output->header_mac[PF_MAC_SIZE-1] ^= 1;}); + + /* These may not be strictly invalid, but they should result in inaccessible PFs */ + BREAK_HEADER("header_aps_2", "invalid allowed_paths_size (x-1)", true, + {output->allowed_paths_size--;}); + + BREAK_HEADER("header_aps_3", "invalid allowed_paths_size (x+1)", true, + {output->allowed_paths_size++;}); + + BREAK_HEADER("header_aps_4", "invalid allowed_paths_size (max)", true, + {output->allowed_paths_size = UINT32_MAX;}); + + BREAK_HEADER("header_ap_1", "invalid allowed_paths", true, + {output->allowed_paths[0]++;}); + + ret = 0; +out: + return ret; +} + +/* if fix is true, also create a file with correct chunk's MAC/encrypted data */ +#define BREAK_CHUNK(file_suffix, msg, fix, ...) \ +{ \ + __BREAK_PF(file_suffix, "Chunk " msg, __VA_ARGS__); \ + if (fix) { \ + uint8_t decrypted[PF_CHUNK_SIZE]; \ + __BREAK_PF(file_suffix "_fixed", "Chunk (fixed) " msg, { \ + openssl_crypto_aes_gcm_decrypt(key, PF_WRAP_KEY_SIZE, chunk->chunk_iv, PF_IV_SIZE, \ + chunk, PF_CHUNK_HEADER_SIZE, \ + chunk->chunk_data, chunk_size, decrypted, \ + chunk->chunk_mac, PF_MAC_SIZE); \ + } \ + __VA_ARGS__ \ + { \ + openssl_crypto_aes_gcm_encrypt(key, PF_WRAP_KEY_SIZE, chunk->chunk_iv, PF_IV_SIZE, \ + chunk, PF_CHUNK_HEADER_SIZE, \ + decrypted, chunk_size, chunk->chunk_data, \ + chunk->chunk_mac, PF_MAC_SIZE); \ + }); \ + } \ +} + +int tamper_chunk(const char* input_name, size_t size, const void* input, const uint8_t* key, + const char* output_dir, char* output_path, size_t output_path_size) { + int ret = -1; + void* output = MAP_FAILED; + pf_chunk_t* chunk; + pf_header_t* header = (pf_header_t*)input; + uint64_t chunks = PF_CHUNKS_COUNT(header->data_size); + + if (chunks == 0) /* no chunks to break */ + return 0; + +#define SET_PTR(mem, idx) chunk = (pf_chunk_t*)(((uint8_t*)mem) + PF_CHUNK_OFFSET(idx)); + + uint64_t idx = 0; + uint64_t chunk_size = PF_CHUNK_DATA_SIZE(header->data_size, idx); + BREAK_CHUNK("chunk_number_1", "invalid number (0->1)", true, + {SET_PTR(output, idx); chunk->chunk_number = 1;}); + + BREAK_CHUNK("chunk_number_2", "invalid number (0->max)", true, + {SET_PTR(output, idx); chunk->chunk_number = UINT64_MAX;}); + + BREAK_CHUNK("chunk_iv", "invalid IV", false, + {SET_PTR(output, idx); chunk->chunk_iv[PF_IV_SIZE-1] ^= 1;}); + + /* padding being zero is not enforced */ + BREAK_CHUNK("chunk_padding_1", "non-zero padding[0]", true, + {SET_PTR(output, idx); chunk->padding[0] = 0xf0;}); + + /* padding being zero is not enforced */ + BREAK_CHUNK("chunk_padding_2", "non-zero padding[7]", true, + {SET_PTR(output, idx); chunk->padding[7] = 0x01;}); + + BREAK_CHUNK("chunk_data_1", "invalid data[0]", false, + {SET_PTR(output, idx); chunk->chunk_data[0] ^= 0xf0;}); + + BREAK_CHUNK("chunk_data_2", "invalid data[size-1]", false, + {SET_PTR(output, idx); chunk->chunk_data[chunk_size-1] ^= 0x01;}); + + BREAK_CHUNK("chunk_mac", "invalid MAC", false, + {SET_PTR(output, idx); chunk->chunk_mac[0] ^= 1;}); + + if (chunks > 1) { + idx = 1; + chunk_size = PF_CHUNK_DATA_SIZE(header->data_size, idx); + BREAK_CHUNK("chunk_number_3", "invalid number (1->0)", true, + {SET_PTR(output, idx); chunk->chunk_number = 0;}); + + BREAK_CHUNK("chunk_number_4", "invalid number (1->-1)", true, + {SET_PTR(output, idx); chunk->chunk_number = -1;}); + + /* reorder chunks */ + BREAK_CHUNK("chunk_reorder", "reordered chunks (0<->1)", false, + { + SET_PTR(output, 1); /* 0->1 */ + memcpy(chunk, (uint8_t*)input + PF_CHUNK_OFFSET(0), PF_CHUNK_SIZE); + SET_PTR(output, 0); /* 1->0 */ + memcpy(chunk, (uint8_t*)input + PF_CHUNK_OFFSET(1), PF_CHUNK_SIZE); + }); + } + + /* last chunk is not full? */ + SET_PTR(input, chunks-1); /* check last chunk size */ + idx = chunks - 1; + chunk_size = PF_CHUNK_DATA_SIZE(header->data_size, idx); + if (chunk_size != PF_CHUNK_DATA_MAX) { + /* padding being zero is not enforced */ + BREAK_CHUNK("chunk_data_3", "non-zero data[size+1]", false, + {SET_PTR(output, idx); chunk->chunk_data[chunk_size+1] = 1;}); + + /* padding being zero is not enforced */ + BREAK_CHUNK("chunk_data_4", "non-zero data[max size-1]", false, + {SET_PTR(output, idx); chunk->chunk_data[PF_CHUNK_DATA_MAX-1] = 1;}); + } + + ret = 0; +out: + return ret; +} + +int main(int argc, char *argv[]) { + int ret = -1; + int this_option = 0; + char* input_path = NULL; + char* output_dir = NULL; + char* output_path = NULL; + char* wrap_key_path = NULL; + int input_fd = -1; + void* input = MAP_FAILED; + uint8_t wrap_key[PF_WRAP_KEY_SIZE]; + + while (true) { + this_option = getopt_long(argc, argv, "i:o:w:vh", g_options, NULL); + if (this_option == -1) + break; + + switch (this_option) { + case 'i': + input_path = optarg; + break; + case 'o': + output_dir = optarg; + break; + case 'w': + wrap_key_path = optarg; + break; + case 'v': + set_verbose(true); + break; + case 'h': + usage(); + exit(0); + default: + ERROR("Unknown option: %c\n", this_option); + usage(); + } + } + + if (!input_path) { + ERROR("Input path not specified\n"); + usage(); + goto out; + } + + if (!output_dir) { + ERROR("Output path not specified\n"); + usage(); + goto out; + } + + if (!wrap_key_path) { + ERROR("Wrap key path not specified\n"); + usage(); + goto out; + } + + input_fd = open(input_path, O_RDONLY); + if (input_fd < 0) { + ERROR("Failed to open input file '%s': %s\n", input_path, strerror(errno)); + goto out; + } + + ssize_t input_size = get_file_size(input_fd); + if (input_size < 0) { + ERROR("Failed to stat input file '%s': %s\n", input_path, strerror(errno)); + goto out; + } + + input = mmap(NULL, input_size, PROT_READ, MAP_PRIVATE, input_fd, 0); + if (input == MAP_FAILED) { + ERROR("Failed to mmap input file '%s': %s\n", input_path, strerror(errno)); + goto out; + } + + load_wrap_key(wrap_key_path, wrap_key); + + const char* input_name = basename(input_path); + size_t output_path_size = strlen(input_name) + strlen(output_dir) + 256; + output_path = malloc(output_path_size); + if (!output_path) { + ERROR("No memory\n"); + goto out; + } + + ret = tamper_truncate(input_name, input_size, input, output_dir, output_path, output_path_size); + if (ret < 0) + goto out; + + ret = tamper_header(input_name, input_size, input, wrap_key, output_dir, output_path, output_path_size); + if (ret < 0) + goto out; + + ret = tamper_chunk(input_name, input_size, input, wrap_key, output_dir, output_path, output_path_size); + +out: + if (input != MAP_FAILED) + munmap(input, input_size); + if (input_fd >= 0) + close(input_fd); + free(output_path); + return ret; +} diff --git a/Pal/src/host/Linux-SGX/tools/pf_util.c b/Pal/src/host/Linux-SGX/tools/pf_util.c new file mode 100644 index 00000000..2bac5bbd --- /dev/null +++ b/Pal/src/host/Linux-SGX/tools/pf_util.c @@ -0,0 +1,603 @@ +/* Copyright (C) 2018,2019 Invisible Things Lab + Rafal Wojdyla + + This file is part of Graphene Library OS. + + Graphene Library OS is free software: you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public License + as published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + Graphene Library OS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +#define _GNU_SOURCE + +#include +#include +#include + +#include +#include + +#include +#include + +#include "../protected_files.h" + +#include "pf_util.h" +#include "util.h" + +/* High-level protected files helper functions. */ + +/* PF callbacks usable in a standard Linux environment. + Assume that pf handle is a pointer to file's fd. */ + +void* linux_malloc(size_t size) { + void* address = malloc(size); + if (address) + memset(address, 0, size); + return address; +} + +int linux_prot(pf_file_mode_t mode) { + int prot = 0; + if (mode & PF_FILE_MODE_READ) + prot |= PROT_READ; + if (mode & PF_FILE_MODE_WRITE) + prot |= PROT_WRITE; + return prot; +} + +pf_status_t linux_map(pf_handle_t handle, pf_file_mode_t mode, size_t offset, size_t size, + void** address) { + int fd = *(int*)handle; + *address = mmap(NULL, size, linux_prot(mode), MAP_SHARED, fd, offset); + + if (*address == MAP_FAILED) { + ERROR("linux_map(%d, %d, %zu, %zu): %s\n", fd, mode, offset, size, strerror(errno)); + return PF_STATUS_CALLBACK_FAILED; + } + + return PF_STATUS_SUCCESS; +} + +pf_status_t linux_unmap(void* address, size_t size) { + int ret = munmap(address, size); + if (ret < 0) { + ERROR("linux_unmap(%p, %zu): %s\n", address, size, strerror(errno)); + return PF_STATUS_CALLBACK_FAILED; + } + + return PF_STATUS_SUCCESS; +} + +pf_status_t linux_truncate(pf_handle_t handle, size_t size) { + int fd = *(int*)handle; + int ret = ftruncate(fd, size); + if (ret < 0) { + ERROR("linux_truncate(%d, %zu): %s\n", fd, size, strerror(errno)); + return PF_STATUS_CALLBACK_FAILED; + } + + return PF_STATUS_SUCCESS; +} + +pf_status_t linux_flush(__attribute__((unused)) pf_handle_t handle) { + return PF_STATUS_NOT_IMPLEMENTED; +} + +void pf_set_linux_callbacks(pf_debug_f debug_f) { + pf_set_callbacks(linux_malloc, free, linux_map, linux_unmap, linux_truncate, linux_flush, + debug_f); +} + +/* Crypto callbacks for OpenSSL */ + +pf_status_t openssl_crypto_aes_gcm_encrypt(const uint8_t* key, size_t key_size, const uint8_t* iv, + size_t iv_size, const void* aad, size_t aad_size, + const void* input, size_t input_size, void* output, + uint8_t* mac, size_t mac_size) { + pf_status_t status = PF_STATUS_CALLBACK_FAILED; + if (iv_size != PF_IV_SIZE) + return PF_STATUS_INVALID_PARAMETER; + + if (key_size != PF_WRAP_KEY_SIZE) + return PF_STATUS_INVALID_PARAMETER; + + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + if (!ctx) + return PF_STATUS_NO_MEMORY; + + /* Choose cipher */ + EVP_EncryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL); + + /* Set IV length */ + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_size, NULL) != 1) { + ERROR("Failed to set AES IV len\n"); + goto out; + } + + /* Set key/iv */ + if (EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv) != 1) { + ERROR("Failed to set AES key/IV\n"); + goto out; + } + + int out_len; + if (aad) { + /* Additional data */ + if (EVP_EncryptUpdate(ctx, NULL, &out_len, aad, aad_size) != 1) { + ERROR("Failed to perform AES encryption for AAD\n"); + goto out; + } + } + + if (input) { + /* Actual data */ + if (EVP_EncryptUpdate(ctx, output, &out_len, input, input_size) != 1) { + ERROR("Failed to perform AES encryption\n"); + goto out; + } + } + + /* Final AES block, doesn't write anything in GCM mode but must be called for proper MAC + calculation */ + if (EVP_EncryptFinal(ctx, NULL, &out_len) != 1) { + ERROR("Failed to perform final AES round\n"); + goto out; + } + + /* Get MAC */ + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, mac_size, mac) != 1) { + ERROR("Failed to get AES MAC\n"); + goto out; + } + status = PF_STATUS_SUCCESS; +out: + EVP_CIPHER_CTX_free(ctx); + return status; +} + +pf_status_t openssl_crypto_aes_gcm_decrypt(const uint8_t* key, size_t key_size, const uint8_t* iv, + size_t iv_size, const void* aad, size_t aad_size, + const void* input, size_t input_size, void* output, + const uint8_t* mac, size_t mac_size) { + pf_status_t status = PF_STATUS_CALLBACK_FAILED; + if (iv_size != PF_IV_SIZE) + return PF_STATUS_INVALID_PARAMETER; + + if (key_size != PF_WRAP_KEY_SIZE) + return PF_STATUS_INVALID_PARAMETER; + + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + if (!ctx) + return PF_STATUS_NO_MEMORY; + + /* Choose cipher */ + EVP_DecryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL); + + /* Set IV length */ + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_size, NULL) != 1) { + ERROR("Failed to set AES IV len\n"); + goto out; + } + + /* Set key/iv */ + if (EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv) != 1) { + ERROR("Failed to set AES key/IV\n"); + goto out; + } + + int out_len; + if (aad) { + /* Additional data */ + if (EVP_DecryptUpdate(ctx, NULL, &out_len, aad, aad_size) != 1) { + ERROR("Failed to perform AES encryption for AAD\n"); + goto out; + } + } + + if (input) { + /* Actual data */ + if (EVP_DecryptUpdate(ctx, output, &out_len, input, input_size) != 1) { + ERROR("Failed to perform AES encryption\n"); + goto out; + } + } + + /* Set expected tag value, doesn't modify mac */ + if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, mac_size, (void*)mac)) { + ERROR("Failed to set expected MAC\n"); + goto out; + } + + /* Final AES block, validates MAC */ + if (EVP_DecryptFinal(ctx, NULL, &out_len) != 1) { + ERROR("Failed to validate decryption\n"); + goto out; + } + status = PF_STATUS_SUCCESS; +out: + EVP_CIPHER_CTX_free(ctx); + return status; +} + +pf_status_t openssl_crypto_random(uint8_t* buffer, size_t size) { + if (!RAND_bytes(buffer, size)) { + ERROR("Failed to get random bytes\n"); + return PF_STATUS_CALLBACK_FAILED; + } + return PF_STATUS_SUCCESS; +} + +void pf_set_openssl_crypto_callbacks() { + pf_set_crypto_callbacks(openssl_crypto_aes_gcm_encrypt, openssl_crypto_aes_gcm_decrypt, + openssl_crypto_random); +} + +/* Debug print callback for protected files */ +static void cb_debug(const char* msg) { + DBG("%s", msg); +} + +/* Initialize protected files for native environment */ +void pf_init() { + pf_set_linux_callbacks(cb_debug); + pf_set_openssl_crypto_callbacks(); +} + +/* Generate random PF key and save it to file */ +int pf_generate_wrap_key(const char* wrap_key_path) { + int ret; + uint8_t wrap_key[PF_WRAP_KEY_SIZE]; + + ret = read_file_part("/dev/urandom", wrap_key, sizeof(wrap_key)); + if (ret < 0) { + ERROR("Failed to read random bytes\n"); + goto out; + } + + if (write_file(wrap_key_path, sizeof(wrap_key), wrap_key) != 0) { + ERROR("Failed to save wrap key\n"); + goto out; + } + + INFO("Wrap key saved to: %s\n", wrap_key_path); + ret = 0; +out: + return ret; +} + +int load_wrap_key(const char* wrap_key_path, uint8_t wrap_key[PF_WRAP_KEY_SIZE]) { + int ret = -1; + ssize_t size = 0; + uint8_t* buf = read_file(wrap_key_path, &size); + + if (!buf) { + ERROR("Failed to read wrap key\n"); + goto out; + } + + if (size != PF_WRAP_KEY_SIZE) { + ERROR("Wrap key size %zu != %zu\n", size, (size_t)PF_WRAP_KEY_SIZE); + goto out; + } + + memcpy(wrap_key, buf, PF_WRAP_KEY_SIZE); + ret = 0; + +out: + free(buf); + return ret; +} + +/* Convert a single file to the protected format */ +int pf_encrypt_file(const char* input_path, const char* output_path, const char* file_name, + const char* prefix, uint8_t wrap_key[PF_WRAP_KEY_SIZE]) { + int ret = -1; + int input = -1; + int output = -1; + void* input_mem = MAP_FAILED; + ssize_t input_size = 0; + pf_context_t* pf = NULL; + size_t chunk_size; + + input = open(input_path, O_RDONLY); + if (input < 0) { + ERROR("Failed to open input file '%s': %s\n", input_path, strerror(errno)); + goto out; + } + + output = open(output_path, O_RDWR|O_CREAT, 0664); + if (output < 0) { + ERROR("Failed to create output file '%s': %s\n", output_path, strerror(errno)); + goto out; + } + + INFO("Processing: %s\n", input_path); + + pf_handle_t handle = (pf_handle_t) &output; + pf_status_t pfs = pf_create(handle, prefix, file_name, wrap_key, &pf); + if (PF_FAILURE(pfs)) { + ERROR("Failed to open output PF: %d\n", pfs); + goto out; + } + + /* Process file contents */ + input_size = get_file_size(input); + if (input_size == -1) { + ERROR("Failed to stat input file '%s': %s\n", input_path, strerror(errno)); + goto out; + } + + int64_t input_offset = 0; + + if (input_size > 0) { + input_mem = mmap(NULL, input_size, PROT_READ, MAP_PRIVATE, input, 0); + if (input_mem == MAP_FAILED) { + ERROR("Failed to mmap input file '%s': %s\n", input_path, strerror(errno)); + goto out; + } + + while (input_offset < input_size) { + chunk_size = input_size - input_offset; + if (chunk_size > PF_CHUNK_DATA_MAX) + chunk_size = PF_CHUNK_DATA_MAX; + pfs = pf_write(pf, input_offset, chunk_size, (uint8_t*)input_mem + input_offset); + if (PF_FAILURE(pfs)) { + ERROR("Failed to write to output PF: %d\n", pfs); + goto out; + } + + input_offset += chunk_size; + } + } + + ret = 0; + +out: + if (pf) + pf_close(pf); + if (input >= 0) + close(input); + if (output >= 0) + close(output); + if (input_mem != MAP_FAILED) + munmap(input_mem, input_size); + return ret; +} + +/* Convert a single file from the protected format */ +int pf_decrypt_file(const char* input_path, const char* output_path, bool verify_path, + uint8_t wrap_key[PF_WRAP_KEY_SIZE]) { + int ret = -1; + int input = -1; + int output = -1; + void* buffer = NULL; + pf_context_t* pf = NULL; + + input = open(input_path, O_RDONLY); + if (input < 0) { + ERROR("Failed to open input file '%s': %s\n", input_path, strerror(errno)); + goto out; + } + + output = open(output_path, O_RDWR|O_CREAT, 0664); + if (output < 0) { + ERROR("Failed to create output file '%s': %s\n", output_path, strerror(errno)); + goto out; + } + + INFO("Processing: %s\n", input_path); + + /* Get input file size */ + struct stat st; + if (fstat(input, &st) < 0) { + ERROR("Failed to stat input file '%s': %s\n", input_path, strerror(errno)); + goto out; + } + + pf_status_t pfs = pf_open((pf_handle_t) &input, st.st_size, PF_FILE_MODE_READ, wrap_key, &pf); + if (PF_FAILURE(pfs)) { + ERROR("Opening protected input file failed: %d\n", pfs); + goto out; + } + + if (verify_path) { + bool allowed; + pfs = pf_check_path(pf, input_path, &allowed); + if (!allowed || PF_FAILURE(pfs)) { + ERROR("Path '%s' doesn't match PF's allowed paths\n", input_path); + goto out; + } else { + DBG("Path '%s' is allowed\n", input_path); + } + } + + buffer = malloc(PF_CHUNK_DATA_MAX); + if (!buffer) { + ERROR("No memory\n"); + goto out; + } + + /* Process file contents */ + uint64_t input_size; + uint64_t input_offset = 0; + uint32_t chunk_data_size = PF_CHUNK_DATA_MAX; + + pfs = pf_get_size(pf, &input_size); + if (PF_FAILURE(pfs)) { + ERROR("pf_get_size failed: %d\n", pfs); + goto out; + } + + while (input_offset < input_size) { + if (input_size - input_offset < chunk_data_size) + chunk_data_size = input_size - input_offset; + + pfs = pf_read(pf, input_offset, chunk_data_size, buffer); + if (PF_FAILURE(pfs)) { + ERROR("Read from protected file failed (offset %" PRIu64 ", size %u): %d\n", + input_offset, chunk_data_size, pfs); + goto out; + } + + if (write(output, buffer, chunk_data_size) != chunk_data_size) { + ret = errno; + ERROR("Write to output file failed: %s\n", strerror(errno)); + goto out; + } + + input_offset += chunk_data_size; + } + + ret = 0; + +out: + free(buffer); + if (pf) + pf_close(pf); + if (input >= 0) + close(input); + if (output >= 0) + close(output); + return ret; +} + +enum processing_mode_t { + MODE_ENCRYPT = 1, + MODE_DECRYPT = 2, +}; + +static int process_files(const char* input_dir, const char* output_dir, const char* prefix, + const char* wrap_key_path, enum processing_mode_t mode, bool verify_path) { + int ret = -1; + uint8_t wrap_key[PF_WRAP_KEY_SIZE]; + struct stat st; + char* input_path = NULL; + char* output_path = NULL; + + if (mode != MODE_ENCRYPT && mode != MODE_DECRYPT) { + ERROR("Invalid mode: %d\n", mode); + goto out; + } + + ret = load_wrap_key(wrap_key_path, wrap_key); + if (ret != 0) + goto out; + + if (stat(input_dir, &st) != 0) { + ERROR("Failed to stat input path %s: %s\n", input_dir, strerror(errno)); + goto out; + } + + /* single file? */ + if (S_ISREG(st.st_mode)) { + if (mode == MODE_ENCRYPT) + return pf_encrypt_file(input_dir, output_dir, basename(output_dir), prefix, wrap_key); + else + return pf_decrypt_file(input_dir, output_dir, verify_path, wrap_key); + } + + ret = mkdir(output_dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + if (ret != 0 && errno != EEXIST) { + ERROR("Failed to create directory %s: %s\n", output_dir, strerror(errno)); + goto out; + } + + /* Process input directory */ + struct dirent* dir; + DIR* dfd = opendir(input_dir); + if (!dfd) { + ERROR("Failed to open input directory: %s\n", strerror(errno)); + goto out; + } + + size_t input_path_size, output_path_size; + while ((dir = readdir(dfd)) != NULL) { + if (!strcmp(dir->d_name, ".")) + continue; + if (!strcmp(dir->d_name, "..")) + continue; + + input_path_size = strlen(input_dir) + 1 + strlen(dir->d_name) + 1; + output_path_size = strlen(output_dir) + 1 + strlen(dir->d_name) + 1; + + input_path = malloc(input_path_size); + if (!input_path) { + ERROR("No memory\n"); + goto out; + } + + output_path = malloc(output_path_size); + if (!output_path) { + ERROR("No memory\n"); + goto out; + } + + snprintf(input_path, input_path_size, "%s/%s", input_dir, dir->d_name); + snprintf(output_path, output_path_size, "%s/%s", output_dir, dir->d_name); + + if (stat(input_path, &st) != 0) { + ERROR("Failed to stat input file %s: %s\n", input_path, strerror(errno)); + goto out; + } + + if (S_ISREG(st.st_mode)) { + if (mode == MODE_ENCRYPT) + ret = pf_encrypt_file(input_path, output_path, dir->d_name, prefix, wrap_key); + else + ret = pf_decrypt_file(input_path, output_path, verify_path, wrap_key); + + if (ret != 0) + goto out; + } else if (S_ISDIR(st.st_mode)) { + /* process directory recursively */ + size_t prefix_size = strlen(prefix) + 1 + strlen(dir->d_name) + 1; + char* prefix_path = malloc(prefix_size); + if (!prefix_path) { + ERROR("No memory\n"); + goto out; + } + + snprintf(prefix_path, prefix_size, "%s/%s", prefix, dir->d_name); + ret = process_files(input_path, output_path, prefix_path, wrap_key_path, mode, + verify_path); + + free(prefix_path); + if (ret != 0) + goto out; + } else { + INFO("Skipping non-regular file %s\n", input_path); + } + + free(input_path); + input_path = NULL; + free(output_path); + output_path = NULL; + } + ret = 0; + +out: + free(input_path); + free(output_path); + return ret; +} + +/* Convert a file or directory (recursively) to the protected format */ +int pf_encrypt_files(const char* input_dir, const char* output_dir, const char* prefix, + const char* wrap_key_path) { + return process_files(input_dir, output_dir, prefix, wrap_key_path, MODE_ENCRYPT, false); +} + +/* Convert a file or directory (recursively) from the protected format */ +int pf_decrypt_files(const char* input_dir, const char* output_dir, bool verify_path, + const char* wrap_key_path) { + return process_files(input_dir, output_dir, NULL, wrap_key_path, MODE_DECRYPT, verify_path); +} diff --git a/Pal/src/host/Linux-SGX/tools/pf_util.h b/Pal/src/host/Linux-SGX/tools/pf_util.h new file mode 100644 index 00000000..510d572b --- /dev/null +++ b/Pal/src/host/Linux-SGX/tools/pf_util.h @@ -0,0 +1,64 @@ +/* Copyright (C) 2018,2019 Invisible Things Lab + Rafal Wojdyla + + This file is part of Graphene Library OS. + + Graphene Library OS is free software: you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public License + as published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + Graphene Library OS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +#ifndef PF_UTIL_H +#define PF_UTIL_H + +#include +#include "../protected_files.h" + +/* High-level protected files helper functions */ + +/*! Initialize protected files for native environment */ +void pf_init(); + +/*! Generate random PF key and save it to file */ +int pf_generate_wrap_key(const char* wrap_key_path); + +/*! Convert a single file to the protected format */ +int pf_encrypt_file(const char* input_path, const char* output_path, const char* file_name, + const char* prefix, uint8_t wrap_key[PF_WRAP_KEY_SIZE]); + +/*! Convert a single file from the protected format */ +int pf_decrypt_file(const char* input_path, const char* output_path, bool verify_path, + uint8_t wrap_key[PF_WRAP_KEY_SIZE]); + +/*! Convert a file or directory (recursively) to the protected format */ +int pf_encrypt_files(const char* input_dir, const char* output_dir, const char* prefix, + const char* wrap_key_path); + +/*! Convert a file or directory (recursively) from the protected format */ +int pf_decrypt_files(const char* input_dir, const char* output_dir, bool verify_path, + const char* wrap_key_path); + +/*! AES-GCM encrypt */ +pf_status_t openssl_crypto_aes_gcm_encrypt(const uint8_t* key, size_t key_size, const uint8_t* iv, + size_t iv_size, const void* aad, size_t aad_size, + const void* input, size_t input_size, void* output, + uint8_t* mac, size_t mac_size); + +/*! AES-GCM decrypt */ +pf_status_t openssl_crypto_aes_gcm_decrypt(const uint8_t* key, size_t key_size, const uint8_t* iv, + size_t iv_size, const void* aad, size_t aad_size, + const void* input, size_t input_size, void* output, + const uint8_t* mac, size_t mac_size); + +/*! Load PF wrap key from file */ +int load_wrap_key(const char* wrap_key_path, uint8_t wrap_key[PF_WRAP_KEY_SIZE]); + +#endif diff --git a/Pal/src/host/Linux-SGX/tools/util.c b/Pal/src/host/Linux-SGX/tools/util.c new file mode 100644 index 00000000..d06c5d3a --- /dev/null +++ b/Pal/src/host/Linux-SGX/tools/util.c @@ -0,0 +1,176 @@ +/* Copyright (C) 2018,2019 Invisible Things Lab + Rafal Wojdyla + + This file is part of Graphene Library OS. + + Graphene Library OS is free software: you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public License + as published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + Graphene Library OS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +#include +#include +#include "util.h" + +/*! Console stdout fd */ +int g_stdout_fd = 1; + +/*! Console stderr fd */ +int g_stderr_fd = 2; + +/*! Verbosity level */ +bool g_verbose = false; + +void set_verbose(bool verbose) { + g_verbose = verbose; + if (verbose) + DBG("Verbose output enabled\n"); + else + DBG("Verbose output disabled\n"); +} + +bool get_verbose() { + return g_verbose; +} + +/* return -1 on error */ +ssize_t get_file_size(int fd) { + struct stat st; + + if (fstat(fd, &st) != 0) + return -1; + + return st.st_size; +} + +/* Read `size` bytes from the file */ +int read_file_part(const char* path, uint8_t* buffer, size_t size) { + FILE* f = NULL; + int ret = -1; + + f = fopen(path, "rb"); + if (!f) { + ERROR("Failed to open file '%s' for reading: %s\n", path, strerror(errno)); + goto out; + } + + if (fread(buffer, size, 1, f) != 1) { + ERROR("Failed to read file '%s'\n", path); + goto out; + } + ret = 0; + +out: + if (f) + fclose(f); + return ret; +} + +/* Read whole file, caller should free the buffer */ +uint8_t* read_file(const char* path, ssize_t* size) { + FILE* f = NULL; + uint8_t* buf = NULL; + + f = fopen(path, "rb"); + if (!f) { + ERROR("Failed to open file '%s' for reading: %s\n", path, strerror(errno)); + goto out; + } + + *size = get_file_size(fileno(f)); + if (*size == -1) { + ERROR("Failed to get size of file '%s': %s\n", path, strerror(errno)); + goto out; + } + + buf = (uint8_t*)malloc(*size); + if (!buf) { + ERROR("No memory\n"); + goto out; + } + + if (fread(buf, *size, 1, f) != 1) { + ERROR("Failed to read file '%s'\n", path); + goto err; + } + +out: + if (f) + fclose(f); + return buf; + +err: + if (f) + fclose(f); + free(buf); + return NULL; +} + +static int write_file_internal(const char* path, size_t size, const void* buffer, bool append) { + FILE* f = NULL; + int status; + + if (append) + f = fopen(path, "ab"); + else + f = fopen(path, "wb"); + + if (!f) { + ERROR("Failed to open file '%s' for writing: %s\n", path, strerror(errno)); + goto out; + } + + if (size > 0 && buffer) { + if (fwrite(buffer, size, 1, f) != 1) { + ERROR("Failed to write file '%s': %s\n", path, strerror(errno)); + goto out; + } + } + + errno = 0; + +out: + status = errno; + if (f) + fclose(f); + return status; +} + +/* Write buffer to file */ +int write_file(const char* path, size_t size, const void* buffer) { + return write_file_internal(path, size, buffer, false); +} + +/* Append buffer to file */ +int append_file(const char* path, size_t size, const void* buffer) { + return write_file_internal(path, size, buffer, true); +} + +/* Set stdout/stderr descriptors */ +void util_set_fd(int stdout_fd, int stderr_fd) { + g_stdout_fd = stdout_fd; + g_stderr_fd = stderr_fd; +} + +/* Print memory as hex */ +void hexdump_mem(void* data, size_t size) { + size_t i; + uint8_t* ptr = (uint8_t*)data; + + for (i = 0; i < size; i++) + INFO("%02x", ptr[i]); + INFO("\n"); +} + +/* Fill memory buffer with zeros */ +void zero_memory(void* buffer, size_t size) { + memset(buffer, 0, size); +} diff --git a/Pal/src/host/Linux-SGX/tools/util.h b/Pal/src/host/Linux-SGX/tools/util.h new file mode 100644 index 00000000..f4af5a42 --- /dev/null +++ b/Pal/src/host/Linux-SGX/tools/util.h @@ -0,0 +1,71 @@ +/* Copyright (C) 2018,2019 Invisible Things Lab + Rafal Wojdyla + + This file is part of Graphene Library OS. + + Graphene Library OS is free software: you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public License + as published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + Graphene Library OS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +#ifndef UTIL_H +#define UTIL_H + +#include +#include +#include +#include +#include + +/* Miscellaneous helper functions */ + +extern int g_stdout_fd; +extern int g_stderr_fd; +extern bool g_verbose; + +/* Print functions */ +#define DBG(fmt, ...) do { if (g_verbose) dprintf(g_stdout_fd, fmt, ##__VA_ARGS__); } while (0) +#define INFO(fmt, ...) do { dprintf(g_stdout_fd, fmt, ##__VA_ARGS__); } while (0) +#define ERROR(fmt, ...) do { dprintf(g_stderr_fd, "%s: " fmt, __FUNCTION__, ##__VA_ARGS__); } while (0) + +/*! Set verbosity level */ +void set_verbose(bool verbose); + +/*! Get verbosity level */ +bool get_verbose(); + +/*! Set stdout/stderr descriptors */ +void util_set_fd(int stdout_fd, int stderr_fd); + +/*! Get file size, return -1 on error */ +ssize_t get_file_size(int fd); + +/*! Read whole file, caller should free the buffer */ +uint8_t* read_file(const char* path, ssize_t* size); + +/*! Read size bytes from the file */ +int read_file_part(const char* path, uint8_t* buffer, size_t size); + +/*! Write buffer to file */ +int write_file(const char* path, size_t size, const void* buffer); + +/*! Append buffer to file */ +int append_file(const char* path, size_t size, const void* buffer); + +/*! Print memory as hex */ +void hexdump_mem(void* data, size_t size); + +#define HEXDUMP(x) hexdump_mem((void*)&(x), sizeof(x)) + +/*! Fill memory buffer with zeros */ +void zero_memory(void* buffer, size_t size); + +#endif diff --git a/Pal/src/host/Linux/pal_linux_error.h b/Pal/src/host/Linux/pal_linux_error.h index fb6b86b6..5bbd943f 100644 --- a/Pal/src/host/Linux/pal_linux_error.h +++ b/Pal/src/host/Linux/pal_linux_error.h @@ -8,6 +8,8 @@ static inline __attribute__((unused)) int unix_to_pal_error(int unix_errno) { switch (unix_errno) { + case 0: + return 0; case ENOENT: return -PAL_ERROR_STREAMNOTEXIST; case EINTR: diff --git a/Scripts/regression.py b/Scripts/regression.py index f0f39648..0e037482 100644 --- a/Scripts/regression.py +++ b/Scripts/regression.py @@ -48,6 +48,26 @@ class RegressionTestCase(unittest.TestCase): return stdout.decode(), stderr.decode() + def run_native_binary(self, args, *, timeout=None, **kwds): + timeout = (max(self.DEFAULT_TIMEOUT, timeout) if timeout is not None + else self.DEFAULT_TIMEOUT) + + with subprocess.Popen([*args], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + preexec_fn=os.setpgrp, + **kwds) as process: + try: + stdout, stderr = process.communicate(timeout=timeout) + except subprocess.TimeoutExpired: + os.killpg(process.pid, signal.SIGKILL) + self.fail('timeout ({} s) expired'.format(timeout)) + + if process.returncode: + raise subprocess.CalledProcessError( + process.returncode, args, stdout, stderr) + + return stdout.decode(), stderr.decode() + @contextlib.contextmanager def expect_returncode(self, returncode): if returncode == 0: