[Linux-SGX] Add protected files implementation

Protected files (PF) are a new type of file that can be specified in
the manifest (SGX only). They are encrypted on disk and transparently
decrypted when accessed by the Graphene payload.

Other features:
- data is integrity protected (tamper resistance)
- file swap protection (a PF can only be accessed when in a specific path)
- transparency (Graphene payload sees PFs as regular files, no need to modify
  the payload)

See Linux-SGX/protected-files.h for more specific information.

The following new manifest elements are added:

sgx.protected_files_key = <16-byte hex value>
sgx.protected_files.<name> = file:<host path>

sgx.protected_files_key specifies the encryption key and is only a temporary
implementation. This key should be provisioned with local/remote attestation
in the future.

Paths specifying PF entries can be files or directories. If a directory is
specified, all files/directories within are registered as protected
recursively (and are expected to be encrypted in the PF format).

Linux-SGX/tools directory contains the pf_crypt utility that converts files
to/from the protected format.

This proof-of-concept implementation without focus on performance shows
roughly a 20% slowdown compared to normal files (based on the LibOS FS tests).
This commit is contained in:
Rafał Wojdyła
2020-01-08 16:08:32 +01:00
parent f8f6a0c42f
commit 9a685898af
29 changed files with 4005 additions and 37 deletions
+8
View File
@@ -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 \
+1
View File
@@ -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 \
+11 -2
View File
@@ -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
+2 -1
View File
@@ -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.
+4
View File
@@ -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
+173
View File
@@ -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()
+1 -1
View File
@@ -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
+65
View File
@@ -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;
+1
View File
@@ -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
+14
View File
@@ -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'. */
+12 -4
View File
@@ -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:
+306 -28
View File
@@ -21,10 +21,7 @@
* "file:" or "dir:".
*/
#include <linux/types.h>
#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 <asm/stat.h>
#include <linux/fs.h>
#include <linux/stat.h>
#include <linux/types.h>
#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;
}
+6
View File
@@ -30,6 +30,7 @@
#include "pal_error.h"
#include "pal_security.h"
#include "api.h"
#include "protected_files.h"
#include <asm/mman.h>
#include <asm/ioctls.h>
@@ -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",
@@ -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;
+557
View File
@@ -0,0 +1,557 @@
/* Copyright (C) 2018,2019 Invisible Things Lab
Rafal Wojdyla <omeg@invisiblethingslab.com>
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 <http://www.gnu.org/licenses/>. */
#include <pal_linux.h>
#include <pal_linux_error.h>
#include <pal_internal.h>
#include <pal_crypto.h>
/*
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;
}
+54
View File
@@ -28,6 +28,7 @@
#include "sgx_api.h"
#include "sgx_attest.h"
#include "enclave_ocalls.h"
#include "protected_files.h"
#include <linux/mman.h>
@@ -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);
+2
View File
@@ -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:
+809
View File
@@ -0,0 +1,809 @@
/* Copyright (C) 2018,2019 Invisible Things Lab
Rafal Wojdyla <omeg@invisiblethingslab.com>
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 <http://www.gnu.org/licenses/>. */
#include <string.h>
#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;
}
+434
View File
@@ -0,0 +1,434 @@
/* Copyright (C) 2018,2019 Invisible Things Lab
Rafal Wojdyla <omeg@invisiblethingslab.com>
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 <http://www.gnu.org/licenses/>. */
#ifndef PROTECTED_FILES_H
#define PROTECTED_FILES_H
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/*
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 */
+2
View File
@@ -0,0 +1,2 @@
pf_crypt
pf_tamper
+40
View File
@@ -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)
+147
View File
@@ -0,0 +1,147 @@
/* Copyright (C) 2018,2019 Invisible Things Lab
Rafal Wojdyla <omeg@invisiblethingslab.com>
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 <http://www.gnu.org/licenses/>. */
#include <getopt.h>
#include <stdlib.h>
#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;
}
+420
View File
@@ -0,0 +1,420 @@
/* Copyright (C) 2019 Invisible Things Lab
Rafal Wojdyla <omeg@invisiblethingslab.com>
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 <http://www.gnu.org/licenses/>. */
#include <fcntl.h>
#include <getopt.h>
#include <libgen.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#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;
}
+603
View File
@@ -0,0 +1,603 @@
/* Copyright (C) 2018,2019 Invisible Things Lab
Rafal Wojdyla <omeg@invisiblethingslab.com>
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 <http://www.gnu.org/licenses/>. */
#define _GNU_SOURCE
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <openssl/pem.h>
#include <openssl/rand.h>
#include <sys/mman.h>
#include <sys/stat.h>
#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);
}
+64
View File
@@ -0,0 +1,64 @@
/* Copyright (C) 2018,2019 Invisible Things Lab
Rafal Wojdyla <omeg@invisiblethingslab.com>
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 <http://www.gnu.org/licenses/>. */
#ifndef PF_UTIL_H
#define PF_UTIL_H
#include <stdint.h>
#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
+176
View File
@@ -0,0 +1,176 @@
/* Copyright (C) 2018,2019 Invisible Things Lab
Rafal Wojdyla <omeg@invisiblethingslab.com>
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 <http://www.gnu.org/licenses/>. */
#include <stdlib.h>
#include <sys/stat.h>
#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);
}
+71
View File
@@ -0,0 +1,71 @@
/* Copyright (C) 2018,2019 Invisible Things Lab
Rafal Wojdyla <omeg@invisiblethingslab.com>
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 <http://www.gnu.org/licenses/>. */
#ifndef UTIL_H
#define UTIL_H
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
/* 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
+2
View File
@@ -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:
+20
View File
@@ -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: