Add the integrity-only mode SEFS

* Add patch to Rust SGX SDK to enable integrity-only SgxFile
* Upgrade to the new SEFS extended with the integrity-only mode
* Use integrity-only SEFS for /bin and /lib in test
* Add the MAC of integrity-only SEFS to Occlum.json in test
* Mount multiple FS according to Occlum.json
* Check the MACs of integrity-only SEFS images
This commit is contained in:
Tate, Hongliang Tian 2019-08-17 04:20:11 +00:00
parent 76f91a1aa3
commit dff0dbf77d
15 changed files with 297 additions and 111 deletions

2
deps/sefs vendored

@ -1 +1 @@
Subproject commit a843c6783ae14842225bb6777616dc707a657583
Subproject commit 2a7101f074439c5c70a1ad5d4d171227817eaf19

@ -1,6 +1,7 @@
use super::*;
use serde::{Deserialize, Serialize};
use std::sgxfs::{SgxFile};
use std::path::{Path, PathBuf};
const LIBOS_CONFIG_PATH : &str = "Occlum.json.protected";
@ -103,16 +104,24 @@ pub struct ConfigProcess {
#[derive(Debug)]
pub struct ConfigMount {
pub type_: String,
pub target: String,
pub source: Option<String>,
pub type_: ConfigMountFsType,
pub target: PathBuf,
pub source: Option<PathBuf>,
pub options: ConfigMountOptions,
}
#[derive(Debug, PartialEq)]
#[allow(non_camel_case_types)]
pub enum ConfigMountFsType {
TYPE_SEFS,
TYPE_HOSTFS,
TYPE_RAMFS,
}
#[derive(Debug)]
pub struct ConfigMountOptions {
pub integrity_only: bool,
pub mac: Option<String>,
pub mac: Option<sgx_aes_gcm_128bit_tag_t>,
}
@ -155,21 +164,22 @@ impl ConfigMount {
fn from_input(input: &InputConfigMount) -> Result<ConfigMount, Error> {
const ALL_FS_TYPES : [&str; 3] = [ "sefs", "hostfs", "ramfs" ];
let type_ = {
let type_ = input.type_.to_string();
if !ALL_FS_TYPES.contains(&type_.as_str()) {
let type_ = match input.type_.as_str() {
"sefs" => ConfigMountFsType::TYPE_SEFS,
"hostfs" => ConfigMountFsType::TYPE_HOSTFS,
"ramfs" => ConfigMountFsType::TYPE_RAMFS,
_ => {
return errno!(EINVAL, "Unsupported file system type");
}
type_
};
let target = {
let target = input.target.clone();
let target = PathBuf::from(&input.target);
if !target.starts_with("/") {
return errno!(EINVAL, "Target must be an absolute path");
}
target
};
let source = input.source.clone();
let source = input.source.as_ref().map(|s| PathBuf::from(s));
let options = ConfigMountOptions::from_input(&input.options)?;
Ok(ConfigMount { type_, target, source, options, })
}
@ -184,7 +194,7 @@ impl ConfigMountOptions {
if input.mac.is_none() {
return errno!(EINVAL, "MAC is expected");
}
(true, input.mac.clone())
(true, Some(parse_mac(&input.mac.as_ref().unwrap())?))
};
Ok(ConfigMountOptions { integrity_only, mac })
}

@ -1,50 +1,5 @@
use rcore_fs::vfs::{FileSystem, FileType, FsError, INode};
use rcore_fs_mountfs::{MountFS, MNode};
use rcore_fs_ramfs::RamFS;
use rcore_fs_sefs::SEFS;
use std::fmt;
use super::hostfs::HostFS;
use super::sgx_impl::SgxStorage;
use super::*;
lazy_static! {
/// The root of file system
pub static ref ROOT_INODE: Arc<INode> = {
// Mount SEFS at /
let device = Box::new(SgxStorage::new("sefs"));
let sefs = SEFS::open(device, &time::OcclumTimeProvider)
.expect("failed to open SEFS");
let rootfs = MountFS::new(sefs);
let root = rootfs.root_inode();
fn mount_default_fs(fs: Arc<dyn FileSystem>, root: &MNode, mount_at: &str) -> Result<(), Error> {
let mount_dir = match root.find(false, mount_at) {
Ok(existing_dir) => {
if existing_dir.metadata()?.type_ != FileType::Dir {
return errno!(EIO, "not a directory");
}
existing_dir
}
Err(_) => {
root.create(mount_at, FileType::Dir, 0o777)?
}
};
mount_dir.mount(fs);
Ok(())
}
let hostfs = HostFS::new(".");
mount_default_fs(hostfs, &root, "host")
.expect("failed to mount HostFS at /host");
let ramfs = RamFS::new();
mount_default_fs(ramfs, &root, "tmp")
.expect("failed to mount RamFS at /tmp");
root
};
}
use std::fmt;
pub struct INodeFile {
inode: Arc<INode>,

@ -10,7 +10,8 @@ pub use self::access::{do_access, do_faccessat, AccessFlags, AccessModes, AT_FDC
pub use self::file::{File, FileRef, SgxFile, StdinFile, StdoutFile};
pub use self::file_table::{FileDesc, FileTable};
use self::inode_file::OpenOptions;
pub use self::inode_file::{INodeExt, INodeFile, ROOT_INODE};
pub use self::inode_file::{INodeExt, INodeFile};
pub use self::root_inode::{ROOT_INODE};
pub use self::io_multiplexing::*;
use self::dev_null::DevNull;
use self::dev_zero::DevZero;
@ -26,6 +27,7 @@ mod file;
mod file_table;
mod hostfs;
mod inode_file;
mod root_inode;
mod io_multiplexing;
mod dev_null;
mod dev_zero;

@ -0,0 +1,131 @@
use super::*;
use super::hostfs::HostFS;
use super::sgx_impl::SgxStorage;
use config::{ConfigMount, ConfigMountFsType};
use std::path::{Path, PathBuf};
use rcore_fs::vfs::{FileSystem, FileType, FsError, INode};
use rcore_fs_sefs::SEFS;
use rcore_fs_sefs::dev::*;
use rcore_fs_mountfs::{MountFS, MNode};
use rcore_fs_ramfs::RamFS;
lazy_static! {
/// The root of file system
pub static ref ROOT_INODE: Arc<INode> = {
let mount_config = &config::LIBOS_CONFIG.mount;
let rootfs = open_or_create_root_fs_according_to(mount_config)
.expect("failed to create or open SEFS for /");
let root = rootfs.root_inode();
mount_nonroot_fs_according_to(mount_config, &root)
.expect("failed to create or open other FS");
root
};
}
fn open_or_create_root_fs_according_to(mount_config: &Vec<ConfigMount>)
-> Result<Arc<MountFS>, Error>
{
let root_sefs_source = {
let root_mount_config = mount_config
.iter()
.find(|m| m.target == Path::new("/"))
.ok_or_else(|| Error::new(Errno::ENOENT, "The mount point at / is not specified"))?;
if root_mount_config.type_ != ConfigMountFsType::TYPE_SEFS {
return errno!(EINVAL, "The mount point at / must be SEFS");
}
if root_mount_config.options.integrity_only {
return errno!(EINVAL, "The root SEFS at / must be encrypted (i.e., integrity-only is not enough)");
}
if root_mount_config.source.is_none() {
return errno!(EINVAL, "The root SEFS must be given a source path (on host)");
}
root_mount_config.source.as_ref().unwrap()
};
let root_sefs = {
SEFS::open(Box::new(SgxStorage::new(root_sefs_source, false)),
&time::OcclumTimeProvider)
}
.or_else(|_| {
SEFS::create(Box::new(SgxStorage::new(root_sefs_source, false)),
&time::OcclumTimeProvider)
})?;
let root_mountable_sefs = MountFS::new(root_sefs);
Ok(root_mountable_sefs)
}
fn mount_nonroot_fs_according_to(mount_config: &Vec<ConfigMount>, root: &MNode)
-> Result<(), Error>
{
for mc in mount_config {
if mc.target == Path::new("/") {
continue;
}
if !mc.target.is_absolute() {
return errno!(EINVAL, "The target path must be absolute");
}
if mc.target.parent().unwrap() != Path::new("/") {
return errno!(EINVAL, "The target mount point must be under /");
}
let target_dirname = mc.target.file_name().unwrap().to_str().unwrap();
use self::ConfigMountFsType::*;
match mc.type_ {
TYPE_SEFS => {
if !mc.options.integrity_only {
return errno!(EINVAL, "Must be integrity-only SEFS")
}
if mc.source.is_none() {
return errno!(EINVAL, "Source is expected for integrity-only SEFS is supported")
}
let source_path = mc.source.as_ref().unwrap();
let device = {
let mut device = Box::new(SgxStorage::new(source_path, true));
device.set_root_mac(mc.options.mac.unwrap());
device
};
let sefs = SEFS::open(device, &time::OcclumTimeProvider)?;
mount_fs_at(sefs, &root, target_dirname)?;
},
TYPE_HOSTFS => {
if mc.source.is_none() {
return errno!(EINVAL, "Source is expected for HostFS")
}
let source_path = mc.source.as_ref().unwrap();
let hostfs = HostFS::new(source_path);
mount_fs_at(hostfs, &root, target_dirname)?;
},
TYPE_RAMFS => {
let ramfs = RamFS::new();
mount_fs_at(ramfs, &root, target_dirname)?;
},
}
}
Ok(())
}
fn mount_fs_at(fs: Arc<dyn FileSystem>, parent_inode: &MNode, dirname: &str)
-> Result<(), Error>
{
let mount_dir = match parent_inode.find(false, dirname) {
Ok(existing_dir) => {
if existing_dir.metadata()?.type_ != FileType::Dir {
return errno!(EIO, "not a directory");
}
existing_dir
}
Err(_) => {
parent_inode.create(dirname, FileType::Dir, 0o777)?
}
};
mount_dir.mount(fs);
Ok(())
}

@ -1,3 +1,4 @@
use super::{sgx_aes_gcm_128bit_tag_t};
use rcore_fs::dev::TimeProvider;
use rcore_fs::vfs::Timespec;
use rcore_fs_sefs::dev::*;
@ -11,39 +12,52 @@ use std::time::{SystemTime, UNIX_EPOCH};
pub struct SgxStorage {
path: PathBuf,
integrity_only: bool,
file_cache: Mutex<BTreeMap<usize, LockedFile>>,
root_mac: Option<sgx_aes_gcm_128bit_tag_t>,
}
impl SgxStorage {
pub fn new(path: impl AsRef<Path>) -> Self {
pub fn new(path: impl AsRef<Path>, integrity_only: bool) -> Self {
// assert!(path.as_ref().is_dir());
SgxStorage {
path: path.as_ref().to_path_buf(),
integrity_only: integrity_only,
file_cache: Mutex::new(BTreeMap::new()),
root_mac: None,
}
}
/// Get file by `file_id`.
/// It lookups cache first, if miss, then call `open_fn` to open one,
/// and add it to cache before return.
#[cfg(feature = "sgx_file_cache")]
fn get(&self, file_id: usize, open_fn: impl FnOnce(&Self) -> LockedFile) -> LockedFile {
fn get(&self, file_id: usize, open_fn: impl FnOnce(&Self) -> DevResult<LockedFile>) -> DevResult<LockedFile> {
// query cache
let mut caches = self.file_cache.lock().unwrap();
if let Some(locked_file) = caches.get(&file_id) {
// hit, return
return locked_file.clone();
return Ok(locked_file.clone());
}
// miss, open one
let locked_file = open_fn(self);
let locked_file = open_fn(self)?;
// add to cache
caches.insert(file_id, locked_file.clone());
locked_file
Ok(locked_file)
}
/// Get file by `file_id` without cache.
#[cfg(not(feature = "sgx_file_cache"))]
fn get(&self, file_id: usize, open_fn: impl FnOnce(&Self) -> LockedFile) -> LockedFile {
fn get(&self, file_id: usize, open_fn: impl FnOnce(&Self) -> DevResult<LockedFile>) -> LockedFile {
open_fn(self)
}
/// Set the expected root MAC of the SGX storage.
///
/// By giving this root MAC, we can be sure that the root file (file_id = 0) opened
/// by the storage has a MAC that is equal to the given root MAC.
pub fn set_root_mac(&mut self, mac: sgx_aes_gcm_128bit_tag_t) -> DevResult<()> {
self.root_mac = Some(mac);
Ok(())
}
}
impl Storage for SgxStorage {
@ -51,15 +65,36 @@ impl Storage for SgxStorage {
let locked_file = self.get(file_id, |this| {
let mut path = this.path.to_path_buf();
path.push(format!("{}", file_id));
// TODO: key
let key = [0u8; 16];
let file = OpenOptions::new()
.read(true)
.update(true)
.open_ex(path, &key)
.expect("failed to open SgxFile");
LockedFile(Arc::new(Mutex::new(file)))
});
let options = {
let mut options = OpenOptions::new();
options.read(true).update(true);
options
};
let file = {
let open_res = if !self.integrity_only {
options.open(path)
} else {
options.open_integrity_only(path)
};
if open_res.is_err() {
return Err(DeviceError);
}
open_res.unwrap()
};
// Check the MAC of the root file against the given root MAC of the storage
if file_id == 0 && self.root_mac.is_some() {
let root_file_mac = file.get_mac()
.expect("Failed to get mac");
if root_file_mac != self.root_mac.unwrap() {
println!("Expected MAC = {:#?}, actual MAC = {:?}",
self.root_mac.unwrap(), root_file_mac);
return Err(DeviceError);
}
}
Ok(LockedFile(Arc::new(Mutex::new(file))))
})?;
Ok(Box::new(locked_file))
}
@ -67,15 +102,24 @@ impl Storage for SgxStorage {
let locked_file = self.get(file_id, |this| {
let mut path = this.path.to_path_buf();
path.push(format!("{}", file_id));
// TODO: key
let key = [0u8; 16];
let file = OpenOptions::new()
.write(true)
.update(true)
.open_ex(path, &key)
.expect("failed to create SgxFile");
LockedFile(Arc::new(Mutex::new(file)))
});
let options = {
let mut options = OpenOptions::new();
options.write(true).update(true);
options
};
let file = {
let open_res = if !self.integrity_only {
options.open(path)
} else {
options.open_integrity_only(path)
};
if open_res.is_err() {
return Err(DeviceError);
}
open_res.unwrap()
};
Ok(LockedFile(Arc::new(Mutex::new(file))))
})?;
Ok(Box::new(locked_file))
}

@ -15,7 +15,7 @@ BUILD_TARGETS := $(TEST_DEPS) $(TESTS) $(BENCHES)
TEST_TARGETS := $(TESTS:%=test-%)
BENCH_TARGETS := $(BENCHES:%=bench-%)
CLEAN_TARGETS := $(BUILD_TARGETS:%=clean-%)
.PHONY: all build test clean sefs $(BUILD_TARGETS) $(TEST_TARGETS) $(BENCH_TARGETS) $(CLEAN_TARGETS)
.PHONY: all build test clean sefs root-sefs bin-sefs lib-sefs $(BUILD_TARGETS) $(TEST_TARGETS) $(BENCH_TARGETS) $(CLEAN_TARGETS)
# Use echo program instead of built-in echo command in shell. This ensures
# that echo can recognize escaped sequences (with -e argument) regardless of
@ -29,6 +29,8 @@ NO_COLOR := \033[0m
FS_PATH := fs
SEFS_PATH := sefs
BIN_SEFS_ROOT_FILE := $(SEFS_PATH)/bin/0
LIB_SEFS_ROOT_FILE := $(SEFS_PATH)/lib/0
#############################################################################
# Build targets
@ -43,33 +45,60 @@ $(BUILD_TARGETS): %:
@$(MAKE) --no-print-directory -C $@
@$(ECHO) "$(GREEN)DONE$(NO_COLOR)"
sefs:
@$(RM) -rf $(SEFS_PATH)
sefs: root-sefs bin-sefs lib-sefs
root-sefs:
@mkdir -p $(SEFS_PATH)/root/
@echo "SEFS => $@"
bin-sefs:
@mkdir -p $(FS_PATH)/bin/
@for test in $(TESTS) ; do \
cp "$$test/$$test" $(FS_PATH)/bin/ ; \
done
@rm -rf $(SEFS_PATH)/bin
@mkdir -p $(SEFS_PATH)
@cd $(PROJECT_DIR)/deps/sefs/sefs-fuse/bin/ && \
./app \
--integrity-only \
$(CUR_DIR)/$(SEFS_PATH)/bin \
$(CUR_DIR)/$(FS_PATH)/bin \
zip
@echo "SEFS => $@"
lib-sefs:
@mkdir -p $(FS_PATH)/lib/
@cp /lib/ld-musl-x86_64.so.1 $(FS_PATH)/lib/
@cp /usr/local/occlum/lib/libc++.so.1 $(FS_PATH)/lib/
@cp /usr/local/occlum/lib/libc++abi.so.1 $(FS_PATH)/lib/
@cp /usr/local/occlum/lib/libunwind.so.1 $(FS_PATH)/lib/
@rm -rf $(SEFS_PATH)/lib
@mkdir -p $(SEFS_PATH)
@cd $(PROJECT_DIR)/deps/sefs/sefs-fuse/bin/ && \
./app \
$(CUR_DIR)/$(SEFS_PATH) \
$(CUR_DIR)/$(FS_PATH) \
--integrity-only \
$(CUR_DIR)/$(SEFS_PATH)/lib \
$(CUR_DIR)/$(FS_PATH)/lib \
zip
@echo "SEFS => $@"
libocclum.signed.so: Occlum.json Enclave_config.xml Enclave_private.pem
@$(PROJECT_DIR)/tools/bin/build-enclave Occlum.json Enclave_config.xml Enclave_private.pem
Occlum.json: Occlum.json.sh $(BIN_SEFS_ROOT_FILE) $(LIB_SEFS_ROOT_FILE)
@./Occlum.json.sh \
`$(PROJECT_DIR)/tools/bin/protect-integrity show-mac $(BIN_SEFS_ROOT_FILE)` \
`$(PROJECT_DIR)/tools/bin/protect-integrity show-mac $(LIB_SEFS_ROOT_FILE)` \
> $@
@echo "GEN => $@"
#############################################################################
# Test targets
#############################################################################
test: build $(TEST_TARGETS)
pal: $(PROJECT_DIR)/src/pal/pal
@cp $< pal
$(TEST_TARGETS): test-%: % pal libocclum.signed.so
$(TEST_TARGETS): test-%: % pal
@$(ECHO) "$(CYAN)RUN TEST => $<$(NO_COLOR)"
@$(MAKE) --no-print-directory -C $< test ; \
if [ $$? -eq 0 ] ; then \
@ -78,6 +107,13 @@ $(TEST_TARGETS): test-%: % pal libocclum.signed.so
$(ECHO) "$(RED)FAILED$(NO_COLOR)" ; \
fi ;
pal: $(PROJECT_DIR)/src/pal/pal
@cp $< pal
$(PROJECT_DIR)/src/pal/pal:
@cd $(PROJECT_DIR)/src/pal && make
#############################################################################
# Benchmark targets
#############################################################################
@ -98,7 +134,7 @@ $(BENCH_TARGETS): bench-%: % pal libocclum.signed.so
#############################################################################
clean: $(CLEAN_TARGETS)
@$(RM) -f pal libocclum.signed.so Occlum.json.protected
@$(RM) -f pal libocclum.signed.so Occlum.json.protected Occlum.json
@$(RM) -rf $(FS_PATH) $(SEFS_PATH)
$(CLEAN_TARGETS): clean-%:

21
test/Occlum.json → test/Occlum.json.sh Normal file → Executable file

@ -1,3 +1,8 @@
#!/bin/bash
bin_sefs_mac=$1
lib_sefs_mac=$2
cat <<EOF
{
"vm": {
"user_space_size": "128MB"
@ -11,15 +16,24 @@
{
"target": "/",
"type": "sefs",
"source": "./root_sefs"
"source": "./sefs/root"
},
{
"target": "/bin",
"type": "sefs",
"source": "./bin_sefs",
"source": "./sefs/bin",
"options": {
"integrity_only": true,
"MAC": "00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-"
"MAC": "$bin_sefs_mac"
}
},
{
"target": "/lib",
"type": "sefs",
"source": "./sefs/lib",
"options": {
"integrity_only": true,
"MAC": "$lib_sefs_mac"
}
},
{
@ -33,3 +47,4 @@
}
]
}
EOF

@ -25,7 +25,7 @@ int main(int argc, const char* argv[]) {
posix_spawn_file_actions_addclose(&file_actions, pipe_rd_fd);
const char* msg = "Echo!\n";
const char* child_prog = "hello_world";
const char* child_prog = "/bin/hello_world";
const char* child_argv[3] = { child_prog, msg, NULL };
int child_pid;
if (posix_spawn(&child_pid, child_prog, &file_actions,

@ -63,7 +63,7 @@ static int test_sched_xetaffinity_with_child_pid() {
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(num - 1 , &mask);
int ret = posix_spawn(&child_pid, "getpid", NULL, NULL, NULL, NULL);
int ret = posix_spawn(&child_pid, "/bin/getpid", NULL, NULL, NULL, NULL);
if (ret < 0 ) {
throw_error("spawn process error");
}

@ -38,7 +38,7 @@ int main(int argc, const char *argv[]) {
int client_pid;
char* client_argv[] = {"client", "127.0.0.1"};
ret = posix_spawn(&client_pid, "client", NULL, NULL, client_argv, NULL);
ret = posix_spawn(&client_pid, "/bin/client", NULL, NULL, client_argv, NULL);
if (ret < 0) {
printf("spawn client process error: %s(errno: %d)\n", strerror(errno), errno);
return -1;

@ -67,7 +67,7 @@ main(int argc, char *argv[]) {
int client_pid;
char* client_argv[] = {"client", "127.0.0.1"};
for(int i=0; i<3; ++i) {
int ret = posix_spawn(&client_pid, "client", NULL, NULL, client_argv, NULL);
int ret = posix_spawn(&client_pid, "/bin/client", NULL, NULL, client_argv, NULL);
if (ret < 0) {
printf("spawn client process error: %s(errno: %d)\n", strerror(errno), errno);
return -1;

@ -8,7 +8,7 @@ int main(int argc, const char* argv[]) {
int ret, child_pid, status;
printf("Run a parent process has pid = %d and ppid = %d\n", getpid(), getppid());
ret = posix_spawn(&child_pid, "getpid", NULL, NULL, NULL, NULL);
ret = posix_spawn(&child_pid, "/bin/getpid", NULL, NULL, NULL, NULL);
if (ret < 0) {
printf("ERROR: failed to spawn a child process\n");
return -1;

@ -11,8 +11,6 @@ C_OBJS := $(C_SRCS:%.c=%.o)
CXX_OBJS := $(CXX_SRCS:%.cc=%.o)
FS_PATH := ../fs
BIN_NAME := $(shell basename $(CUR_DIR))
BIN_FS_PATH := $(BIN_NAME)
BIN_PATH := $(FS_PATH)/$(BIN_FS_PATH)
OBJDUMP_FILE := bin.objdump
READELF_FILE := bin.readelf
@ -28,12 +26,7 @@ LINK_FLAGS = $(C_FLAGS) -pie $(EXTRA_LINK_FLAGS)
# Build
#############################################################################
all: $(BIN_PATH)
$(BIN_PATH): $(BIN_NAME)
@mkdir -p $(shell dirname $@)
@cp $^ $@
@echo "COPY => $@"
all: $(BIN_NAME)
# Compile C/C++ test program
#
@ -61,7 +54,7 @@ $(CXX_OBJS): %.o: %.cc
#############################################################################
test: $(BIN_ENC_NAME)
@cd $(CUR_DIR)/.. && RUST_BACKTRACE=1 ./pal $(BIN_FS_PATH) $(BIN_ARGS)
@cd $(CUR_DIR)/.. && RUST_BACKTRACE=1 ./pal /bin/$(BIN_NAME) $(BIN_ARGS)
#############################################################################
# Misc

@ -83,7 +83,7 @@ int main(int argc, const char* argv[]) {
posix_spawn_file_actions_addclose(&file_actions, socket_rd_fd);
const char* msg = "Echo!\n";
const char* child_prog = "hello_world";
const char* child_prog = "/bin/hello_world";
const char* child_argv[3] = { child_prog, msg, NULL };
int child_pid;
if (posix_spawn(&child_pid, child_prog, &file_actions,