-
Notifications
You must be signed in to change notification settings - Fork 61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Userspace runtime loader support for sub-library c18n #2267
base: dev
Are you sure you want to change the base?
Conversation
bsdjhb
commented
Dec 11, 2024
- rtld: Remove code to track start and end offsets of text and rodata
- rtld: Support multiple PT_GNU_RELRO program headers
- rtld: Support multiple PLTs
- elf: Add definition of PT_CHERI_BOUNDS program header type
- elftoolchain: Add support for PT_CHERI_BOUNDS
- rtld: Support narrower PCC bounds via PT_CHERI_BOUNDS
- rtld: Require PT_CHERI_BOUNDS to be exact
- elf: Add definitions for PT_COMPARTMENT and DT_C18NSTRTAB*
- elftoolchain: Add support for PT_COMPARTMENT and DT_C18NSTRTAB*
- rtld: Add compartments for sub-object compartments described by PT_COMPARTMENT
- rtld_c18n: Fix copy/paste typo in _rtld_safebox_code error messages
- rtld: Use compartment IDs from sub-object compartments for policy enforcement
These are the userspace rtld changes to support the psABI extensions to date for sub-library c18n: specifically multiple PT_GNU_RELRO (already merged upstream), multiple PLTs per object, PT_CHERI_BOUNDS, and PT_COMPARTMENT. |
addr = (Elf_Addr)(uintptr_t)obj->relocbase + offset; | ||
for (unsigned long i = 0; i < obj->npcc_caps; i++) { | ||
const char *pcc_cap = obj->pcc_caps[i]; | ||
if (addr >= (ptraddr_t)pcc_cap && addr < cheri_gettop(pcc_cap)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This won’t work for one-past-the-end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don’t know if it’s enough to fall back on the PCC cap with that top if no better one is found or whether picking up bounds from the next one if it exists could be a security concern. If the latter then we’d have to keep them non-contiguous and make this inclusive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm, my initial thought is that for relocations with an addend, we should use the address of the symbol to find a compartment or PCC cap, but things like R_MORELLO_RELATIVE
may not have a symbol.
For code pointers, do we think we will likely have a code pointer that is one-past-the-end? I could see why a data pointer might do that? One question I also have about both APIs I have added here are if the arguments should be an address and length or just an address?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a check that PCC caps do not overlap, including the top (so limit + 1) which I think means that an off by one will now just fail to resolve to anything. I can't really imagine that there are valid code capabilities that are one past the end.
This was used in CHERI-MIPS to enforce stricter PCC bounds for some ABIs but is no longer used.
Iterate over all the program headers in obj_remap_relro and remove the relro fields from Obj_Entry. Skip the call to obj_enforce_relro() in relocate_object() for the rtld object as well as the main program object. obj_enforce_relro() is called later when it safe to reference globals such as page_size. Reviewed by: kib Obtained from: CheriBSD Sponsored by: AFRL, DARPA Differential Revision: https://reviews.freebsd.org/D47884 (cherry picked from commit fda0403eb0839b29b0b271c69c5cb6bfc874a3b5)
Add a new Plt_Entry structure that describes the data associated with a PLT including the ELF relocation table and PLT GOT. Count the number of PLTs and allocate an array of Plt_Entry objects in the Obj_Entry. Instead of storing the Obj_Entry pointer in GOTs, store the Plt_Entry pointer. The special wrinkles for PowerPC are a bit hackish here and would need some tweaks before upstreaming.
If an object file includes one or more PT_CHERI_BOUNDS program headers, derive new code capabilities for each header and derive code pointers from the code capability that contains the address. This does not yet check for overlapping bounds or if the resulting PCC bounds are wider than the phdr due to precision.
Fail with an error message if the resulting PCC bounds do not match the segment's relocated address and length.
…MPARTMENT - Recognize the new c18n string table via DT_C18NSTRTAB* and save bounded pointers in each Obj_Entry. - Define a new Compart_Entry type to hold information about a sub-object compartment including its compartment ID, virtual address bounds, and name. The name for sub-object is "<obj name>:<compartment name>". Currently the default compartment for an object does not have a suffix. Possibly it should. - This requires reworking compartment assignment to be more explicitly timed (always after digest_dynamic) rather than a side effect of object_add_name. Since we now always have DT_SONAME if it is present, save a pointer to DT_SONAME in Obj_Entry and prefer it for the library name for a compartment (instead of using the first name added).
…orcement - 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.
return (true); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate blank line
#ifdef __CHERI_PURE_CAPABILITY__ | ||
/* | ||
* XXX: Can't handle different code caps for individual | ||
* cap_reloc entries. cap_relocs needs to die. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure you can, just as we have init_linker_file_cap_relocs that does it manually for VNET/DPCPU. It just means not using the implementation from cheri_init_globals.h. Whether it's worth doing that is a different matter though, but given the PI meeting timeline might be simpler than doing ELF relocations for RISC-V?
#elif __has_feature(capabilities) | ||
data_cap = cheri_getdefault(); | ||
text_rodata_cap = cheri_getpcc(); | ||
code_cap = cheri_getpcc(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DDC + PCC is a bit of a strange choice to use given caprelocs uses get_(code|data)segment_cap. The difference is the latter will be bounded to the mapping so won't let you create capabilities that extend outside your object. Whether that's right or not for hybrid is a question in and of itself, but we should probably be consistent between caprelocs and ELF relocations. Note also that process_r_cheri_capability will enforce bounds within the object too, it's really here that's the odd one out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With that change, one could have a pcc_cap for hybrid that's get_codesegment_cap(obj) + value
and avoid ifdefs everywhere. You could also have a pcc_ptr that's either pcc_cap in purecap or relocbase + value otherwise, but I think e_entry is the only use for that so may not be worth it?
@@ -1478,6 +1478,17 @@ create_pcc_caps(Obj_Entry *obj) | |||
case PT_CHERI_BOUNDS: | |||
pcc_cap = obj->text_rodata_cap + ph->p_vaddr; | |||
pcc_cap = cheri_setbounds(pcc_cap, ph->p_memsz); | |||
if (cheri_getbase(pcc_cap) != |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe a TODO that in future this should be simplified to cheri_setbounds_exact + cheri_gettag, once we can assume tag clearing on RISC-V (which is almost there...)?
@@ -533,6 +533,7 @@ typedef struct { | |||
#define PT_PHDR 6 /* Location of program header itself. */ | |||
#define PT_TLS 7 /* Thread local storage segment */ | |||
#define PT_LOOS 0x60000000 /* First OS-specific. */ | |||
#define PT_COMPARTMENT 0x64331380 /* Sub-object compartment. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this should be some PT_C18N_FOO to group things in case there are other C18N headers in future? Especially given the ASCII "C18" in the numeric value.
@@ -959,7 +962,7 @@ _rtld(Elf_Addr *sp, func_ptr_type *exit_proc, Obj_Entry **objp) | |||
/* | |||
* Manually register the main object after the policy is loaded. | |||
*/ | |||
object_add_name(obj_main, obj_main->path); | |||
(void)c18n_add_obj(obj_main, obj_main->path); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!...) rtld_die()?
@@ -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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just pass the Plt_Entry instead of the Obj_Entry and use reqplt->compart_id everywhere?
size_t len; | ||
|
||
assert(obj->compart_id == 0); | ||
obj->compart_id = compart_id_allocate(name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a bit confusing that both Obj_Entry and Compart_Entry have a compart_id field, and is just asking for someone to accidentally use the Obj_Entry one