sfs-ram.c

A wrapper implementation of the Sancus File System (SFS) interface that first performs Sancus-module-grained access control using non-persistent RAM data structures and thereafter translates the SFS call to a CFS -compatible back-end call (pluggable at link time).


/**
 * A wrapper implementation of the Sancus File System (SFS) interface that first 
 * performs Sancus-module-grained access control using non-persistent RAM data
 * structures and thereafter translates the SFS call to a CFS-compatible back-end
 * call (pluggable at link time).
 *
 * \author Jo Van Bulck
 */

#include <sancus/sm_support.h>
#include <stdbool.h>
#include "sfs-debug.h"
#include "sfs.h"
#include "cfs/cfs.h"

#ifndef SUCCESS
    #define SUCCESS         1       // a positive value, to indicate success
#endif
#ifndef EOF
    #define EOF             -1
#endif
#ifndef FAILURE
    #define FAILURE         -1     // a negative value, to indicate failure
#endif

#ifdef MEASURE_CFS_BACKEND
    #include "../../benchmark.h"
#else
    #define TSC1()
    #define TSC2(str)
#endif

DECLARE_SM(sfs, 0x1234);

// ############################### FILE SYS PARAM #################################

// the system-wide max number of open files; defines open-file-cache size
#define MAX_NB_OPEN_FILES       8
// the system-wide max number of different files; defines file_pool size
#define MAX_NB_FILES            5
// the system-wide max number of assignable file permissions; defines perm_pool size
#define MAX_NB_PERMS            10

// ######################### GLOBAL PROTECTED DATA STRUCTURES #####################

// used to pass a character pointer to the uprotected back-end file system
char buf;
// used to translate the filename_t to a string ptr for the CFS backend
char public_str[2];

/**
 * two-phase lookup linked list structs to lookup by filename and sm_id
 *  (un)populated by sfs_creat() and sfs_remove()
 *
 * @invar(file->acl != NULL): a file has exactely one creator ACL entry
 *  on creation: sfs_creat(): file->acl->flags == SFS_CREATOR
 *  on revocation: sfs_chmod(): no adding/altering/removing of an SFS_CREATOR entry
 */

// these structs are aligned on a power of 2 so they can be indexed by the Sancus
// compiler, but actually need less space (e.g. byte flags instead of ints, etc)
struct FILE_PERM {
    sm_id sm_id;                        // 2 bytes
    int flags;                          // 2 bytes
    struct OPEN_FILE *file;             // 2 bytes
    struct FILE_PERM *next;             // 2 bytes
};

struct OPEN_FILE {
    filename_t name;                    // 2 bytes
    int open_count;                     // 2 bytes
    struct FILE_PERM *acl;              // 2 bytes
    struct OPEN_FILE *next;             // 2 bytes
};

// protected memory pools
struct FILE_PERM SM_DATA("sfs") perm_pool[MAX_NB_PERMS];
struct OPEN_FILE SM_DATA("sfs") file_pool[MAX_NB_FILES];

// open file cache: index with fd into the cache to get the corresponding permission
// (un)populated by sfs_open() and sfs_close()
struct FILE_PERM SM_DATA("sfs") *fd_cache[MAX_NB_OPEN_FILES];

// list management pointers
struct FILE_PERM SM_DATA("sfs") *free_perm_list;
struct OPEN_FILE SM_DATA("sfs") *free_file_list;
struct OPEN_FILE SM_DATA("sfs") *open_file_list;

// indicates data structures are intialized; set to false (zero) on SM creation
bool SM_DATA("sfs") INIT_DONE;

// ############################# HELPER MACROS ####################################

/******************* file descriptor checks *******************/

#define IS_VALID_FD(fd) \
    (fd >= 0 && fd < MAX_NB_OPEN_FILES)

