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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1211 additions and 4 deletions

View File

@ -413,6 +413,15 @@ jobs:
./build.sh
./run.sh
- name: Build Sample [file]
if: ${{ matrix.light == 'green' }}
run: |
cd samples/file
mkdir build && cd build
cmake ..
cmake --build . --config Release --parallel 4
./src/iwasm -f wasm-app/file.wasm -d .
- name: Build Sample [multi-thread]
if: ${{ matrix.light == 'green' }}
run: |

View File

@ -356,6 +356,15 @@ jobs:
./build.sh
./run.sh
- name: Build Sample [file]
if: ${{ matrix.light == 'green' }}
run: |
cd samples/file
mkdir build && cd build
cmake ..
cmake --build . --config Release --parallel 4
./src/iwasm -f wasm-app/file.wasm -d .
- name: Build Sample [multi-thread]
if: ${{ matrix.light == 'green' }}
run: |

View File

@ -140,6 +140,7 @@ jobs:
# "-DWAMR_BUILD_SIMD=1",
"-DWAMR_BUILD_TAIL_CALL=1",
"-DWAMR_DISABLE_HW_BOUND_CHECK=1",
"-DWAMR_BUILD_SGX_IPFS=1",
]
os: [ubuntu-20.04]
platform: [linux-sgx]
@ -363,6 +364,15 @@ jobs:
./build.sh
./run.sh
- name: Build Sample [file]
if: ${{ matrix.light == 'green' }}
run: |
cd samples/file
mkdir build && cd build
cmake ..
cmake --build . --config Release --parallel 4
./src/iwasm -f wasm-app/file.wasm -d .
- name: Build Sample [multi-thread]
if: ${{ matrix.light == 'green' }}
run: |

View File

