/* * 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 #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 // Internal buffer filled with zeroes and used when extending the size of // protected files. #define ZEROES_PADDING_LENGTH 32 * 1024 char zeroes_padding[ZEROES_PADDING_LENGTH] = { 0 }; // 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); } // Writes a given number of zeroes in file at the current offset. // The return value is zero if successful; otherwise non-zero. static int ipfs_write_zeroes(void *sgx_file, size_t len) { int min_count; while (len > 0) { min_count = len < ZEROES_PADDING_LENGTH ? len : ZEROES_PADDING_LENGTH; if (sgx_fwrite(zeroes_padding, 1, min_count, sgx_file) == 0) { errno = convert_sgx_errno(sgx_ferror(sgx_file)); return -1; } len -= min_count; } return 0; } 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_SET) == -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, 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 is similar to the table in the official // specifications: // https://pubs.opengroup.org/onlinepubs/9699919799/functions/fopen.html // Note that POSIX has obtained a file descriptor beforehand. // If opened with a destructive mode ("w" or "w+"), the truncate operation // already occurred and must not be repeated because this will invalidate // the file descriptor obtained by POSIX. Therefore, we do NOT map to the // modes that truncate the file ("w" and "w+"). Instead, we map to a // non-destructive mode ("r+"). if (read_only) mode = "r"; else if (write_only && must_create && must_truncate) // Rather than "w", we map to a non-destructive mode mode = "r+"; else if (write_only && must_create && must_append) mode = "a"; else if (read_write && must_create && must_append) mode = "a+"; else if (read_write) // Rather than "w+", we map to a non-destructive mode mode = "r+"; else mode = NULL; // Cannot map the requested access to the SGX IPFS if (mode == NULL) { errno = __WASI_ENOTCAPABLE; return NULL; } // Determine the symbolic link of the file descriptor, because IPFS does not // support opening a relative path to a file descriptor (i.e., openat). // Using the symbolic link in /proc/self allows to retrieve the same path as // opened by the initial openat and respects the chroot of WAMR. size_t ret; char symbolic_path[32]; ret = snprintf(symbolic_path, sizeof(symbolic_path), "/proc/self/fd/%d", fd); if (ret >= sizeof(symbolic_path)) { errno = ENAMETOOLONG; return NULL; } // Resolve the symbolic link to real absolute path, because IPFS can only // open a file with a same file name it was initially created. Otherwise, // IPFS throws SGX_ERROR_FILE_NAME_MISMATCH. char real_path[PATH_MAX] = { 0 }; ret = readlink(symbolic_path, real_path, PATH_MAX - 1); if (ret == -1) return NULL; // Opening the file using the real path void *sgx_file = sgx_fopen_auto_key(real_path, 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 cursor_current_location; 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) { cursor_current_location = (off_t)sgx_ftell(sgx_file); if (cursor_current_location == -1) { errno = convert_sgx_errno(sgx_ferror(sgx_file)); return -1; } return cursor_current_location; } int fseek_result = sgx_fseek(sgx_file, offset, nwhence); if (fseek_result == 0) { off_t new_offset = (off_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. // If the whence is the current cursor location, retrieve it if (nwhence == SEEK_CUR) { cursor_current_location = (off_t)sgx_ftell(sgx_file); } // 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; } // Compute the number of zeroes to append. int64_t number_of_zeroes; switch (nwhence) { case SEEK_SET: number_of_zeroes = offset - sgx_ftell(sgx_file); break; case SEEK_END: number_of_zeroes = offset; break; case SEEK_CUR: number_of_zeroes = cursor_current_location + offset - sgx_ftell(sgx_file); break; default: errno = EINVAL; return -1; } // Write the missing zeroes if (ipfs_write_zeroes(sgx_file, number_of_zeroes) != 0) { 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. if (ipfs_write_zeroes(sgx_file, len - file_size) != 0) { 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 */