From ce8d4ecf6d222642b40a4891e6bbbf9a0f75233a 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. Co-authored-by: Dapeng Gao --- libexec/rtld-elf/aarch64/reloc.c | 12 ++-- libexec/rtld-elf/aarch64/rtld_c18n_machdep.c | 6 +- libexec/rtld-elf/aarch64/rtld_machdep.h | 6 +- libexec/rtld-elf/rtld.c | 50 +++++++++++++---- libexec/rtld-elf/rtld.h | 5 +- libexec/rtld-elf/rtld_c18n.c | 59 +++++++++++--------- libexec/rtld-elf/rtld_c18n.h | 3 +- libexec/rtld-elf/rtld_lock.c | 2 +- 8 files changed, 96 insertions(+), 47 deletions(-) diff --git a/libexec/rtld-elf/aarch64/reloc.c b/libexec/rtld-elf/aarch64/reloc.c index 5ba8295e19fd..c741469d11ce 100644 --- a/libexec/rtld-elf/aarch64/reloc.c +++ b/libexec/rtld-elf/aarch64/reloc.c @@ -492,7 +492,8 @@ reloc_plt(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, + target = (uintptr_t)tramp_intern(plt, + 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(plt, 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,8 @@ 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(plt, 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..d2ba2b221b33 100644 --- a/libexec/rtld-elf/aarch64/rtld_c18n_machdep.c +++ b/libexec/rtld-elf/aarch64/rtld_c18n_machdep.c @@ -67,6 +67,7 @@ tramp_compile(char **entry, const struct tramp_data *data) char *buf = *entry; size_t hook_off, count_off; size_t header_off, target_off, landing_off, unused_regs; + compart_id_t callee; bool executive = cheri_getperm(data->target) & CHERI_PERM_EXECUTIVE; bool count = ld_compartment_switch_count != NULL; bool hook = ld_compartment_utrace != NULL || @@ -143,10 +144,11 @@ tramp_compile(char **entry, const struct tramp_data *data) target_off = size + offsetof(struct tramp_header, target); *entry = buf + size; size += offsetof(struct tramp_header, entry); + callee = compart_id_for_address(data->defobj, (ptraddr_t)data->target); COPY(push_frame); PATCH_LDR_IMM(push_frame, target, target_off); - PATCH_MOV(push_frame, cid, cid_to_index(data->defobj->compart_id).val); + PATCH_MOV(push_frame, cid, cid_to_index(callee).val); PATCH_ADD(push_frame, ret_args, data->sig.valid ? data->sig.ret_args << (16 - 12) : 0); landing_off = PATCH_OFF(push_frame, landing); @@ -282,7 +284,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 934d4426028a..499bed704f7b 100644 --- a/libexec/rtld-elf/rtld.c +++ b/libexec/rtld-elf/rtld.c @@ -1152,7 +1152,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) { @@ -1166,7 +1167,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) { @@ -1187,7 +1189,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, @@ -1238,7 +1240,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(plt, plt->compart_id, + &(struct tramp_data) { .target = (void *)target, .defobj = defobj, .def = def, @@ -3644,7 +3647,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) { @@ -3657,7 +3661,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) { @@ -4854,7 +4859,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) { @@ -5165,7 +5170,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 @@ -5175,7 +5180,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 @@ -6343,8 +6348,8 @@ c18n_setup_compartments(Obj_Entry *obj, const char *name) const Elf_Phdr *ph; size_t len; - assert(obj->compart_id == 0); - obj->compart_id = compart_id_allocate(name); + assert(obj->default_compart_id == 0); + obj->default_compart_id = compart_id_allocate(name); for (ph = obj->phdr; (const char *)ph < (const char *)obj->phdr + obj->phsize; ph++) { @@ -6382,6 +6387,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->default_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) { @@ -6406,6 +6433,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 624f75f0cf66..c7fc133f5dba 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; @@ -306,7 +309,7 @@ typedef struct Struct_Obj_Entry { #ifdef CHERI_LIB_C18N const char *soname; - uint16_t compart_id; + uint16_t default_compart_id; const struct func_sig *sigtab; #endif diff --git a/libexec/rtld-elf/rtld_c18n.c b/libexec/rtld-elf/rtld_c18n.c index 39be1f4947ea..c8208ed552b4 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 Plt_Entry *plt, compart_id_t caller, + const struct tramp_data *data) { + compart_id_t callee; const char *sym; /* XXX: This not be needed once function pointers are wrapped. */ @@ -565,23 +567,26 @@ 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) - return (true); + callee = compart_id_for_address(data->defobj, (ptraddr_t)data->target); - if (reqobj->compart_id == data->defobj->compart_id) + /* + * Jump slots within the same compartment do not require a + * trampoline. + */ + if (plt != NULL && 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); } /* @@ -1348,7 +1353,8 @@ tramp_check_sig(const struct tramp_header *found, const Obj_Entry *reqobj, "%s requests " C18N_SIG_FORMAT_STRING " but " "%s provides " C18N_SIG_FORMAT_STRING, symname(found->defobj, found->symnum), - reqobj->path, C18N_SIG_FORMAT(data->sig), + reqobj != NULL ? reqobj->path : "", + C18N_SIG_FORMAT(data->sig), found->defobj->path, C18N_SIG_FORMAT(sig)); } } @@ -1379,7 +1385,8 @@ tramp_make_entry(const struct tramp_header *header) } void * -tramp_intern(const Obj_Entry *reqobj, const struct tramp_data *data) +tramp_intern(const Plt_Entry *plt, compart_id_t caller, + const struct tramp_data *data) { RtldLockState lockstate; const struct tramp_header *header; @@ -1398,15 +1405,11 @@ tramp_intern(const Obj_Entry *reqobj, const struct tramp_data *data) assert(cheri_gettag(data->defobj)); if (data->def == NULL) /* - * XXX-DG: reqobj != NULL causes policies to be evaluated which - * might result in a trampoline being elided. This is only safe - * to do for jump slot relocations. - * * Currently, the decision to elide the trampoline or not is * coupled with the decision of whether the symbol should be - * made accesible to the requesting object. This is insecure. + * made accessible to the requesting object. This is insecure. */ - assert(reqobj == NULL); + assert(plt == NULL); else if (data->def == &sym_zero) assert(data->target == NULL); else @@ -1415,7 +1418,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(plt, caller, data)) return (data->target); start: @@ -1516,7 +1519,7 @@ tramp_intern(const Obj_Entry *reqobj, const struct tramp_data *data) end: lock_release(rtld_tramp_lock, &lockstate); - tramp_check_sig(header, reqobj, data); + tramp_check_sig(header, plt != NULL ? plt->obj : NULL, data); /* * Most consumers use type (void *) for function pointers. @@ -1724,7 +1727,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) { @@ -1748,7 +1752,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) { @@ -1892,7 +1897,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) { @@ -2130,13 +2136,16 @@ _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) { + callee = compart_id_for_address(defobj, (ptraddr_t)sigfunc); + sigfunc = tramp_intern(NULL, RTLD_COMPART_ID, + &(struct tramp_data) { .target = sigfunc, .defobj = defobj }); - } else - defobj = header->defobj; - callee = defobj->compart_id; + } else { + callee = compart_id_for_address(header->defobj, + (ptraddr_t)header->target); + } callee_idx = cid_to_index(callee); /* diff --git a/libexec/rtld-elf/rtld_c18n.h b/libexec/rtld-elf/rtld_c18n.h index 6a4c5bf3829a..d9a853d0dbf5 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 Plt_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) { \