@ -151,6 +151,7 @@ The WAMR [samples](./samples) integrate the iwasm VM core, application manager a
- [**basic**](./samples/basic): Demonstrating how to use runtime exposed API's to call WASM functions, how to register native functions and call them, and how to call WASM function from native function.
- **[simple](./samples/simple/README.md)**: The runtime is integrated with most of the WAMR APP libraries, and a few WASM applications are provided for testing the WAMR APP API set. It uses **built-in libc** and executes apps in **interpreter** mode by default.
- **[file](./samples/file/README.md)**: Demonstrating the supported file interaction API of WASI. This sample can also demonstrate the SGX IPFS (Intel Protected File System), enabling an enclave to seal and unseal data at rest.
- **[littlevgl](./samples/littlevgl/README.md)**: Demonstrating the graphic user interface application usage on WAMR. The whole [LVGL](https://github.com/lvgl/lvgl) 2D user graphic library and the UI application are built into WASM application. It uses **WASI libc** and executes apps in **AOT mode** by default.
- **[gui](./samples/gui/README.md)**: Move the [LVGL](https://github.com/lvgl/lvgl) library into the runtime and define a WASM application interface by wrapping the littlevgl API. It uses **WASI libc** and executes apps in **interpreter** mode by default.
- **[multi-thread](./samples/multi-thread/)**: Demonstrating how to run wasm application which creates multiple threads to execute wasm functions concurrently, and uses mutex/cond by calling pthread related API's.

View File

@ -287,3 +287,7 @@ if (WAMR_BUILD_STACK_GUARD_SIZE GREATER 0)
add_definitions (-DWASM_STACK_GUARD_SIZE=${WAMR_BUILD_STACK_GUARD_SIZE})
message (" Custom stack guard size: " ${WAMR_BUILD_STACK_GUARD_SIZE})
endif ()
if (WAMR_BUILD_SGX_IPFS EQUAL 1)
add_definitions (-DWASM_ENABLE_SGX_IPFS=1)
message (" SGX IPFS enabled")
endif ()

View File

@ -407,4 +407,8 @@
#define WASM_ENABLE_REF_TYPES 0
#endif
#ifndef WASM_ENABLE_SGX_IPFS
#define WASM_ENABLE_SGX_IPFS 0
#endif
#endif /* end of _CONFIG_H_ */

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)

View File

@ -195,6 +195,57 @@ typedef enum EcallCmd {
};
```
SGX Intel Protected File System
-------------------------------
Intel SGX introduced a feature called [Intel Protection File System Library (IPFS)](https://www.intel.com/content/www/us/en/developer/articles/technical/overview-of-intel-protected-file-system-library-using-software-guard-extensions.html) to create, operate and delete files inside the enclave.
WAMR supports the mapping of IPFS on WASI functions related to file interactions, providing seamless persistence with confidentiality and integrity to the hosted WebAssembly applications in the enclave.
The usage of SGX IPFS is an optional feature.
To opt-in, the support of IPFS requires the following changes:
- set the flag `WAMR_BUILD_SGX_IPFS=1` when running `cmake`,
- the enclave must be linked with the trusted IPFS library (`-lsgx_tprotected_fs`),
- the application outside of the enclave must be linked with the untrusted IPFS library (`-lsgx_uprotected_fs`),
- the EDL file must include the following import statement:
```edl
from "sgx_tprotected_fs.edl" import *;
```
When using the [enclave-sample](../product-mini/platforms/linux-sgx/enclave-sample/) project, setting the flag `WAMR_BUILD_SGX_IPFS=1` when running `cmake` enables these changes automatically.
### Verification of SGX IPFS
One can observe the usage of IPFS by running the [file sample](../samples/file/) WebAssembly application.
Enabling the SGX IPFS on this sample project leads to the generation of an encrypted text file.
### Mapping of WASI/POSIX to IPFS
This table summarizes how WASI is mapped to POSIX and IPFS.
Since IPFS is a subset of the WASI/POSIX, emulation is performed to fill the missing implementation.
| WASI | POSIX | IPFS |
|------------------------|-------------------|-------------------------------------------------------------------------------------------------------------------------|
| `fd_read` | `readv` | `sgx_fread` |
| `fd_write` | `writev` | `sgx_fwrite` |
| `fd_close` | `close` | `sgx_fclose` |
| `path_open` | `openat` | `sgx_fopen` |
| `fd_datasync` | `fsync` | `sgx_fflush` |
| `fd_tell` | `lseek` | `sgx_ftell` |
| `fd_filestat_set_size` | `ftruncate` | Shrinking files is not supported, nor emulated. Extending files is emulated using `sgx_fseek`/`sgx_ftell`/`sgx_fwrite`. |
| `fd_seek` | `lseek` | The POSIX and IPFS behaviors differ. Emulated using `sgx_fseek`/`sgx_ftell`/`sgx_fwrite`. |
| `fd_pwrite` | `pwrite` | Not supported. Emulated using `sgx_fseek`/`sgx_ftell`/`sgx_fwrite`. |
| `fd_pread` | `pread` | Not supported. Emulated using `sgx_fseek`/`sgx_ftell`/`sgx_fread`. |
| `fd_allocate` | `posix_fallocate` | Not supported. Emulated using `sgx_fseek`/`sgx_ftell`/`sgx_fwrite`/`sgx_fflush`. |
### Performance overheads
Many benchmarks have assessed the overheads caused by IPFS through WASI functions using Twine, an early and academic adaptation of WAMR in Intel SGX with WASI support.
The results can be found in [this paper](https://arxiv.org/abs/2103.15860).
### Limitations
The threat model and the limitations of SGX IPFS can be found in [the official documentation](https://www.intel.com/content/dam/develop/external/us/en/documents/overviewofintelprotectedfilesystemlibrary.pdf).
Others
------

View File

@ -84,6 +84,11 @@ if (NOT DEFINED WAMR_BUILD_SIMD)
set (WAMR_BUILD_SIMD 0)
endif ()
if (NOT DEFINED WAMR_BUILD_SGX_IPFS)
# Disable SGX IPFS by default
set (WAMR_BUILD_SGX_IPFS 0)
endif ()
if (COLLECT_CODE_COVERAGE EQUAL 1)
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage")
endif ()
@ -117,3 +122,23 @@ else()
OUTPUT_VARIABLE cmdOutput
)
endif()
if (WAMR_BUILD_SGX_IPFS EQUAL 1)
execute_process(
COMMAND bash -c "sed -i -E 's/^#define SGX_IPFS 0/#define SGX_IPFS 1/g' ${CMAKE_CURRENT_SOURCE_DIR}/enclave-sample/Enclave/Enclave.edl"
OUTPUT_VARIABLE cmdOutput
)
execute_process(
COMMAND bash -c "sed -i -E 's/^SGX_IPFS = 0/SGX_IPFS = 1/g' ${CMAKE_CURRENT_SOURCE_DIR}/enclave-sample/Makefile"
OUTPUT_VARIABLE cmdOutput
)
else()
execute_process(
COMMAND bash -c "sed -i -E 's/^#define SGX_IPFS 1/#define SGX_IPFS 0/g' ${CMAKE_CURRENT_SOURCE_DIR}/enclave-sample/Enclave/Enclave.edl"
OUTPUT_VARIABLE cmdOutput
)
execute_process(
COMMAND bash -c "sed -i -E 's/^SGX_IPFS = 1/SGX_IPFS = 0/g' ${CMAKE_CURRENT_SOURCE_DIR}/enclave-sample/Makefile"
OUTPUT_VARIABLE cmdOutput
)
endif()

View File

@ -4,6 +4,7 @@
*/
#define LIB_RATS 0
#define SGX_IPFS 0
enclave {
from "sgx_tstdc.edl" import *;
@ -12,6 +13,9 @@ enclave {
#if LIB_RATS != 0
from "rats.edl" import *;
#endif
#if SGX_IPFS != 0
from "sgx_tprotected_fs.edl" import *;
#endif
trusted {
/* define ECALLs here. */

View File

@ -9,6 +9,9 @@ SGX_ARCH ?= x64
SGX_DEBUG ?= 0
SPEC_TEST ?= 0
# This variable is automatically set by CMakeLists.txt
SGX_IPFS = 0
VMLIB_BUILD_DIR ?= $(CURDIR)/../build
LIB_RATS_SRC ?= $(VMLIB_BUILD_DIR)/_deps/librats-build
LIB_RATS := $(shell if [ -d $(LIB_RATS_SRC) ]; then echo 1; else echo 0; fi)
@ -106,6 +109,12 @@ else
Trts_Library_Name := sgx_trts
Service_Library_Name := sgx_tservice
endif
ifeq ($(SGX_IPFS), 1)
Intel_Ipfs_Trusted_Flag = -lsgx_tprotected_fs
App_Link_Flags += -lsgx_uprotected_fs
endif
Crypto_Library_Name := sgx_tcrypto
WAMR_ROOT := $(CURDIR)/../../../../
@ -139,7 +148,7 @@ endif
Enclave_Cpp_Flags := $(Enclave_C_Flags) -std=c++11 -nostdinc++
Enclave_Link_Flags := $(SGX_COMMON_CFLAGS) -Wl,--no-undefined -nostdlib -nodefaultlibs -nostartfiles -L$(SGX_LIBRARY_PATH) ${Rats_Lib_Link_Dirs} \
-Wl,--whole-archive -l$(Trts_Library_Name) ${Rats_Lib_Link_libs} -Wl,--no-whole-archive \
-Wl,--whole-archive -l$(Trts_Library_Name) ${Rats_Lib_Link_libs} $(Intel_Ipfs_Trusted_Flag) -Wl,--no-whole-archive \
-Wl,--start-group -lsgx_tstdc -lsgx_tcxx -lsgx_pthread -lsgx_tkey_exchange -l$(Crypto_Library_Name) -l$(Service_Library_Name) -lsgx_dcap_tvl -Wl,--end-group \
-Wl,-Bstatic -Wl,-Bsymbolic -Wl,--no-undefined \
-Wl,-pie,-eenclave_entry -Wl,--export-dynamic \
@ -217,7 +226,6 @@ $(App_Name): App/Enclave_u.o $(App_Cpp_Objects) libvmlib_untrusted.a
######## Enclave Objects ########
Enclave/Enclave_t.c: $(SGX_EDGER8R) Enclave/Enclave.edl librats
@cd Enclave && $(SGX_EDGER8R) --trusted ../Enclave/Enclave.edl $(Enclave_Edl_Search_Path)
@echo "GEN => $@"

View File

@ -0,0 +1,9 @@
# Copyright (C) 2022 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
cmake_minimum_required(VERSION 3.0)
project(file)
################ wasm application ###############
add_subdirectory(src)
add_subdirectory(wasm-app)

112
samples/file/README.md Normal file
View File

@ -0,0 +1,112 @@
# "file" sample introduction
This sample demonstrates the supported file interaction API of WASI.
This sample can also demonstrate the SGX IPFS (Intel Protected File System), enabling an enclave to seal and unseal data at rest.
## Preparation
Please install WASI SDK, download the [wasi-sdk release](https://github.com/CraneStation/wasi-sdk/releases) and extract the archive to default path `/opt/wasi-sdk`.
For testing with SGX IPFS, follow the instructions in [the documentation of SGX for WAMR](../../doc/linux_sgx.md#sgx-intel-protected-file-system).
## Build the sample
```bash
mkdir build
cd build
cmake ..
make
```
The WebAssembly application is the file located at `wasm-app/file.wasm`.
## Run workload
Either use [iwasm-sample](../../product-mini/platforms/linux/) for Linux, or [enclave-sample](../../product-mini/platforms/linux-sgx/enclave-sample/) for Intel SGX to run the sample, with the argument to allow the file system interaction with the current folder (`--dir=.`).
The output with Linux and POSIX is like:
```bash
Opening a file..
[Test] File opening passed.
Writing to the file..
[Test] File writing passed.
Moving the cursor to the start of the file..
Reading from the file, up to 1000 characters..
Text read: Hello, world!
[Test] File reading passed.
Determine whether we reach the end of the file..
Is the end of file? 1
[Test] End of file detection passed.
Getting the plaintext size..
The plaintext size is 13.
[Test] Retrieving file offset passed.
Force actual write of all the cached data to the disk..
[Test] Retrieving file offset passed.
Writing 5 characters at offset 7..
File current offset: 13
[Test] Writing at specified offset passed.
Reading 5 characters at offset 7..
Text read: James
File current offset: 13
[Test] Reading at specified offset passed.
Allocate more space to the file..
File current offset: 13
Moving to the end..
File current offset: 23
[Test] Allocation or more space passed.
Extend the file size of 10 bytes using ftruncate..
File current offset: 23
Moving to the end..
File current offset: 33
[Test] Extension of the file size passed.
Closing from the file..
[Test] Closing file passed.
Getting the size of the file on disk..
The file size is 33.
All the tests passed!
```
The output with SGX and IPFS is like:
```bash
Opening a file..
[Test] File opening passed.
Writing to the file..
[Test] File writing passed.
Moving the cursor to the start of the file..
Reading from the file, up to 1000 characters..
Text read: Hello, world!
[Test] File reading passed.
Determine whether we reach the end of the file..
Is the end of file? 1
[Test] End of file detection passed.
Getting the plaintext size..
The plaintext size is 13.
[Test] Retrieving file offset passed.
Force actual write of all the cached data to the disk..
[Test] Retrieving file offset passed.
Writing 5 characters at offset 7..
File current offset: 13
[Test] Writing at specified offset passed.
Reading 5 characters at offset 7..
Text read: James
File current offset: 13
[Test] Reading at specified offset passed.
Allocate more space to the file..
File current offset: 23
Moving to the end..
File current offset: 23
[Test] Allocation or more space passed.
Extend the file size of 10 bytes using ftruncate..
File current offset: 23
Moving to the end..
File current offset: 33
[Test] Extension of the file size passed.
Closing from the file..
[Test] Closing file passed.
Getting the size of the file on disk..
The file size is 4096.
All the tests passed!
```
For SGX IPFS, refer to [SGX Intel Protected File System](../../doc/linux_sgx.md#sgx-intel-protected-file-system) for more details.

View File

@ -0,0 +1,87 @@
# Copyright (C) 2022 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
cmake_minimum_required (VERSION 3.0)
if (NOT WAMR_BUILD_PLATFORM STREQUAL "windows")
project (iwasm)
else()
project (iwasm C ASM)
enable_language (ASM_MASM)
endif()
################ runtime settings ################
string (TOLOWER ${CMAKE_HOST_SYSTEM_NAME} WAMR_BUILD_PLATFORM)
if (APPLE)
add_definitions(-DBH_PLATFORM_DARWIN)
endif ()
# Reset default linker flags
set (CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
set (CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "")
# WAMR features switch
# Set WAMR_BUILD_TARGET, currently values supported:
# "X86_64", "AMD_64", "X86_32", "AARCH64[sub]", "ARM[sub]", "THUMB[sub]",
# "MIPS", "XTENSA", "RISCV64[sub]", "RISCV32[sub]"
if (NOT DEFINED WAMR_BUILD_TARGET)
if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm64|aarch64)")
set (WAMR_BUILD_TARGET "AARCH64")
elseif (CMAKE_SYSTEM_PROCESSOR STREQUAL "riscv64")
set (WAMR_BUILD_TARGET "RISCV64")
elseif (CMAKE_SIZEOF_VOID_P EQUAL 8)
# Build as X86_64 by default in 64-bit platform
set (WAMR_BUILD_TARGET "X86_64")
elseif (CMAKE_SIZEOF_VOID_P EQUAL 4)
# Build as X86_32 by default in 32-bit platform
set (WAMR_BUILD_TARGET "X86_32")
else ()
message(SEND_ERROR "Unsupported build target platform!")
endif ()
endif ()
if (NOT CMAKE_BUILD_TYPE)
set (CMAKE_BUILD_TYPE Release)
endif ()
set (WAMR_BUILD_INTERP 1)
set (WAMR_BUILD_AOT 1)
set (WAMR_BUILD_JIT 0)
set (WAMR_BUILD_LIBC_BUILTIN 1)
if (NOT MSVC)
set (WAMR_BUILD_LIBC_WASI 1)
endif ()
if (NOT MSVC)
# linker flags
set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pie -fPIE")
if (NOT (CMAKE_C_COMPILER MATCHES ".*clang.*" OR CMAKE_C_COMPILER_ID MATCHES ".*Clang"))
set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections")
endif ()
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wformat -Wformat-security")
if (WAMR_BUILD_TARGET MATCHES "X86_.*" OR WAMR_BUILD_TARGET STREQUAL "AMD_64")
if (NOT (CMAKE_C_COMPILER MATCHES ".*clang.*" OR CMAKE_C_COMPILER_ID MATCHES ".*Clang"))
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mindirect-branch-register")
endif ()
endif ()
endif ()
# build out vmlib
set (WAMR_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../../..)
include (${WAMR_ROOT_DIR}/build-scripts/runtime_lib.cmake)
add_library(vmlib ${WAMR_RUNTIME_LIB_SOURCE})
################ application related ################
include_directories(${CMAKE_CURRENT_LIST_DIR})
include (${SHARED_DIR}/utils/uncommon/shared_uncommon.cmake)
add_executable (iwasm main.c ${UNCOMMON_SHARED_SOURCE})
if (APPLE)
target_link_libraries (iwasm vmlib -lm -ldl -lpthread)
else ()
target_link_libraries (iwasm vmlib -lm -ldl -lpthread -lrt)
endif ()

117
samples/file/src/main.c Normal file
View File

@ -0,0 +1,117 @@
/*
* Copyright (C) 2022 Intel Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*/
#include "wasm_export.h"
#include "bh_read_file.h"
void
print_usage(void)
{
fprintf(stdout, "Required arguments:\r\n");
fprintf(stdout, " -f [path of wasm file] \n");
fprintf(stdout, " -d [path of host directory] \n");
}
int
main(int argc, char *argv_main[])
{
static char global_heap_buf[512 * 1024];
char *buffer, error_buf[128];
const char *wasm_path = NULL, *wasi_dir = NULL;
int opt;
wasm_module_t module = NULL;
wasm_module_inst_t module_inst = NULL;
wasm_exec_env_t exec_env = NULL;
uint32 buf_size, stack_size = 8092, heap_size = 8092;
uint32_t wasm_buffer = 0;
RuntimeInitArgs init_args;
memset(&init_args, 0, sizeof(RuntimeInitArgs));
while ((opt = getopt(argc, argv_main, "hf:d:")) != -1) {
switch (opt) {
case 'f':
wasm_path = optarg;
break;
case 'd':
wasi_dir = optarg;
break;
case 'h':
print_usage();
return 0;
case '?':
print_usage();
return 0;
}
}
if (wasm_path == NULL || wasi_dir == NULL) {
print_usage();
return 0;
}
init_args.mem_alloc_type = Alloc_With_Pool;
init_args.mem_alloc_option.pool.heap_buf = global_heap_buf;
init_args.mem_alloc_option.pool.heap_size = sizeof(global_heap_buf);
if (!wasm_runtime_full_init(&init_args)) {
printf("Init runtime environment failed.\n");
return -1;
}
buffer = bh_read_file_to_buffer(wasm_path, &buf_size);
if (!buffer) {
printf("Open wasm app file [%s] failed.\n", wasm_path);
goto fail;
}
module = wasm_runtime_load(buffer, buf_size, error_buf, sizeof(error_buf));
if (!module) {
printf("Load wasm module failed. error: %s\n", error_buf);
goto fail;
}
wasm_runtime_set_wasi_args_ex(module, &wasi_dir, 1, NULL, 0, NULL, 0, NULL,
0, 0, 1, 2);
module_inst = wasm_runtime_instantiate(module, stack_size, heap_size,
error_buf, sizeof(error_buf));
if (!module_inst) {
printf("Instantiate wasm module failed. error: %s\n", error_buf);
goto fail;
}
exec_env = wasm_runtime_create_exec_env(module_inst, stack_size);
if (!exec_env) {
printf("Create wasm execution environment failed.\n");
goto fail;
}
if (wasm_application_execute_main(module_inst, 0, NULL)) {
printf("Main wasm function successfully finished.\n");
}
else {
printf("call wasm function main failed. error: %s\n",
wasm_runtime_get_exception(module_inst));
goto fail;
}
fail:
if (exec_env)
wasm_runtime_destroy_exec_env(exec_env);
if (module_inst) {
if (wasm_buffer)
wasm_runtime_module_free(module_inst, wasm_buffer);
wasm_runtime_deinstantiate(module_inst);
}
if (module)
wasm_runtime_unload(module);
if (buffer)
BH_FREE(buffer);
wasm_runtime_destroy();
return 0;
}

View File

@ -0,0 +1,26 @@
# Copyright (C) 2022 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
cmake_minimum_required(VERSION 3.0)
project(wasm-app)
set (WAMR_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../..)
if (APPLE)
set (HAVE_FLAG_SEARCH_PATHS_FIRST 0)
set (CMAKE_C_LINK_FLAGS "")
set (CMAKE_CXX_LINK_FLAGS "")
endif ()
set (CMAKE_SYSTEM_PROCESSOR wasm32)
if (NOT DEFINED WASI_SDK_DIR)
set (WASI_SDK_DIR "/opt/wasi-sdk")
endif ()
set (CMAKE_C_COMPILER_TARGET "wasm32-wasi")
set (CMAKE_C_COMPILER "${WASI_SDK_DIR}/bin/clang")
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -Wno-unused-command-line-argument")
add_executable(file.wasm main.c)
target_link_libraries(file.wasm)

View File

@ -0,0 +1,132 @@
/*
* Copyright (C) 2019 Intel Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#define PATH_TEST_FILE "test.txt"
#define FILE_TEXT "Hello, world!"
#define WORLD_OFFSET 7
#define NAME_REPLACMENT "James"
#define NAME_REPLACMENT_LEN (sizeof(NAME_REPLACMENT) - 1)
#define ADDITIONAL_SPACE 10
int
main(int argc, char **argv)
{
FILE *file;
const char *text = FILE_TEXT;
char buffer[1000];
int ret;
// Test: File opening (fopen)
printf("Opening a file..\n");
file = fopen(PATH_TEST_FILE, "w+");
if (file == NULL) {
printf("Error! errno: %d\n", errno);
}
assert(file != NULL);
printf("[Test] File opening passed.\n");
// Test: Writing to a file (fprintf)
printf("Writing to the file..\n");
ret = fprintf(file, "%s", text);
assert(ret == strlen(text));
printf("[Test] File writing passed.\n");
// Test: Reading from a file (fseek)
printf("Moving the cursor to the start of the file..\n");
ret = fseek(file, 0, SEEK_SET);
assert(ret == 0);
printf("Reading from the file, up to 1000 characters..\n");
fread(buffer, 1, sizeof(buffer), file);
printf("Text read: %s\n", buffer);
assert(strncmp(text, buffer, strlen(text)) == 0);
printf("[Test] File reading passed.\n");
// Test: end of file detection (feof)
printf("Determine whether we reach the end of the file..\n");
int is_end_of_file = feof(file);
printf("Is the end of file? %d\n", is_end_of_file);
assert(is_end_of_file == 1);
printf("[Test] End of file detection passed.\n");
// Test: retrieving file offset (ftell)
printf("Getting the plaintext size..\n");
long plaintext_size = ftell(file);
printf("The plaintext size is %ld.\n", plaintext_size);
assert(plaintext_size == 13);
printf("[Test] Retrieving file offset passed.\n");
// Test: persist changes on disk (fflush)
printf("Force actual write of all the cached data to the disk..\n");
ret = fflush(file);
assert(ret == 0);
printf("[Test] Retrieving file offset passed.\n");
// Test: writing at specified offset (pwrite)
printf("Writing 5 characters at offset %d..\n", WORLD_OFFSET);
ret = pwrite(fileno(file), NAME_REPLACMENT, NAME_REPLACMENT_LEN,
WORLD_OFFSET);
printf("File current offset: %ld\n", ftell(file));
assert(ret == NAME_REPLACMENT_LEN);
assert(ftell(file) == strlen(FILE_TEXT));
printf("[Test] Writing at specified offset passed.\n");
// Test: reading at specified offset (pread)
printf("Reading %ld characters at offset %d..\n", NAME_REPLACMENT_LEN,
WORLD_OFFSET);
buffer[NAME_REPLACMENT_LEN] = '\0';
pread(fileno(file), buffer, NAME_REPLACMENT_LEN, WORLD_OFFSET);
printf("Text read: %s\n", buffer);
printf("File current offset: %ld\n", ftell(file));
assert(strcmp(NAME_REPLACMENT, buffer) == 0);
assert(ftell(file) == strlen(FILE_TEXT));
printf("[Test] Reading at specified offset passed.\n");
// Test: allocate more space to the file (posix_fallocate)
printf("Allocate more space to the file..\n");
posix_fallocate(fileno(file), ftell(file), ADDITIONAL_SPACE);
printf("File current offset: %ld\n", ftell(file));
printf("Moving to the end..\n");
fseek(file, 0, SEEK_END);
printf("File current offset: %ld\n", ftell(file));
assert(ftell(file) == strlen(text) + ADDITIONAL_SPACE);
printf("[Test] Allocation or more space passed.\n");
// Test: allocate more space to the file (ftruncate)
printf("Extend the file size of 10 bytes using ftruncate..\n");
ftruncate(fileno(file), ftell(file) + 10);
assert(ftell(file) == strlen(text) + ADDITIONAL_SPACE);
printf("File current offset: %ld\n", ftell(file));
printf("Moving to the end..\n");
fseek(file, 0, SEEK_END);
printf("File current offset: %ld\n", ftell(file));
assert(ftell(file) == strlen(text) + 2 * ADDITIONAL_SPACE);
printf("[Test] Extension of the file size passed.\n");
// Test: closing the file (fclose)
printf("Closing from the file..\n");
ret = fclose(file);
assert(ret == 0);
printf("[Test] Closing file passed.\n");
// Display some debug information
printf("Getting the size of the file on disk..\n");
struct stat st;
stat(PATH_TEST_FILE, &st);
printf("The file size is %lld.\n", st.st_size);
printf("All the tests passed!\n");
return 0;
}