linux-sgx: Implement SGX IPFS as POSIX backend for file interaction (#1489)

This PR integrates an Intel SGX feature called Intel Protection File System Library (IPFS)
into the runtime to create, operate and delete files inside the enclave, while guaranteeing
the confidentiality and integrity of the data persisted. IPFS can be referred to here:
https://www.intel.com/content/www/us/en/developer/articles/technical/overview-of-intel-protected-file-system-library-using-software-guard-extensions.html

Introduce a cmake variable `WAMR_BUILD_SGX_IPFS`, when enabled, the files interaction
API of WASI will leverage IPFS, instead of the regular POSIX OCALLs. The implementation
has been written with light changes to sgx platform layer, so all the security aspects
WAMR relies on are conserved.

In addition to this integration, the following changes have been made:
 - The CI workflow has been adapted to test the compilation of the runtime and sample
    with the flag `WAMR_BUILD_SGX_IPFS` set to true
 - Introduction of a new sample that demonstrates the interaction of the files (called `file`),
 - Documentation of this new feature
This commit is contained in:
Jämes Ménétrey
2022-09-28 07:09:58 +02:00
committed by GitHub
parent fa736d1ee9
commit dfd16f8e4f
20 changed files with 1211 additions and 4 deletions

View File

@ -7,6 +7,10 @@
#include "sgx_error.h"
#include "sgx_file.h"
#if WASM_ENABLE_SGX_IPFS != 0
#include "sgx_ipfs.h"
#endif
#ifndef SGX_DISABLE_WASI
#define TRACE_FUNC() os_printf("undefined %s\n", __FUNCTION__)
@ -184,6 +188,22 @@ openat(int dirfd, const char *pathname, int flags, ...)
if (fd == -1)
errno = get_errno();
#if WASM_ENABLE_SGX_IPFS != 0
// When WAMR uses Intel SGX IPFS to enabled, it opens a second
// file descriptor to interact with the secure file.
// The first file descriptor opened earlier is used to interact
// with the metadata of the file (e.g., time, flags, etc.).
int ret;
void *file_ptr = ipfs_fopen(fd, pathname, flags);
if (file_ptr == NULL) {
if (ocall_close(&ret, fd) != SGX_SUCCESS) {
TRACE_OCALL_FAIL();
}
return -1;
}
#endif
return fd;
}
@ -192,6 +212,13 @@ close(int fd)
{
int ret;
#if WASM_ENABLE_SGX_IPFS != 0
// Close the IPFS file pointer in addition of the file descriptor
ret = ipfs_close(fd);
if (ret == -1)
errno = get_errno();
#endif
if (ocall_close(&ret, fd) != SGX_SUCCESS) {
TRACE_OCALL_FAIL();
return -1;
@ -345,6 +372,12 @@ readv_internal(int fd, const struct iovec *iov, int iovcnt, bool has_offset,
if (total_size >= UINT32_MAX)
return -1;
#if WASM_ENABLE_SGX_IPFS != 0
if (fd > 2) {
return ipfs_read(fd, iov, iovcnt, has_offset, offset);
}
#endif
iov1 = BH_MALLOC((uint32)total_size);
if (iov1 == NULL)
@ -410,6 +443,12 @@ writev_internal(int fd, const struct iovec *iov, int iovcnt, bool has_offset,
if (total_size >= UINT32_MAX)
return -1;
#if WASM_ENABLE_SGX_IPFS != 0
if (fd > 2) {
return ipfs_write(fd, iov, iovcnt, has_offset, offset);
}
#endif
iov1 = BH_MALLOC((uint32)total_size);
if (iov1 == NULL)
@ -468,12 +507,18 @@ off_t
lseek(int fd, off_t offset, int whence)
{
off_t ret;
#if WASM_ENABLE_SGX_IPFS != 0
ret = ipfs_lseek(fd, offset, whence);
#else
if (ocall_lseek(&ret, fd, (long)offset, whence) != SGX_SUCCESS) {
TRACE_OCALL_FAIL();
return -1;
}
if (ret == -1)
errno = get_errno();
#endif
return ret;
}
@ -482,12 +527,17 @@ ftruncate(int fd, off_t length)
{
int ret;
#if WASM_ENABLE_SGX_IPFS != 0
ret = ipfs_ftruncate(fd, length);
#else
if (ocall_ftruncate(&ret, fd, length) != SGX_SUCCESS) {
TRACE_OCALL_FAIL();
return -1;
}
if (ret == -1)
errno = get_errno();
#endif
return ret;
}
@ -554,12 +604,17 @@ fsync(int fd)
{
int ret;
#if WASM_ENABLE_SGX_IPFS != 0
ret = ipfs_fflush(fd);
#else
if (ocall_fsync(&ret, fd) != SGX_SUCCESS) {
TRACE_OCALL_FAIL();
return -1;
}
if (ret == -1)
errno = get_errno();
#endif
return ret;
}
@ -568,12 +623,17 @@ fdatasync(int fd)
{
int ret;
#if WASM_ENABLE_SGX_IPFS != 0
ret = ipfs_fflush(fd);
#else
if (ocall_fdatasync(&ret, fd) != SGX_SUCCESS) {
TRACE_OCALL_FAIL();
return -1;
}
if (ret == -1)
errno = get_errno();
#endif
return ret;
}
@ -801,10 +861,14 @@ posix_fallocate(int fd, off_t offset, off_t len)
{
int ret;
#if WASM_ENABLE_SGX_IPFS != 0
ret = ipfs_posix_fallocate(fd, offset, len);
#else
if (ocall_posix_fallocate(&ret, fd, offset, len) != SGX_SUCCESS) {
TRACE_OCALL_FAIL();
return -1;
}
#endif
return ret;
}

View File

@ -0,0 +1,460 @@
/*
* Copyright (C) 2022 Intel Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*/
#if WASM_ENABLE_SGX_IPFS != 0
#include "ssp_config.h"
#include "bh_platform.h"
#include "sgx_ipfs.h"
#include <errno.h>
#include "sgx_tprotected_fs.h"
#define SGX_ERROR_FILE_LOWEST_ERROR_ID SGX_ERROR_FILE_BAD_STATUS
#define SGX_ERROR_FILE_HIGHEST_ERROR_ID SGX_ERROR_FILE_CLOSE_FAILED
// The mapping between file descriptors and IPFS file pointers.
static HashMap *ipfs_file_list;
// Converts an SGX error code to a POSIX error code.
static __wasi_errno_t
convert_sgx_errno(int error)
{
if (error >= SGX_ERROR_FILE_LOWEST_ERROR_ID
&& error <= SGX_ERROR_FILE_HIGHEST_ERROR_ID) {
switch (error) {
/* The file is in bad status */
case SGX_ERROR_FILE_BAD_STATUS:
return ENOTRECOVERABLE;
/* The Key ID field is all zeros, can't re-generate the encryption
* key */
case SGX_ERROR_FILE_NO_KEY_ID:
return EKEYREJECTED;
/* The current file name is different then the original file name
* (not allowed, substitution attack) */
case SGX_ERROR_FILE_NAME_MISMATCH:
return EIO;
/* The file is not an SGX file */
case SGX_ERROR_FILE_NOT_SGX_FILE:
return EEXIST;
/* A recovery file can't be opened, so flush operation can't
* continue (only used when no EXXX is returned) */
case SGX_ERROR_FILE_CANT_OPEN_RECOVERY_FILE:
return EIO;
/* A recovery file can't be written, so flush operation can't
* continue (only used when no EXXX is returned) */
case SGX_ERROR_FILE_CANT_WRITE_RECOVERY_FILE:
return EIO;
/* When openeing the file, recovery is needed, but the recovery
* process failed */
case SGX_ERROR_FILE_RECOVERY_NEEDED:
return EIO;
/* fflush operation (to disk) failed (only used when no EXXX is
* returned) */
case SGX_ERROR_FILE_FLUSH_FAILED:
return EIO;
/* fclose operation (to disk) failed (only used when no EXXX is
* returned) */
case SGX_ERROR_FILE_CLOSE_FAILED:
return EIO;
}
}
return error;
}
static void *
fd2file(int fd)
{
return bh_hash_map_find(ipfs_file_list, (void *)(intptr_t)fd);
}
static void
ipfs_file_destroy(void *sgx_file)
{
sgx_fclose(sgx_file);
}
int
ipfs_init()
{
ipfs_file_list =
bh_hash_map_create(32, true, (HashFunc)fd_hash, (KeyEqualFunc)fd_equal,
NULL, (ValueDestroyFunc)ipfs_file_destroy);
return ipfs_file_list != NULL ? BHT_OK : BHT_ERROR;
}
void
ipfs_destroy()
{
bh_hash_map_destroy(ipfs_file_list);
}
int
ipfs_posix_fallocate(int fd, off_t offset, size_t len)
{
void *sgx_file = fd2file(fd);
if (!sgx_file) {
return EBADF;
}
// The wrapper for fseek takes care of extending the file if sought beyond
// the end
if (ipfs_lseek(fd, offset + len, SEEK_CUR) == -1) {
return errno;
}
// Make sure the file is allocated by flushing it
if (sgx_fflush(sgx_file) != 0) {
return errno;
}
return 0;
}
size_t
ipfs_read(int fd, const struct iovec *iov, int iovcnt, bool has_offset,
off_t offset)
{
int i;
off_t original_offset = 0;
void *sgx_file = fd2file(fd);
size_t read_result, number_of_read_bytes = 0;
if (!sgx_file) {
errno = EBADF;
return -1;
}
if (has_offset) {
// Save the current offset, to restore it after the read operation
original_offset = (off_t)sgx_ftell(sgx_file);
if (original_offset == -1) {
errno = convert_sgx_errno(sgx_ferror(sgx_file));
return -1;
}
// Move to the desired location
if (sgx_fseek(sgx_file, offset, SEEK_SET) == -1) {
errno = convert_sgx_errno(sgx_ferror(sgx_file));
return -1;
}
}
// For each element in the vector
for (i = 0; i < iovcnt; i++) {
if (iov[i].iov_len == 0)
continue;
read_result = sgx_fread(iov[i].iov_base, 1, iov[i].iov_len, sgx_file);
number_of_read_bytes += read_result;
if (read_result != iov[i].iov_len) {
if (!sgx_feof(sgx_file)) {
errno = convert_sgx_errno(sgx_ferror(sgx_file));
return -1;
}
}
}
if (has_offset) {
// Restore the position of the cursor
if (sgx_fseek(sgx_file, original_offset, SEEK_SET) == -1) {
errno = convert_sgx_errno(sgx_ferror(sgx_file));
return -1;
}
}
return number_of_read_bytes;
}
size_t
ipfs_write(int fd, const struct iovec *iov, int iovcnt, bool has_offset,
off_t offset)
{
int i;
off_t original_offset = 0;
void *sgx_file = fd2file(fd);
size_t write_result, number_of_written_bytes = 0;
if (!sgx_file) {
errno = EBADF;
return -1;
}
if (has_offset) {
// Save the current offset, to restore it after the read operation
original_offset = (off_t)sgx_ftell(sgx_file);
if (original_offset == -1) {
errno = convert_sgx_errno(sgx_ferror(sgx_file));
return -1;
}
// Move to the desired location
if (sgx_fseek(sgx_file, offset, SEEK_SET) == -1) {
errno = convert_sgx_errno(sgx_ferror(sgx_file));
return -1;
}
}
// For each element in the vector
for (i = 0; i < iovcnt; i++) {
if (iov[i].iov_len == 0)
continue;
write_result = sgx_fwrite(iov[i].iov_base, 1, iov[i].iov_len, sgx_file);
number_of_written_bytes += write_result;
if (write_result != iov[i].iov_len) {
errno = convert_sgx_errno(sgx_ferror(sgx_file));
return -1;
}
}
if (has_offset) {
// Restore the position of the cursor
if (sgx_fseek(sgx_file, original_offset, SEEK_SET) == -1) {
errno = convert_sgx_errno(sgx_ferror(sgx_file));
return -1;
}
}
return number_of_written_bytes;
}
int
ipfs_close(int fd)
{
void *sgx_file;
if (!bh_hash_map_remove(ipfs_file_list, (void *)(intptr_t)fd, NULL,
&sgx_file)) {
errno = EBADF;
return -1;
}
if (sgx_fclose(sgx_file)) {
errno = convert_sgx_errno(sgx_ferror(sgx_file));
return -1;
}
return 0;
}
void *
ipfs_fopen(int fd, const char *filename, int flags)
{
// Mapping back the mode
const char *mode;
bool must_create = (flags & O_CREAT) != 0;
bool must_truncate = (flags & O_TRUNC) != 0;
bool must_append = (flags & O_APPEND) != 0;
bool read_only = (flags & O_ACCMODE) == O_RDONLY;
bool write_only = (flags & O_ACCMODE) == O_WRONLY;
bool read_write = (flags & O_ACCMODE) == O_RDWR;
// The mapping of the mode are described in the table in the official
// specifications:
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/fopen.html
if (read_only)
mode = "r";
else if (write_only && must_create && must_truncate)
mode = "w";
else if (write_only && must_create && must_append)
mode = "a";
else if (read_write && must_create && must_truncate)
mode = "w+";
else if (read_write && must_create && must_append)
mode = "a+";
else if (read_write && must_create)
mode = "w+";
else if (read_write)
mode = "r+";
else
mode = NULL;
// Cannot map the requested access to the SGX IPFS
if (mode == NULL) {
errno = __WASI_ENOTCAPABLE;
return NULL;
}
// Opening the file
void *sgx_file = sgx_fopen_auto_key(filename, mode);
if (sgx_file == NULL) {
errno = convert_sgx_errno(sgx_ferror(sgx_file));
return NULL;
}
if (!bh_hash_map_insert(ipfs_file_list, (void *)(intptr_t)fd, sgx_file)) {
errno = __WASI_ECANCELED;
sgx_fclose(sgx_file);
os_printf("An error occurred while inserting the IPFS file pointer in "
"the map.");
return NULL;
}
return sgx_file;
}
int
ipfs_fflush(int fd)
{
void *sgx_file = fd2file(fd);
if (!sgx_file) {
errno = EBADF;
return EOF;
}
int ret = sgx_fflush(sgx_file);
if (ret == 1) {
errno = convert_sgx_errno(sgx_ferror(sgx_file));
return EOF;
}
return ret;
}
off_t
ipfs_lseek(int fd, off_t offset, int nwhence)
{
off_t new_offset;
void *sgx_file = fd2file(fd);
if (!sgx_file) {
errno = EBADF;
return -1;
}
// Optimization: if the offset is 0 and the whence is SEEK_CUR,
// this is equivalent of a call to ftell.
if (offset == 0 && nwhence == SEEK_CUR) {
int64_t ftell_result = (off_t)sgx_ftell(sgx_file);
if (ftell_result == -1) {
errno = convert_sgx_errno(sgx_ferror(sgx_file));
return -1;
}
return ftell_result;
}
int fseek_result = sgx_fseek(sgx_file, offset, nwhence);
if (fseek_result == 0) {
new_offset = (__wasi_filesize_t)sgx_ftell(sgx_file);
if (new_offset == -1) {
errno = convert_sgx_errno(sgx_ferror(sgx_file));
return -1;
}
return new_offset;
}
else {
// In the case fseek returned an error
int sgx_error = sgx_ferror(sgx_file);
if (sgx_error != EINVAL) {
errno = convert_sgx_errno(sgx_error);
return -1;
}
// We must consider a difference in behavior of sgx_fseek and the POSIX
// fseek. If the cursor is moved beyond the end of the file, sgx_fseek
// returns an error, whereas POSIX fseek accepts the cursor move and
// fill with zeroes the difference for the next write. This
// implementation handle zeroes completion and moving the cursor forward
// the end of the file, but does it now (during the fseek), which is
// different compared to POSIX implementation, that writes zeroes on the
// next write. This avoids the runtime to keep track of the cursor
// manually.
// Assume the error is raised because the cursor is moved beyond the end
// of the file. Try to move the cursor at the end of the file.
if (sgx_fseek(sgx_file, 0, SEEK_END) == -1) {
errno = convert_sgx_errno(sgx_ferror(sgx_file));
return -1;
}
// Write the missing zeroes
char zero = 0;
int64_t number_of_zeroes = offset - sgx_ftell(sgx_file);
if (sgx_fwrite(&zero, 1, number_of_zeroes, sgx_file) == 0) {
errno = convert_sgx_errno(sgx_ferror(sgx_file));
return -1;
}
// Move again at the end of the file
if (sgx_fseek(sgx_file, 0, SEEK_END) == -1) {
errno = convert_sgx_errno(sgx_ferror(sgx_file));
return -1;
}
return offset;
}
}
// The official API does not provide a way to truncate files.
// Only files extension is supported.
int
ipfs_ftruncate(int fd, off_t len)
{
void *sgx_file = fd2file(fd);
if (!sgx_file) {
errno = EBADF;
return -1;
}
off_t original_offset = sgx_ftell(sgx_file);
// Optimization path: if the length is smaller than the offset,
// IPFS does not support truncate to a smaller size.
if (len < original_offset) {
os_printf(
"SGX IPFS does not support truncate files to smaller sizes.\n");
return __WASI_ECANCELED;
}
// Move to the end of the file to determine whether this is
// a file extension or reduction.
if (sgx_fseek(sgx_file, 0, SEEK_END) == -1) {
errno = convert_sgx_errno(sgx_ferror(sgx_file));
return -1;
}
off_t file_size = sgx_ftell(sgx_file);
// Reducing the file space is not supported by IPFS.
if (len < file_size) {
os_printf(
"SGX IPFS does not support truncate files to smaller sizes.\n");
return __WASI_ECANCELED;
}
// Increasing the size is equal to writing from the end of the file
// with null bytes.
char null_byte = 0;
if (sgx_fwrite(&null_byte, 1, len - file_size, sgx_file) == 0) {
errno = convert_sgx_errno(sgx_ferror(sgx_file));
return -1;
}
// Restore the position of the cursor
if (sgx_fseek(sgx_file, original_offset, SEEK_SET) == -1) {
errno = convert_sgx_errno(sgx_ferror(sgx_file));
return -1;
}
return 0;
}
#endif /* end of WASM_ENABLE_SGX_IPFS */

View File

@ -0,0 +1,61 @@
/*
* Copyright (C) 2022 Intel Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*/
#ifndef _LIBC_WASI_SGX_PFS_H
#define _LIBC_WASI_SGX_PFS_H
#include "bh_hashmap.h"
#include "wasmtime_ssp.h"
#ifdef __cplusplus
extern "C" {
#endif
int
ipfs_init();
void
ipfs_destroy();
int
ipfs_posix_fallocate(int fd, off_t offset, size_t len);
size_t
ipfs_read(int fd, const struct iovec *iov, int iovcnt, bool has_offset,
off_t offset);
size_t
ipfs_write(int fd, const struct iovec *iov, int iovcnt, bool has_offset,
off_t offset);
int
ipfs_close(int fd);
void *
ipfs_fopen(int fd, const char *filename, int flags);
int
ipfs_fflush(int fd);
off_t
ipfs_lseek(int fd, off_t offset, int nwhence);
int
ipfs_ftruncate(int fd, off_t len);
/**
* Whether two file descriptors are equal.
*/
inline static bool
fd_equal(int left, int right)
{
return left == right ? true : false;
}
/**
* Returns the file descriptor as a hash value.
*/
inline static uint32
fd_hash(int fd)
{
return (uint32)fd;
}
#ifdef __cplusplus
}
#endif
#endif /* end of _LIBC_WASI_SGX_PFS_H */

View File

@ -7,17 +7,31 @@
#include "platform_api_extension.h"
#include "sgx_rsrv_mem_mngr.h"
#if WASM_ENABLE_SGX_IPFS != 0
#include "sgx_ipfs.h"
#endif
static os_print_function_t print_function = NULL;
int
bh_platform_init()
{
return 0;
int ret = BHT_OK;
#if WASM_ENABLE_SGX_IPFS != 0
ret = ipfs_init();
#endif
return ret;
}
void
bh_platform_destroy()
{}
{
#if WASM_ENABLE_SGX_IPFS != 0
ipfs_destroy();
#endif
}
void *
os_malloc(unsigned size)