mirror of
https://github.com/clearlinux/graphene.git
synced 2026-09-06 22:01:29 +00:00
[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:
committed by
Michał Kowalczyk
co-authored by
Dmitrii Kuvaiskii
parent
897429d5f2
commit
345d271e66
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user