Secure Resource Sharing for Embedded Protected Module Architectures
Jo Van Bulck, Job Noorman, Jan Tobias Mühlberg and Frank Piessens
This page provides the source code and usage examples of the protected file system, presented at the 9th WISTP International Conference on Information Security Theory and Practice (WISTP'15).
Below, we repeat the abstract of the paper and provide an example program to demonstrate our protected file system API. Finally, we clarify the implementation of the access control layer by means of short code snippets.
Abstract
Low-end embedded devices and the Internet of Things (IoT) are becoming increasingly important for our lives. They are being used in domains such as infrastructure management, and medical and healthcare systems, where business interests and our security and privacy are at stake. Yet, security mechanisms have been appallingly neglected on many IoT platforms. In this paper we present a secure access control mechanism for extremely lightweight embedded microcontrollers. Being based on Sancus, a hardware-only Trusted Computing Base and Protected Module Architecture for the embedded domain, our mechanism allows for multiple software modules on an IoT-node to securely share resources. We implement and evaluate our approach for two application scenarios, a shared memory system and a shared flash drive. Our implementation is based on a Sancus-enabled TI MSP430 microcontroller. We show that our mechanism can give high security guarantees at small runtime overheads and a moderately increased size of the Trusted Computing Base.
Example Client Program
In this section we provide a minimal example that uses our
SFS file system API. The program simple-test.c listed
below defines two Sancus
modules, named clientA and clientB. ClientA first creates a new
empty file and automatically becomes the "owner" of this file. ClientA
thereafter writes out some data bytes and assigns read-only permissions for clientB.
Next, control is transferred to clientB, which successfully opens the file,
reads the data and returns. ClientA then revokes the access permissions
for clientB on the logical file and transfers control to clientB once more. Of course,
this time access should be denied. Finally, clientA closes and removes the logical file.
#include "../common.h" // utility functions #include <sancus/sm_support.h> // Sancus support #include "../sfs/sfs.h" // Sancus File System #define THE_FILENAME 'a' #define THE_INIT_FILESIZE 100 #define THE_TEST_DATA "Lorem ipsum dolor sit amet, consectetur adipiscing elit." DECLARE_SM(clientB, 0x1234); #define B "[clientB] " void SM_ENTRY("clientB") run_clientB(void) { printf_int_int(B "Hi, I have id %d and was called from %d\n", sancus_get_self_id(), sancus_get_caller_id()); int fd, i = 0; char cur; puts(B "attempting to open the file and read the data"); fd = sfs_open(THE_FILENAME, SFS_READ, SFS_OPEN_EXISTING); if (fd < 0) { puts(B "access to the file has been denied"); return; } printf_str(B "the chars are: \""); while((cur = sfs_getc(fd)) != EOF) printf_int("%c", cur); printf_str("\"\n"); sfs_close(fd); } DECLARE_SM(clientA, 0x1234); #define A "[clientA] " void SM_ENTRY("clientA") run_clientA(void) { printf_int_int(A "Hi, I have id %d and was called from %d\n", sancus_get_self_id(), sancus_get_caller_id()); int fd, i; char *str = THE_TEST_DATA; sm_id idB = sancus_get_id(clientB.public_start); puts(A "creating the file and writing the data"); fd = sfs_open(THE_FILENAME, SFS_CREATOR, THE_INIT_FILESIZE); for(i = 0; str[i]; i++) sfs_putc(fd, str[i]); puts(A "assigning read-only permissions for clientB and dumping ACLs"); sfs_chmod(THE_FILENAME, idB, SFS_READ); sfs_dump(); run_clientB(); puts(A "revoking permissions for clientB and dumping ACLs"); sfs_chmod(THE_FILENAME, idB, SFS_NIL); sfs_dump(); run_clientB(); puts(A "closing and removing the logical file"); sfs_close(fd); sfs_remove(THE_FILENAME); } int main() { WDTCTL = WDTPW | WDTHOLD; uart_init(); puts("\n---------------\nmain started"); sancus_enable(&sfs); sancus_enable(&clientA); sancus_enable(&clientB); run_clientA(); puts("[main] exiting\n-----------------"); while(1) {} }
To compile and run the above example, one should first
install Sancus.
The following Makefile builds the test program and uploads it to the FPGA. The desired
CFS
back-end can be chosen at compile time through the SFS_BACKEND constant.
The SHM back-end operates on a Sancus-protected memory buffer and thus
realises a form of protected shared memory. The COFFEE back-end corresponds
to
Contiki's Coffee file system, optimised for embedded flash drives.
SANCUS_SUPPORT_DIR = /usr/local/share/sancus-support DEVICE = /dev/ttyUSB0 VENDOR_KEY = 4078d505d82099ba CC = sancus-cc LD = sancus-ld CRYPTO = sancus-crypto LOAD = sancus-loader RM = rm -f DEBUG_LEVEL = -DSFS_WARNING #-DNODEBUG #-DNOCOLOR #-DCOFFEE_DEBUG -DFLASH_DEBUG CFS_FORMAT = -DNO_CFS_FORMAT # comment to format the flash drive on boot CFLAGS = -I$(SANCUS_SUPPORT_DIR)/include/ -g --verbose -Wfatal-errors $(DEBUG_LEVEL) $(CFS_FORMAT) LDFLAGS = --standalone --verbose --ram-size 24K --rom-size 32K LIBS = -L$(SANCUS_SUPPORT_DIR)/lib -ldev-uart -ldev-spi CRYPTOFLAGS = --key $(VENDOR_KEY) LOADFLAGS = -device $(DEVICE) -baudrate 115200 COFFEE = ../sfs/cfs/cfs-coffee.o SHM = ../sfs/shm/shared-mem.o ../sfs/shm/my_malloc.o SFS_BACKEND = $(COFFEE) #$(SHM) SFS = ../sfs/sfs-ram.o $(SFS_BACKEND) OBJECTS = simple-test.o ../common.o $(SFS) TARGET = sfs-test.elf TARGET_NO_MACS = $(TARGET)-no-macs $(TARGET): $(TARGET_NO_MACS) $(CRYPTO) $(CRYPTOFLAGS) -o $@ $< $(TARGET_NO_MACS): $(OBJECTS) $(LD) $(LDFLAGS) -o $@ $^ $(LIBS) .PHONY: load load: $(TARGET) $(LOAD) $(LOADFLAGS) $< .PHONY: clean clean: $(RM) $(TARGET) $(TARGET_NO_MACS) $(OBJECTS)
The output of the test program looks as follows:

Access Control Layer Implementation
In this section we illustrate the
implementation of the generic front-end access
control layer by means of short code snippets. The front-end is conceived as a wrapper
implementation that associates an Access Control List (ACL)
of (smID, permission_flags) pairs
per logical file to validate the caller's permissions before passing the call to the
back-end. Logical files are represented by a linked list of OPEN_FILE
structs that each hold the file name and a linked list of FILE_PERM structs
that make up the ACL. Once a file has been successfully opened, the corresponding
FILE_PERM can be looked up quickly via a file-descriptor indexed array
fd_cache.
Access control is ensured through the two macros listed below. The CHK_FD
macro checks whether a specified file descriptor is within bounds and belongs to a
specified SM, whereas the CHK_PERM macro can be used to verify bitmask-permissions.
#define CHK_FD(fd, sm) \ if ((fd < 0 || fd >= MAX_NB_OPEN_FILES) || !fd_cache[fd] || fd_cache[fd]->sm_id != sm) \ { \ printerror_int_int("the provided file descriptor %d isn't valid or " \ "doesn't belong to calling SM %d", fd, sm); \ return FAILURE; \ } #define CHK_PERM(p_have, p_want) \ do { \ if ((p_have & p_want) != p_want) { \ printerror_int_int("permission check failed p_have=0x%x ; p_want = 0x%x", \ p_have, p_want); \ return FAILURE; \ } \ } while(0)
The following code snippet defines the sfs_getc() implementation that
uses the sancus_get_caller_id() hardware instruction and the above macros
to enforce SM-grained logical file access control. The function returns FAILURE
when access is denied and otherwise returns the result of the cfs_read()
back-end call.
int SM_ENTRY("sfs") sfs_getc(int fd) { sm_id caller_id = sancus_get_caller_id(); CHK_FD(fd, caller_id) CHK_PERM(fd_cache[fd]->flags, SFS_READ); return (cfs_read(fd, &buf, 1) > 0)? buf : EOF; }
The following code snippet defines the implementation of the sfs_chmod
function, which can be used to manipulate the SM-grained ACL of a logical file. As
evident from the implementation below, only calling SMs that hold the special
SFS_ROOT permission are allowed to change the associated ACL. The creator/owner
of a logical file always holds this permission.
The access rights for the caller are looked up in two phases: the
OPEN_FILE struct for the given file name is first located and the
associated ACL is thereafter traversed to locate the according FILE_PERM
struct. The code snippet below furthermore shows how the special SFS_NIL
permission is used to revoke existing access rights.
int SM_ENTRY("sfs") sfs_chmod(filename_t name, sm_id id, int perm_flags) { sm_id caller_id = sancus_get_caller_id(); // only root can chmod struct FILE_PERM *p_caller; if (two_phase_lookup(name, SFS_ROOT, caller_id, &p_caller) < 0) return FAILURE; if (perm_flags == SFS_NIL) return revoke_acl(p_caller->file, id); return add_acl(p_caller->file, id, perm_flags); }