From 3032b5bd2db19ef1d15b136bfbd576cade99cf02 Mon Sep 17 00:00:00 2001 From: Andrei Lascu Date: Mon, 9 Oct 2023 16:40:55 +0100 Subject: [PATCH] Initial support for `.so` compartments Implement support for compartments to be gives as dynamic libraries (i.e., `so` files). This includes looking for library dependencies, and loading those in the same compartment as the given user file. These libraries are expected to be found in the path given by the environment variable `COMP_LIBRARY_PATH`. What this means is that we now also add a "local" `libc` to each compartment that needs it (likely all?). Thus, we can greatly reduce the number of intercepts we require. Perhaps we can overhaul the mechanism entirely to only intercept hard-coded functions we know we need (e.g., allocator calls). The support for static binaries has not been removed completely, but likely has been broken due to this overhaul. Whether to leave as is, to remove completely, or to add support for it remains to be decided. Further small changes: * Rework testing infrastructure to account for `so` compartments * Replace `false` `assert`s and `exit`s with `err`s * Replace all `pread`s with `do_pread` wrappers * Rename intercept functions from `manager_` prefix to `intercepted_` * Slight `README` update * Replace `intptr_t` usage with `void*` where appopriate * Fixed a bug where we would find symbols in dependency libraries with a value of 0x0 to be eagerly relocated. Assumption is these symbols are meant to be relocated in the dependency library itself. We now simply filter out 0x0 addresses for symbols to eagerly relocate --- .run_cheri_qemu_and_test.py | 5 +- CMakeLists.txt | 6 +- README.md | 47 +- include/compartment.h | 125 ++-- include/intercept.h | 82 +++ include/manager.h | 110 +--- include/mem_mng.h | 2 - src/CMakeLists.txt | 1 + src/compartment.c | 975 +++++++++++++++++++----------- src/intercept.c | 172 ++++++ src/manager.c | 320 +++++----- src/mem_mng.c | 5 +- src/transition.S | 35 +- tests/CMakeLists.txt | 198 +++--- tests/init_test.py | 47 ++ tests/manager_arg_passer.c | 18 +- tests/manager_caller.c | 15 +- tests/run_test.py | 21 +- tests/simple.c | 6 +- tests/test_args_near_unmapped.c | 15 +- tests/test_map.c | 14 +- tests/test_two_comps.c | 16 +- tests/test_two_comps_inter_call.c | 16 +- third-party/lua.patch | 25 +- 24 files changed, 1409 insertions(+), 867 deletions(-) create mode 100644 include/intercept.h create mode 100644 src/intercept.c create mode 100755 tests/init_test.py diff --git a/.run_cheri_qemu_and_test.py b/.run_cheri_qemu_and_test.py index 423b1bc..60a1e58 100644 --- a/.run_cheri_qemu_and_test.py +++ b/.run_cheri_qemu_and_test.py @@ -24,9 +24,12 @@ def run_tests(qemu: boot_cheribsd.QemuCheriBSDInstance, args: argparse.Namespace boot_cheribsd.set_ld_library_path_with_sysroot(qemu) boot_cheribsd.info("Running tests for CHERI-ELF-compartments") + # Test environment setup + subprocess.run(["./tests/init_test.py"], check = True) + # Run command on host to test the executed client os.chdir(f"{args.build_dir}/build") - subprocess.run(["ctest", "--output-on-failure"], check=True) + subprocess.run(["ctest", "--output-on-failure"], check = True) return True if __name__ == '__main__': diff --git a/CMakeLists.txt b/CMakeLists.txt index d7e4c51..60239ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.16) project(CHERI_ELF_Compartments LANGUAGES C ASM) # Set global compilation options -set(CMAKE_COMPILE_WARNING_AS_ERROR ON) +add_compile_options(-pedantic -Wno-gnu-binary-literal -Wno-language-extension-token -Werror) # Set useful directory variables set(TEST_DIR ${CMAKE_SOURCE_DIR}/tests) @@ -12,12 +12,12 @@ set(INCLUDE_DIR ${CMAKE_SOURCE_DIR}/include) # Build lua set(LUA_DIR ${CMAKE_SOURCE_DIR}/third-party/lua) set(LUA_INSTALL_DIR ${LUA_DIR}) -set(LUA_LIB_PATH ${LUA_DIR}/liblua.a) +set(LUA_LIB_PATH ${LUA_DIR}/liblua.so) set(LUA_INCLUDE_DIR ${LUA_DIR}) add_custom_command( OUTPUT ${LUA_LIB_PATH} - COMMAND make a + COMMAND make WORKING_DIRECTORY ${LUA_DIR} ) diff --git a/README.md b/README.md index 94a30c8..0a724b8 100644 --- a/README.md +++ b/README.md @@ -24,18 +24,15 @@ ELF binary. Each compartment will be restricted to its own memory chunk, and should not be able to access (i.e., load or store) any memory outside the chunk determined by the manager. -Compartments are loaded from a file on disk, in the form of a pre-compiled -static ELF binary. We prefer static binaries, as this ensures (most) of the -compartment functionality is self contained (further discussion on exceptions -[in this Section](#function-interception)). Compartments are then allocated a -chunk of the manager process' memory, and the executable code of the -compartment is loaded inside that chunk. Additional memory is reserved within -the chunk for the compartment's stack and heap. A capability is defined around -the allocated chunk. Whenever we transition to a given compartment, we load its -respective capability inside the DDC, essentially limiting memory loads and -stores within the compartment's allocated memory region. Once the compartment -is loaded into memory, we are able to call functions defined in the binary from -the manager. +Compartments are loaded from a file on disk, in the form of a pre-compiled ELF +binary. Compartments are then allocated a chunk of the manager process' memory, +and the executable code of the compartment is loaded inside that chunk. +Additional memory is reserved within the chunk for the compartment's stack and +heap. A capability is defined around the allocated chunk. Whenever we +transition to a given compartment, we load its respective capability inside the +DDC, essentially limiting memory loads and stores within the compartment's +allocated memory region. Once the compartment is loaded into memory, we are +able to call functions defined in the binary from the manager. There is one exception when a compartment may access memory outside its designated region: if it is passed a capability by another compartment, then @@ -45,6 +42,26 @@ useful for sharing more complex data across compartments, but doing so essentially removes the security over the shared region for the original owner compartment, so must be done so sparsely. +## Structure overview + +The project is split into the following components (subject to change): +* `manager` - this exposes the main API that users are expected to use. It + offers functions to initialize a compartment from a given ELF binary, execute + a compartment, and general compartment management features. It currently does + not support deleting compartments. +* `compartment` - this is mainly to do with compartment internals, and reading + ELF data for the given input file, managing memory, and various other + interesting bits we do to ensure code can function when + DDC-compartmentalized. +* `intercept` - a feature to automatically intercept functions within + compartments that need to be executed within a higher-trust level. To our + knowledge, these are functions that might call into vDSO[^1][^2], or perform + system calls. Other situations might be added here as we explore more. +* `mem_mng` - we implement a simple bump allocator to ensure that internal + compartment memory allocations are done within the compartment, in the area + specifically marked to be used as heap. This is the implementation of the + allocator + ### Executing a compartment TODO @@ -71,6 +88,8 @@ function, then transitioning back into the compartment. Current limitations (as this is a work in progress, some of these are planned to be addressed): * single compartment; -* the user-code must be compiled with `--static` and `--image-base` set to some - pre-determined variable; * only 3 arguments supported for argument passing, not floats or pointers. +* did not check for support for capabilities within compartments + +[^1]: https://en.wikipedia.org/wiki/VDSO +[^2]: https://lwn.net/Articles/446528/ diff --git a/include/compartment.h b/include/compartment.h index f7f385d..fc9198d 100644 --- a/include/compartment.h +++ b/include/compartment.h @@ -3,8 +3,12 @@ #include #include +#include +#include #include +#include #include +#include #include #include #include @@ -15,8 +19,6 @@ #include "cheriintrin.h" -#include "manager.h" - // Morello `gcc` defines `ptraddr_t` in `stddef.h`, while `clang` does so in // `stdint.h` // TODO are there any other diverging definition files? @@ -27,17 +29,13 @@ #define align_down(x, align) __builtin_align_down(x, align) #define align_up(x, align) __builtin_align_up(x, align) -// Maximum number of allowed segments per loaded ELF file -// TODO rethink number/make it a parameter -#define SEG_MAX_COUNT 20 - // TODO once things stabilize, recheck if all struct members are required // currently there's quite a bit of redundancy to make things easier to think // about -struct func_intercept; void compartment_transition_out(); -int64_t comp_exec_in(void*, void* __capability, void*, void*, size_t); +int64_t comp_exec_in(void*, void* __capability, void*, void*, size_t, + void* __capability); void comp_exec_out(); // Declare built-in function for cache synchronization: @@ -51,8 +49,14 @@ extern void __clear_cache(void*, void*); // Number of instructions required by the transition function #define COMP_TRANS_FN_INSTR_CNT 4 +extern void* __capability sealed_redirect_cap; +extern void* __capability comp_return_caps[2]; + /* For a function to be intercepted, information required to insert the * redirect code and perform redirection + * + * TODO recheck this is properly used, or re-design into a more light-weight + * approach with pre-given transition capabilities */ struct intercept_patch { @@ -73,25 +77,12 @@ struct intercept_patch #error Expecting 64-bit Arm Morello platform #endif -/* Struct representing configuration data for one entry point; this is just - * information that we expect to appear in the compartment, as given by its - * compartment configuration file - */ -struct ConfigEntryPoint -{ - const char* name; - size_t arg_count; - char** args_type; -}; - /* Struct representing a valid entry point to a compartment */ struct entry_point { const char* fn_name; - uintptr_t fn_addr; - size_t arg_count; - char** arg_types; + void* fn_addr; }; /* Struct representing one segment of an ELF binary. @@ -99,15 +90,49 @@ struct entry_point * TODO expand */ struct SegmentMap { - uintptr_t mem_bot; - uintptr_t mem_top; + void* mem_bot; + void* mem_top; size_t offset; - size_t correction; + ptrdiff_t correction; size_t mem_sz; size_t file_sz; int prot_flags; }; +/* Struct representing an eager relocation to be made at map-time - instead of + * lazily looking up function addresses once a function is called at runtime, + * via PLT/GOT, we update the expected addresses eagerly once the code is + * mapped into memory, via `comp_map` + */ +struct CompRelaMapping +{ + char* rela_name; + void* rela_address; // address of relocation in compartment + void* target_func_address; // address of actual function +}; + +/* Struct representing a symbol entry of a dependency library + */ +struct LibDependencySymbol +{ + char* sym_name; + intptr_t sym_offset; +}; + +/* Struct representing a library dependency for one of our given compartments + */ +struct LibDependency +{ + char* lib_name; + char* lib_path; + size_t lib_segs_count; + size_t lib_segs_size; + void* lib_mem_base; + struct SegmentMap** lib_segs; + size_t lib_syms_count; + struct LibDependencySymbol* lib_syms; +}; + /* Struct representing ELF data necessary to load and eventually execute a * compartment */ @@ -119,65 +144,67 @@ struct Compartment Elf64_Half elf_type; // Execution info Elf64_Half phdr; - Elf64_Half phentsize; - Elf64_Half phnum; void* __capability ddc; // ELF data - size_t size; - uintptr_t base; + size_t size; // size of compartment in memory + void* base; // address where to load compartment size_t entry_point_count; - struct entry_point** comp_fns; // TODO - uintptr_t* relas; - size_t relas_cnt; + struct entry_point** comp_fns; + void* mem_top; bool mapped; bool mapped_full; // Segments data - struct SegmentMap* segs[SEG_MAX_COUNT]; // TODO + struct SegmentMap** segs; size_t seg_count; size_t segs_size; // Scratch memory - uintptr_t scratch_mem_base; + void* scratch_mem_base; size_t scratch_mem_size; size_t scratch_mem_alloc; size_t scratch_mem_heap_size; - uintptr_t scratch_mem_stack_top; + void* scratch_mem_stack_top; size_t scratch_mem_stack_size; - uintptr_t stack_pointer; + void* stack_pointer; struct mem_alloc* alloc_head; - uintptr_t manager_caps; + void* manager_caps; size_t max_manager_caps_count; size_t active_manager_caps_count; - uintptr_t mng_trans_fn; + void* mng_trans_fn; size_t mng_trans_fn_sz; + + // Only for shared object compartments + size_t rela_maps_count; + struct CompRelaMapping* rela_maps; + size_t lib_deps_count; + struct LibDependency** lib_deps; + // Hardware info - maybe move size_t page_size; + // Misc short curr_intercept_count; - struct intercept_patch patches[INTERCEPT_FUNC_COUNT]; + struct intercept_patch* intercept_patches; }; -extern struct Compartment** comps; - int entry_point_cmp(const void*, const void*); struct Compartment* comp_init(); -struct Compartment* comp_from_elf(char*, struct ConfigEntryPoint*, size_t); -void comp_register_ddc(struct Compartment*); -void comp_add_intercept(struct Compartment*, uintptr_t, struct func_intercept); +struct Compartment* comp_from_elf(char*, char**, size_t, char**, void**, size_t, void*); +void comp_add_intercept(struct Compartment*, uintptr_t, uintptr_t); void comp_stack_push(struct Compartment*, const void*, size_t); void comp_map(struct Compartment*); void comp_map_full(struct Compartment*); int64_t comp_exec(struct Compartment*, char*, void*, size_t); void comp_clean(struct Compartment*); -void log_new_comp(struct Compartment*); struct Compartment* find_comp(struct Compartment*); -void setup_stack(struct Compartment*); - -void comp_print(struct Compartment*); -void segmap_print(struct SegmentMap*); +static ssize_t do_pread(int, void*, size_t, off_t); +static Elf64_Sym* find_symbols(const char**, size_t, bool, Elf64_Sym*, char*, size_t); +static char* find_in_dir(const char*, char*); +static void init_comp_scratch_mem(struct Compartment*); +static void init_lib_dep_info(struct LibDependency*, struct Compartment*); #endif // _COMPARTMENT_H diff --git a/include/intercept.h b/include/intercept.h new file mode 100644 index 0000000..4ce7e7b --- /dev/null +++ b/include/intercept.h @@ -0,0 +1,82 @@ +#ifndef _INTERCEPT_H +#define _INTERCEPT_H + +#include +#include +#include +#include + +// vDSO wrapper needed includes +#include + +#include "cheriintrin.h" + +#include "mem_mng.h" + +// Forward declarations +struct Compartment; +extern struct Compartment* loaded_comp; +int64_t exec_comp(struct Compartment*, char*, char**); +struct Compartment* manager_get_compartment_by_id(size_t); + +extern void* __capability manager_ddc; + +// Number of capabilities required to perform a transition +#define COMP_RETURN_CAPS_COUNT 2 + +// Capabilities required to transition back into the manager once compartment +// execution has finished +extern void* __capability comp_return_caps[COMP_RETURN_CAPS_COUNT]; + +// Capability used to point to pair of capabilities when transitioning out of a +// compartment via an intercept +extern void* __capability sealed_redirect_cap; + +/* Data required to perform the transition for an intercepted function + */ +struct func_intercept { + char* func_name; + void* redirect_func; + void* __capability intercept_pcc; +}; + +/* This function expects the argument be passed in `x10`, rather than `x0`, as + * well as using `c29` as an argument for the DDC to transition to in order to + * allow the intercept to work. It is expected to be called only in very + * specific circumstances, and the signature is more illustrative than + * functional. As such, it shouldn't be called from a C context, as that will + * most likely break things. + */ +void intercept_wrapper(); + +void setup_intercepts(); + +time_t intercepted_time(time_t*); +FILE* intercepted_fopen(const char*, const char*); +size_t intercepted_fread(void* __restrict, size_t, size_t, FILE* __restrict); +size_t intercepted_fwrite(void* __restrict, size_t, size_t, FILE* __restrict); +int intercepted_fclose(FILE*); +int intercepted_getc(FILE*); +int intercepted_fputc(int, FILE*); +int intercepted___srget(FILE*); + +void* my_realloc(void*, size_t); +void* my_malloc(size_t); +void my_free(void*); +int my_fprintf(FILE*, const char*, ...); + +size_t my_call_comp(size_t, char*, void*, size_t); +static const struct func_intercept to_intercept_funcs[] = { + /* vDSO funcs */ + { "time" , (void*) intercepted_time }, + /* Mem funcs */ + { "malloc" , (void*) my_malloc }, + { "realloc" , (void*) my_realloc }, + { "free" , (void*) my_free }, +}; +// +// Functions to be intercepted and associated data +#define INTERCEPT_FUNC_COUNT sizeof(to_intercept_funcs) / sizeof(to_intercept_funcs[0]) +extern struct func_intercept comp_intercept_funcs[INTERCEPT_FUNC_COUNT]; + +#endif // _INTERCEPT_H diff --git a/include/manager.h b/include/manager.h index 281eb0e..00036d0 100644 --- a/include/manager.h +++ b/include/manager.h @@ -2,83 +2,28 @@ #define _MANAGER_H #include -#include -#include #include -#include -#include +#include #include #include #include #include - -// vDSO wrapper needed includes -#include +#include +#include +#include // Third-party libraries #include "toml.h" -extern void* __capability manager_ddc; +#include "intercept.h" +#include "compartment.h" -/* Data required to perform the transition for an intercepted function - */ -struct func_intercept { - char* func_name; - uintptr_t redirect_func; - void* __capability intercept_ddc; - void* __capability intercept_pcc; - void* __capability redirect_cap; -}; +#define align_down(x, align) __builtin_align_down(x, align) +#define align_up(x, align) __builtin_align_up(x, align) -/* This function expects the argument be passed in `x10`, rather than `x0`, as - * well as using `c29` as an argument for the DDC to transition to in order to - * allow the intercept to work. It is expected to be called only in very - * specific circumstances, and the signature is more illustrative than - * functional. As such, it shouldn't be called from a C context, as that will - * most likely break things. - */ -void intercept_wrapper(void* to_call_fn); - -void setup_intercepts(); - -time_t manager_time(time_t*); -FILE* manager_fopen(const char*, const char*); -size_t manager_fread(void* __restrict, size_t, size_t, FILE* __restrict); -size_t manager_fwrite(void* __restrict, size_t, size_t, FILE* __restrict); -int manager_fclose(FILE*); -int manager_getc(FILE*); -int manager_fputc(int, FILE*); -int manager___srget(FILE*); - -void* my_realloc(void*, size_t); -void* my_malloc(size_t); -void my_free(void*); -int my_fprintf(FILE*, const char*, ...); - -size_t my_call_comp(size_t, char*, void*, size_t); -static const struct func_intercept to_intercept_funcs[] = { - /* vDSO funcs */ - { "time" , (uintptr_t) manager_time }, - /* Mem funcs */ - { "malloc" , (uintptr_t) my_malloc }, - { "realloc" , (uintptr_t) my_realloc }, - { "free" , (uintptr_t) my_free }, - { "fprintf" , (uintptr_t) my_fprintf }, - /* Compartment funcs */ - { "call_comp", (uintptr_t) my_call_comp }, - /* Other funcs */ - { "fopen" , (uintptr_t) manager_fopen }, - { "fread" , (uintptr_t) manager_fread }, - { "fwrite" , (uintptr_t) manager_fwrite }, - { "fclose" , (uintptr_t) manager_fclose }, - { "getc" , (uintptr_t) manager_getc }, - { "fputc" , (uintptr_t) manager_fputc }, - { "__srget" , (uintptr_t) manager___srget }, -}; -// -// Functions to be intercepted and associated data -#define INTERCEPT_FUNC_COUNT sizeof(to_intercept_funcs) / sizeof(to_intercept_funcs[0]) -extern struct func_intercept comp_intercept_funcs[INTERCEPT_FUNC_COUNT]; +extern void* __capability manager_ddc; +extern struct CompWithEntries** comps; +extern struct Compartment* loaded_comp; /******************************************************************************* * Utility Functions @@ -90,21 +35,34 @@ void print_full_cap(uintcap_t); * Compartment ******************************************************************************/ -// Number of capabilities required to perform a transition -#define COMP_RETURN_CAPS_COUNT 2 - // Compartment configuration file suffix extern const char* comp_config_suffix; -// Capabilities required to transition back into the manager once compartment -// execution has finished -extern void* __capability comp_return_caps[COMP_RETURN_CAPS_COUNT]; +/* Struct representing configuration data for one entry point; this is just + * information that we expect to appear in the compartment, as given by its + * compartment configuration file + */ +struct ConfigEntryPoint +{ + const char* name; + size_t arg_count; + char** args_type; +}; + +struct CompWithEntries +{ + struct Compartment* comp; + struct ConfigEntryPoint* cep; +}; + +void* get_next_comp_addr(void); +struct Compartment* register_new_comp(char*, bool); +int64_t exec_comp(struct Compartment*, char*, char**); struct Compartment* manager_find_compartment_by_addr(void*); struct Compartment* manager_find_compartment_by_ddc(void* __capability); struct Compartment* manager_get_compartment_by_id(size_t); -#include "compartment.h" // TODO stack setup when we transition into the compartment; unsure if needed, // but keep for now, just in case @@ -125,11 +83,9 @@ union arg_holder }; char* prep_config_filename(char*); -struct ConfigEntryPoint* parse_compartment_config(char*, size_t*); +void clean_all_comps(); +void clean_comp(struct Compartment*); void clean_compartment_config(struct ConfigEntryPoint*, size_t); -struct ConfigEntryPoint get_entry_point(char*, struct ConfigEntryPoint*, size_t); -void* prepare_compartment_args(char** args, struct ConfigEntryPoint); -struct ConfigEntryPoint* set_default_entry_point(struct ConfigEntryPoint*); /******************************************************************************* * Memory allocation diff --git a/include/mem_mng.h b/include/mem_mng.h index 976e8e2..6ac65cc 100644 --- a/include/mem_mng.h +++ b/include/mem_mng.h @@ -10,8 +10,6 @@ #include "compartment.h" -struct Compartment; - // TODO consider single linked list struct mem_alloc { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1594131..d9a9cf2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,6 +3,7 @@ add_library(chcomp STATIC manager.c mem_mng.c compartment.c + intercept.c transition.S ) target_include_directories(chcomp PRIVATE ${INCLUDE_DIR} ${TOML_INCLUDE_DIR}) diff --git a/src/compartment.c b/src/compartment.c index 6837288..6aadb24 100644 --- a/src/compartment.c +++ b/src/compartment.c @@ -1,8 +1,5 @@ #include "compartment.h" -static size_t comps_id = 0; -struct Compartment** comps; - /* Initialize some values of the Compartment struct. The rest are expected to * be set in `comp_from_elf`. */ @@ -10,8 +7,8 @@ struct Compartment* comp_init() { // TODO order - struct Compartment* new_comp = (struct Compartment*) malloc(sizeof(struct Compartment)); - new_comp->id = comps_id++; + struct Compartment* new_comp = + (struct Compartment*) malloc(sizeof(struct Compartment)); new_comp->phdr = 0; new_comp->ddc = NULL; @@ -25,10 +22,11 @@ comp_init() new_comp->seg_count = 0; new_comp->segs_size = 0; new_comp->mng_trans_fn_sz = sizeof(uint32_t) * COMP_TRANS_FN_INSTR_CNT; // TODO ptr arithmetic - new_comp->relas_cnt = 0; - new_comp->page_size = sysconf(_SC_PAGESIZE); new_comp->phdr = 0; new_comp->alloc_head = NULL; + + new_comp->rela_maps_count = 0; + new_comp->page_size = sysconf(_SC_PAGESIZE); new_comp->curr_intercept_count = 0; @@ -53,17 +51,18 @@ entry_point_cmp(const void* val1, const void* val2) * a `struct Compartment`. At this point, we only read data. */ struct Compartment* -comp_from_elf(char* filename, struct ConfigEntryPoint* entry_points, size_t entry_point_count) +comp_from_elf(char* filename, + char** entry_points, size_t entry_point_count, + char** intercepts, void** intercept_addrs, size_t intercept_count, + void* new_comp_base) { struct Compartment* new_comp = comp_init(); - int pread_res; new_comp->fd = open(filename, O_RDONLY); if (new_comp->fd == -1) { - printf("Error opening compartment file %s!\n", filename); free(new_comp); - exit(1); + errx(1, "Error opening compartment file %s!\n", filename); } assert(entry_points); @@ -73,73 +72,72 @@ comp_from_elf(char* filename, struct ConfigEntryPoint* entry_points, size_t entr // Read elf headers Elf64_Ehdr comp_ehdr; assert(new_comp->fd != -1); - pread_res = pread(new_comp->fd, &comp_ehdr, sizeof(comp_ehdr), 0); - assert(pread_res != -1); + do_pread(new_comp->fd, &comp_ehdr, sizeof(Elf64_Ehdr), 0); new_comp->elf_type = comp_ehdr.e_type; assert(new_comp->elf_type == ET_DYN || new_comp->elf_type == ET_EXEC); struct stat elf_fd_stat; fstat(new_comp->fd, &elf_fd_stat); + // TODO re-check these new_comp->size = elf_fd_stat.st_size; - new_comp->phentsize = comp_ehdr.e_phentsize; - new_comp->phnum = comp_ehdr.e_phnum; // Read program headers Elf64_Phdr comp_phdr; - size_t align_size_correction; + ptrdiff_t align_size_correction; bool first_load_header = true; for (size_t i = 0; i < comp_ehdr.e_phnum; ++i) { - pread_res = pread((int) new_comp->fd, &comp_phdr, sizeof(comp_phdr), - comp_ehdr.e_phoff + i * sizeof(comp_phdr)); - assert(pread_res != -1); - - /*if (comp_phdr.p_offset <= comp_ehdr.e_phoff &&*/ - /*(comp_ehdr.e_phoff + comp_ehdr.e_phnum * comp_ehdr.e_phentsize)*/ - /*<= comp_phdr.p_offset + comp_phdr.p_filesz)*/ - /*{*/ - /*new_comp->phdr = 0; // TODO?*/ - /*continue;*/ - /*}*/ + do_pread((int) new_comp->fd, &comp_phdr, sizeof(comp_phdr), + comp_ehdr.e_phoff + i * sizeof(comp_phdr)); + // We only need to keep `PT_LOAD` segments, so we can map them later if (comp_phdr.p_type != PT_LOAD) { continue; } - // Compute loading address of compartment + if (new_comp->elf_type == ET_DYN) + { + new_comp->base = new_comp_base; + } + // Compute loading address of compartment for static binary // TODO empirically, the first `LOAD` program header seems to expect to be // loaded at the lowest address; is this correct? - if (first_load_header) + else if (first_load_header) { - const unsigned long new_comp_base = comp_phdr.p_vaddr; - assert(new_comp_base % new_comp->page_size == 0); + void* new_comp_base = (void*) comp_phdr.p_vaddr; + assert((uintptr_t) new_comp_base % new_comp->page_size == 0); new_comp->base = new_comp_base; first_load_header = false; } + // Setup mapping info for the current segment struct SegmentMap* this_seg = (struct SegmentMap*) malloc(sizeof(struct SegmentMap)); assert(this_seg != NULL); if (new_comp->elf_type == ET_DYN /*|| new_comp->elf_type == ET_EXEC*/) // TODO distinguish PIE exec vs non-PIE exec { - unsigned long curr_seg_base = new_comp->base + comp_phdr.p_vaddr; + void* curr_seg_base = (char*) new_comp->base + comp_phdr.p_vaddr; this_seg->mem_bot = align_down(curr_seg_base, new_comp->page_size); - align_size_correction = curr_seg_base - this_seg->mem_bot; - this_seg->mem_top = curr_seg_base + comp_phdr.p_memsz; + align_size_correction = (char*) curr_seg_base - (char*) this_seg->mem_bot; + this_seg->mem_top = (char*) curr_seg_base + comp_phdr.p_memsz; } else if (new_comp->elf_type == ET_EXEC) { - this_seg->mem_bot = align_down(comp_phdr.p_vaddr, new_comp->page_size); - align_size_correction = comp_phdr.p_vaddr - this_seg->mem_bot; - this_seg->mem_top = comp_phdr.p_vaddr + comp_phdr.p_memsz; + // TODO maybe just remove this if if we don't want to support + // static binaries anymore + assert(false); + this_seg->mem_bot = + align_down((void*) comp_phdr.p_vaddr, new_comp->page_size); + align_size_correction = + (char*) comp_phdr.p_vaddr - (char*) this_seg->mem_bot; + this_seg->mem_top = (char*) comp_phdr.p_vaddr + comp_phdr.p_memsz; } else { - assert(false && "Unhandled elf type"); // TODO move elsewhere + errx(1, "Unhandled ELF type"); } this_seg->offset = align_down(comp_phdr.p_offset, new_comp->page_size); - /*this_seg->size = comp_phdr.p_filesz + (comp_phdr.p_offset & (new_comp->page_size - 1)); // TODO ????*/ this_seg->mem_sz = comp_phdr.p_memsz + align_size_correction; this_seg->file_sz = comp_phdr.p_filesz + align_size_correction; this_seg->correction = align_size_correction; @@ -147,184 +145,282 @@ comp_from_elf(char* filename, struct ConfigEntryPoint* entry_points, size_t entr (comp_phdr.p_flags & PF_W ? PROT_WRITE : 0) | (comp_phdr.p_flags & PF_X ? PROT_EXEC : 0); + new_comp->segs = + realloc(new_comp->segs, + (new_comp->seg_count + 1) * sizeof(struct SegmentMap*)); new_comp->segs[new_comp->seg_count] = this_seg; new_comp->seg_count += 1; - new_comp->segs_size += this_seg->mem_sz; + new_comp->segs_size += align_up(this_seg->mem_sz, comp_phdr.p_align); } - // Define scratch memory available - new_comp->scratch_mem_base = align_up(new_comp->segs[new_comp->seg_count - 1]->mem_top + new_comp->page_size, new_comp->page_size); - new_comp->max_manager_caps_count = 10; // TODO - new_comp->scratch_mem_heap_size = 0x800000UL; - new_comp->scratch_mem_size = - new_comp->scratch_mem_heap_size + - new_comp->max_manager_caps_count * sizeof(void* __capability) + - new_comp->mng_trans_fn_sz;; - new_comp->scratch_mem_alloc = 0; - new_comp->scratch_mem_stack_top = align_down(new_comp->scratch_mem_base + new_comp->scratch_mem_heap_size, 16); - new_comp->scratch_mem_stack_size = 0x80000UL; - new_comp->manager_caps = new_comp->scratch_mem_stack_top; - new_comp->active_manager_caps_count = 0; - new_comp->mng_trans_fn = new_comp->manager_caps + new_comp->max_manager_caps_count * sizeof(void* __capability); - assert(new_comp->scratch_mem_base % 16 == 0); - assert((new_comp->scratch_mem_base + new_comp->scratch_mem_size) % 16 == 0); - assert(new_comp->scratch_mem_stack_top % 16 == 0); - assert((new_comp->scratch_mem_stack_top - new_comp->scratch_mem_stack_size) % 16 == 0); - assert(new_comp->scratch_mem_size % 16 == 0); - - // Find functions of interest, particularly entry points, and functions to - // intercept - Elf64_Shdr comp_symtb_hdr; // TODO change name + // Load `.shstr` section, so we can check section names + Elf64_Shdr comp_sh_strtb_hdr; + do_pread((int) new_comp->fd, &comp_sh_strtb_hdr, sizeof(Elf64_Shdr), + comp_ehdr.e_shoff + comp_ehdr.e_shstrndx * sizeof(Elf64_Shdr)); + char* comp_sh_strtb = malloc(comp_sh_strtb_hdr.sh_size); + do_pread((int) new_comp->fd, comp_sh_strtb, comp_sh_strtb_hdr.sh_size, + comp_sh_strtb_hdr.sh_offset); + + init_comp_scratch_mem(new_comp); + new_comp->mem_top = new_comp->scratch_mem_stack_top; + + // Find indices of interest that we'll use later + const size_t headers_of_interest_count = 3; + size_t found_headers = 0; + Elf64_Shdr comp_symtb_shdr; + Elf64_Shdr comp_rela_plt_shdr; + Elf64_Shdr comp_dynamic_shdr; + Elf64_Shdr curr_shdr; for (size_t i = 0; i < comp_ehdr.e_shnum; ++i) { - pread_res = pread((int) new_comp->fd, &comp_symtb_hdr, sizeof(Elf64_Shdr), - comp_ehdr.e_shoff + i * sizeof(Elf64_Shdr)); - assert(pread_res != -1); - - // Find functions of interest in injected ELF file - if (comp_symtb_hdr.sh_type == SHT_SYMTAB) - { - Elf64_Shdr comp_strtb_hdr; - pread_res = pread((int) new_comp->fd, &comp_strtb_hdr, sizeof(Elf64_Shdr), comp_ehdr.e_shoff + comp_symtb_hdr.sh_link * sizeof(Elf64_Shdr)); - assert(pread_res != -1); - char* comp_strtb = (char*) malloc(comp_strtb_hdr.sh_size); - pread_res = pread((int) new_comp->fd, comp_strtb, comp_strtb_hdr.sh_size, comp_strtb_hdr.sh_offset); - assert(pread_res != -1); - - Elf64_Sym* comp_symtb = (Elf64_Sym*) malloc(comp_symtb_hdr.sh_size); - pread_res = pread((int) new_comp->fd, comp_symtb, comp_symtb_hdr.sh_size, comp_symtb_hdr.sh_offset); - assert(pread_res != -1); - - size_t syms_count = comp_symtb_hdr.sh_size / sizeof(Elf64_Sym); - Elf64_Sym curr_sym; - size_t syms_to_find_count = entry_point_count; - const char** syms_found = malloc(syms_to_find_count); - size_t syms_found_count = 0; - for (size_t j = 0; j < syms_count; ++j) - { - curr_sym = comp_symtb[j]; - for (size_t k = 0; k < syms_to_find_count; ++k) - { - if (!strcmp(entry_points[k].name, &comp_strtb[curr_sym.st_name])) - { - struct entry_point* new_entry_point = malloc(sizeof(struct entry_point)); - new_entry_point->fn_name = entry_points[k].name; - new_entry_point->arg_count = entry_points[k].arg_count; - new_entry_point->arg_types = entry_points[k].args_type; - switch(new_comp->elf_type) - { - case ET_DYN: - { - new_entry_point->fn_addr - = new_comp->base + curr_sym.st_value; - break; - } - case ET_EXEC: - { - new_entry_point->fn_addr - = curr_sym.st_value; - break; - } - default: - assert(false); - } - new_comp->comp_fns[new_comp->entry_point_count] = - new_entry_point; - new_comp->entry_point_count += 1; - syms_found[syms_found_count] = entry_points[k].name; - syms_found_count += 1; - } - else - { - for (size_t i = 0; i < sizeof(to_intercept_funcs) / sizeof(to_intercept_funcs[0]); ++i) - { - if (!strcmp(comp_intercept_funcs[i].func_name, &comp_strtb[curr_sym.st_name])) - { - comp_add_intercept(new_comp, curr_sym.st_value, comp_intercept_funcs[i]); - break; - } - } - } - } - } - free(comp_symtb); - free(comp_strtb); - if (syms_found_count != syms_to_find_count) + do_pread((int) new_comp->fd, &curr_shdr, sizeof(Elf64_Shdr), + comp_ehdr.e_shoff + i * sizeof(Elf64_Shdr)); + + if (curr_shdr.sh_type == SHT_SYMTAB) { - const char** syms_not_found = malloc(syms_to_find_count); - size_t not_found_idx = 0; - for (size_t i = 0; i < syms_to_find_count; ++i) - { - bool not_found = true; - for (size_t j = 0; j < syms_found_count; ++j) - { - if (!strcmp(syms_found[j], entry_points[i].name)) - { - not_found = false; - break; - } - } - if (not_found) - { - syms_not_found[not_found_idx] = entry_points[i].name; - not_found_idx += 1; - } - } - printf("Did not find following entry points [ "); - for (size_t i = 0; i < not_found_idx; ++i) - { - printf("%s ", syms_not_found[i]); - } - printf("]\n"); - free(syms_not_found); - free(syms_found); - assert(false); + comp_symtb_shdr = curr_shdr; + found_headers += 1; + } + // Lookup `.rela.plt` to eagerly load relocatable function addresses + else if (curr_shdr.sh_type == SHT_RELA && + !strcmp(&comp_sh_strtb[curr_shdr.sh_name], ".rela.plt")) + { + comp_rela_plt_shdr = curr_shdr; + found_headers += 1; } - free(syms_found); + // Lookup `.dynamic` to find library dependencies + else if (curr_shdr.sh_type == SHT_DYNAMIC) + { + comp_dynamic_shdr = curr_shdr; + found_headers += 1; } - // TODO still need relas check or consider only static executables? - else if (comp_symtb_hdr.sh_type == SHT_RELA) // TODO change name && consider SH_REL + + if (headers_of_interest_count == found_headers) { - assert(false && "currently we only handle static executables"); - if (comp_symtb_hdr.sh_info == 0) // TODO better identify the plt relocation section + break; + } + } + assert(headers_of_interest_count == found_headers); + + if (new_comp->elf_type == ET_DYN) + { + // Traverse `.rela.plt`, so we can see which function addresses we need + // to eagerly load + Elf64_Rela* comp_rela_plt = malloc(comp_rela_plt_shdr.sh_size); + do_pread((int) new_comp->fd, comp_rela_plt, comp_rela_plt_shdr.sh_size, + comp_rela_plt_shdr.sh_offset); + size_t rela_count = comp_rela_plt_shdr.sh_size / sizeof(Elf64_Rela); + + Elf64_Shdr dyn_sym_hdr; + do_pread((int) new_comp->fd, &dyn_sym_hdr, + sizeof(Elf64_Shdr), + comp_ehdr.e_shoff + comp_rela_plt_shdr.sh_link * sizeof(Elf64_Shdr)); + Elf64_Sym* dyn_sym_tbl = malloc(dyn_sym_hdr.sh_size); + do_pread((int) new_comp->fd, dyn_sym_tbl, dyn_sym_hdr.sh_size, + dyn_sym_hdr.sh_offset); + + Elf64_Shdr dyn_str_hdr; + do_pread((int) new_comp->fd, &dyn_str_hdr, + sizeof(Elf64_Shdr), + comp_ehdr.e_shoff + dyn_sym_hdr.sh_link * sizeof(Elf64_Shdr)); + char* dyn_str_tbl = malloc(dyn_str_hdr.sh_size); + do_pread((int) new_comp->fd, dyn_str_tbl, dyn_str_hdr.sh_size, + dyn_str_hdr.sh_offset); + + new_comp->rela_maps = calloc(rela_count, sizeof(struct CompRelaMapping)); + new_comp->rela_maps_count = rela_count; + + // Log symbols that will need to be relocated eagerly at maptime + Elf64_Rela curr_rela; + for (size_t j = 0; j < new_comp->rela_maps_count; ++j) + { + curr_rela = comp_rela_plt[j]; + size_t curr_rela_sym_idx = ELF64_R_SYM(curr_rela.r_info); + Elf64_Sym curr_rela_sym = dyn_sym_tbl[curr_rela_sym_idx]; + char* curr_rela_name = malloc(strlen(&dyn_str_tbl[curr_rela_sym.st_name]) + 1); + strcpy(curr_rela_name, &dyn_str_tbl[curr_rela_sym.st_name]); + if (ELF64_ST_BIND(curr_rela_sym.st_info) == STB_WEAK) { + // Do not handle weak-bind symbols + // TODO should we? + struct CompRelaMapping crm = { curr_rela_name, 0, 0 }; + new_comp->rela_maps[j] = crm; continue; + } // TODO collapse + + struct CompRelaMapping crm = { + curr_rela_name, + curr_rela.r_offset + (char*) new_comp->base, + NULL }; + new_comp->rela_maps[j] = crm; + } + free(comp_rela_plt); + free(dyn_sym_tbl); + + // Find additional library dependencies + Elf64_Dyn* comp_dyn_entries = malloc(comp_dynamic_shdr.sh_size); + do_pread((int) new_comp->fd, comp_dyn_entries, + comp_dynamic_shdr.sh_size, comp_dynamic_shdr.sh_offset); + + for (size_t i = 0; + i < comp_dynamic_shdr.sh_size / sizeof(Elf64_Dyn); + ++i) + { + if (comp_dyn_entries[i].d_tag == DT_NEEDED) + { + struct LibDependency* new_lib_dep = + malloc(sizeof(struct LibDependency)); + new_lib_dep->lib_name = + malloc(strlen(&dyn_str_tbl[comp_dyn_entries[i].d_un.d_val]) + 1); + strcpy( + new_lib_dep->lib_name, + &dyn_str_tbl[comp_dyn_entries[i].d_un.d_val]); + new_comp->lib_deps_count += 1; + new_comp->lib_deps = + realloc(new_comp->lib_deps, + new_comp->lib_deps_count * + sizeof(struct LibDependency)); + new_comp->lib_deps[new_comp->lib_deps_count - 1] = new_lib_dep; } + } - new_comp->relas_cnt = comp_symtb_hdr.sh_size / sizeof(Elf64_Rela); - new_comp->relas = (uintptr_t*) malloc(new_comp->relas_cnt * sizeof(uintptr_t)); - Elf64_Rela* comp_relas = (Elf64_Rela*) malloc(comp_symtb_hdr.sh_size); - pread_res = pread((int) new_comp->fd, comp_relas, comp_symtb_hdr.sh_size, comp_symtb_hdr.sh_offset); - assert(pread_res != -1); + free(dyn_str_tbl); + free(comp_dyn_entries); + } - for (size_t j = 0; j < new_comp->relas_cnt; ++j) + // Find library files in `COMP_LIBRARY_PATH` to fulfill dependencies + for (size_t i = 0; i < new_comp->lib_deps_count; ++i) + { + struct LibDependency* curr_dep = new_comp->lib_deps[i]; + // TODO move env var name to constant + assert(getenv("COMP_LIBRARY_PATH")); + char* lib_path = + find_in_dir(curr_dep->lib_name, getenv("COMP_LIBRARY_PATH")); + if (!lib_path) + { + errx(1, "Could not find file for dependency %s!\n", curr_dep->lib_name); + } + curr_dep->lib_path = malloc(strlen(lib_path)); + strcpy(curr_dep->lib_path, lib_path); + init_lib_dep_info(curr_dep, new_comp); + new_comp->mem_top = + (char*) curr_dep->lib_mem_base + + (uintptr_t) curr_dep->lib_segs[curr_dep->lib_segs_count - 1]->mem_top; + } + + // Find functions of interest, particularly entry points, and functions to + // intercept + Elf64_Shdr comp_strtb_hdr; + do_pread((int) new_comp->fd, &comp_strtb_hdr, sizeof(Elf64_Shdr), + comp_ehdr.e_shoff + comp_symtb_shdr.sh_link * sizeof(Elf64_Shdr)); + + // XXX The string table is read in `comp_strtb` as a sequence of + // variable-length strings. Then, symbol names are obtained by indexing at + // the offset where the name for that symbol begins. Therefore, the type + // `char*` for the string table makes sense. + char* comp_strtb = malloc(comp_strtb_hdr.sh_size); + do_pread((int) new_comp->fd, comp_strtb, comp_strtb_hdr.sh_size, comp_strtb_hdr.sh_offset); + + Elf64_Sym* comp_symtb = malloc(comp_symtb_shdr.sh_size); + do_pread((int) new_comp->fd, comp_symtb, comp_symtb_shdr.sh_size, comp_symtb_shdr.sh_offset); + + // Find symbols for entry_points + Elf64_Sym* ep_syms = + find_symbols((const char**) entry_points, entry_point_count, + true, comp_symtb, comp_strtb, comp_symtb_shdr.sh_size); + for (size_t i = 0; i < entry_point_count; ++i) + { + struct entry_point* new_entry_point = malloc(sizeof(struct entry_point)); + new_entry_point->fn_name = entry_points[i]; + switch(new_comp->elf_type) + { + case ET_DYN: + { + new_entry_point->fn_addr = (char*) new_comp->base + ep_syms[i].st_value; + break; + } + case ET_EXEC: { - new_comp->relas[j] = comp_relas[j].r_offset; - // TODO double check - /*comp_relas[j].r_offset += new_comp->base;*/ - /*uintptr_t old_plt_val = (uintptr_t) *((void**) comp_relas[j].r_offset);*/ - /*old_plt_val += new_comp->base;*/ + new_entry_point->fn_addr = (void*) ep_syms[i].st_value; + break; } + default: + errx(1, "Invalid ELF type"); + } + new_comp->comp_fns[new_comp->entry_point_count] = new_entry_point; + new_comp->entry_point_count += 1; + } + free(ep_syms); - free(comp_relas); + // Find symbols for intercepts + char** intercept_names = calloc(intercept_count, sizeof(char*)); + const char* so_plt_suffix = "@plt"; + for (size_t i = 0; i < intercept_count; ++i) + { + if (new_comp->elf_type == ET_DYN) + { + size_t to_intercept_name_len = strlen(intercepts[i]) + strlen(so_plt_suffix) + 1; + intercept_names[i] = malloc(to_intercept_name_len); + strcpy(intercept_names[i], intercepts[i]); + strcat(intercept_names[i], so_plt_suffix); + } + else if (new_comp->elf_type == ET_EXEC) + { + intercept_names[i] = malloc(strlen(intercepts[i]) + 1); + strcpy(intercept_names[i], intercepts[i]); } else + { + errx(1, "Invalid ELF type"); + } + } + Elf64_Sym* intercept_syms = + find_symbols((const char**) intercept_names, intercept_count, false, + comp_symtb, comp_strtb, comp_symtb_shdr.sh_size); + for (size_t i = 0; i < intercept_count; ++i) + { + // TODO better way to check if we didn't find an intercept? + if (intercept_syms[i].st_value != 0) + { + comp_add_intercept(new_comp, intercept_syms[i].st_value, (uintptr_t) intercept_addrs[i]); + } + free(intercept_names[i]); + } + free(intercept_names); + free(intercept_syms); + + // Find all symbols for eager relocation mapping + for (size_t i = 0; i < new_comp->rela_maps_count; ++i) + { + // Ignore relocations we don't want to load, as earlier set on lookup + // (e.g., weak-bound symbols) + if (new_comp->rela_maps[i].rela_address == 0) { continue; } + for (size_t j = 0; j < new_comp->lib_deps_count; ++j) + { + for (size_t k = 0; k < new_comp->lib_deps[j]->lib_syms_count; ++k) + { + if (!strcmp(new_comp->rela_maps[i].rela_name, + new_comp->lib_deps[j]->lib_syms[k].sym_name)) + { + new_comp->rela_maps[i].target_func_address = + (char*) new_comp->lib_deps[j]->lib_mem_base + + new_comp->lib_deps[j]->lib_syms[k].sym_offset; + goto found; + } + } + } + errx(1, "Did not find symbol %s!\n", new_comp->rela_maps[i].rela_name); + found: + (void) 0; } - comp_register_ddc(new_comp); - return new_comp; -} + free(comp_symtb); + free(comp_strtb); -void -comp_register_ddc(struct Compartment* new_comp) -{ - void* __capability new_ddc = cheri_address_set(manager_ddc, new_comp->base); - new_ddc = cheri_bounds_set(new_ddc, new_comp->size + new_comp->scratch_mem_size); - // TODO bounds double-check - new_comp->ddc = new_ddc; + return new_comp; } /* For a given Compartment `new_comp`, an address `intercept_target` pointing @@ -335,16 +431,20 @@ comp_register_ddc(struct Compartment* new_comp) * to call the appropriate function with higher privileges. */ void -comp_add_intercept(struct Compartment* new_comp, uintptr_t intercept_target, struct func_intercept intercept_data) +comp_add_intercept(struct Compartment* new_comp, uintptr_t intercept_target, uintptr_t redirect_addr) { // TODO check whether negative values break anything in all these generated functions int32_t new_instrs[INTERCEPT_INSTR_COUNT]; size_t new_instr_idx = 0; - uintptr_t comp_manager_cap_addr = new_comp->manager_caps + new_comp->active_manager_caps_count * sizeof(void* __capability); // TODO + const ptraddr_t comp_manager_cap_addr = + (ptraddr_t) new_comp->manager_caps + + new_comp->active_manager_caps_count * sizeof(void* __capability); // TODO const int32_t arm_function_target_register = 0b01010; // use `x10` for now const int32_t arm_transition_target_register = 0b01011; // use `x11` for now + // `x10` is used to hold the address of the manager function we want to + // execute after a jump out of the compartment // TODO ideally we want 1 `movz` and 3 `movk`, to be able to access any // address, but this is sufficient for now // movz x0, $target_fn_addr:lo16 @@ -352,8 +452,8 @@ comp_add_intercept(struct Compartment* new_comp, uintptr_t intercept_target, str assert(intercept_target < ((ptraddr_t) 1 << 32)); const uint32_t arm_movz_instr_mask = 0b11010010100 << 21; const uint32_t arm_movk_instr_mask = 0b11110010101 << 21; - const ptraddr_t target_address_lo16 = (intercept_data.redirect_func & ((1 << 16) - 1)) << 5; - const ptraddr_t target_address_hi16 = (intercept_data.redirect_func >> 16) << 5; + const ptraddr_t target_address_lo16 = (redirect_addr & ((1 << 16) - 1)) << 5; + const ptraddr_t target_address_hi16 = (redirect_addr >> 16) << 5; const int32_t arm_movz_intr = arm_movz_instr_mask | target_address_lo16 | arm_function_target_register; const int32_t arm_movk_intr = arm_movk_instr_mask | target_address_hi16 | arm_function_target_register; new_instrs[new_instr_idx++] = arm_movz_intr; @@ -389,7 +489,7 @@ comp_add_intercept(struct Compartment* new_comp, uintptr_t intercept_target, str new_instrs[new_instr_idx++] = arm_ldr_instr; // `b` instr generation - ptraddr_t arm_b_instr_offset = (new_comp->mng_trans_fn - (intercept_target + new_instr_idx * sizeof(uint32_t))) / 4; + ptraddr_t arm_b_instr_offset = (((uintptr_t) new_comp->mng_trans_fn) - (intercept_target + new_instr_idx * sizeof(uint32_t))) / 4; assert(arm_b_instr_offset < (1 << 27)); arm_b_instr_offset &= (1 << 26) - 1; const uint32_t arm_b_instr_mask = 0b101 << 26; @@ -402,27 +502,20 @@ comp_add_intercept(struct Compartment* new_comp, uintptr_t intercept_target, str memcpy(new_patch.instr, new_instrs, sizeof(new_instrs)); __clear_cache(new_patch.instr, new_patch.instr + sizeof(new_instrs)); new_patch.comp_manager_cap_addr = comp_manager_cap_addr; - new_patch.manager_cap = intercept_data.redirect_cap; - new_comp->patches[new_comp->curr_intercept_count] = new_patch; + new_patch.manager_cap = sealed_redirect_cap; new_comp->curr_intercept_count += 1; + new_comp->intercept_patches = realloc(new_comp->intercept_patches, new_comp->curr_intercept_count * sizeof(struct intercept_patch)); + new_comp->intercept_patches[new_comp->curr_intercept_count - 1] = new_patch; } void comp_stack_push(struct Compartment* comp, const void* to_push, size_t to_push_sz) { - comp->stack_pointer -= to_push_sz; + comp->stack_pointer = (char*) comp->stack_pointer - to_push_sz; memcpy((void*) comp->stack_pointer, to_push, to_push_sz); - assert(comp->stack_pointer > comp->scratch_mem_stack_top - comp->scratch_mem_stack_size); + assert(comp->stack_pointer > (void*) ((char*) comp->scratch_mem_stack_top - comp->scratch_mem_stack_size)); } -// TODO code needed if we reimplement auxp/envp for the loader -/*void*/ -/*comp_stack_auxval_push(struct Compartment* comp, uint64_t at_type, uint64_t at_val)*/ -/*{*/ - /*Elf64_Auxinfo new_auxv = {at_type, {at_val} };*/ - /*comp_stack_push(comp, &new_auxv, sizeof(new_auxv));*/ -/*}*/ - /* Map a struct Compartment into memory, making it ready for execution */ void @@ -436,28 +529,45 @@ comp_map(struct Compartment* to_map) for (size_t i = 0; i < to_map->seg_count; ++i) { curr_seg = to_map->segs[i]; - if (curr_seg->file_sz == curr_seg->mem_sz) + map_result = mmap((void*) curr_seg->mem_bot, + curr_seg->mem_sz, + /*curr_seg->prot_flags,*/ // TODO currently need read/write to inject the intercepts, consider better option + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, + -1, 0); + if (map_result == MAP_FAILED) { - map_result = mmap((void*) curr_seg->mem_bot, - curr_seg->file_sz, - /*curr_seg->prot_flags,*/ // TODO currently need read/write to inject the intercepts, consider better option - PROT_READ | PROT_WRITE | PROT_EXEC, - MAP_PRIVATE | MAP_FIXED, - to_map->fd, curr_seg->offset); + errx(1, "Error mapping comp segment idx %zu", i); } - else + do_pread(to_map->fd, (void*) curr_seg->mem_bot, curr_seg->file_sz, + curr_seg->offset); + } + + // Map compartment library dependencies segments + struct LibDependency* lib_dep; + struct SegmentMap* lib_dep_seg; + int lib_dep_fd; + for (size_t i = 0; i < to_map->lib_deps_count; ++i) + { + lib_dep = to_map->lib_deps[i]; + lib_dep_fd = open(lib_dep->lib_path, O_RDONLY); + for (size_t j = 0; j < lib_dep->lib_segs_count; ++j) { - assert(curr_seg->mem_sz > curr_seg->file_sz); - map_result = mmap((void*) curr_seg->mem_bot, - curr_seg->mem_sz, - curr_seg->prot_flags, - MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, - -1, 0); - assert(map_result != MAP_FAILED); - int pread_res = pread(to_map->fd, (void*) curr_seg->mem_bot, - curr_seg->file_sz, curr_seg->offset); - assert(pread_res != -1); + lib_dep_seg = lib_dep->lib_segs[j]; + map_result = mmap((char*) lib_dep->lib_mem_base + (uintptr_t) lib_dep_seg->mem_bot, + lib_dep_seg->mem_sz, + PROT_READ | PROT_WRITE | PROT_EXEC, // TODO fix + MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, + -1, 0); + if (map_result == MAP_FAILED) + { + errx(1, "Error mapping library %s dependency segment idx %zu", + lib_dep->lib_name, j); + } + do_pread(lib_dep_fd, (char*) lib_dep->lib_mem_base + (uintptr_t) lib_dep_seg->mem_bot, + lib_dep_seg->file_sz, lib_dep_seg->offset); } + close(lib_dep_fd); } // Map compartment scratch memory @@ -469,7 +579,7 @@ comp_map(struct Compartment* to_map) assert(map_result != MAP_FAILED); // Map compartment stack - map_result = mmap((void*) to_map->scratch_mem_stack_top - to_map->scratch_mem_stack_size, + map_result = mmap((char*) to_map->scratch_mem_stack_top - to_map->scratch_mem_stack_size, to_map->scratch_mem_stack_size, PROT_READ | PROT_WRITE | PROT_EXEC, // TODO fix this MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS | MAP_STACK, @@ -477,18 +587,11 @@ comp_map(struct Compartment* to_map) to_map->stack_pointer = to_map->scratch_mem_stack_top; assert(map_result != MAP_FAILED); - for (size_t i = 0; i < to_map->relas_cnt; ++i) - { - uintptr_t rela_addr = to_map->relas[i] + to_map->base; - uintptr_t old_plt_val = (uintptr_t) *((void**) rela_addr); - old_plt_val += to_map->base; - *((uintptr_t *) rela_addr) = old_plt_val; - } - // Inject intercept instructions within identified intercepted functions for (size_t i = 0; i < to_map->curr_intercept_count; ++i) { - struct intercept_patch to_patch = to_map->patches[i]; + struct intercept_patch to_patch = to_map->intercept_patches[i]; + // TODO change to memcpy? for (size_t j = 0; j < INTERCEPT_INSTR_COUNT; ++j) { int32_t* curr_addr = to_patch.patch_addr + j; @@ -498,7 +601,19 @@ comp_map(struct Compartment* to_map) } // Inject manager transfer function - memcpy((void*) to_map->mng_trans_fn, &compartment_transition_out, to_map->mng_trans_fn_sz); + memcpy(to_map->mng_trans_fn, (void*) &compartment_transition_out, to_map->mng_trans_fn_sz); + + // Bind `.got.plt` entries + for (size_t i = 0; i < to_map->rela_maps_count; ++i) + { + if (to_map->rela_maps[i].rela_address == 0) + { + continue; + } + memcpy((void*) to_map->rela_maps[i].rela_address, + &to_map->rela_maps[i].target_func_address, + sizeof(void*)); + } to_map->mapped = true; } @@ -535,14 +650,12 @@ comp_exec(struct Compartment* to_exec, char* fn_name, void* args, size_t args_co } if (!fn) { - printf("Did not find entry point `%s`!\n", fn_name); - assert(false); + errx(1, "Did not find entry point `%s`!\n", fn_name); } void* wrap_sp; - // TODO check if we still need this + // TODO check if we need anything from here // https://git.morello-project.org/morello/kernel/linux/-/wikis/Morello-pure-capability-kernel-user-Linux-ABI-specification - /*setup_stack(to_exec);*/ int64_t result; @@ -558,8 +671,8 @@ comp_exec(struct Compartment* to_exec, char* fn_name, void* args, size_t args_co /*arg = cheri_perms_and(arg, !(CHERI_PERM_STORE | CHERI_PERM_EXECUTE));*/ /*args_caps[i] = arg;*/ /*}*/ - result = comp_exec_in((void*) to_exec->stack_pointer, to_exec->ddc, fn, args, args_count); - + result = comp_exec_in((void*) to_exec->stack_pointer, to_exec->ddc, fn, + args, args_count, sealed_redirect_cap); return result; } @@ -569,151 +682,293 @@ comp_clean(struct Compartment* to_clean) close(to_clean->fd); if (to_clean->mapped) { - for (size_t i = 0; i < to_clean->seg_count; ++i) - { - free(to_clean->segs[i]); - // TODO unmap - } + // TODO unmap } else if (to_clean->mapped_full) { // TODO unmap } + + for (size_t i = 0; i < to_clean->seg_count; ++i) + { + free(to_clean->segs[i]); + } + free(to_clean->segs); + for (size_t i = 0; i < to_clean->entry_point_count; ++i) { free((char*) to_clean->comp_fns[i]->fn_name); free(to_clean->comp_fns[i]); } - free(to_clean); - // TODO -} -void -log_new_comp(struct Compartment* to_log) -{ - comps = realloc(comps, sizeof(comps) + sizeof(struct Compartment)); - comps[to_log->id] = to_log; -} + for (size_t i = 0; i < to_clean->rela_maps_count; ++i) + { + free(to_clean->rela_maps[i].rela_name); + } + free(to_clean->rela_maps); -struct Compartment* -find_comp_by_addr(void* to_find) -{ - assert(comps[0]->base <= (uintptr_t) to_find); - assert(comps[0]->base + comps[0]->size > (uintptr_t) to_find); - return comps[0]; // TODO + struct LibDependency* ld; + for (size_t i = 0; i < to_clean->lib_deps_count; ++i) + { + ld = to_clean->lib_deps[i]; + free(ld->lib_name); + free(ld->lib_path); + for (size_t j = 0; j < ld->lib_segs_count; ++j) + { + free(ld->lib_segs[j]); + } + free(ld->lib_segs); + for (size_t j = 0; j < ld->lib_syms_count; ++j) + { + free(ld->lib_syms[j].sym_name); + } + free(ld->lib_syms); + free(ld); + } + free(to_clean->lib_deps); + free(to_clean->intercept_patches); + + + free(to_clean); + // TODO } /******************************************************************************* * Helper functions ******************************************************************************/ -/* TODO - * WIP attempt to set the stack correctly upon entering the compartment - * executable. This is related to `argv` and `envp` stack setup for normal - * binary executions. Might be needed for argument passing, and is related to - * vDSO loosely. - * - * Morello reference: - * https://git.morello-project.org/morello/kernel/linux/-/wikis/Morello-pure-capability-kernel-user-Linux-ABI-specification#arguments-argv-and-environment-variables-envp - */ -/*void*/ -/*setup_stack(struct Compartment* to_setup)*/ -/*{*/ - /*assert(to_setup->stack_pointer % 16 == 0);*/ - - /*uintptr_t init_sp = to_setup->stack_pointer;*/ - /*uintptr_t argv_ptrs[to_setup->argc];*/ - /*for (size_t i = 0; i < to_setup->argc; ++i)*/ - /*{*/ - /*comp_stack_push(to_setup, to_setup->argv[i], strlen(to_setup->argv[i]));*/ - /*argv_ptrs[i] = to_setup->stack_pointer;*/ - /*}*/ +static +ssize_t +do_pread(int fd, void* buf, size_t count, off_t offset) +{ + size_t res = pread(fd, buf, count, offset); + if (res == -1) + { + err(1, "Error in pread"); + } + return res; +} - /*uintptr_t envp_ptrs[ENV_FIELDS_CNT];*/ - /*const char* envp_val;*/ - /*for (size_t i = 0; i < ENV_FIELDS_CNT; ++i)*/ - /*{*/ - /*envp_val = get_env_str(comp_env_fields[i]);*/ - /*comp_stack_push(to_setup, envp_val, strlen(envp_val));*/ - /*envp_ptrs[i] = to_setup->stack_pointer;*/ - /*}*/ +static +Elf64_Sym* +find_symbols(const char** names, size_t names_to_find_count, bool find_all, + Elf64_Sym* symtb, char* strtb, size_t symtb_sz) +{ + Elf64_Sym* found_syms = calloc(names_to_find_count, sizeof(Elf64_Sym)); + Elf64_Sym curr_sym; + size_t found_syms_count = 0; + for (size_t i = 0; i < symtb_sz / sizeof(Elf64_Sym); ++i) + { + curr_sym = symtb[i]; + for (size_t j = 0; j < names_to_find_count; ++j) + { + // XXX As a follow-up from how we handle the string table, here we + // get symbol names by indexing at the `char` offset, then getting + // the string pointer (equivalent to `strtb + curr_sym.st_name`). + if (!strcmp(names[j], &strtb[curr_sym.st_name])) + { + found_syms[j] = curr_sym; + found_syms_count += 1; + } + } + } - /*size_t stack_push_size = (1 + 1 + 1 + sizeof(envp_ptrs) + sizeof(argv_ptrs)) * sizeof(uint64_t);*/ - /*void* null_delim = NULL;*/ - /*[> argc <]*/ - /*size_t stack_argc = to_setup->argc;*/ - /*comp_stack_push(to_setup, &stack_argc, sizeof(uint64_t));*/ - /*[> argv <]*/ - /*for (size_t i = 0; i < to_setup->argc; ++i)*/ - /*{*/ - /*comp_stack_push(to_setup, (void*) &argv_ptrs[i], sizeof(uint64_t));*/ - /*}*/ - /*[> argv NULL delimiter <]*/ - /*comp_stack_push(to_setup, &null_delim, sizeof(null_delim));*/ - /*[> envp <]*/ - /*for (size_t i = 0; i < ENV_FIELDS_CNT; ++i) // envp*/ - /*{*/ - /*[>comp_stack_push(comp_envs[i], strlen(comp_envs[1] + 1);<]*/ - /*comp_stack_push(to_setup, (void*) &envp_ptrs[i], sizeof(uint64_t));*/ - /*}*/ - /*[> envp NULL delimiter <]*/ - /*comp_stack_push(to_setup, &null_delim, sizeof(null_delim));*/ - /*[> auxv <]*/ - /*comp_stack_auxval_push(to_setup, AT_PAGESZ, elf_aux_info(AT_PAGESZ, NULL, sizeof(size_t)));*/ - /*comp_stack_auxval_push(to_setup, AT_PHDR, to_setup->phdr);*/ - /*comp_stack_auxval_push(to_setup, AT_PHENT, to_setup->phentsize);*/ - /*comp_stack_auxval_push(to_setup, AT_PHNUM, to_setup->phnum);*/ - /*[>comp_stack_auxval_push(to_setup, AT_SECURE, 0);<]*/ - /*[>comp_stack_auxval_push(to_setup, AT_RANDOM, rand());<]*/ - /*comp_stack_auxval_push(to_setup, AT_NULL, 0);*/ + // If we didn't find all symbols that we wanted to intercept, throw an error + if (find_all && found_syms_count != names_to_find_count) + { + const char** not_found_syms = malloc(names_to_find_count); + size_t not_found_idx = 0; + for (size_t i = 0; i < names_to_find_count; ++i) + { + bool not_found = true; + for (size_t j = 0; j < found_syms_count; ++j) + { + if (!strcmp(&strtb[found_syms[j].st_name], names[i])) + { + not_found = false; + break; + } + } + if (not_found) + { + not_found_syms[not_found_idx] = names[i]; + not_found_idx += 1; + } + } + printf("Did not find following entry points [ "); + for (size_t i = 0; i < not_found_idx; ++i) + { + printf("%s ", not_found_syms[i]); + } + printf("]\n"); + free(not_found_syms); + free(found_syms); + errx(1, NULL); + } - /*to_setup->stack_pointer = init_sp;*/ + return found_syms; +} -/*}*/ +static +char* +find_in_dir(const char* lib_name, char* search_dir) +{ + errno = 0; + char** search_paths = malloc(sizeof(char*)); + search_paths[0] = search_dir; + FTS* dir = fts_open(search_paths, FTS_LOGICAL, NULL); + if (!dir) + { + err(1, "Failed fts_open for path %s.\n", search_dir); + } -/******************************************************************************* - * Print functions - * TODO complete these once structs stabilize - ******************************************************************************/ + FTSENT* curr_entry; + while ((curr_entry = fts_read(dir)) != NULL) + { + if (!strcmp(lib_name, curr_entry->fts_name)) + { + return curr_entry->fts_path; + } + } + fts_close(dir); + free(search_paths); + return NULL; +} +static void -comp_print(struct Compartment* to_print) +init_comp_scratch_mem(struct Compartment* new_comp) { - printf("=== COMPARTMENT ===\n"); - printf("\t * ID --- %lu\n", to_print->id); - printf("\t * FD --- %d\n", to_print->fd); - - printf("\t * PHDR --- %hu\n", to_print->phdr); - /*printf("\t * DDC --- %lu\n", to_print->ddc);*/ - - printf("\t * SIZE --- %lu\n", to_print->size); - printf("\t * BASE --- %#010x\n", (unsigned int) to_print->base); - printf("\t * ENTRY CNT --- %lu\n", to_print->entry_point_count); - printf("\t * MAPD --- %d\n", to_print->mapped); - printf("\t * MAPDF --- %d\n", to_print->mapped_full); - - /*printf("\t * ENTRY --- %#010x\n", (unsigned int) to_print->entry_point);*/ - printf("\t * RELACNT --- %lu\n", to_print->relas_cnt); - printf("\t * SEGC --- %lu\n", to_print->seg_count); - printf("\t * SEGS --- "); - for (size_t i = 0; i < to_print->seg_count; ++i) - { - printf("%p, ", to_print->segs[i]); - } - printf("\n"); - printf("\t * SEGSZ --- %lu\n", to_print->segs_size); - printf("\t * PGSZ --- %lu\n", to_print->page_size); + new_comp->scratch_mem_base = + align_up( + (char*) new_comp->segs[new_comp->seg_count - 1]->mem_top + + new_comp->page_size, + new_comp->page_size); + new_comp->max_manager_caps_count = 10; // TODO + new_comp->scratch_mem_heap_size = 0x800000UL; // TODO + new_comp->scratch_mem_size = + new_comp->scratch_mem_heap_size + + new_comp->max_manager_caps_count * sizeof(void* __capability) + + new_comp->mng_trans_fn_sz; + new_comp->scratch_mem_alloc = 0; + new_comp->scratch_mem_stack_top = + align_down( + (char*) new_comp->scratch_mem_base + + new_comp->scratch_mem_heap_size, + 16); + new_comp->scratch_mem_stack_size = 0x80000UL; // TODO + new_comp->manager_caps = new_comp->scratch_mem_stack_top; + new_comp->active_manager_caps_count = 0; + new_comp->mng_trans_fn = + (char*) new_comp->manager_caps + + new_comp->max_manager_caps_count * sizeof(void* __capability); + + assert(((uintptr_t) new_comp->scratch_mem_base) % 16 == 0); + assert((((uintptr_t) new_comp->scratch_mem_base) + new_comp->scratch_mem_size) % 16 == 0); + assert(((uintptr_t) new_comp->scratch_mem_stack_top) % 16 == 0); + assert( + (((uintptr_t) new_comp->scratch_mem_stack_top) - + new_comp->scratch_mem_stack_size) % 16 == 0); + assert(new_comp->scratch_mem_size % 16 == 0); } +/* Get the segment data for segments we will be mapping for a library dependency + */ +static void -segmap_print(struct SegmentMap* to_print) +init_lib_dep_info(struct LibDependency* lib_dep, struct Compartment* new_comp) { - printf("=== SEGMENT MAP ===\n"); - printf("\t * BOT --- %#010x\n", (unsigned int) to_print->mem_bot); - printf("\t * TOP --- %#010x\n", (unsigned int) to_print->mem_top); - printf("\t * OFF --- %zu\n", to_print->offset); - printf("\t * M_SZ --- %zu\n", to_print->mem_sz); - printf("\t * F_SZ --- %zu\n", to_print->file_sz); - printf("\t * CORR --- %zu\n", to_print->correction); - printf("\t * FLAG --- %d\n", to_print->prot_flags); + lib_dep->lib_segs_count = 0; + int lib_fd = open(lib_dep->lib_path, O_RDONLY); + assert(lib_fd != -1 && "Error opening `lib_fd`"); + Elf64_Ehdr lib_ehdr; + Elf64_Phdr lib_phdr; + do_pread(lib_fd, &lib_ehdr, sizeof(Elf64_Ehdr), 0); + + // Get segment data + for (size_t i = 0; i < lib_ehdr.e_phnum; ++i) + { + do_pread((int) lib_fd, &lib_phdr, sizeof(Elf64_Phdr), + lib_ehdr.e_phoff + i * sizeof(lib_phdr)); + if (lib_phdr.p_type != PT_LOAD) + { + continue; + } + + struct SegmentMap* this_seg = malloc(sizeof(struct SegmentMap)); + this_seg->mem_bot = (void*) align_down(lib_phdr.p_vaddr, new_comp->page_size); + this_seg->correction = (char*) lib_phdr.p_vaddr - (char*) this_seg->mem_bot; + this_seg->mem_top = (char*) lib_phdr.p_vaddr + lib_phdr.p_memsz; + this_seg->offset = align_down(lib_phdr.p_offset, new_comp->page_size); + this_seg->mem_sz = lib_phdr.p_memsz + this_seg->correction; + this_seg->file_sz = lib_phdr.p_filesz + this_seg->correction; + this_seg->prot_flags = (lib_phdr.p_flags & PF_R ? PROT_READ : 0) | + (lib_phdr.p_flags & PF_W ? PROT_WRITE : 0) | + (lib_phdr.p_flags & PF_X ? PROT_EXEC : 0); + + lib_dep->lib_segs_count += 1; + lib_dep->lib_segs_size += align_up(this_seg->mem_sz, lib_phdr.p_align); // TODO check + lib_dep->lib_segs = + realloc(lib_dep->lib_segs, + lib_dep->lib_segs_count * sizeof(struct SegmentMap)); + lib_dep->lib_segs[lib_dep->lib_segs_count - 1] = this_seg; + } + + lib_dep->lib_mem_base = + align_down((char*) new_comp->mem_top + new_comp->page_size, new_comp->page_size); + new_comp->size += new_comp->page_size + lib_dep->lib_segs_size; + + // Get symbol table + Elf64_Shdr curr_shdr; + Elf64_Shdr link_shdr; + Elf64_Sym curr_sym; + for (size_t i = 0; i < lib_ehdr.e_shnum; ++i) + { + do_pread((int) lib_fd, &curr_shdr, sizeof(Elf64_Shdr), + lib_ehdr.e_shoff + i * sizeof(Elf64_Shdr)); + if (curr_shdr.sh_type != SHT_SYMTAB) + { + continue; + } + + assert(curr_shdr.sh_link); + do_pread((int) lib_fd, &link_shdr, sizeof(Elf64_Shdr), + lib_ehdr.e_shoff + curr_shdr.sh_link * sizeof(Elf64_Shdr)); + + Elf64_Sym* sym_tb = malloc(curr_shdr.sh_size); + do_pread((int) lib_fd, sym_tb, curr_shdr.sh_size, curr_shdr.sh_offset); + char* str_tb = malloc(link_shdr.sh_size); + do_pread((int) lib_fd, str_tb, link_shdr.sh_size, link_shdr.sh_offset); + + lib_dep->lib_syms_count = curr_shdr.sh_size / sizeof(Elf64_Sym); + size_t actual_syms = 0; + struct LibDependencySymbol* ld_syms = + malloc(lib_dep->lib_syms_count * sizeof(struct LibDependencySymbol)); + for (size_t j = 0; j < lib_dep->lib_syms_count; ++j) + { + curr_sym = sym_tb[j]; + // TODO only handling FUNC symbols for now + if (ELF64_ST_TYPE(curr_sym.st_info) != STT_FUNC) + { + continue; + } + if (curr_sym.st_value == 0) + { + continue; + } + ld_syms[actual_syms].sym_offset = curr_sym.st_value; + char* sym_name = &str_tb[curr_sym.st_name]; + ld_syms[actual_syms].sym_name = malloc(strlen(sym_name) + 1); + strcpy(ld_syms[actual_syms].sym_name, sym_name); + actual_syms += 1; + } + ld_syms = realloc(ld_syms, actual_syms * sizeof(struct LibDependencySymbol)); + lib_dep->lib_syms_count = actual_syms; + lib_dep->lib_syms = ld_syms; + + free(sym_tb); + free(str_tb); + } + + close(lib_fd); } diff --git a/src/intercept.c b/src/intercept.c new file mode 100644 index 0000000..f796eab --- /dev/null +++ b/src/intercept.c @@ -0,0 +1,172 @@ +#include "intercept.h" + +struct func_intercept comp_intercept_funcs[INTERCEPT_FUNC_COUNT]; +void* __capability comp_return_caps[COMP_RETURN_CAPS_COUNT]; +void* __capability sealed_redirect_cap; + +/* Setup required capabilities on the heap to jump from within compartments via + * a context switch + * + * For each function to be intercepted, we define the following: + * redirect_func function to be executed at a higher privilege level + * TODO I think the below three are common and can be lifted + * intercept_ddc ddc to be installed for the transition + * intercept_pcc + * higher privileged pcc pointing to the transition support function + * sealed_redirect_cap + * sealed capability pointing to the consecutive intercept capabilities; + * this is the only component visible to the compartments + */ +void +setup_intercepts() +{ + for (size_t i = 0; i < sizeof(to_intercept_funcs) / sizeof(to_intercept_funcs[0]); ++i) + { + comp_intercept_funcs[i].func_name = to_intercept_funcs[i].func_name; + comp_intercept_funcs[i].redirect_func = to_intercept_funcs[i].redirect_func; + comp_intercept_funcs[i].intercept_pcc = + cheri_address_set(cheri_pcc_get(), (uintptr_t) intercept_wrapper); + } + sealed_redirect_cap = manager_ddc; + sealed_redirect_cap = cheri_address_set( + sealed_redirect_cap, + (intptr_t) comp_return_caps); + asm("SEAL %[cap], %[cap], lpb\n\t" + : [cap]"+C"(sealed_redirect_cap) + : /**/ ); + comp_return_caps[0] = manager_ddc; // TODO does this need to be sealed? + comp_return_caps[1] = cheri_address_set(cheri_pcc_get(), (uintptr_t) comp_exec_out); +} + +/******************************************************************************* + * Intercept functions + * + * These functions are meant to be executed within a manager context, by + * intercepting certain functions within compartments which must have higher + * privlige + ******************************************************************************/ + +time_t +intercepted_time(time_t* t) +{ + return time(t); +} + +/* As we are performing data compartmentalization, we must store relevant + * information for accessing an opened file within compartment memory. However, + * as we are using a bump allocator for internal memory management, we do not + * have the capability of `free`ing this memory. A future implementation of a + * better memory allocator will resolve this issue. + */ +FILE* +intercepted_fopen(const char* filename, const char* mode) +{ + FILE* res = fopen(filename, mode); + assert(res != NULL); + /*struct Compartment* comp = manager_find_compartment_by_ddc(cheri_ddc_get()); // TODO*/ + void* comp_addr = manager_register_mem_alloc(loaded_comp, sizeof(FILE)); + memcpy(comp_addr, res, sizeof(FILE)); + return comp_addr; +} + +size_t +intercepted_fread(void* __restrict buf, size_t size, size_t count, FILE* __restrict fp) +{ + return fread(buf, size, count, fp); +} + +size_t +intercepted_fwrite(void* __restrict buf, size_t size, size_t count, FILE* __restrict fp) +{ + return fwrite(buf, size, count, fp); +} + +int +intercepted_fputc(int chr, FILE* stream) +{ + return fputc(chr, stream); +} + +int +intercepted_fclose(FILE* fp) +{ + int res = fclose(fp); + assert(res == 0); + return res; +} + +int +intercepted_getc(FILE* stream) +{ + return getc(stream); +} + +// Needed by test `lua_script` +int +intercepted___srget(FILE* stream) +{ + return __srget(stream); +} + +void* +my_realloc(void* ptr, size_t to_alloc) +{ + // TODO revisit this logic; do we keep a pointer in the manager of the + // currently loaded compartment (would probably require this to be set in + // the transition function), or do we get this information from the + // intercept source (could check the compartment mapping to see which + // compartment the source address comes from) + /*struct Compartment* comp = manager_find_compartment_by_ddc(cheri_ddc_get());*/ + struct Compartment* comp = loaded_comp; + + if (ptr == NULL) + { + return my_malloc(to_alloc); // TODO + } + + void* new_ptr = manager_register_mem_alloc(comp, to_alloc); + struct mem_alloc* old_alloc = get_alloc_struct_from_ptr(comp, (uintptr_t) ptr); + memcpy(new_ptr, ptr, to_alloc < old_alloc->size ? to_alloc : old_alloc->size); + manager_free_mem_alloc(comp, ptr); + return new_ptr; +} + +void* +my_malloc(size_t to_alloc) +{ + /*struct Compartment* comp = manager_find_compartment_by_ddc(cheri_ddc_get());*/ + struct Compartment* comp = loaded_comp; + assert(comp->scratch_mem_alloc + to_alloc < comp->scratch_mem_size); + void* new_mem = manager_register_mem_alloc(comp, to_alloc); + return new_mem; +} + +void +my_free(void* ptr) +{ + if (ptr == NULL) + { + return; + } + /*struct Compartment* comp = manager_find_compartment_by_ddc(cheri_ddc_get());*/ + manager_free_mem_alloc(loaded_comp, ptr); // TODO + return; +} + +int +my_fprintf(FILE* stream, const char* format, ...) +{ + va_list va_args; + va_start(va_args, format); + int res = vfprintf(stream, format, va_args); + va_end(va_args); + return res; +} + +size_t +my_call_comp(size_t comp_id, char* fn_name, void* args, size_t args_count) +{ + struct Compartment* to_call = manager_get_compartment_by_id(comp_id); + return exec_comp(to_call, fn_name, args); + /*return exec_comp(to_call, fn_name, args, args_count);*/ +} diff --git a/src/manager.c b/src/manager.c index 09c390e..c35730c 100644 --- a/src/manager.c +++ b/src/manager.c @@ -1,13 +1,28 @@ #include "manager.h" +// TODO consider moving to a struct or some global thing +static size_t comps_count = 0; +struct CompWithEntries** comps; +struct Compartment* loaded_comp = NULL; + +// Variables and functions related to laying compartments in memory +// TODO make start address configurable +const uintptr_t comp_start_addr = 0x1000000UL; +const unsigned short comp_page_interval_count = 2; +void* min_next_comp_addr = NULL; + const char* comp_env_fields[] = { "PATH", }; -void* __capability comp_return_caps[COMP_RETURN_CAPS_COUNT]; void* __capability manager_ddc = 0; -struct Compartment* loaded_comp = NULL; // TODO -struct func_intercept comp_intercept_funcs[INTERCEPT_FUNC_COUNT]; const char* comp_config_suffix = ".comp"; +static struct ConfigEntryPoint* parse_compartment_config(char*, size_t*, bool); +static struct ConfigEntryPoint* make_default_entry_point(); +static struct ConfigEntryPoint get_entry_point(char*, struct ConfigEntryPoint*, size_t); +static void* prepare_compartment_args(char** args, struct ConfigEntryPoint); + +static struct CompWithEntries* get_comp_with_entries(struct Compartment*); + const char* get_env_str(const char* env_name) { @@ -21,206 +36,186 @@ get_env_str(const char* env_name) } /******************************************************************************* - * Intercept functions - * - * These functions are meant to be executed within a manager context, by - * intercepting certain functions within compartments which must have higher - * privlige + * Utility functions ******************************************************************************/ -time_t -manager_time(time_t* t) -{ - return time(t); -} - -/* As we are performing data compartmentalization, we must store relevant - * information for accessing an opened file within compartment memory. However, - * as we are using a bump allocator for internal memory management, we do not - * have the capability of `free`ing this memory. A future implementation of a - * better memory allocator will resolve this issue. - */ -FILE* -manager_fopen(const char* filename, const char* mode) -{ - FILE* res = fopen(filename, mode); - assert(res != NULL); - struct Compartment* comp = manager_find_compartment_by_ddc(cheri_ddc_get()); // TODO - void* comp_addr = manager_register_mem_alloc(comp, sizeof(FILE)); - memcpy(comp_addr, res, sizeof(FILE)); - return comp_addr; -} - -size_t -manager_fread(void* __restrict buf, size_t size, size_t count, FILE* __restrict fp) -{ - return fread(buf, size, count, fp); -} - -size_t -manager_fwrite(void* __restrict buf, size_t size, size_t count, FILE* __restrict fp) -{ - return fwrite(buf, size, count, fp); -} - -int -manager_fputc(int chr, FILE* stream) -{ - return fputc(chr, stream); +void print_full_cap(uintcap_t cap) { + uint32_t words[4]; // Hack to demonstrate! In real code, be more careful about sizes, etc. + printf("0x%d", cheri_tag_get(cap) ? 1 : 0); + memcpy(words, &cap, sizeof(cap)); + for (int i = 3; i >= 0; i--) { + printf("_%08x", words[i]); + } + printf("\n"); } -int -manager_fclose(FILE* fp) +void* +get_next_comp_addr(void) { - int res = fclose(fp); - assert(res == 0); - return res; + if (min_next_comp_addr == NULL) + { + min_next_comp_addr = (void*) comp_start_addr; + } + return min_next_comp_addr; } -int -manager_getc(FILE* stream) +struct Compartment* +register_new_comp(char* filename, bool allow_default_entry) { - return getc(stream); -} + size_t new_comp_ep_count; + struct ConfigEntryPoint* new_cep = + parse_compartment_config(filename, &new_comp_ep_count, allow_default_entry); -// Needed by test `lua_script` -int -manager___srget(FILE* stream) -{ - return __srget(stream); -} + char** ep_names = calloc(new_comp_ep_count, sizeof(char*)); + for (size_t i = 0; i < new_comp_ep_count; ++i) + { + ep_names[i] = malloc(strlen(new_cep[i].name) + 1); + strcpy(ep_names[i], new_cep[i].name); + } -void* -my_realloc(void* ptr, size_t to_alloc) -{ - struct Compartment* comp = manager_find_compartment_by_ddc(cheri_ddc_get()); + char** intercept_names = calloc(INTERCEPT_FUNC_COUNT, sizeof(char*)); + void** intercept_addrs = calloc(INTERCEPT_FUNC_COUNT, sizeof(uintptr_t)); + for (size_t i = 0; i < INTERCEPT_FUNC_COUNT; ++i) + { + intercept_names[i] = malloc(strlen(comp_intercept_funcs[i].func_name)); + strcpy(intercept_names[i], comp_intercept_funcs[i].func_name); + intercept_addrs[i] = comp_intercept_funcs[i].redirect_func; + } - if (ptr == NULL) + struct Compartment* new_comp = + comp_from_elf(filename, ep_names, new_comp_ep_count, intercept_names, + intercept_addrs, INTERCEPT_FUNC_COUNT, get_next_comp_addr()); + new_comp->id = comps_count; + void* __capability new_comp_ddc = cheri_address_set(cheri_ddc_get(), (uintptr_t) new_comp->base); + // TODO double check second parameter of `cheri_bounds_set` + new_comp_ddc = cheri_bounds_set(new_comp_ddc, (uintptr_t) new_comp->scratch_mem_stack_top); + new_comp->ddc = new_comp_ddc; + + struct CompWithEntries* new_cwe = malloc(sizeof(struct CompWithEntries)); + comps = realloc(comps, comps_count * sizeof(struct CompWithEntries*)); + comps[comps_count] = malloc(sizeof(struct CompWithEntries)); + comps[comps_count]->comp = new_comp; + comps[comps_count]->cep = new_cep; + comps_count += 1; + + min_next_comp_addr = + align_up((char*) comp_start_addr + new_comp->size + + comp_page_interval_count * sysconf(_SC_PAGESIZE), + sysconf(_SC_PAGESIZE)); + + for (size_t i = 0; i < new_comp_ep_count; ++i) + { + free(ep_names[i]); + } + free(ep_names); + for (size_t i = 0; i < INTERCEPT_FUNC_COUNT; ++i) { - return my_malloc(to_alloc); // TODO + free(intercept_names[i]); } + free(intercept_names); + free(intercept_addrs); - void* new_ptr = manager_register_mem_alloc(comp, to_alloc); - struct mem_alloc* old_alloc = get_alloc_struct_from_ptr(comp, (uintptr_t) ptr); - memcpy(new_ptr, ptr, to_alloc < old_alloc->size ? to_alloc : old_alloc->size); - manager_free_mem_alloc(comp, ptr); - return new_ptr; + return new_comp; } -void* -my_malloc(size_t to_alloc) +int64_t +exec_comp(struct Compartment* to_exec, char* entry_fn, char** entry_fn_args) { - struct Compartment* comp = manager_find_compartment_by_ddc(cheri_ddc_get()); - assert(comp->scratch_mem_alloc + to_alloc < comp->scratch_mem_size); - void* new_mem = manager_register_mem_alloc(comp, to_alloc); - return new_mem; + struct CompWithEntries* comp_to_run = get_comp_with_entries(to_exec); + struct ConfigEntryPoint comp_entry = get_entry_point(entry_fn, comp_to_run->cep, to_exec->entry_point_count); + void* comp_args = prepare_compartment_args(entry_fn_args, comp_entry); + + struct Compartment* old_comp = loaded_comp; + loaded_comp = to_exec; + int64_t exec_res = comp_exec(to_exec, entry_fn, comp_args, comp_entry.arg_count); + loaded_comp = old_comp; + + return exec_res; } void -my_free(void* ptr) +clean_all_comps() { - if (ptr == NULL) + for (size_t i = 0; i < comps_count; ++i) { - return; + clean_comp(comps[i]->comp); } - struct Compartment* comp = manager_find_compartment_by_ddc(cheri_ddc_get()); - manager_free_mem_alloc(comp, ptr); // TODO - return; + free(comps); } -int -my_fprintf(FILE* stream, const char* format, ...) +void +clean_comp(struct Compartment* to_clean) { - va_list va_args; - va_start(va_args, format); - int res = vfprintf(stream, format, va_args); - va_end(va_args); - return res; + comp_clean(to_clean); + struct CompWithEntries* cwe = get_comp_with_entries(to_clean); + free(cwe->comp); + free(cwe->cep); + free(cwe); + // TODO move around memory from `comps` } -size_t -my_call_comp(size_t comp_id, char* fn_name, void* args, size_t args_count) +static +struct CompWithEntries* +get_comp_with_entries(struct Compartment* to_find) { - struct Compartment* to_call = manager_get_compartment_by_id(comp_id); - return comp_exec(to_call, fn_name, args, args_count); -} - -/******************************************************************************* - * Utility functions - ******************************************************************************/ - -void print_full_cap(uintcap_t cap) { - uint32_t words[4]; // Hack to demonstrate! In real code, be more careful about sizes, etc. - printf("0x%d", cheri_tag_get(cap) ? 1 : 0); - memcpy(words, &cap, sizeof(cap)); - for (int i = 3; i >= 0; i--) { - printf("_%08x", words[i]); - } - printf("\n"); -} - -/* Setup required capabilities on the heap to jump from within compartments via - * a context switch - * - * For each function to be intercepted, we define the following: - * redirect_func function to be executed at a higher privilege level - * TODO I think the below three are common and can be lifted - * intercept_ddc ddc to be installed for the transition - * intercept_pcc - * higher privileged pcc pointing to the transition support function - * sealed_redirect_cap - * sealed capability pointing to the consecutive intercept capabilities; - * this is the only component visible to the compartments - */ -void -setup_intercepts() -{ - for (size_t i = 0; i < sizeof(to_intercept_funcs) / sizeof(to_intercept_funcs[0]); ++i) + for (size_t i = 0; i < comps_count; ++i) { - comp_intercept_funcs[i].func_name = to_intercept_funcs[i].func_name; - comp_intercept_funcs[i].redirect_func = to_intercept_funcs[i].redirect_func; - comp_intercept_funcs[i].intercept_ddc = manager_ddc; - comp_intercept_funcs[i].intercept_pcc = - cheri_address_set(cheri_pcc_get(), (uintptr_t) intercept_wrapper); - void* __capability sealed_redirect_cap = - cheri_address_set(manager_ddc, (uintptr_t) &comp_intercept_funcs[i].intercept_ddc); - asm("SEAL %[cap], %[cap], lpb\n\t" - : [cap]"+C"(sealed_redirect_cap) - : /**/ ); - comp_intercept_funcs[i].redirect_cap = sealed_redirect_cap; + if (comps[i]->comp->id == to_find->id) + { + return comps[i]; + } } - comp_return_caps[0] = manager_ddc; // TODO does this need to be sealed? - comp_return_caps[1] = cheri_address_set(cheri_pcc_get(), (uintptr_t) comp_exec_out); + errx(1, "Couldn't find requested compartment with id %zu.", to_find->id); } struct Compartment* -manager_find_compartment_by_addr(void* ptr) +manager_find_compartment_by_addr(void* addr) { - return loaded_comp; // TODO + size_t i; + for (i = 0; i < comps_count; ++i) + { + if (comps[i]->comp->base <= addr && + (void*) ((char*) comps[i]->comp->base + comps[i]->comp->size) > addr) + { + break; + } + } + assert(i != comps_count); + return comps[i]->comp; } struct Compartment* manager_find_compartment_by_ddc(void* __capability ddc) { - return loaded_comp; // TODO + size_t i; + for (i = 0; i < comps_count; ++i) + { + if (comps[i]->comp->ddc == ddc) + { + return comps[i]->comp; + } + } + // TODO improve error message with ddc + errx(1, "Could not find compartment."); } struct Compartment* manager_get_compartment_by_id(size_t id) { - return comps[id]; + assert(id < comps_count); + return comps[id]->comp; } void toml_parse_error(char* error_msg, char* errbuf) { - printf("%s: %s\n", error_msg, errbuf); - exit(1); + errx(1, "%s: %s\n", error_msg, errbuf); } char* prep_config_filename(char* filename) { + // TODO do these string manipulation things leak? const char* prefix = "./"; if (!strncmp(filename, prefix, strlen(prefix))) { @@ -229,12 +224,19 @@ prep_config_filename(char* filename) const char* suffix_to_add = ".comp"; char* config_filename = malloc(strlen(filename) + strlen(suffix_to_add) + 1); strcpy(config_filename, filename); + const char* suffix = ".so"; + char* suffix_start = strrchr(config_filename, '.'); + if (suffix_start && !strcmp(suffix_start, suffix)) + { + *suffix_start = '\0'; + } strcat(config_filename, suffix_to_add); return config_filename; } +static struct ConfigEntryPoint* -parse_compartment_config(char* comp_filename, size_t* entry_point_count) +parse_compartment_config(char* comp_filename, size_t* entry_point_count, bool allow_default) { // Get config file name char* config_filename = prep_config_filename(comp_filename); @@ -242,7 +244,10 @@ parse_compartment_config(char* comp_filename, size_t* entry_point_count) free(config_filename); if (!config_fd) { - return NULL; + assert(allow_default); + errno = 0; + *entry_point_count = 1; + return make_default_entry_point(); } // Parse config file @@ -283,6 +288,7 @@ clean_compartment_config(struct ConfigEntryPoint* cep, size_t entry_point_count) { for (size_t i = 0; i < entry_point_count; ++i) { + free((void*) cep[i].name); for (size_t j = 0; j < cep[i].arg_count; ++j) { free(cep[i].args_type[j]); @@ -292,6 +298,7 @@ clean_compartment_config(struct ConfigEntryPoint* cep, size_t entry_point_count) free(cep); } +static struct ConfigEntryPoint get_entry_point(char* entry_point_fn, struct ConfigEntryPoint* ceps, size_t cep_count) { @@ -305,10 +312,10 @@ get_entry_point(char* entry_point_fn, struct ConfigEntryPoint* ceps, size_t cep_ } cep_count -= 1; } - printf("Did not find entry point for function %s!\n", entry_point_fn); - assert(false); + errx(1, "Did not find entry point for function %s!\n", entry_point_fn); } +static void* prepare_compartment_args(char** args, struct ConfigEntryPoint cep) { @@ -345,17 +352,18 @@ prepare_compartment_args(char** args, struct ConfigEntryPoint cep) } else { - printf("Unhandled compartment argument type %s!\n", cep.args_type[i]); - assert(false); + errx(1, "Unhandled compartment argument type %s!\n", cep.args_type[i]); } - memcpy(parsed_args + i * COMP_ARG_SIZE, &tmp, to_copy); + memcpy((char*) parsed_args + i * COMP_ARG_SIZE, &tmp, to_copy); } return parsed_args; } +static struct ConfigEntryPoint* -set_default_entry_point(struct ConfigEntryPoint* cep) +make_default_entry_point() { + struct ConfigEntryPoint* cep = malloc(sizeof(struct ConfigEntryPoint)); cep->name = malloc(strlen("main") + 1); strcpy((char*) cep->name, "main"); cep->arg_count = 0; diff --git a/src/mem_mng.c b/src/mem_mng.c index fc7ddc9..7630f3a 100644 --- a/src/mem_mng.c +++ b/src/mem_mng.c @@ -13,7 +13,7 @@ void* manager_register_mem_alloc(struct Compartment* comp, size_t mem_size) { // TODO better algorithm to find blocks of memory available for mapping - void* new_mem = (void*) (comp->scratch_mem_base + comp->scratch_mem_alloc); + void* new_mem = (char*)comp->scratch_mem_base + comp->scratch_mem_alloc; struct mem_alloc* new_alloc = malloc(sizeof(struct mem_alloc)); new_alloc->ptr = (uintptr_t) new_mem; new_alloc->size = mem_size; @@ -111,6 +111,5 @@ get_alloc_struct_from_ptr(struct Compartment* comp, uintptr_t ptr) } curr_alloc = curr_alloc->next_alloc; } - printf("ERROR: Could not find allocated pointer %Pu!\n", ptr); - assert(false); + errx(1, "ERROR: Could not find allocated pointer %Pu!\n", ptr); } diff --git a/src/transition.S b/src/transition.S index 2581774..2d82496 100644 --- a/src/transition.S +++ b/src/transition.S @@ -2,8 +2,10 @@ .balign 4 /* Wrapper to an intercept function, executed within a manager context; - * required in order to maintain a consisten execution state. This also manages + * required in order to maintain a consistent execution state. This also manages * setting the `DDC` as needed. + * + * This function takes `x10` as argument, pointing to the address to jump to */ .global intercept_wrapper .type intercept_wrapper, "function" @@ -24,6 +26,7 @@ intercept_wrapper: .global compartment_transition_out .type compartment_transition_out, "function" compartment_transition_out: + // TODO stp c29, clr, [sp, #-32]! ldpblr c29, [c11] ldp c29, clr, [sp], #32 @@ -31,7 +34,8 @@ compartment_transition_out: compartment_transition_out_end: /* comp_exec_in(void* comp_sp, void* __capability comp_ddc, void* fn, - void* args, size_t args_count) */ + void* args, size_t args_count, + void* __capability sealed_redirect_cap) */ /* Instructions to enter a compartment. There is no `ret`, as we need to * perform a context switch upon exiting, which is done via `ldpbr` */ @@ -42,15 +46,15 @@ comp_exec_in: / - `x19` is callee-saved, used to hold the stack pointer between `comp_exec_in` and `comp_exec_out` - `lr` is used to remember where we came from, so we can return - - `x29` is used for TODO??? + - `x29` is the frame-pointer, to be restored on exit Stack layout: - `SP + 5 * 8` --> ------------------ - `SP + 4 * 8` --> | < PADDING > | - `SP + 3 * 8` > | old x29 | - `SP + 2 * 8` > | old LR | - `SP + 1 * 8` > | callee-saved x19 | - ` new SP` > ------------------ + `SP + 5 * 8` --> --------------------- + `SP + 4 * 8` --> | | + `SP + 3 * 8` > | old x29 | + `SP + 2 * 8` > | old LR | + `SP + 1 * 8` > | callee-saved x19 | + ` new SP` > --------------------- */ stp x19, lr, [sp, #-32]! str x29, [sp, #16] @@ -60,8 +64,10 @@ comp_exec_in: mov x9, x0 mov c10, c1 mov x11, x2 + mov c20, c5 // Load params (only handle 3 params): + // TODO increase to 6 loading_params: cbz x4, loaded_params // Check if we loaded all parameters bl load_param @@ -88,12 +94,19 @@ loaded_params: mov x4, xzr mov x5, xzr + // Transition into new compartment mov sp, x9 msr DDC, c10 + // We are now fully in the new context + + // Save capability needed to restore context + // TODO I think this should be somewhere within the compartment + str c20, [sp, #-32]! + + // Call required function within compartment blr x11 - adr x11, comp_return_caps - cvtp c11, x11 + ldr c11, [sp] ldpbr c29, [c11] /* Instructions to perform once a compartment has finished execution. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 415d2a2..f517ef3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -28,7 +28,7 @@ define_property(TARGET FULL_DOCS "Additional file dependencies to run a test.") # Helper functions -function(get_deps target) +function(get_deps target deps_var) set(deps_var "") list(APPEND deps_var $) get_property(has_config TARGET ${target} PROPERTY compartment_config SET) @@ -43,149 +43,149 @@ function(get_deps target) set(deps_var ${deps_var} PARENT_SCOPE) endfunction() -# Library test functions -function(new_target test_name compartment) +# Internal library tests +function(new_func_test test_name) add_executable(${test_name} ${test_name}.c) - target_link_libraries(${test_name} PRIVATE chcomp dl m) - target_link_libraries(${test_name} PRIVATE lualib) + target_link_libraries(${test_name} PRIVATE chcomp) + target_include_directories(${test_name} PRIVATE + ${CMAKE_SOURCE_DIR}/src ${INCLUDE_DIR} ${TOML_INCLUDE_DIR}) +endfunction() + +# Compartment tests +function(new_comp_test test_name) + add_library(${test_name} SHARED + ${test_name}.c) + set_target_properties(${test_name} PROPERTIES PREFIX "") + target_link_libraries(${test_name} PRIVATE chcomp lualib dl m) target_include_directories(${test_name} PRIVATE ${INCLUDE_DIR} ${LUA_INCLUDE_DIR}) - if(${compartment}) - target_compile_options(${test_name} PRIVATE -static) - if(${ARGC} GREATER 2) - target_link_options(${test_name} PRIVATE -static "LINKER:-image-base=${ARGV2}") - else() - target_link_options(${test_name} PRIVATE -static "LINKER:-image-base=0x1000000") - endif() - set_property(TARGET ${test_name} PROPERTY compartment TRUE) - if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${test_name}.comp) - set_property(TARGET ${test_name} PROPERTY compartment_config ${CMAKE_CURRENT_SOURCE_DIR}/${test_name}.comp) - endif() - else() - target_link_libraries(${test_name} PRIVATE tomllib) - target_include_directories(${test_name} PRIVATE ${TOML_INCLUDE_DIR}) + + set_property(TARGET ${test_name} PROPERTY compartment TRUE) + if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${test_name}.comp) + set_property(TARGET ${test_name} PROPERTY compartment_config ${CMAKE_CURRENT_SOURCE_DIR}/${test_name}.comp) endif() endfunction() -function(new_test test_name test_runner test_bin) - if(test_bin) - get_deps(${test_bin}) +function(new_test test_name) + if(${ARGC} EQUAL "1") + get_property(is_comp TARGET ${test_name} PROPERTY compartment SET) + get_deps(${test_name} deps_var) + if(is_comp) + add_test(NAME ${test_name} + COMMAND ${CMAKE_SOURCE_DIR}/tests/run_test.py + manager_call + --test-args $ + --dependencies ${deps_var} + COMMAND_EXPAND_LISTS) + else() + add_test(NAME ${test_name} + COMMAND ${CMAKE_SOURCE_DIR}/tests/run_test.py + $ + --dependencies ${deps_var} + COMMAND_EXPAND_LISTS) + endif() + elseif(${ARGC} GREATER_EQUAL "3") + set(test_bin ${ARGV1}) + list(JOIN ARGV2 " " test_args) + get_property(is_comp TARGET ${test_bin} PROPERTY compartment SET) + get_deps(${test_bin} deps_var) + if(is_comp) + add_test(NAME ${test_name} + COMMAND ${CMAKE_SOURCE_DIR}/tests/run_test.py + manager_args + --test-args $ ${test_args} + --dependencies ${deps_var} + COMMAND_EXPAND_LISTS) + else() + message(FATAL_ERROR "Shouldn't get here") + endif() else() - get_deps(${test_runner}) - endif() - list(PREPEND test_cmd ${deps_var}) - if(test_cmd) - list(PREPEND test_cmd "--dependencies") - endif() - if(test_bin) - list(PREPEND test_cmd ${ARGN}) - list(PREPEND test_cmd ${test_bin}) - list(PREPEND test_cmd "--test-args") + message(FATAL_ERROR "Missing arguments to test.") endif() - list(PREPEND test_cmd $) - add_test(NAME ${test_name} - COMMAND ${CMAKE_SOURCE_DIR}/tests/run_test.py ${test_cmd} - COMMAND_EXPAND_LISTS) endfunction() function(new_dependency target dep_file) set_property(TARGET ${target} APPEND PROPERTY extra_deps ${dep_file}) endfunction() -# Binaries to build +# Library tests set(func_binaries "test_map" - "test_args_near_unmapped" - "test_ddc_overwrite_manager" - "test_two_comps" - "test_two_comps_inter_call" + #"test_args_near_unmapped" + #"test_two_comps" + #"test_two_comps_inter_call" ) set(comp_binaries "simple" - "time" - "lua_simple" - "lua_script" + #"time" + #"lua_simple" + #"lua_script" "args_simple" - "test_ddc_overwrite" - "test_two_comps-comp1" - "test_two_comps-comp2 0x2000000" + #"test_two_comps-comp1" + #"test_two_comps-comp2 0x2000000" ) -# Tests with no arguments -set(tests_direct +set(tests + "simple" + #"time" + #"lua_simple" + #"lua_script" "test_map" - "test_args_near_unmapped" - "test_two_comps" - "test_two_comps_inter_call" - "test_ddc_overwrite_manager" - ) - -# Tests with arguments -# FORMAT : [...] -set(tests_args - "simple manager_call simple" - "time manager_call time" - "lua_simple manager_call lua_simple" - "lua_script manager_call lua_script" - "args-simple manager_args args_simple check_simple 40 2" - "args-more manager_args args_simple check_simple 40 2 2 2" # Check additional arguments are ignored - "args-combined manager_args args_simple check_combined 400 2 20" - "args-negative manager_args args_simple check_negative -42" - "args-long-max manager_args args_simple check_llong_max 9223372036854775807" - "args-long-min manager_args args_simple check_llong_min -9223372036854775808" - "args-ulong-max manager_args args_simple check_ullong_max 18446744073709551615" + #"test_args_near_unmapped" + #"test_two_comps" + #"test_two_comps_inter_call" + "args-simple args_simple check_simple 40 2" + "args-more args_simple check_simple 40 2 2 2" # Check additional arguments are ignored + "args-combined args_simple check_combined 400 2 20" + "args-negative args_simple check_negative -42" + "args-long-max args_simple check_llong_max 9223372036854775807" + "args-long-min args_simple check_llong_min -9223372036854775808" + "args-ulong-max args_simple check_ullong_max 18446744073709551615" ) -# Build targets +# Build targets foreach(comp_t IN LISTS comp_binaries) string(FIND ${comp_t} " " space_pos) if(${space_pos} EQUAL -1) - new_target(${comp_t} TRUE) + new_comp_test(${comp_t} TRUE) else() string(SUBSTRING ${comp_t} 0 ${space_pos} tgt_name) string(SUBSTRING ${comp_t} ${space_pos} -1 img_base) string(STRIP ${img_base} img_base) - new_target(${tgt_name} TRUE ${img_base}) + new_comp_test(${tgt_name} TRUE ${img_base}) endif() endforeach() foreach(func_t IN LISTS func_binaries) - new_target(${func_t} FALSE) + new_func_test(${func_t} FALSE) endforeach() - # Additional dependencies new_dependency(test_map $) -new_dependency(test_args_near_unmapped $) -new_dependency(test_args_near_unmapped ${CMAKE_CURRENT_SOURCE_DIR}/args_simple.comp) - -new_dependency(test_two_comps $) -new_dependency(test_two_comps $) -new_dependency(test_two_comps ${CMAKE_CURRENT_SOURCE_DIR}/test_two_comps-comp1.comp) -new_dependency(test_two_comps_inter_call $) -new_dependency(test_two_comps_inter_call $) -new_dependency(test_two_comps_inter_call ${CMAKE_CURRENT_SOURCE_DIR}/test_two_comps-comp1.comp) +#new_dependency(lua_script ${CMAKE_CURRENT_SOURCE_DIR}/hello_world.lua) +#new_dependency(test_args_near_unmapped $) +#new_dependency(test_args_near_unmapped ${CMAKE_CURRENT_SOURCE_DIR}/args_simple.comp) -new_dependency(test_ddc_overwrite_manager $) -new_dependency(test_ddc_overwrite_manager ${CMAKE_CURRENT_SOURCE_DIR}/test_ddc_overwrite.comp) +#new_dependency(test_two_comps $) +#new_dependency(test_two_comps $) +#new_dependency(test_two_comps ${CMAKE_CURRENT_SOURCE_DIR}/test_two_comps-comp1.comp) -new_dependency(lua_script ${CMAKE_CURRENT_SOURCE_DIR}/hello_world.lua) - -foreach(test_d IN LISTS tests_direct) - new_test(${test_d} ${test_d} "" "") -endforeach() +#new_dependency(test_two_comps_inter_call $) +#new_dependency(test_two_comps_inter_call $) +#new_dependency(test_two_comps_inter_call ${CMAKE_CURRENT_SOURCE_DIR}/test_two_comps-comp1.comp) # Prepare tests -foreach(test_a IN LISTS tests_args) - string(REPLACE " " ";" test_args_list ${test_a}) - list(GET test_args_list 0 test_name) - list(GET test_args_list 1 test_runner) - list(GET test_args_list 2 test_bin) - list(LENGTH test_args_list test_args_len) - if(${test_args_len} GREATER 3) - list(SUBLIST test_args_list 3 -1 test_bin_args) +foreach(test_t IN LISTS tests) + string(REPLACE " " ";" test_t_list ${test_t}) + list(LENGTH test_t_list test_t_len) + if(${test_t_len} EQUAL "1") + new_test(${test_t}) + else() + list(GET test_t_list 0 test_name) + list(GET test_t_list 1 test_bin) + list(SUBLIST test_t_list 2 -1 test_args) + new_test(${test_name} ${test_bin} "${test_args}") endif() - new_test(${test_name} ${test_runner} ${test_bin} ${test_bin_args}) endforeach() diff --git a/tests/init_test.py b/tests/init_test.py new file mode 100755 index 0000000..4cf409d --- /dev/null +++ b/tests/init_test.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +import os + +from fabric import Connection + +################################################################################ +# Constants +################################################################################ + +CHERIBSD_PORT = 10086 +CHERIBSD_USER = "root" +CHERIBSD_HOST = "localhost" + +CHERIBSD_TEST_DIR = "testing" +COMP_LIBRARY_PATH = "testing/libs" + +LOCAL_LIBS = [ + "./third-party/lua/liblua.so", + ] +REMOTE_LIBS = [ + "/lib/libc.so.7", + "/usr/lib/libdl.so.1", + "/lib/libm.so.5", + ] + +################################################################################ +# Main +################################################################################ + +vm_conn = Connection(host = CHERIBSD_HOST, user = CHERIBSD_USER, port = CHERIBSD_PORT) + +home_dir = vm_conn.run("echo $HOME", hide = True).stdout.strip() +CHERIBSD_TEST_DIR = os.path.join(home_dir, CHERIBSD_TEST_DIR) +COMP_LIBRARY_PATH = os.path.join(home_dir, COMP_LIBRARY_PATH) +remote_env = { + 'COMP_LIBRARY_PATH': COMP_LIBRARY_PATH, + 'LD_LIBRARY_PATH': COMP_LIBRARY_PATH, + } + +vm_conn.run(f'mkdir -p {CHERIBSD_TEST_DIR}') +vm_conn.run(f'mkdir -p {COMP_LIBRARY_PATH}') +for lib in LOCAL_LIBS: + vm_conn.put(lib, remote = f'{COMP_LIBRARY_PATH}', ) +for lib in REMOTE_LIBS: + cmd = f'cd {COMP_LIBRARY_PATH} ; ln -s {lib}' + vm_conn.run(cmd) +vm_conn.close() diff --git a/tests/manager_arg_passer.c b/tests/manager_arg_passer.c index b497166..efb9639 100644 --- a/tests/manager_arg_passer.c +++ b/tests/manager_arg_passer.c @@ -10,8 +10,6 @@ * but plan to move this to some configuration file in the near future */ -extern struct Compartment* loaded_comp; - int main(int argc, char** argv) { @@ -21,25 +19,13 @@ main(int argc, char** argv) assert(argc >= 4 && "Expect at least three arguments: binary file for compartment, entry function for compartment, and at least one argument to pass to compartment function."); char* file = argv[1]; - size_t entry_point_count = 0; - struct ConfigEntryPoint* cep = parse_compartment_config(file, &entry_point_count); - if (!cep) - { - cep = malloc(sizeof(struct ConfigEntryPoint)); - cep = set_default_entry_point(cep); - } - struct Compartment* arg_comp = comp_from_elf(file, cep, entry_point_count); - loaded_comp = arg_comp; // TODO - log_new_comp(arg_comp); + struct Compartment* arg_comp = register_new_comp(file, false); comp_map(arg_comp); char* entry_func = argv[2]; char** entry_func_args = &argv[3]; - struct ConfigEntryPoint comp_entry = get_entry_point(entry_func, cep, arg_comp->entry_point_count); - void* comp_args = prepare_compartment_args(entry_func_args, comp_entry); - int comp_result = comp_exec(arg_comp, entry_func, comp_args, comp_entry.arg_count); - clean_compartment_config(cep, arg_comp->entry_point_count); + int comp_result = exec_comp(arg_comp, argv[2], &argv[3]); comp_clean(arg_comp); return comp_result; } diff --git a/tests/manager_caller.c b/tests/manager_caller.c index 510a42f..90ce1f7 100644 --- a/tests/manager_caller.c +++ b/tests/manager_caller.c @@ -1,7 +1,5 @@ #include "manager.h" -extern struct Compartment* loaded_comp; - int main(int argc, char** argv) { @@ -17,18 +15,9 @@ main(int argc, char** argv) file += strlen(prefix); } - // Set default entry point with no arguments to pass - struct ConfigEntryPoint* main_cep = malloc(sizeof(struct ConfigEntryPoint)); - main_cep = set_default_entry_point(main_cep); - - struct Compartment* hw_comp = comp_from_elf(file, main_cep, 1); - loaded_comp = hw_comp; // TODO - log_new_comp(hw_comp); + struct Compartment* hw_comp = register_new_comp(file, true); comp_map(hw_comp); - int comp_result; - size_t comp_args_count = 0; - comp_result = comp_exec(hw_comp, "main", NULL, 0); + int comp_result = exec_comp(hw_comp, "main", NULL); comp_clean(hw_comp); - free(main_cep); return comp_result; } diff --git a/tests/run_test.py b/tests/run_test.py index 2e8c964..08f0c4e 100755 --- a/tests/run_test.py +++ b/tests/run_test.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import argparse import pathlib +import os from fabric import Connection @@ -11,7 +12,9 @@ CHERIBSD_PORT = 10086 CHERIBSD_USER = "root" CHERIBSD_HOST = "localhost" -CHERIBSD_TEST_DIR = "./testing" + +CHERIBSD_TEST_DIR = "testing" +COMP_LIBRARY_PATH = "testing/libs" ################################################################################ # Arguments @@ -33,17 +36,25 @@ def put_file(conn, src_file): conn.put(src_file, remote = f'{CHERIBSD_TEST_DIR}/') -def exec_cmd(conn, cmd): - conn.run(cmd, echo = True) +def exec_cmd(conn, cmd, remote_env): + return conn.run(cmd, env = remote_env, echo = True) ################################################################################ # Main ################################################################################ vm_conn = Connection(host = CHERIBSD_HOST, user = CHERIBSD_USER, port = CHERIBSD_PORT) -exec_cmd(vm_conn, f'mkdir -p {CHERIBSD_TEST_DIR}') + +home_dir = vm_conn.run("echo $HOME", hide = True).stdout.strip() +CHERIBSD_TEST_DIR = os.path.join(home_dir, CHERIBSD_TEST_DIR) +COMP_LIBRARY_PATH = os.path.join(home_dir, COMP_LIBRARY_PATH) +remote_env = { + 'COMP_LIBRARY_PATH': COMP_LIBRARY_PATH, + 'LD_LIBRARY_PATH': COMP_LIBRARY_PATH, + } + file_deps = [args.test, *args.dependencies] for dep in file_deps: put_file(vm_conn, dep) -exec_cmd(vm_conn, f'cd {CHERIBSD_TEST_DIR} && ./{args.test.name} {" ".join(args.test_args)}') +exec_cmd(vm_conn, f'cd {CHERIBSD_TEST_DIR} ; ./{args.test.name} {" ".join(args.test_args)}', remote_env) vm_conn.close() diff --git a/tests/simple.c b/tests/simple.c index 7a4723f..2b6717a 100644 --- a/tests/simple.c +++ b/tests/simple.c @@ -1,9 +1,11 @@ #include -#include +#include +#include int main(void) { - time_t seconds = time(NULL); + assert(ceil(1.4) == 2); + assert(pow(2, 4) == 16); return 0; } diff --git a/tests/test_args_near_unmapped.c b/tests/test_args_near_unmapped.c index 78a5ec2..a437d9b 100644 --- a/tests/test_args_near_unmapped.c +++ b/tests/test_args_near_unmapped.c @@ -10,8 +10,6 @@ * but plan to move this to some configuration file in the near future */ -extern struct Compartment* loaded_comp; - int main(int argc, char** argv) { @@ -20,17 +18,8 @@ main(int argc, char** argv) setup_intercepts(); char* file = "args_simple"; - size_t entry_point_count = 0; - struct ConfigEntryPoint* cep = parse_compartment_config(file, &entry_point_count); - if (!cep) - { - cep = malloc(sizeof(struct ConfigEntryPoint)); - cep = set_default_entry_point(cep); - } - - struct Compartment* arg_comp = comp_from_elf(file, cep, entry_point_count); - loaded_comp = arg_comp; // TODO - log_new_comp(arg_comp); + + struct Compartment* arg_comp = register_new_comp(file, false); comp_map(arg_comp); char* entry_func = "check_simple"; diff --git a/tests/test_map.c b/tests/test_map.c index 67c0480..ea481f5 100644 --- a/tests/test_map.c +++ b/tests/test_map.c @@ -1,21 +1,15 @@ #include "manager.h" +#include "compartment.c" int -main(int argc, char** argv) +main() { manager_ddc = cheri_ddc_get(); setup_intercepts(); - char* file = "./simple"; - - // Set default entry point with no arguments to pass - struct ConfigEntryPoint* main_cep = malloc(sizeof(struct ConfigEntryPoint)); - main_cep = set_default_entry_point(main_cep); - - struct Compartment* hw_comp = comp_from_elf(file, main_cep, 1); - log_new_comp(hw_comp); + char* file = "./simple.so"; + struct Compartment* hw_comp = register_new_comp(file, true); comp_map(hw_comp); comp_clean(hw_comp); - free(main_cep); return 0; } diff --git a/tests/test_two_comps.c b/tests/test_two_comps.c index 3fbda10..192384a 100644 --- a/tests/test_two_comps.c +++ b/tests/test_two_comps.c @@ -1,7 +1,5 @@ #include "manager.h" -extern struct Compartment* loaded_comp; - int main(int argc, char** argv) { @@ -12,35 +10,25 @@ main(int argc, char** argv) char* comp_file_1 = "test_two_comps-comp1"; char* comp_file_2 = "test_two_comps-comp2"; - // Set default entry point with no arguments to pass - // Used for both compartments - struct ConfigEntryPoint* main_cep = malloc(sizeof(struct ConfigEntryPoint)); - main_cep = set_default_entry_point(main_cep); - // Load comp1 - struct Compartment* comp1 = comp_from_elf(comp_file_1, main_cep, 1); - log_new_comp(comp1); + struct Compartment* comp1 = register_new_comp(comp_file_1, true); comp_map(comp1); fprintf(stdout, "Mapped Comp 1\n"); // Load comp2 - struct Compartment* comp2 = comp_from_elf(comp_file_2, main_cep, 1); - log_new_comp(comp2); + struct Compartment* comp2 = register_new_comp(comp_file_2, true); comp_map(comp2); fprintf(stdout, "Mapped Comp 2\n"); int comp_result; // Run Comp 1 - loaded_comp = comp1; comp_result = comp_exec(comp1, "main", NULL, 0); comp_clean(comp1); // Run Comp 2 - loaded_comp = comp2; comp_result |= comp_exec(comp2, "main", NULL, 0); comp_clean(comp2); - free(main_cep); return comp_result; } diff --git a/tests/test_two_comps_inter_call.c b/tests/test_two_comps_inter_call.c index a967fa7..113dcbd 100644 --- a/tests/test_two_comps_inter_call.c +++ b/tests/test_two_comps_inter_call.c @@ -1,7 +1,5 @@ #include "manager.h" -extern struct Compartment* loaded_comp; - int main(int argc, char** argv) { @@ -12,32 +10,22 @@ main(int argc, char** argv) char* comp_file_1 = "test_two_comps-comp1"; char* comp_file_2 = "test_two_comps-comp2"; - // Read entry point data for compartment 1 - size_t ep_count = 0; - struct ConfigEntryPoint* comp1_cep = parse_compartment_config(comp_file_1, &ep_count); - struct ConfigEntryPoint* main_cep = malloc(sizeof(struct ConfigEntryPoint)); - main_cep = set_default_entry_point(main_cep); - // Load comp1 - struct Compartment* comp1 = comp_from_elf(comp_file_1, comp1_cep, 1); - log_new_comp(comp1); + struct Compartment* comp1 = register_new_comp(comp_file_1, false); comp_map(comp1); fprintf(stdout, "Mapped Comp 1\n"); // Load comp2 - struct Compartment* comp2 = comp_from_elf(comp_file_2, main_cep, 1); - log_new_comp(comp2); + struct Compartment* comp2 = register_new_comp(comp_file_2, true); comp_map(comp2); fprintf(stdout, "Mapped Comp 2\n"); // Run Comp 1 - loaded_comp = comp1; int comp_result = comp_exec(comp1, "inter_call", NULL, 0); assert(comp_result == 0); comp_clean(comp1); comp_clean(comp2); - free(comp1_cep); return comp_result; } diff --git a/third-party/lua.patch b/third-party/lua.patch index a453a70..b5fb27b 100644 --- a/third-party/lua.patch +++ b/third-party/lua.patch @@ -1,5 +1,5 @@ diff --git a/makefile b/makefile -index d46e650c..de080ae0 100644 +index d46e650c..42ff1904 100644 --- a/makefile +++ b/makefile @@ -28,8 +28,8 @@ CWARNSCPP= \ @@ -13,18 +13,18 @@ index d46e650c..de080ae0 100644 # The next warnings are neither valid nor needed for C++ -@@ -66,19 +66,17 @@ LOCAL = $(TESTS) $(CWARNS) +@@ -66,25 +66,22 @@ LOCAL = $(TESTS) $(CWARNS) # enable Linux goodies -MYCFLAGS= $(LOCAL) -std=c99 -DLUA_USE_LINUX -DLUA_USE_READLINE -MYLDFLAGS= $(LOCAL) -Wl,-E -MYLIBS= -ldl -lreadline -+MYCFLAGS= --config cheribsd-morello-hybrid.cfg -g $(LOCAL) -std=c99 -DLUA_USE_LINUX -+MYLDFLAGS= --config cheribsd-morello-hybrid.cfg -g $(LOCAL) -Wl,-E ++MYCFLAGS= --config cheribsd-morello-hybrid.cfg -g -O0 $(LOCAL) -std=c99 -DLUA_USE_LINUX -DLUAI_ASSERT -fPIC ++MYLDFLAGS= --config cheribsd-morello-hybrid.cfg -g $(LOCAL) -Wl,-E -fPIC +MYLIBS= -ldl - +- -CC= gcc -CFLAGS= -Wall -O2 $(MYCFLAGS) -fno-stack-protector -fno-common -march=native +CC?=/home/cheriworker/cheri/output/morello-sdk/bin/clang @@ -38,3 +38,18 @@ index d46e650c..de080ae0 100644 # == END OF USER SETTINGS. NO NEED TO CHANGE ANYTHING BELOW THIS LINE ========= + LIBS = -lm + +-CORE_T= liblua.a ++CORE_T= liblua.a liblua.so + CORE_O= lapi.o lcode.o lctype.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o \ + lmem.o lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o \ + ltm.o lundump.o lvm.o lzio.o ltests.o +@@ -110,6 +107,7 @@ a: $(ALL_A) + $(CORE_T): $(CORE_O) $(AUX_O) $(LIB_O) + $(AR) $@ $? + $(RANLIB) $@ ++ $(CC) -shared -ldl -Wl,-soname,liblua.so -o liblua.so $? -lm $(MYLDFLAGS) + + $(LUA_T): $(LUA_O) $(CORE_T) + $(CC) -o $@ $(MYLDFLAGS) $(LUA_O) $(CORE_T) $(LIBS) $(MYLIBS) $(DL)