#define CHK_FD(fd, sm) \
    if (!IS_VALID_FD(fd) || !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 CLOSE_FD(fd) \
do { \
    fd_cache[fd]->file->open_count--; \
    fd_cache[fd] = NULL; \
    cfs_close(fd); \
} while(0)

/********************** permission checks *********************/

#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)

// on success, p will point to the corresponding FILE_PERM struct
#define CHK_ROOT(name, id, p) \
    if (two_phase_lookup(name, SFS_ROOT, id, &p) < 0) \
        return FAILURE; \

// on success, p will point to the corresponding FILE_PERM struct
// and prev will point to the preceding OPEN_FILE struct
#define CHK_ROOT_PREV(name, id, p, prev) \
    if (two_phase_lookup_prev(name, SFS_ROOT, id, &p, &prev) < 0) \
        return FAILURE; \

/***************** permission list management *****************/

#define ALLOC_PERM(p, the_id, the_flags, the_file, the_next) \
do { \
    if (!free_perm_list) \
    { \
        printerror("no more FILE_PERM structs left"); \
        return FAILURE; \
    } \
    p = free_perm_list; \
    free_perm_list = free_perm_list->next; \
    p->sm_id = the_id; \
    p->flags = the_flags; \
    p->file = the_file; \
    p->next = the_next; \
} while(0)

#define FREE_PERM(p) \
do { \
    p->next = free_perm_list; \
    free_perm_list = p; \
} while(0)

// cur will point to the FILE_PERM struct if found; else NULL
#define LOOKUP_PERM(file, id, cur) \
do { \
    for (cur = file->acl; cur != NULL; cur = cur->next) \
        if (cur->sm_id == id) \
            break; \
} while (0)

/******************** file list management ********************/

#define ALLOC_FILE(the_file, the_name, the_acl, the_next) \
do { \
    if (!free_file_list) \
    { \
        printerror("no more OPEN_FILE structs left"); \
        return FAILURE; \
    } \
    the_file = free_file_list; \
    free_file_list = free_file_list->next; \
    the_file->name = the_name; \
    the_file->open_count = 0; \
    the_file->acl = the_acl; \
    the_file->next = the_next; \
} while(0)

#define FREE_FILE(f) \
do { \
    f->next = free_file_list; \
    free_file_list = f; \
} while(0)

// cur will point to the OPEN_FILE struct if found; else NULL
#define LOOKUP_FILE(name, cur) \
do { \
    for (cur = open_file_list; cur != NULL; cur = cur->next) \
        if (cur->name == name) \
            break; \
} while (0)

// cur will point to the OPEN_FILE struct if found; else NULL
// and prev will point to the preceding OPEN_FILE struct; or the last one
#define LOOKUP_FILE_PREV(name, cur, prev) \
do { \
    for (cur = prev = open_file_list; cur != NULL; prev = cur, cur = cur->next) \
        if (cur->name == name) \
            break; \
} while (0)

/*************************** various **************************/

#define DO_INIT() \
    if (!INIT_DONE) { \
        init_data_structures(); \
    }

#define TO_PUBLIC_STR(char_name) \
do { \
    public_str[0] = char_name; \
    /* optimalisation: following line is not needed (done on init_data_structures): \
     * public_str[1] = '\0'; \
     */ \
} while(0)

// ############################# HELPER FUNCTIONS #################################

void SM_FUNC("sfs") init_data_structures(void)
{
    printd_info("initializing data structures");
    printdi_debug("struct FILE_PERM size is %d", sizeof(struct FILE_PERM));
    printdi_debug("struct OPEN_FILE size is %d", sizeof(struct OPEN_FILE));

    // populate free_file_list linked list
    free_file_list = file_pool;
    int i; struct OPEN_FILE *cur_f = file_pool;
    for (i = 1; i < MAX_NB_FILES; i++, cur_f = cur_f->next)
        cur_f->next = &file_pool[i];
    cur_f->next = NULL;
    
    // populate free_perm_list linked list
    free_perm_list = perm_pool;
    struct FILE_PERM *cur_p = perm_pool;
    for (i = 1; i < MAX_NB_PERMS; i++, cur_p = cur_p->next)
        cur_p->next = &perm_pool[i];
    cur_p->next = NULL;
    
    // empty open_fd_cache
    // when using compiler optimalisations, requires MAX_NB_OPEN_FILES a power of 2
    for (i=0; i < MAX_NB_OPEN_FILES; i++)
        fd_cache[i] = 0;
    
    // empty open_file_list
    open_file_list = NULL;
    
    // init the null-termination character of te one-character string for cfs backend
    public_str[1] = '\0';
    
#ifndef NO_CFS_FORMAT
    printd_info("calling cfs_format");
    TSC1()
    cfs_format();
    TSC2("cfs_format")
#endif
    
    INIT_DONE = 1;
}

/**
 * Searches for the file associated with @param(name) and then checks permissions
 * @param(flags) for SM with id @param(sm). On success, zero is returned and
 * @param(p_ptr) points to the matching FILE_PERM struct. On failure -1 is returned.
 */
int SM_FUNC("sfs") two_phase_lookup_prev (filename_t name, int flags, sm_id sm,
    struct FILE_PERM **p_ptr, struct OPEN_FILE **prev_ptr)
{
    printdname_debug("two_phase_lookup for file", name);
    printdii_debug(" with permission flags 0x%x and SM %d", flags, sm);

    struct OPEN_FILE *file, *prev;
    if (prev_ptr)
    {
        LOOKUP_FILE_PREV(name, file, prev);
        *prev_ptr = prev;
    }
    else
        LOOKUP_FILE(name, file);
    
    if (!file)
        return FAILURE;
    
    printdi_debug("found open_file struct at address %#x; now searching ACL", file);
    struct FILE_PERM *p;
    LOOKUP_PERM(file, sm, p);
    if (!p)
    {
        printerror_int("found no perm entry for SM %d", sm);
        return FAILURE;
    }
    printdi_debug("found file_perm struct at address %#x", p);
    
    *p_ptr = p;
    CHK_PERM(p->flags, flags); // might return failure
    
    return SUCCESS;
}

#define two_phase_lookup(name, flags, sm, p) \
    two_phase_lookup_prev(name, flags, sm, p, NULL)

/**
 * Attempts to open the file @p(name) in the CFS back-end and populates the
 * corresponding fd_cache entry with @p(p) iff a valid back-end fd is returned.
 */
int SM_FUNC("sfs") open_back_end_file(char name, int size, struct FILE_PERM *p)
{
    // open with read write in the back-end; front-end will take care of finer
    // grained permissions
    printd_debug("opening file in back-end");
    TO_PUBLIC_STR(name);
    TSC1()
    int fd = cfs_open(public_str, CFS_WRITE | CFS_READ, size);
    TSC2("cfs_open")
    printdi_debug("the returned back-end fd is %d", fd);
    
    if (!IS_VALID_FD(fd))
    {
        printerror_int("returned back-end fd %d out of range", fd);
        cfs_close(fd);
        return FAILURE;
    }
    p->file->open_count++;
    fd_cache[fd] = p;
    
    return fd;
}

/**
 * Helper function to revoke the ACL entry for SM @param(id) on file @param(file)
 * iff non-SFS_CREATOR ACL entry (to satisfy invar).
 */
int SM_FUNC("sfs") revoke_acl(struct OPEN_FILE *file, sm_id id)
{
    // walk the ACL to check for existing entry
    struct FILE_PERM *cur, *prev; int i;
    for (cur = prev = file->acl; cur != NULL; prev = cur, cur = cur->next)
        if (cur->sm_id == id)
        {
            if (cur->flags == SFS_CREATOR)
            {
                printerror("SFS_CREATOR permission is non-revocable");
                return FAILURE;
            }
            else
            {
                // free the permission entry; close any open file descriptors first
                for (i = 0; i < MAX_NB_OPEN_FILES; i++)
                    if (fd_cache[i] == cur)
                    {
                        printdi_warning("ACL entry currently open; now closing fd %d", i);
                        CLOSE_FD(i);                        
                    }
            
                printdi_warning("removing ACL entry at address %#x", cur);
                prev->next = cur->next;
                FREE_PERM(cur);
                return SUCCESS;
            }
        }
    
    // no existing entry in the ACL; postcondition is ok
    return SUCCESS;
}

/**
 * Helper function to add/override the ACL entry with permissions @param(perm_flags)
 * for SM @param(id) on file @param(file) iff non-SFS_CREATOR (to satisfy invar).
 */
int SM_FUNC("sfs") add_acl(struct OPEN_FILE *file, sm_id id, int perm_flags)
{
    // walk the access control list; override any existing permission
    struct FILE_PERM *p, *prev;
    for (p = prev = file->acl; p != NULL; prev = p, p = p->next)
        if (p->sm_id == id)
        {
            if (p->flags == SFS_CREATOR)
            {
                printerror("SFS_CREATOR permission is non-overrideable");
                return FAILURE;
            }
            else
            {
                printdi_warning("overriding existing ACL entry flag %#x", p->flags);
                p->flags = perm_flags;
                return SUCCESS;
            }
        }
        
    // no existing entry in the access control list; try to append new one
    printd_debug("appending additional ACL entry");
    if (perm_flags == SFS_CREATOR)
    {
        printerror("SFS_CREATOR permission is non-assignable");
        return FAILURE;
    }
    
    ALLOC_PERM(p, id, perm_flags, file, NULL);
    // prev != NULL because a file always has at least the owner ACL entry (invar)
    prev->next = p;

    return SUCCESS;   
}

// ######################### SFS API IMPLEMENTATION ###############################

void SM_ENTRY("sfs") sfs_ping(void)
{   
    printdii_info(FCT("sfs_ping") "Hi from sfs-ram; I have id %d and was called " \
     "by %d; now calling cfs_ping()", sancus_get_self_id(), sancus_get_caller_id());

#if !defined(NODEBUG) || defined(MEASURE_CFS_BACKEND)
    TSC1()
    cfs_ping();
    TSC2("cfs_ping()")
#endif

    return;
}

void SM_ENTRY("sfs") sfs_init(void)
{
    printd_info(FCT("sfs_init") "calling DO_INIT macro");
    DO_INIT()
}

void SM_ENTRY("sfs") sfs_dump(void)
{
#ifndef NODEBUG
    struct OPEN_FILE *f; struct FILE_PERM *p; int i;
    DO_INIT()
        
    printd_info(FCT("sfs_dump") "dumping global protected ACL data structures:");
    puts("\t----------------------------------------------------------------");
    for (f = open_file_list; f != NULL; f = f->next)
    {
        printf_int(BOLD "\tFILE" NONE " with name '%c'", f->name);
        printf_int(" at %#x", (intptr_t) f);
        printf_int("; open_count = %d; ", f->open_count);
        printf_int("next_ptr = %#x\n", (intptr_t) f->next);
        for (p = f->acl; p != NULL; p = p->next)
        {
            printf_int(BOLD "\t\tPERM" NONE " (%d", p->sm_id);
            printf_int(", 0x%02x) ", p->flags);
            printf_int("at %#x; ", (intptr_t) p);
            printf_int("file_ptr = %#x; ", (intptr_t) p->file);
            printf_int("next_ptr = %#x\n", (intptr_t) p->next);
        }
    }
    puts("\t----------------------------------------------------------------");
    
    printd_info(FCT("sfs_dump") "dumping global protected file descriptor cache:");
    printf_str("\t");
    for (i = 0; i < MAX_NB_OPEN_FILES; i++)
        printf_int_int("(%d, 0x%x); ", i, (intptr_t) fd_cache[i]);
    printf_str("\n");
    
    for (i = 0, f = free_file_list; f != NULL; i++, f = f->next)
        ;
    printdii_debug("free file list size is %d max is %d\n", i, MAX_NB_FILES);
    
    for (i = 0, p = free_perm_list; p!=NULL; i++, p = p->next)
        ;
    printdii_debug("free perm list size is %d max is %d\n", i, MAX_NB_PERMS);


#ifdef CFS_DUMP    
    printd_info(FCT("sfs-dump") "now calling cfs_dump");
    cfs_dump();
#endif

#else // NODEBUG
    return;
#endif
}

int SM_ENTRY("sfs") sfs_open(filename_t name, int flags, int size)
{
    sm_id caller_id = sancus_get_caller_id();
    DO_INIT()
    
    struct OPEN_FILE *file; struct FILE_PERM *p;
    LOOKUP_FILE(name, file);
    if (!file)
    {
        if (size < 0)
        {
            printerror_int(FCT("sfs_open") "caller requested not to create new " \
            "non-existing file '%c'; returning failure...", (int) name);
            return FAILURE;
        }
        printdname_info(FCT("sfs_open") "creating new file with name", name);
        
        /* 
        printd_debug("removing any existing file of the same name in the back-end");
        TO_PUBLIC_STR(name);
        cfs_remove(public_str);
        */
    
        if (!free_perm_list || !free_file_list)
        {
            printerror("need a free OPEN_FILE struct and FILE_PERM struct");
            return FAILURE;
        }
    
        // create the new file and add it in front of open_file_list
        ALLOC_FILE(file, name, NULL, open_file_list);
        open_file_list = file;
        ALLOC_PERM(p, caller_id, SFS_CREATOR, file, NULL);
        file->acl = p;
    
        return open_back_end_file(name, size, p);
    }

    printdname_info(FCT("sfs_open") "opening existing file with name", name);
    LOOKUP_PERM(file, caller_id, p);
    if (!p)
    {
        printerror_int("found no permission entry for SM %d", caller_id);
        return FAILURE;
    }
    CHK_PERM(p->flags, flags); // might return failure
    
    return open_back_end_file(name, size, p);
}

int SM_ENTRY("sfs") sfs_close(int fd)
{
    sm_id caller_id = sancus_get_caller_id();
    DO_INIT()
    printdi_info(FCT("sfs_close") "file with fd %d", fd);

    CHK_FD(fd, caller_id)    
    fd_cache[fd]->file->open_count--;
    fd_cache[fd] = NULL;

    TSC1()
    cfs_close(fd);
    TSC2("cfs_close")
    
    return SUCCESS;
}

/**
 * this implementation refuses to remove a file that is still open
 * by some SM; an alternative would be that the implementation closes
 * any remaining open file connection on behalf of the caller (owner of the file)
 */
int SM_ENTRY("sfs") sfs_remove(filename_t name)
{
    sm_id caller_id = sancus_get_caller_id();
    DO_INIT()
    printdname_info(FCT("sfs_remove") "trying to remove file", name);
    
    // only root can remove a file
    struct FILE_PERM *p; struct OPEN_FILE *cur, *prev;
    CHK_ROOT_PREV(name, caller_id, p, prev);
    cur = p->file;
    
    if (cur->open_count)
    {
        printerror_int("there are %d remaining open file connections;" \
        " close them first", cur->open_count);
        return FAILURE;
    }
    
    // remove associated access control data structures
    prev->next = cur->next;
    struct FILE_PERM *curp, *nextp;
    for (curp = cur->acl; curp != NULL; curp = nextp)
    {
        nextp = curp->next;
        FREE_PERM(curp);
    }
    if (open_file_list == cur)
        open_file_list = cur->next;
    FREE_FILE(cur);
    
    printd_debug("now removing file in CFS back-end");
    TO_PUBLIC_STR(name);
    TSC1()
    int rv = cfs_remove(public_str);
    TSC2("cfs_remove")
    
    return rv;
}

int SM_ENTRY("sfs") sfs_getc(int fd)
{
    sm_id caller_id = sancus_get_caller_id();
    DO_INIT()
    printdi_info(FCT("sfs_getc") "read a char from file with fd %d", fd);
   
    CHK_FD(fd, caller_id)
    CHK_PERM(fd_cache[fd]->flags, SFS_READ);

    TSC1()
    int rv = cfs_read(fd, &buf, 1);
    TSC2("cfs_read_one_char")

    printdi_debug("cfs_read returned %d", rv);
    return (rv > 0)? buf : EOF;
}

int SM_ENTRY("sfs") sfs_putc(int fd, unsigned char c)
{
    sm_id caller_id = sancus_get_caller_id();
    DO_INIT()
    printdi_info(FCT("sfs_putc") "write a char to file with fd %d", fd);

    CHK_FD(fd, caller_id)
    CHK_PERM(fd_cache[fd]->flags, SFS_WRITE);
    
    TSC1()
    buf = c; // this is part of the back-end function call overhead...
    int rv = cfs_write(fd, &buf, 1);
    TSC2("cfs_write_one_char")

    printdi_debug("cfs_write returned %d", rv);
    return (rv > 0)? buf : EOF;
}

int SM_ENTRY("sfs") sfs_seek(int fd, int offset, int origin)
{
    sm_id caller_id = sancus_get_caller_id();
    DO_INIT()
    printdi_info(FCT("sfs_seek") "now trying to seek in file with fd %d", fd);
    CHK_FD(fd, caller_id)

    TSC1()
    int rv = cfs_seek(fd, offset, origin);
    TSC2("cfs_seek")
    
    return rv;
}

int SM_ENTRY("sfs") sfs_chmod(filename_t name, sm_id id, int perm_flags)
{
    sm_id caller_id = sancus_get_caller_id();
    DO_INIT()

    printdname_info(FCT("sfs_chmod") "trying to modify ACL for file", name);
    printdi_debug("wanted permission entry for SM %d", id);
    printdi_debug("and flags %#x", perm_flags);

    // only root can chmod
    struct FILE_PERM *p_caller;
    CHK_ROOT(name, caller_id, p_caller)
    
    if (perm_flags == SFS_NIL)
        return revoke_acl(p_caller->file, id);
   
    return add_acl(p_caller->file, id, perm_flags);
}

int SM_ENTRY("sfs") sfs_attest(filename_t name, sm_id owner)
{
    printdii_info(FCT("sfs_attest") "validating file '%c' was created by SM %d",
        (char) name, owner);

    struct OPEN_FILE *file; struct FILE_PERM *p;
    LOOKUP_FILE(name, file);
    if (!file)
        return FAILURE;
    
    LOOKUP_PERM(file, owner, p);
    if (!p)
        return FAILURE;
    
    CHK_PERM(p->flags, SFS_CREATOR);
    
    return SUCCESS;
}