mirror of
https://github.com/clearlinux/graphene.git
synced 2026-09-05 21:31:36 +00:00
[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:
@@ -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 \
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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'. */
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -0,0 +1,2 @@
|
||||
pf_crypt
|
||||
pf_tamper
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user