To aid in libc development, additional auxiliary vector entries are added in the native elf loader. This is supplemented by code that determines the beginning and end addresses of the load segments in the elf and interpreter. A full description of the entries and their constant values can be found in the PCuABI specification at [1].
[1]: https://git.morello-project.org/morello/kernel/linux/-/wikis/Morello-pure-ca...
Signed-off-by: Sherwin da Cruz sherwin.dacruz@arm.com --- fs/binfmt_elf.c | 100 ++++++++++++++++++++++++++++++++++-- include/linux/auxvec.h | 2 +- include/uapi/linux/auxvec.h | 13 +++++ 3 files changed, 110 insertions(+), 5 deletions(-)
diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c index e9e6ab6570bc..920e3515080e 100644 --- a/fs/binfmt_elf.c +++ b/fs/binfmt_elf.c @@ -49,6 +49,10 @@ #include <asm/param.h> #include <asm/page.h>
+#ifdef CONFIG_CHERI_PURECAP_UABI +#include <cheriintrin.h> +#endif + #ifndef ELF_COMPAT #define ELF_COMPAT 0 #endif @@ -177,10 +181,19 @@ static int padzero(unsigned long elf_bss) #define ELF_BASE_PLATFORM NULL #endif
+struct elf_load_info { + unsigned long start_elf_rx; + unsigned long end_elf_rx; + unsigned long start_elf_rw; + unsigned long end_elf_rw; +}; + static int create_elf_tables(struct linux_binprm *bprm, const struct elfhdr *exec, unsigned long interp_load_addr, - unsigned long e_entry, unsigned long phdr_addr) + unsigned long e_entry, unsigned long phdr_addr, + const struct elf_load_info *exec_load_info, + const struct elf_load_info *interp_load_info) { struct mm_struct *mm = current->mm; unsigned long p = bprm->p; @@ -199,6 +212,9 @@ create_elf_tables(struct linux_binprm *bprm, const struct elfhdr *exec, int ei_index; const struct cred *cred = current_cred(); struct vm_area_struct *vma; +#if defined(CONFIG_CHERI_PURECAP_UABI) && (ELF_COMPAT == 0) + elf_stack_item_t *mm_at_argv, *mm_at_envp; +#endif
/* * In some cases (e.g. Hyper-Threading), we want to avoid L1 @@ -293,6 +309,42 @@ create_elf_tables(struct linux_binprm *bprm, const struct elfhdr *exec, if (bprm->have_execfd) { NEW_AUX_ENT(AT_EXECFD, bprm->execfd); } +#if defined(CONFIG_CHERI_PURECAP_UABI) && (ELF_COMPAT == 0) + /* + * TODO [PCuABI] - Restrict bounds/perms for AT_CHERI_* entries + */ + NEW_AUX_ENT(AT_CHERI_EXEC_RW_CAP, + (exec_load_info->start_elf_rw != ~0UL ? + elf_uaddr_to_user_ptr(exec_load_info->start_elf_rw) : + NULL)); + NEW_AUX_ENT(AT_CHERI_EXEC_RX_CAP, + elf_uaddr_to_user_ptr(exec_load_info->start_elf_rx)); + NEW_AUX_ENT(AT_CHERI_INTERP_RW_CAP, + ((interp_load_addr && interp_load_info->start_elf_rw != ~0UL) ? + elf_uaddr_to_user_ptr(interp_load_info->start_elf_rw) : + NULL)); + NEW_AUX_ENT(AT_CHERI_INTERP_RX_CAP, + (interp_load_addr ? + elf_uaddr_to_user_ptr(interp_load_info->start_elf_rx) : + NULL)); + NEW_AUX_ENT(AT_CHERI_STACK_CAP, elf_uaddr_to_user_ptr(0)); + NEW_AUX_ENT(AT_CHERI_SEAL_CAP, + cheri_bounds_set_exact(elf_uaddr_to_user_ptr(0), 1 << 15)); + NEW_AUX_ENT(AT_CHERI_CID_CAP, elf_uaddr_to_user_ptr(0)); + + /* + * Since the auxv entries are inserted into the mm struct before the + * argv/envp entries are placed on the stack (unknown at this time), + * we save pointers to their positions in the mm struct to update them + * after their positions on the stack are determined. + */ + NEW_AUX_ENT(AT_ARGC, argc); + NEW_AUX_ENT(AT_ARGV, 0); + mm_at_argv = elf_info - 1; + NEW_AUX_ENT(AT_ENVC, envc); + NEW_AUX_ENT(AT_ENVP, 0); + mm_at_envp = elf_info - 1; +#endif /* CONFIG_CHERI_PURECAP_UABI && ELF_COMPAT == 0 */ #undef NEW_AUX_ENT /* AT_NULL is zero; clear the rest too */ memset(elf_info, 0, (char *)mm->saved_auxv + @@ -332,6 +384,9 @@ create_elf_tables(struct linux_binprm *bprm, const struct elfhdr *exec, return -EFAULT;
/* Populate list of argv pointers back to argv strings. */ +#if defined(CONFIG_CHERI_PURECAP_UABI) && (ELF_COMPAT == 0) + *mm_at_argv = (elf_stack_item_t)sp; +#endif p = mm->arg_end = mm->arg_start; while (argc-- > 0) { size_t len; @@ -347,6 +402,9 @@ create_elf_tables(struct linux_binprm *bprm, const struct elfhdr *exec, mm->arg_end = p;
/* Populate list of envp pointers back to envp strings. */ +#if defined(CONFIG_CHERI_PURECAP_UABI) && (ELF_COMPAT == 0) + *mm_at_envp = (elf_stack_item_t)sp; +#endif mm->env_end = mm->env_start = p; while (envc-- > 0) { size_t len; @@ -597,7 +655,7 @@ static inline int make_prot(u32 p_flags, struct arch_elf_state *arch_state, static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex, struct file *interpreter, unsigned long no_base, struct elf_phdr *interp_elf_phdata, - struct arch_elf_state *arch_state) + struct arch_elf_state *arch_state, struct elf_load_info *load_info) { struct elf_phdr *eppnt; unsigned long load_addr = 0; @@ -625,6 +683,12 @@ static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex, goto out; }
+ load_info->start_elf_rx = ~0UL; + load_info->start_elf_rw = ~0UL; + + load_info->end_elf_rx = 0; + load_info->end_elf_rw = 0; + eppnt = interp_elf_phdata; for (i = 0; i < interp_elf_ex->e_phnum; i++, eppnt++) { if (eppnt->p_type == PT_LOAD) { @@ -666,6 +730,9 @@ static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex, error = -ENOMEM; goto out; } + if (eppnt->p_flags & PF_W) + load_info->start_elf_rw = min(k, load_info->start_elf_rw); + load_info->start_elf_rx = min(k, load_info->start_elf_rx);
/* * Find the end of the file mapping for this phdr, and @@ -674,6 +741,9 @@ static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex, k = load_addr + eppnt->p_vaddr + eppnt->p_filesz; if (k > elf_bss) elf_bss = k; + if (eppnt->p_flags & PF_W) + load_info->end_elf_rw = max(k, load_info->end_elf_rw); + load_info->end_elf_rx = max(k, load_info->end_elf_rx);
/* * Do the same thing for the memory mapping - between @@ -848,6 +918,7 @@ static int load_elf_binary(struct linux_binprm *bprm) struct arch_elf_state arch_state = INIT_ARCH_ELF_STATE; struct mm_struct *mm; struct pt_regs *regs; + struct elf_load_info exec_load_info, interp_load_info;
retval = -ENOEXEC; /* First of all, some simple consistency checks */ @@ -1034,6 +1105,12 @@ static int load_elf_binary(struct linux_binprm *bprm) start_data = 0; end_data = 0;
+ exec_load_info.start_elf_rx = ~0UL; + exec_load_info.start_elf_rw = ~0UL; + + exec_load_info.end_elf_rx = 0; + exec_load_info.end_elf_rw = 0; + /* Now we do a little grungy work by mmapping the ELF image into the correct location in memory. */ for(i = 0, elf_ppnt = elf_phdata; @@ -1200,6 +1277,9 @@ static int load_elf_binary(struct linux_binprm *bprm) start_code = k; if (start_data < k) start_data = k; + if (elf_ppnt->p_flags & PF_W) + exec_load_info.start_elf_rw = min(k, exec_load_info.start_elf_rw); + exec_load_info.start_elf_rx = min(k, exec_load_info.start_elf_rx);
/* * Check to see if the section's size will overflow the @@ -1222,6 +1302,10 @@ static int load_elf_binary(struct linux_binprm *bprm) end_code = k; if (end_data < k) end_data = k; + if (elf_ppnt->p_flags & PF_W) + exec_load_info.end_elf_rw = max(k, exec_load_info.end_elf_rw); + exec_load_info.end_elf_rx = max(k, exec_load_info.end_elf_rx); + k = elf_ppnt->p_vaddr + elf_ppnt->p_memsz; if (k > elf_brk) { bss_prot = elf_prot; @@ -1237,6 +1321,13 @@ static int load_elf_binary(struct linux_binprm *bprm) end_code += load_bias; start_data += load_bias; end_data += load_bias; + exec_load_info.start_elf_rx += load_bias; + exec_load_info.end_elf_rx += load_bias; + /* ELF has at least one writable load segment */ + if (exec_load_info.start_elf_rw != ~0UL) { + exec_load_info.start_elf_rw += load_bias; + exec_load_info.end_elf_rw += load_bias; + }
/* Calling set_brk effectively mmaps the pages that we need * for the bss and break sections. We must do this before @@ -1255,7 +1346,7 @@ static int load_elf_binary(struct linux_binprm *bprm) elf_entry = load_elf_interp(interp_elf_ex, interpreter, load_bias, interp_elf_phdata, - &arch_state); + &arch_state, &interp_load_info); if (!IS_ERR((void *)elf_entry)) { /* * load_elf_interp() returns relocation @@ -1295,7 +1386,8 @@ static int load_elf_binary(struct linux_binprm *bprm) #endif /* ARCH_HAS_SETUP_ADDITIONAL_PAGES */
retval = create_elf_tables(bprm, elf_ex, interp_load_addr, - e_entry, phdr_addr); + e_entry, phdr_addr, + &exec_load_info, &interp_load_info); if (retval < 0) goto out;
diff --git a/include/linux/auxvec.h b/include/linux/auxvec.h index f68d0ec2d740..41780858473c 100644 --- a/include/linux/auxvec.h +++ b/include/linux/auxvec.h @@ -4,6 +4,6 @@
#include <uapi/linux/auxvec.h>
-#define AT_VECTOR_SIZE_BASE 20 /* NEW_AUX_ENT entries in auxiliary table */ +#define AT_VECTOR_SIZE_BASE 31 /* NEW_AUX_ENT entries in auxiliary table */ /* number of "#define AT_.*" above, minus {AT_NULL, AT_IGNORE, AT_NOTELF} */ #endif /* _LINUX_AUXVEC_H */ diff --git a/include/uapi/linux/auxvec.h b/include/uapi/linux/auxvec.h index c7e502bf5a6f..31362b7cb3cc 100644 --- a/include/uapi/linux/auxvec.h +++ b/include/uapi/linux/auxvec.h @@ -37,4 +37,17 @@ #define AT_MINSIGSTKSZ 51 /* minimal stack size for signal delivery */ #endif
+#define AT_CHERI_EXEC_RW_CAP 60 +#define AT_CHERI_EXEC_RX_CAP 61 +#define AT_CHERI_INTERP_RW_CAP 62 +#define AT_CHERI_INTERP_RX_CAP 63 +#define AT_CHERI_STACK_CAP 64 +#define AT_CHERI_SEAL_CAP 65 +#define AT_CHERI_CID_CAP 66 + +#define AT_ARGC 80 +#define AT_ARGV 81 +#define AT_ENVC 82 +#define AT_ENVP 83 + #endif /* _UAPI_LINUX_AUXVEC_H */