From f1799127c00d3a70ed0637f837395f6d60d02ec5 Mon Sep 17 00:00:00 2001 From: John Baldwin Date: Mon, 9 Dec 2024 14:12:36 -0500 Subject: [PATCH] rtld: Use compartment IDs from sub-object compartments for policy enforcement - Add a helper function to lookup the relevant compartment ID for a given virtual address and shared object. - Save a compartment ID for each PLT (based on the address of the associated PLT GOT) and use this as the "subject" (caller) for policy enforcement when handling PLT GOT relocations. - Use the target address of a function call to determine the "object" (callee). Note: rtld currently does not enforce any policy for access to data via the normal GOT. --- libexec/rtld-elf/aarch64/reloc.c | 11 +++-- libexec/rtld-elf/aarch64/rtld_c18n_machdep.c | 2 +- libexec/rtld-elf/aarch64/rtld_machdep.h | 6 ++- libexec/rtld-elf/rtld.c | 46 ++++++++++++++++---- libexec/rtld-elf/rtld.h | 3 ++ libexec/rtld-elf/rtld_c18n.c | 38 ++++++++++------ libexec/rtld-elf/rtld_c18n.h | 3 +- libexec/rtld-elf/rtld_lock.c | 2 +- 8 files changed, 81 insertions(+), 30 deletions(-) diff --git a/libexec/rtld-elf/aarch64/reloc.c b/libexec/rtld-elf/aarch64/reloc.c index 44c75e610480..03424fc21d15 100644 --- a/libexec/rtld-elf/aarch64/reloc.c +++ b/libexec/rtld-elf/aarch64/reloc.c @@ -493,6 +493,7 @@ reloc_plt(Plt_Entry *plt, int flags, RtldLockState *lockstate) defobj); #ifdef CHERI_LIB_C18N target = (uintptr_t)tramp_intern(obj, + plt->compart_id, &(struct tramp_data) { .target = (void *)target, .defobj = defobj, @@ -571,7 +572,8 @@ reloc_jmpslots(Plt_Entry *plt, int flags, RtldLockState *lockstate) } target = (uintptr_t)make_function_pointer(def, defobj); #ifdef CHERI_LIB_C18N - target = (uintptr_t)tramp_intern(obj, &(struct tramp_data) { + target = (uintptr_t)tramp_intern(obj, plt->compart_id, + &(struct tramp_data) { .target = (void *)target, .defobj = defobj, .def = def, @@ -632,7 +634,8 @@ reloc_iresolve_one(Obj_Entry *obj, const Elf_Rela *rela, #endif lock_release(rtld_bind_lock, lockstate); #ifdef CHERI_LIB_C18N - ptr = (uintptr_t)tramp_intern(NULL, &(struct tramp_data) { + ptr = (uintptr_t)tramp_intern(NULL, RTLD_COMPART_ID, + &(struct tramp_data) { .target = (void *)ptr, .defobj = obj, .sig = (struct func_sig) { .valid = true, @@ -729,7 +732,9 @@ reloc_gnu_ifunc_plt(Plt_Entry *plt, int flags, RtldLockState *lockstate) lock_release(rtld_bind_lock, lockstate); target = (uintptr_t)rtld_resolve_ifunc(defobj, def); #ifdef CHERI_LIB_C18N - target = (uintptr_t)tramp_intern(obj, &(struct tramp_data) { + target = (uintptr_t)tramp_intern(obj, + plt->compart_id, + &(struct tramp_data) { .target = (void *)target, .defobj = defobj, .def = def, diff --git a/libexec/rtld-elf/aarch64/rtld_c18n_machdep.c b/libexec/rtld-elf/aarch64/rtld_c18n_machdep.c index d4b28f35e9f9..22bcf713e1ac 100644 --- a/libexec/rtld-elf/aarch64/rtld_c18n_machdep.c +++ b/libexec/rtld-elf/aarch64/rtld_c18n_machdep.c @@ -282,7 +282,7 @@ _rtld_sandbox_code(void *target, struct func_sig sig) target = cheri_sealentry(target_unsealed); } - target = tramp_intern(NULL, &(struct tramp_data) { + target = tramp_intern(NULL, RTLD_COMPART_ID, &(struct tramp_data) { .target = target, .defobj = obj, .sig = sig diff --git a/libexec/rtld-elf/aarch64/rtld_machdep.h b/libexec/rtld-elf/aarch64/rtld_machdep.h index 1b945fec8476..b6884af2f662 100644 --- a/libexec/rtld-elf/aarch64/rtld_machdep.h +++ b/libexec/rtld-elf/aarch64/rtld_machdep.h @@ -86,7 +86,8 @@ uintptr_t reloc_jmpslot(uintptr_t *where, uintptr_t target, /* TODO: Per-function captable/PLT/FNDESC support */ #ifdef CHERI_LIB_C18N #define call_init_array_pointer(_obj, _target) \ - (((InitArrFunc)tramp_intern(NULL, &(struct tramp_data) { \ + (((InitArrFunc)tramp_intern(NULL, RTLD_COMPART_ID, \ + &(struct tramp_data) { \ .target = (void *)(_target).value, \ .defobj = _obj, \ .sig = (struct func_sig) { .valid = true, \ @@ -94,7 +95,8 @@ uintptr_t reloc_jmpslot(uintptr_t *where, uintptr_t target, }))(main_argc, main_argv, environ)) #define call_fini_array_pointer(_obj, _target) \ - (((InitFunc)tramp_intern(NULL, &(struct tramp_data) { \ + (((InitFunc)tramp_intern(NULL, RTLD_COMPART_ID, \ + &(struct tramp_data) { \ .target = (void *)(_target).value, \ .defobj = _obj, \ .sig = (struct func_sig) { .valid = true, \ diff --git a/libexec/rtld-elf/rtld.c b/libexec/rtld-elf/rtld.c index 3c2015305ce9..08e503a23834 100644 --- a/libexec/rtld-elf/rtld.c +++ b/libexec/rtld-elf/rtld.c @@ -1151,7 +1151,8 @@ _rtld(Elf_Addr *sp, func_ptr_type *exit_proc, Obj_Entry **objp) if (rtld_exit_ptr == NULL) { rtld_exit_ptr = make_rtld_function_pointer(rtld_exit); #ifdef CHERI_LIB_C18N - rtld_exit_ptr = tramp_intern(NULL, &(struct tramp_data) { + rtld_exit_ptr = tramp_intern(NULL, RTLD_COMPART_ID, + &(struct tramp_data) { .target = rtld_exit_ptr, .defobj = &obj_rtld, .sig = (struct func_sig) { @@ -1165,7 +1166,8 @@ _rtld(Elf_Addr *sp, func_ptr_type *exit_proc, Obj_Entry **objp) *objp = obj_main; #ifdef CHERI_LIB_C18N - return ((func_ptr_type)tramp_intern(NULL, &(struct tramp_data) { + return ((func_ptr_type)tramp_intern(NULL, RTLD_COMPART_ID, + &(struct tramp_data) { .target = cheri_sealentry(obj_main->entry), .defobj = obj_main, .sig = (struct func_sig) { @@ -1186,7 +1188,7 @@ rtld_resolve_ifunc(const Obj_Entry *obj, const Elf_Sym *def) ptr = (void *)make_function_pointer(def, obj); #ifdef CHERI_LIB_C18N - ptr = tramp_intern(NULL, &(struct tramp_data) { + ptr = tramp_intern(NULL, RTLD_COMPART_ID, &(struct tramp_data) { .target = ptr, .defobj = obj, .def = def, @@ -1237,7 +1239,8 @@ _rtld_bind(Plt_Entry *plt, Elf_Size reloff) #ifdef __CHERI_PURE_CAPABILITY__ target = (uintptr_t)make_function_pointer(def, defobj); #ifdef CHERI_LIB_C18N - target = (uintptr_t)tramp_intern(obj, &(struct tramp_data) { + target = (uintptr_t)tramp_intern(obj, plt->compart_id, + &(struct tramp_data) { .target = (void *)target, .defobj = defobj, .def = def, @@ -3638,7 +3641,8 @@ objlist_call_init(Objlist *list, RtldLockState *lockstate) if (reg != NULL) { func_ptr_type exit_ptr = make_rtld_function_pointer(rtld_exit); #ifdef CHERI_LIB_C18N - exit_ptr = tramp_intern(NULL, &(struct tramp_data) { + exit_ptr = tramp_intern(NULL, RTLD_COMPART_ID, + &(struct tramp_data) { .target = exit_ptr, .defobj = &obj_rtld, .sig = (struct func_sig) { @@ -3651,7 +3655,8 @@ objlist_call_init(Objlist *list, RtldLockState *lockstate) reg(exit_ptr); rtld_exit_ptr = make_rtld_function_pointer(rtld_nop_exit); #ifdef CHERI_LIB_C18N - rtld_exit_ptr = tramp_intern(NULL, &(struct tramp_data) { + rtld_exit_ptr = tramp_intern(NULL, RTLD_COMPART_ID, + &(struct tramp_data) { .target = rtld_exit_ptr, .defobj = &obj_rtld, .sig = (struct func_sig) { @@ -4848,7 +4853,7 @@ dl_iterate_phdr(__dl_iterate_hdr_callback callback, void *param) error = 0; #ifdef CHERI_LIB_C18N - callback = tramp_intern(NULL, &(struct tramp_data) { + callback = tramp_intern(NULL, RTLD_COMPART_ID, &(struct tramp_data) { .target = callback, .defobj = obj_from_addr(callback), .sig = (struct func_sig) { @@ -5159,7 +5164,7 @@ get_program_var_addr(const char *name, RtldLockState *lockstate) if (ELF_ST_TYPE(req.sym_out->st_info) == STT_FUNC) { void *target = make_function_pointer(req.sym_out, req.defobj_out); #ifdef CHERI_LIB_C18N - target = tramp_intern(NULL, &(struct tramp_data) { + target = tramp_intern(NULL, RTLD_COMPART_ID, &(struct tramp_data) { .target = target, .defobj = req.defobj_out, .def = req.sym_out @@ -5169,7 +5174,7 @@ get_program_var_addr(const char *name, RtldLockState *lockstate) } else if (ELF_ST_TYPE(req.sym_out->st_info) == STT_GNU_IFUNC) { void *target = rtld_resolve_ifunc(req.defobj_out, req.sym_out); #ifdef CHERI_LIB_C18N - target = tramp_intern(NULL, &(struct tramp_data) { + target = tramp_intern(NULL, RTLD_COMPART_ID, &(struct tramp_data) { .target = target, .defobj = req.defobj_out, .def = req.sym_out @@ -6376,6 +6381,28 @@ c18n_setup_compartments(Obj_Entry *obj, const char *name) } } +compart_id_t +compart_id_for_address(const Obj_Entry *obj, Elf_Addr addr) +{ + assert(cheri_is_address_inbounds(obj->relocbase, addr)); + + for (unsigned long i = 0; i < obj->ncomparts; i++) { + if (addr >= obj->comparts[i].start && + addr < obj->comparts[i].end) + return (obj->comparts[i].compart_id); + } + return (obj->compart_id); +} + +static void +c18n_assign_plt_compartments(Obj_Entry *obj) +{ + for (unsigned long i = 0; i < obj->nplts; i++) { + obj->plts[i].compart_id = compart_id_for_address(obj, + (ptraddr_t)obj->plts[i].pltgot); + } +} + static bool c18n_add_obj(Obj_Entry *obj, const char *name) { @@ -6400,6 +6427,7 @@ c18n_add_obj(Obj_Entry *obj, const char *name) } c18n_setup_compartments(obj, name); + c18n_assign_plt_compartments(obj); return (true); } #endif diff --git a/libexec/rtld-elf/rtld.h b/libexec/rtld-elf/rtld.h index 680a88124b70..167f70cec2c0 100644 --- a/libexec/rtld-elf/rtld.h +++ b/libexec/rtld-elf/rtld.h @@ -174,6 +174,9 @@ typedef struct Struct_Plt_Entry { const Elf_Rela *rela; /* PLT relocation entries with addend */ unsigned long relasize; /* Size in bytes of PLT addend reloc info */ bool jmpslots_done : 1; /* Already have relocated the jump slots */ +#ifdef CHERI_LIB_C18N + uint16_t compart_id; +#endif MD_PLT_ENTRY; } Plt_Entry; diff --git a/libexec/rtld-elf/rtld_c18n.c b/libexec/rtld-elf/rtld_c18n.c index a698ebbed9ef..3302911abb13 100644 --- a/libexec/rtld-elf/rtld_c18n.c +++ b/libexec/rtld-elf/rtld_c18n.c @@ -546,8 +546,10 @@ evaluate_rules(compart_id_t caller, compart_id_t callee, const char *sym) } static bool -tramp_should_include(const Obj_Entry *reqobj, const struct tramp_data *data) +tramp_should_include(const Obj_Entry *reqobj, compart_id_t caller, + const struct tramp_data *data) { + compart_id_t callee; const char *sym; if (data->def == NULL) @@ -561,23 +563,28 @@ tramp_should_include(const Obj_Entry *reqobj, const struct tramp_data *data) if (string_base_search(&uni_compart.trusts, sym) != -1) return (false); - if (reqobj == NULL) + if (reqobj == NULL) { + assert(caller == RTLD_COMPART_ID); return (true); + } - if (reqobj->compart_id == data->defobj->compart_id) + callee = compart_id_for_address(data->defobj, + (ptraddr_t)data->defobj->relocbase + data->def->st_value); + + if (caller == callee) return (false); - if (string_base_search(&comparts.data[reqobj->compart_id].trusts, sym) + if (string_base_search(&comparts.data[caller].trusts, sym) != -1) return (false); - if (evaluate_rules(reqobj->compart_id, data->defobj->compart_id, sym)) + if (evaluate_rules(caller, callee, sym)) return (true); rtld_fatal("c18n: Policy violation: %s is not allowed to access symbol " "%s defined by %s", - comparts.data[reqobj->compart_id].name, sym, - comparts.data[data->defobj->compart_id].name); + comparts.data[caller].name, sym, + comparts.data[callee].name); } /* @@ -1375,7 +1382,8 @@ tramp_make_entry(const struct tramp_header *header) } void * -tramp_intern(const Obj_Entry *reqobj, const struct tramp_data *data) +tramp_intern(const Obj_Entry *reqobj, compart_id_t caller, + const struct tramp_data *data) { RtldLockState lockstate; const struct tramp_header *header; @@ -1411,7 +1419,7 @@ tramp_intern(const Obj_Entry *reqobj, const struct tramp_data *data) data->defobj->dynsymcount); assert(func_sig_legal(data->sig)); - if (!tramp_should_include(reqobj, data)) + if (!tramp_should_include(reqobj, caller, data)) return (data->target); start: @@ -1720,7 +1728,8 @@ c18n_init2(Obj_Entry *obj_rtld) * XXX: Manually wrap _rtld_unw_setcontext_impl in a trampoline for now * because it is called via a function pointer. */ - _rtld_unw_setcontext_ptr = tramp_intern(NULL, &(struct tramp_data) { + _rtld_unw_setcontext_ptr = tramp_intern(NULL, RTLD_COMPART_ID, + &(struct tramp_data) { .target = &_rtld_unw_setcontext_impl, .defobj = obj_rtld, .sig = (struct func_sig) { @@ -1744,7 +1753,8 @@ _rtld_thread_start_init(void (*p)(struct pthread *)) { assert((cheri_getperm(p) & CHERI_PERM_EXECUTIVE) == 0); assert(thr_thread_start == NULL); - thr_thread_start = tramp_intern(NULL, &(struct tramp_data) { + thr_thread_start = tramp_intern(NULL, RTLD_COMPART_ID, + &(struct tramp_data) { .target = p, .defobj = obj_from_addr(p), .sig = (struct func_sig) { @@ -1888,7 +1898,8 @@ _rtld_sighandler_init(__siginfohandler_t *handler) { assert((cheri_getperm(handler) & CHERI_PERM_EXECUTIVE) == 0); assert(signal_dispatcher == sigdispatch); - signal_dispatcher = tramp_intern(NULL, &(struct tramp_data) { + signal_dispatcher = tramp_intern(NULL, RTLD_COMPART_ID, + &(struct tramp_data) { .target = handler, .defobj = obj_from_addr(handler), .sig = (struct func_sig) { @@ -2126,7 +2137,8 @@ _rtld_siginvoke(int sig, siginfo_t *info, ucontext_t *ucp, header = tramp_reflect(sigfunc); if (header == NULL) { defobj = obj_from_addr(sigfunc); - sigfunc = tramp_intern(NULL, &(struct tramp_data) { + sigfunc = tramp_intern(NULL, RTLD_COMPART_ID, + &(struct tramp_data) { .target = sigfunc, .defobj = defobj }); diff --git a/libexec/rtld-elf/rtld_c18n.h b/libexec/rtld-elf/rtld_c18n.h index 6a4c5bf3829a..97c7a8b82050 100644 --- a/libexec/rtld-elf/rtld_c18n.h +++ b/libexec/rtld-elf/rtld_c18n.h @@ -61,6 +61,7 @@ typedef uint16_t compart_id_t; typedef struct { uint16_t val; } stk_table_index; compart_id_t compart_id_allocate(const char *); +compart_id_t compart_id_for_address(const Obj_Entry *, Elf_Addr); /* * Stack switching @@ -244,7 +245,7 @@ void tramp_hook(void); size_t tramp_compile(char **, const struct tramp_data *); -void *tramp_intern(const Obj_Entry *reqobj, const struct tramp_data *); +void *tramp_intern(const Obj_Entry *, compart_id_t, const struct tramp_data *); struct tramp_header *tramp_reflect(const void *); struct func_sig sigtab_get(const Obj_Entry *, unsigned long); diff --git a/libexec/rtld-elf/rtld_lock.c b/libexec/rtld-elf/rtld_lock.c index 7194cdf0b575..f75f4e2f1763 100644 --- a/libexec/rtld-elf/rtld_lock.c +++ b/libexec/rtld-elf/rtld_lock.c @@ -428,7 +428,7 @@ _rtld_thread_init(struct RtldLockInfo *pli) #ifdef CHERI_LIB_C18N tmplockinfo = *pli; #define WRAP(_target, _valid, _reg_args, _mem_args, _ret_args) \ - _target = tramp_intern(NULL, &(struct tramp_data) { \ + _target = tramp_intern(NULL, RTLD_COMPART_ID, &(struct tramp_data) { \ .target = _target, \ .defobj = obj, \ .sig = (struct func_sig) { \