[Pal/Linux-SGX] Test malicious modifications to protected files

This commit adds a new PF utility `pf_tamper` that tampers with
valid protected files and uses this utility to test that the PF
logic in Linux-SGX detects such malicious modifications.

This commit also moves out the PF-format macros and structs from
`protected_files_internal.h` to `protected_files_format.h` for
better readability.

Co-authored-by: Dmitrii Kuvaiskii <dmitrii.kuvaiskii@intel.com>
This commit is contained in:
Rafał Wojdyła
2021-01-04 21:13:03 +01:00
committed by Michał Kowalczyk
co-authored by Dmitrii Kuvaiskii
parent 897429d5f2
commit 345d271e66
8 changed files with 658 additions and 139 deletions
+7 -15
View File
@@ -193,24 +193,20 @@ class TC_50_ProtectedFiles(TC_00_FileSystem):
def __corrupt_file(self, input_path, output_path):
cmd = [self.PF_TAMPER, '-w', self.WRAP_KEY, '-i', input_path, '-o', output_path]
return self.run_native_binary(cmd)
return self.run_native_binary(cmd, libpath=os.path.join(os.getcwd(), 'lib'))
# invalid/corrupted files
@expectedFailureIf(HAS_SGX)
# pylint: disable=fixme
def test_500_invalid(self):
# TODO: port these to the new file format
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]
self.__encrypt_file(self.INPUT_FILES[-1], original_input)
# 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)
@@ -218,17 +214,13 @@ class TC_50_ProtectedFiles(TC_00_FileSystem):
input_path = 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_path)
should_pass = any(s in name for s in should_pass)
try:
args = ['decrypt', '-V', '-w', self.WRAP_KEY, '-i', input_path, '-o', output_path]
self.__pf_crypt(args)
except subprocess.CalledProcessError as exc:
if should_pass:
self.assertEqual(exc.returncode, 0)
else:
self.assertNotEqual(exc.returncode, 0)
# decryption of invalid file must fail with -1 (wrapped to 255)
self.assertEqual(exc.returncode, 255)
else:
if not should_pass:
print('[!] Fail: successfully decrypted file: ' + name)
self.fail()
print('[!] Fail: successfully decrypted file: ' + name)
self.fail()
@@ -5,6 +5,8 @@
*/
#include "api.h"
#include "protected_files.h"
#include "protected_files_format.h"
#include "protected_files_internal.h"
#ifndef IN_PAL
@@ -65,20 +67,6 @@ static pf_random_f g_cb_random = NULL;
static pf_iv_t g_empty_iv = {0};
static bool g_initialized = false;
#define METADATA_KEY_NAME "SGX-PROTECTED-FS-METADATA-KEY"
#define MAX_LABEL_SIZE 64
static_assert(sizeof(METADATA_KEY_NAME) <= MAX_LABEL_SIZE, "label too long");
#pragma pack(push, 1)
typedef struct {
uint32_t index;
char label[MAX_LABEL_SIZE]; // must be NULL terminated
pf_keyid_t nonce;
uint32_t output_len; // in bits
} kdf_input_t;
#pragma pack(pop)
// The key derivation function follow recommendations from NIST Special Publication 800-108:
// Recommendation for Key Derivation Using Pseudorandom Functions
// https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf
@@ -0,0 +1,139 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* Copyright (C) 2019-2020 Invisible Things Lab
* Rafal Wojdyla <omeg@invisiblethingslab.com>
* Copyright (C) 2011-2020 Intel Corporation
*/
#ifndef PROTECTED_FILES_FORMAT_H_
#define PROTECTED_FILES_FORMAT_H_
#include <limits.h>
#include "assert.h"
#include "list.h"
#include "protected_files.h"
#define PF_FILE_ID 0x46505f4850415247 /* GRAPH_PF */
#define PF_MAJOR_VERSION 0x01
#define PF_MINOR_VERSION 0x00
#define METADATA_KEY_NAME "SGX-PROTECTED-FS-METADATA-KEY"
#define MAX_LABEL_SIZE 64
static_assert(sizeof(METADATA_KEY_NAME) <= MAX_LABEL_SIZE, "label too long");
#pragma pack(push, 1)
typedef struct _metadata_plain {
uint64_t file_id;
uint8_t major_version;
uint8_t minor_version;
pf_keyid_t metadata_key_id;
pf_mac_t metadata_gmac; /* GCM mac */
} metadata_plain_t;
#define PATH_MAX_SIZE (260 + 512)
// these are all defined as relative to node size, so we can decrease node size in tests
// and have deeper tree
#define MD_USER_DATA_SIZE (PF_NODE_SIZE * 3 / 4) // 3072
static_assert(MD_USER_DATA_SIZE == 3072, "bad struct size");
typedef struct _metadata_encrypted {
char path[PATH_MAX_SIZE];
uint64_t size;
pf_key_t mht_key;
pf_mac_t mht_gmac;
uint8_t data[MD_USER_DATA_SIZE];
} metadata_encrypted_t;
typedef uint8_t metadata_encrypted_blob_t[sizeof(metadata_encrypted_t)];
#define METADATA_NODE_SIZE PF_NODE_SIZE
typedef uint8_t metadata_padding_t[METADATA_NODE_SIZE -
(sizeof(metadata_plain_t) + sizeof(metadata_encrypted_blob_t))];
typedef struct _metadata_node {
metadata_plain_t plain_part;
metadata_encrypted_blob_t encrypted_part;
metadata_padding_t padding;
} metadata_node_t;
static_assert(sizeof(metadata_node_t) == PF_NODE_SIZE, "sizeof(metadata_node_t)");
typedef struct _data_node_crypto {
pf_key_t key;
pf_mac_t gmac;
} gcm_crypto_data_t;
// for PF_NODE_SIZE == 4096, we have 96 attached data nodes and 32 mht child nodes
// for PF_NODE_SIZE == 2048, we have 48 attached data nodes and 16 mht child nodes
// for PF_NODE_SIZE == 1024, we have 24 attached data nodes and 8 mht child nodes
// 3/4 of the node size is dedicated to data nodes
#define ATTACHED_DATA_NODES_COUNT ((PF_NODE_SIZE / sizeof(gcm_crypto_data_t)) * 3 / 4)
static_assert(ATTACHED_DATA_NODES_COUNT == 96, "ATTACHED_DATA_NODES_COUNT");
// 1/4 of the node size is dedicated to child mht nodes
#define CHILD_MHT_NODES_COUNT ((PF_NODE_SIZE / sizeof(gcm_crypto_data_t)) * 1 / 4)
static_assert(CHILD_MHT_NODES_COUNT == 32, "CHILD_MHT_NODES_COUNT");
typedef struct _mht_node {
gcm_crypto_data_t data_nodes_crypto[ATTACHED_DATA_NODES_COUNT];
gcm_crypto_data_t mht_nodes_crypto[CHILD_MHT_NODES_COUNT];
} mht_node_t;
static_assert(sizeof(mht_node_t) == PF_NODE_SIZE, "sizeof(mht_node_t)");
typedef struct _data_node {
uint8_t data[PF_NODE_SIZE];
} data_node_t;
static_assert(sizeof(data_node_t) == PF_NODE_SIZE, "sizeof(data_node_t)");
typedef struct _encrypted_node {
uint8_t cipher[PF_NODE_SIZE];
} encrypted_node_t;
static_assert(sizeof(encrypted_node_t) == PF_NODE_SIZE, "sizeof(encrypted_node_t)");
#define MAX_PAGES_IN_CACHE 48
typedef enum {
FILE_MHT_NODE_TYPE = 1,
FILE_DATA_NODE_TYPE = 2,
} mht_node_type_e;
// make sure these are the same size
static_assert(sizeof(mht_node_t) == sizeof(data_node_t),
"sizeof(mht_node_t) == sizeof(data_node_t)");
DEFINE_LIST(_file_node);
typedef struct _file_node {
LIST_TYPE(_file_node) list;
uint8_t type;
uint64_t node_number;
struct _file_node* parent;
bool need_writing;
bool new_node;
struct {
uint64_t physical_node_number;
encrypted_node_t encrypted; // the actual data from the disk
};
union { // decrypted data
mht_node_t mht;
data_node_t data;
} decrypted;
} file_node_t;
DEFINE_LISTP(_file_node);
typedef struct {
uint32_t index;
char label[MAX_LABEL_SIZE]; // must be NULL terminated
pf_keyid_t nonce;
uint32_t output_len; // in bits
} kdf_input_t;
#pragma pack(pop)
#endif /* PROTECTED_FILES_FORMAT_H_ */
@@ -13,116 +13,7 @@
#include "list.h"
#include "lru_cache.h"
#include "protected_files.h"
#define PF_FILE_ID 0x46505f4850415247 /* GRAPH_PF */
#define PF_MAJOR_VERSION 0x01
#define PF_MINOR_VERSION 0x00
#pragma pack(push, 1)
typedef struct _metadata_plain {
uint64_t file_id;
uint8_t major_version;
uint8_t minor_version;
pf_keyid_t metadata_key_id;
pf_mac_t metadata_gmac; /* GCM mac */
} metadata_plain_t;
#define PATH_MAX_SIZE (260 + 512)
// these are all defined as relative to node size, so we can decrease node size in tests
// and have deeper tree
#define MD_USER_DATA_SIZE (PF_NODE_SIZE * 3 / 4) // 3072
static_assert(MD_USER_DATA_SIZE == 3072, "bad struct size");
typedef struct _metadata_encrypted {
char path[PATH_MAX_SIZE];
uint64_t size;
pf_key_t mht_key;
pf_mac_t mht_gmac;
uint8_t data[MD_USER_DATA_SIZE];
} metadata_encrypted_t;
typedef uint8_t metadata_encrypted_blob_t[sizeof(metadata_encrypted_t)];
#define METADATA_NODE_SIZE PF_NODE_SIZE
typedef uint8_t metadata_padding_t[METADATA_NODE_SIZE -
(sizeof(metadata_plain_t) + sizeof(metadata_encrypted_blob_t))];
typedef struct _metadata_node {
metadata_plain_t plain_part;
metadata_encrypted_blob_t encrypted_part;
metadata_padding_t padding;
} metadata_node_t;
static_assert(sizeof(metadata_node_t) == PF_NODE_SIZE, "sizeof(metadata_node_t)");
typedef struct _data_node_crypto {
pf_key_t key;
pf_mac_t gmac;
} gcm_crypto_data_t;
// for PF_NODE_SIZE == 4096, we have 96 attached data nodes and 32 mht child nodes
// for PF_NODE_SIZE == 2048, we have 48 attached data nodes and 16 mht child nodes
// for PF_NODE_SIZE == 1024, we have 24 attached data nodes and 8 mht child nodes
// 3/4 of the node size is dedicated to data nodes
#define ATTACHED_DATA_NODES_COUNT ((PF_NODE_SIZE / sizeof(gcm_crypto_data_t)) * 3 / 4)
static_assert(ATTACHED_DATA_NODES_COUNT == 96, "ATTACHED_DATA_NODES_COUNT");
// 1/4 of the node size is dedicated to child mht nodes
#define CHILD_MHT_NODES_COUNT ((PF_NODE_SIZE / sizeof(gcm_crypto_data_t)) * 1 / 4)
static_assert(CHILD_MHT_NODES_COUNT == 32, "CHILD_MHT_NODES_COUNT");
typedef struct _mht_node {
gcm_crypto_data_t data_nodes_crypto[ATTACHED_DATA_NODES_COUNT];
gcm_crypto_data_t mht_nodes_crypto[CHILD_MHT_NODES_COUNT];
} mht_node_t;
static_assert(sizeof(mht_node_t) == PF_NODE_SIZE, "sizeof(mht_node_t)");
typedef struct _data_node {
uint8_t data[PF_NODE_SIZE];
} data_node_t;
static_assert(sizeof(data_node_t) == PF_NODE_SIZE, "sizeof(data_node_t)");
typedef struct _encrypted_node {
uint8_t cipher[PF_NODE_SIZE];
} encrypted_node_t;
static_assert(sizeof(encrypted_node_t) == PF_NODE_SIZE, "sizeof(encrypted_node_t)");
#define MAX_PAGES_IN_CACHE 48
typedef enum {
FILE_MHT_NODE_TYPE = 1,
FILE_DATA_NODE_TYPE = 2,
} mht_node_type_e;
// make sure these are the same size
static_assert(sizeof(mht_node_t) == sizeof(data_node_t),
"sizeof(mht_node_t) == sizeof(data_node_t)");
DEFINE_LIST(_file_node);
typedef struct _file_node {
LIST_TYPE(_file_node) list;
uint8_t type;
uint64_t node_number;
struct _file_node* parent;
bool need_writing;
bool new_node;
struct {
uint64_t physical_node_number;
encrypted_node_t encrypted; // the actual data from the disk
};
union { // decrypted data
mht_node_t mht;
data_node_t data;
} decrypted;
} file_node_t;
DEFINE_LISTP(_file_node);
#pragma pack(pop)
#include "protected_files_format.h"
struct pf_context {
metadata_node_t file_metadata; // actual data from disk's meta data node
+1
View File
@@ -12,3 +12,4 @@ $(targets):
$(MAKE) -C verify-ias-report $@
$(MAKE) -C ra-tls $@
$(MAKE) -C pf_crypt $@
$(MAKE) -C pf_tamper $@
@@ -0,0 +1 @@
/pf_tamper
@@ -0,0 +1,31 @@
include ../../../../../../Scripts/Makefile.configs
include ../../../../../../Scripts/Makefile.rules
CFLAGS += -I../.. \
-I../common \
-I../../protected-files \
-I../../../../../include/lib \
-D_GNU_SOURCE
LDLIBS += -L../common \
-L../../../../../lib/crypto/mbedtls/install/lib \
-lsgx_util -lmbedcrypto
PREFIX ?= /usr/local
pf_tamper: pf_tamper.o
$(call cmd,csingle)
.PHONY: all
all: pf_tamper
.PHONY: install
install:
install -D pf_tamper -t ${PREFIX}/bin
.PHONY: clean
clean:
$(RM) *.o pf_tamper
.PHONY: distclean
distclean: clean
@@ -0,0 +1,476 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* Copyright (C) 2019-2020 Invisible Things Lab
* Rafal Wojdyla <omeg@invisiblethingslab.com>
*/
#include <fcntl.h>
#include <getopt.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include "pf_util.h"
#include "protected_files.h"
#include "protected_files_format.h"
#include "util.h"
/* Tamper with a PF in various ways for testing purposes. The PF is assumed to be valid and have at
* least enough data to contain two MHT nodes. */
/* 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 }
};
static void usage(void) {
INFO("\nUsage: pf_tamper [options]\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");
}
#define FATAL(fmt, ...) do { \
ERROR(fmt, ##__VA_ARGS__); \
exit(-1); \
} while (0)
ssize_t g_input_size = 0;
char* g_input_name = NULL;
void* g_input_data = MAP_FAILED;
char* g_output_dir = NULL;
char* g_output_path = NULL;
size_t g_output_path_size = 0;
pf_key_t g_wrap_key;
pf_key_t g_meta_key;
static pf_iv_t g_empty_iv = {0};
static void derive_main_key(const pf_key_t* kdk, const pf_keyid_t* key_id, pf_key_t* out_key) {
kdf_input_t buf = {0};
pf_status_t status;
buf.index = 1;
strncpy(buf.label, METADATA_KEY_NAME, MAX_LABEL_SIZE);
memcpy(&buf.nonce, key_id, sizeof(buf.nonce));
buf.output_len = 0x80;
status = mbedtls_aes_gcm_encrypt(kdk, &g_empty_iv, &buf, sizeof(buf), NULL, 0, NULL, out_key);
if (PF_FAILURE(status))
FATAL("key derivation failed\n");
}
static void make_output_path(const char* suffix) {
snprintf(g_output_path, g_output_path_size, "%s/%s.%s", g_output_dir, g_input_name, suffix);
INFO("[*] %s\n", g_output_path);
}
/* PF layout (node size is PF_NODE_SIZE):
* - Node 0: metadata (metadata_node_t)
* - metadata_plain_t
* - metadata_encrypted_t (may include MD_USER_DATA_SIZE bytes of data)
* - metadata_padding_t
* - Node 1: MHT (mht_node_t)
* - Node 2-97: data (ATTACHED_DATA_NODES_COUNT == 96)
* - Node 98: MHT
* - Node 99-195: data
* - ...
*/
static void truncate_file(const char* suffix, size_t output_size) {
int ret;
make_output_path(suffix);
if (output_size < g_input_size) {
ret = write_file(g_output_path, output_size, g_input_data);
} else {
ret = write_file(g_output_path, g_input_size, g_input_data);
if (ret < 0)
goto out;
ret = truncate(g_output_path, output_size);
}
out:
if (ret < 0)
FATAL("truncate_file failed: %d\n", ret);
}
#define FIELD_SIZEOF(t, f) (sizeof(((t*)0)->f))
#define FIELD_TRUNCATED(t, f) (offsetof(t, f) + (FIELD_SIZEOF(t, f) / 2))
#define DATA_CRYPTO_SIZE (FIELD_SIZEOF(mht_node_t, data_nodes_crypto))
static void tamper_truncate(void) {
size_t mdps = sizeof(metadata_plain_t);
DBG("size(metadata_plain_t) = 0x%04lx\n", sizeof(metadata_plain_t));
DBG("metadata_plain_t.file_id : 0x%04lx (0x%04lx)\n",
offsetof(metadata_plain_t, file_id), FIELD_SIZEOF(metadata_plain_t, file_id));
DBG("metadata_plain_t.major_version : 0x%04lx (0x%04lx)\n",
offsetof(metadata_plain_t, major_version), FIELD_SIZEOF(metadata_plain_t, major_version));
DBG("metadata_plain_t.minor_version : 0x%04lx (0x%04lx)\n",
offsetof(metadata_plain_t, minor_version), FIELD_SIZEOF(metadata_plain_t, minor_version));
DBG("metadata_plain_t.metadata_key_id : 0x%04lx (0x%04lx)\n",
offsetof(metadata_plain_t, metadata_key_id),
FIELD_SIZEOF(metadata_plain_t, metadata_key_id));
DBG("metadata_plain_t.metadata_gmac : 0x%04lx (0x%04lx)\n",
offsetof(metadata_plain_t, metadata_gmac),
FIELD_SIZEOF(metadata_plain_t, metadata_gmac));
DBG("size(metadata_encrypted_t) = 0x%04lx\n", sizeof(metadata_encrypted_t));
DBG("metadata_encrypted_t.path : 0x%04lx (0x%04lx)\n",
mdps + offsetof(metadata_encrypted_t, path),
FIELD_SIZEOF(metadata_encrypted_t, path));
DBG("metadata_encrypted_t.size : 0x%04lx (0x%04lx)\n",
mdps + offsetof(metadata_encrypted_t, size), FIELD_SIZEOF(metadata_encrypted_t, size));
DBG("metadata_encrypted_t.mht_key : 0x%04lx (0x%04lx)\n",
mdps + offsetof(metadata_encrypted_t, mht_key),
FIELD_SIZEOF(metadata_encrypted_t, mht_key));
DBG("metadata_encrypted_t.mht_gmac : 0x%04lx (0x%04lx)\n",
mdps + offsetof(metadata_encrypted_t, mht_gmac),
FIELD_SIZEOF(metadata_encrypted_t, mht_gmac));
DBG("metadata_encrypted_t.data : 0x%04lx (0x%04lx)\n",
mdps + offsetof(metadata_encrypted_t, data), FIELD_SIZEOF(metadata_encrypted_t, data));
DBG("size(metadata_padding_t) = 0x%04lx\n", sizeof(metadata_padding_t));
DBG("metadata_padding_t : 0x%04lx (0x%04lx)\n",
mdps + sizeof(metadata_encrypted_t), sizeof(metadata_padding_t));
/* node 0: metadata + 3k of user data */
/* plain metadata */
truncate_file("trunc_meta_plain_0", 0);
truncate_file("trunc_meta_plain_1", FIELD_TRUNCATED(metadata_plain_t, file_id));
truncate_file("trunc_meta_plain_2", offsetof(metadata_plain_t, major_version));
truncate_file("trunc_meta_plain_3", offsetof(metadata_plain_t, minor_version));
truncate_file("trunc_meta_plain_4", offsetof(metadata_plain_t, metadata_key_id));
truncate_file("trunc_meta_plain_5", FIELD_TRUNCATED(metadata_plain_t, metadata_key_id));
truncate_file("trunc_meta_plain_6", offsetof(metadata_plain_t, metadata_gmac));
truncate_file("trunc_meta_plain_7", FIELD_TRUNCATED(metadata_plain_t, metadata_gmac));
/* encrypted metadata */
truncate_file("trunc_meta_enc_0", mdps + offsetof(metadata_encrypted_t, path));
truncate_file("trunc_meta_enc_1", mdps + FIELD_TRUNCATED(metadata_encrypted_t, path));
truncate_file("trunc_meta_enc_2", mdps + offsetof(metadata_encrypted_t, size));
truncate_file("trunc_meta_enc_3", mdps + FIELD_TRUNCATED(metadata_encrypted_t, size));
truncate_file("trunc_meta_enc_4", mdps + offsetof(metadata_encrypted_t, mht_key));
truncate_file("trunc_meta_enc_5", mdps + FIELD_TRUNCATED(metadata_encrypted_t, mht_key));
truncate_file("trunc_meta_enc_6", mdps + offsetof(metadata_encrypted_t, mht_gmac));
truncate_file("trunc_meta_enc_7", mdps + FIELD_TRUNCATED(metadata_encrypted_t, mht_gmac));
truncate_file("trunc_meta_enc_8", mdps + offsetof(metadata_encrypted_t, data));
truncate_file("trunc_meta_enc_9", mdps + FIELD_TRUNCATED(metadata_encrypted_t, data));
/* padding */
truncate_file("trunc_meta_pad_0", mdps + sizeof(metadata_encrypted_t));
truncate_file("trunc_meta_pad_1", mdps + sizeof(metadata_encrypted_t)
+ sizeof(metadata_padding_t) / 2);
/* node 1: mht root */
/* after node 0 */
truncate_file("trunc_mht_0", PF_NODE_SIZE);
/* middle of data_nodes_crypto[0].key */
truncate_file("trunc_mht_1", PF_NODE_SIZE + PF_KEY_SIZE / 2);
/* after data_nodes_crypto[0].key */
truncate_file("trunc_mht_2", PF_NODE_SIZE + PF_KEY_SIZE);
/* middle of data_nodes_crypto[0].gmac */
truncate_file("trunc_mht_3", PF_NODE_SIZE + PF_KEY_SIZE + PF_MAC_SIZE / 2);
/* after data_nodes_crypto[0].gmac */
truncate_file("trunc_mht_4", PF_NODE_SIZE + PF_KEY_SIZE + PF_MAC_SIZE);
/* after data_nodes_crypto */
truncate_file("trunc_mht_5", PF_NODE_SIZE + DATA_CRYPTO_SIZE);
/* middle of mht_nodes_crypto[0].key */
truncate_file("trunc_mht_6", PF_NODE_SIZE + DATA_CRYPTO_SIZE + PF_KEY_SIZE / 2);
/* after mht_nodes_crypto[0].key */
truncate_file("trunc_mht_7", PF_NODE_SIZE + DATA_CRYPTO_SIZE + PF_KEY_SIZE);
/* middle of mht_nodes_crypto[0].gmac */
truncate_file("trunc_mht_8", PF_NODE_SIZE + DATA_CRYPTO_SIZE + PF_KEY_SIZE + PF_MAC_SIZE / 2);
/* after mht_nodes_crypto[0].gmac */
truncate_file("trunc_mht_9", PF_NODE_SIZE + DATA_CRYPTO_SIZE + PF_KEY_SIZE + PF_MAC_SIZE);
/* node 2-3: data #0, #1 */
/* after mht root */
truncate_file("trunc_data_0", 2 * PF_NODE_SIZE);
/* middle of data #0 */
truncate_file("trunc_data_1", 2 * PF_NODE_SIZE + PF_NODE_SIZE / 2);
/* after data #0 */
truncate_file("trunc_data_2", 3 * PF_NODE_SIZE);
/* middle of data #1 */
truncate_file("trunc_data_3", 3 * PF_NODE_SIZE + PF_NODE_SIZE / 2);
/* extend */
truncate_file("extend_0", g_input_size + 1);
truncate_file("extend_1", g_input_size + PF_NODE_SIZE / 2);
truncate_file("extend_2", g_input_size + PF_NODE_SIZE);
truncate_file("extend_3", g_input_size + PF_NODE_SIZE + PF_NODE_SIZE / 2);
}
/* returns mmap'd output contents */
static void* create_output(const char* path) {
void* mem = MAP_FAILED;
int fd = open(path, O_RDWR|O_CREAT, 0664);
if (fd < 0)
FATAL("Failed to open output file '%s': %s\n", path, strerror(errno));
if (ftruncate(fd, g_input_size) < 0)
FATAL("Failed to ftruncate output file '%s': %s\n", path, strerror(errno));
mem = mmap(NULL, g_input_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (mem == MAP_FAILED)
FATAL("Failed to mmap output file '%s': %s\n", path, strerror(errno));
memcpy(mem, g_input_data, g_input_size);
close(fd);
return mem;
}
static void pf_decrypt(const void* encrypted, size_t size, const pf_key_t* key, const pf_mac_t* mac,
void* decrypted, const char* msg) {
pf_status_t status = mbedtls_aes_gcm_decrypt(key, &g_empty_iv, NULL, 0,
encrypted, size,
decrypted, mac);
if (PF_FAILURE(status))
FATAL("decrypting %s failed\n", msg);
}
static void pf_encrypt(const void* decrypted, size_t size, const pf_key_t* key, pf_mac_t* mac,
void* encrypted, const char* msg) {
pf_status_t status = mbedtls_aes_gcm_encrypt(key, &g_empty_iv, NULL, 0,
decrypted, size,
encrypted, mac);
if (PF_FAILURE(status))
FATAL("encrypting %s failed\n", msg);
}
/* copy input PF and apply some modifications */
#define __BREAK_PF(suffix, ...) do { \
make_output_path(suffix); \
meta = create_output(g_output_path); \
out = (uint8_t*)meta; \
pf_decrypt(&meta->encrypted_part, sizeof(meta->encrypted_part), &g_meta_key, \
&meta->plain_part.metadata_gmac, meta_dec, "metadata"); \
mht_enc = (mht_node_t*)(out + PF_NODE_SIZE); \
pf_decrypt(mht_enc, sizeof(*mht_enc), &meta_dec->mht_key, &meta_dec->mht_gmac, mht_dec, \
"mht"); \
__VA_ARGS__ \
munmap(meta, g_input_size); \
} while (0)
/* if update is true, also create a file with correct metadata MAC */
#define BREAK_PF(suffix, update, ...) do { \
__BREAK_PF(suffix, __VA_ARGS__); \
if (update) { \
__BREAK_PF(suffix "_fixed", __VA_ARGS__ { \
pf_encrypt(meta_dec, sizeof(*meta_dec), &g_meta_key, \
&meta->plain_part.metadata_gmac, meta->encrypted_part, \
"metadata"); \
} ); \
} \
} while (0)
#define BREAK_MHT(suffix, ...) do { \
__BREAK_PF(suffix, __VA_ARGS__ { \
pf_encrypt(mht_dec, sizeof(*mht_dec), &meta_dec->mht_key, &meta_dec->mht_gmac, \
mht_enc, "mht"); \
} ); \
} while (0)
#define LAST_BYTE(array) (((uint8_t*)&array)[sizeof(array) - 1])
static void tamper_modify(void) {
metadata_node_t* meta = NULL;
uint8_t* out = NULL;
metadata_encrypted_t* meta_dec = malloc(sizeof(*meta_dec));
if (!meta_dec)
FATAL("Out of memory\n");
mht_node_t* mht_enc = NULL;
mht_node_t* mht_dec = malloc(sizeof(*mht_dec));
if (!mht_dec)
FATAL("Out of memory\n");
/* plain part of the metadata isn't covered by the MAC so no point updating it */
BREAK_PF("meta_plain_id_0", /*update=*/false,
{ meta->plain_part.file_id = 0; });
BREAK_PF("meta_plain_id_1", /*update=*/false,
{ meta->plain_part.file_id = UINT64_MAX; });
BREAK_PF("meta_plain_version_0", /*update=*/false,
{ meta->plain_part.major_version = 0; });
BREAK_PF("meta_plain_version_1", /*update=*/false,
{ meta->plain_part.major_version = 0xff; });
BREAK_PF("meta_plain_version_2", /*update=*/false,
{ meta->plain_part.minor_version = 0xff; });
/* metadata_key_id is the keying material for encrypted metadata key derivation, so create also
* PFs with updated MACs */
BREAK_PF("meta_plain_keyid_0", /*update=*/true,
{ meta->plain_part.metadata_key_id[0] ^= 1; });
BREAK_PF("meta_plain_keyid_1", /*update=*/true,
{ LAST_BYTE(meta->plain_part.metadata_key_id) ^= 0xfe; });
BREAK_PF("meta_plain_mac_0", /*update=*/true,
{ meta->plain_part.metadata_gmac[0] ^= 0xfe; });
BREAK_PF("meta_plain_mac_1", /*update=*/true,
{ LAST_BYTE(meta->plain_part.metadata_gmac) &= 1; });
BREAK_PF("meta_enc_filename_0", /*update=*/true,
{ meta_dec->path[0] = 0; });
BREAK_PF("meta_enc_filename_1", /*update=*/true,
{ meta_dec->path[0] ^= 1; });
BREAK_PF("meta_enc_filename_2", /*update=*/true,
{ LAST_BYTE(meta_dec->path) ^= 0xfe; });
BREAK_PF("meta_enc_size_0", /*update=*/true,
{ meta_dec->size = 0; });
BREAK_PF("meta_enc_size_1", /*update=*/true,
{ meta_dec->size = g_input_size - 1; });
BREAK_PF("meta_enc_size_2", /*update=*/true,
{ meta_dec->size = g_input_size + 1; });
BREAK_PF("meta_enc_size_3", /*update=*/true,
{ meta_dec->size = UINT64_MAX; });
BREAK_PF("meta_enc_mht_key_0", /*update=*/true,
{ meta_dec->mht_key[0] ^= 1; });
BREAK_PF("meta_enc_mht_key_1", /*update=*/true,
{ LAST_BYTE(meta_dec->mht_key) ^= 0xfe; });
BREAK_PF("meta_enc_mht_mac_0", /*update=*/true,
{ meta_dec->mht_gmac[0] ^= 1; });
BREAK_PF("meta_enc_mht_mac_1", /*update=*/true,
{ LAST_BYTE(meta_dec->mht_gmac) ^= 0xfe; });
BREAK_PF("meta_enc_data_0", /*update=*/true,
{ meta_dec->data[0] ^= 0xfe; });
BREAK_PF("meta_enc_data_1", /*update=*/true,
{ LAST_BYTE(meta_dec->data) ^= 1; });
/* padding is ignored */
BREAK_PF("meta_padding_0", /*update=*/false,
{ meta->padding[0] ^= 1; });
BREAK_PF("meta_padding_1", /*update=*/false,
{ LAST_BYTE(meta->padding) ^= 0xfe; });
BREAK_MHT("mht_0", { mht_dec->data_nodes_crypto[0].key[0] ^= 1; });
BREAK_MHT("mht_1", { mht_dec->data_nodes_crypto[0].gmac[0] ^= 1; });
BREAK_MHT("mht_2", { mht_dec->mht_nodes_crypto[0].key[0] ^= 1; });
BREAK_MHT("mht_3", { mht_dec->mht_nodes_crypto[0].gmac[0] ^= 1; });
BREAK_MHT("mht_4", { mht_dec->data_nodes_crypto[ATTACHED_DATA_NODES_COUNT - 1].key[0] ^= 1; });
BREAK_MHT("mht_5", { mht_dec->data_nodes_crypto[ATTACHED_DATA_NODES_COUNT - 1].gmac[0] ^= 1; });
BREAK_MHT("mht_6", { mht_dec->mht_nodes_crypto[CHILD_MHT_NODES_COUNT - 1].key[0] ^= 1; });
BREAK_MHT("mht_7", { mht_dec->mht_nodes_crypto[CHILD_MHT_NODES_COUNT - 1].gmac[0] ^= 1; });
BREAK_MHT("mht_8", {
gcm_crypto_data_t crypto;
memcpy(&crypto, &mht_dec->data_nodes_crypto[0], sizeof(crypto));
memcpy(&mht_dec->data_nodes_crypto[0], &mht_dec->data_nodes_crypto[1], sizeof(crypto));
memcpy(&mht_dec->data_nodes_crypto[1], &crypto, sizeof(crypto));
});
BREAK_MHT("mht_9", {
gcm_crypto_data_t crypto;
memcpy(&crypto, &mht_dec->mht_nodes_crypto[0], sizeof(crypto));
memcpy(&mht_dec->mht_nodes_crypto[0], &mht_dec->mht_nodes_crypto[1], sizeof(crypto));
memcpy(&mht_dec->mht_nodes_crypto[1], &crypto, sizeof(crypto));
});
/* data nodes start from node #2 */
BREAK_PF("data_0", /*update=*/false,
{ *(out + 2 * PF_NODE_SIZE) ^= 1; });
BREAK_PF("data_1", /*update=*/false,
{ *(out + 3 * PF_NODE_SIZE - 1) ^= 1; });
BREAK_PF("data_2", /*update=*/false, {
/* swap data nodes */
memcpy(out + 2 * PF_NODE_SIZE, g_input_data + 3 * PF_NODE_SIZE, PF_NODE_SIZE);
memcpy(out + 3 * PF_NODE_SIZE, g_input_data + 2 * PF_NODE_SIZE, PF_NODE_SIZE);
});
free(mht_dec);
free(meta_dec);
}
int main(int argc, char* argv[]) {
int ret = -1;
int option = 0;
char* input_path = NULL;
char* wrap_key_path = NULL;
int input_fd = -1;
while (true) {
option = getopt_long(argc, argv, "i:o:w:vh", g_options, NULL);
if (option == -1)
break;
switch (option) {
case 'i':
input_path = optarg;
break;
case 'o':
g_output_dir = optarg;
break;
case 'w':
wrap_key_path = optarg;
break;
case 'v':
set_verbose(true);
break;
case 'h':
usage();
return 0;
default:
ERROR("Unknown option: %c\n", option);
usage();
}
}
if (!input_path) {
ERROR("Input path not specified\n");
usage();
goto out;
}
if (!g_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;
}
g_input_size = get_file_size(input_fd);
if (g_input_size < 0) {
ERROR("Failed to stat input file '%s': %s\n", input_path, strerror(errno));
goto out;
}
g_input_data = mmap(NULL, g_input_size, PROT_READ, MAP_PRIVATE, input_fd, 0);
if (g_input_data == MAP_FAILED) {
ERROR("Failed to mmap input file '%s': %s\n", input_path, strerror(errno));
goto out;
}
load_wrap_key(wrap_key_path, &g_wrap_key);
derive_main_key(&g_wrap_key, &((metadata_plain_t*)g_input_data)->metadata_key_id,
&g_meta_key);
g_input_name = basename(input_path);
g_output_path_size = strlen(g_input_name) + strlen(g_output_dir) + 256;
g_output_path = malloc(g_output_path_size);
if (!g_output_path) {
ERROR("No memory\n");
goto out;
}
tamper_truncate();
tamper_modify();
ret = 0;
out:
/* skip cleanup as we are in main() */
return ret;
}