v2..v3: patch 4: Added new compat_start_thread() for compat64 binaries. This change was made in a separate patch and inserted before the previous patch 4 that sets c0-c3.
The branch at [1] has been updated with the new commits
---
This patchset adds partial support for the new aux entries in the PCuABI, which mainly passes capabilites to the program. The registers c0-c3 are also set to argc/argv/envp/auxv which currently still resides on the stack for backwards compatability. The patches are available at [1]. The constants for the new aux entries can be found at [2]. For reference the transitional PCuABI can be found at [3].
The capabilities placed in the aux vector do not have restricted permissions or bounds but have the address set to the specified value, except for AT_CHERI_STACK_CAP and AT_CHERI_CID_CAP. The upper and lower bounds of the RX and RW regions as defined in the PCuABI spec for both the interpreter and program are also determined, though the upper bounds are left unused since the bounds are currently not set on the new aux entries.
A morello kselftest ensures that the register values of c0-c3 in startup code matches the corresponding values on the stack.
[1] https://git.morello-project.org/sherwin-dc/linux/-/commits/morello/next/ [2] https://git.morello-project.org/morello/kernel/linux/-/wikis/Morello-pure-ca... [3] https://git.morello-project.org/morello/kernel/linux/-/wikis/Transitional-Mo...
Sherwin da Cruz (6): arm64: elf.h: Remove register init macro fs/binfmt_elf: Add additional Aux vector entries elf: Return result from START_THREAD macro arm64/elf: Use separate compat_start_thread() for compat64 arm64: Set c0-c3 to argc/argv/envp/auxv in start_thread() kselftests/arm64/morello: Test for new aux entries and init registers
arch/arm64/include/asm/elf.h | 11 +- arch/arm64/include/asm/processor.h | 55 +++++++++- fs/binfmt_elf.c | 103 +++++++++++++++++- fs/compat_binfmt_elf.c | 24 ++-- fs/exec.c | 4 +- include/linux/auxvec.h | 2 +- include/linux/elf.h | 7 +- include/uapi/linux/auxvec.h | 13 +++ .../selftests/arm64/morello/bootstrap.c | 53 ++++++--- .../arm64/morello/freestanding_start.S | 12 +- 10 files changed, 239 insertions(+), 45 deletions(-)
The arm64 specific ELF_PLAT_INIT macro is removed. This was called in binfmt_elf.c to init registers when loading binaries but is instead done in start_thread() which clears all registers.
Signed-off-by: Sherwin da Cruz sherwin.dacruz@arm.com --- arch/arm64/include/asm/elf.h | 7 ------- 1 file changed, 7 deletions(-)
diff --git a/arch/arm64/include/asm/elf.h b/arch/arm64/include/asm/elf.h index cb86840eabe0..9eb86b4cb691 100644 --- a/arch/arm64/include/asm/elf.h +++ b/arch/arm64/include/asm/elf.h @@ -164,13 +164,6 @@ typedef unsigned long elf_greg_t; typedef elf_greg_t elf_gregset_t[ELF_NGREG]; typedef struct user_fpsimd_state elf_fpregset_t;
-/* - * When the program starts, a1 contains a pointer to a function to be - * registered with atexit, as per the SVR4 ABI. A value of 0 means we have no - * such handler. - */ -#define ELF_PLAT_INIT(_r, load_addr) (_r)->regs[0] = 0 - #define SET_PERSONALITY_AARCH64() \ ({ \ current->personality &= ~READ_IMPLIES_EXEC; \
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 */
The START_THREAD arch hook called in binfmt_elf is modified to return a value which the elf loader returns as the final result. Since no arch has specialised this macro it is being modified to allow any arch specialising this macro to either 1. Abort execution of an elf binary from within start_thread() by returning a negative value or 2. Place a positive value in the first result register for the executing program to access by returning the positive value.
This modified macro also takes in the bprm struct instead of the sp to allow any arch to access more information about the binary contained within. Compatibility is maintained in previous versions by passing the sp from bprm to the default start_thread() implementation.
This required changing kernel_execve() to always return 0 on success instead of the aforementioned positive value. This is because only a return value of 0 would indicate that the init process has started correctly during boot, in which kernel_execve() is called.
Signed-off-by: Sherwin da Cruz sherwin.dacruz@arm.com --- fs/binfmt_elf.c | 3 +-- fs/compat_binfmt_elf.c | 6 +++++- fs/exec.c | 4 ++-- include/linux/elf.h | 7 +++++-- 4 files changed, 13 insertions(+), 7 deletions(-)
diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c index 920e3515080e..7e391025ffe8 100644 --- a/fs/binfmt_elf.c +++ b/fs/binfmt_elf.c @@ -1442,8 +1442,7 @@ static int load_elf_binary(struct linux_binprm *bprm) #endif
finalize_exec(bprm); - START_THREAD(elf_ex, regs, elf_entry, bprm->p); - retval = 0; + retval = START_THREAD(elf_ex, regs, elf_entry, bprm); out: return retval;
diff --git a/fs/compat_binfmt_elf.c b/fs/compat_binfmt_elf.c index 413922b85be6..247b9ebd8fbb 100644 --- a/fs/compat_binfmt_elf.c +++ b/fs/compat_binfmt_elf.c @@ -127,7 +127,11 @@
#ifdef COMPAT_START_THREAD #undef START_THREAD -#define START_THREAD COMPAT_START_THREAD +#define START_THREAD(elf_ex, regs, elf_entry, bprm) \ +({ \ + COMPAT_START_THREAD(elf_ex, regs, elf_entry, bprm->p); \ + 0; /* binfmt_elf return value */ \ +}) #endif
#ifdef compat_arch_setup_additional_pages diff --git a/fs/exec.c b/fs/exec.c index c177f8cc3e00..3edd9601ae9a 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1793,7 +1793,7 @@ static int exec_binprm(struct linux_binprm *bprm) trace_sched_process_exec(current, old_pid, bprm); ptrace_event(PTRACE_EVENT_EXEC, old_vpid); proc_exec_connector(current); - return 0; + return ret; }
/* @@ -2003,7 +2003,7 @@ int kernel_execve(const char *kernel_filename, free_bprm(bprm); out_ret: putname(filename); - return retval; + return retval < 0 ? retval : 0; }
static int do_execve(struct filename *filename, diff --git a/include/linux/elf.h b/include/linux/elf.h index 50ca9e34763c..039ad1867045 100644 --- a/include/linux/elf.h +++ b/include/linux/elf.h @@ -23,8 +23,11 @@ #endif
#ifndef START_THREAD -#define START_THREAD(elf_ex, regs, elf_entry, start_stack) \ - start_thread(regs, elf_entry, start_stack) +#define START_THREAD(elf_ex, regs, elf_entry, bprm) \ +({ \ + start_thread(regs, elf_entry, bprm->p); \ + 0; /* binfmt_elf return value */ \ +}) #endif
#if defined(ARCH_HAS_SETUP_ADDITIONAL_PAGES) && !defined(ARCH_SETUP_ADDITIONAL_PAGES)
Currently in arm64 the start_thread() function is used to initialise registers for both native and compat64 binaries. This is now split into a separate function for both native and compat64 binaries.
Signed-off-by: Sherwin da Cruz sherwin.dacruz@arm.com --- arch/arm64/include/asm/elf.h | 3 ++- arch/arm64/include/asm/processor.h | 20 +++++++++++++++++++- fs/compat_binfmt_elf.c | 28 ++++++++++++++-------------- 3 files changed, 35 insertions(+), 16 deletions(-)
diff --git a/arch/arm64/include/asm/elf.h b/arch/arm64/include/asm/elf.h index 9eb86b4cb691..5802ddd1d28a 100644 --- a/arch/arm64/include/asm/elf.h +++ b/arch/arm64/include/asm/elf.h @@ -224,6 +224,8 @@ extern int arch_setup_additional_pages(struct linux_binprm *bprm,
#ifdef CONFIG_COMPAT
+#define compat_start_thread compat_start_thread + #ifdef CONFIG_COMPAT64
#define compat_elf_check_arch(x) ((x)->e_machine == EM_AARCH64 && \ @@ -260,7 +262,6 @@ typedef compat_elf_greg_t compat_elf_gregset_t[COMPAT_ELF_NGREG]; #define EF_ARM_EABI_MASK 0xff000000 int compat_elf_check_arch(const struct elf32_hdr *); #define compat_elf_check_arch compat_elf_check_arch -#define compat_start_thread compat_start_thread /* * Unlike the native SET_PERSONALITY macro, the compat version maintains * READ_IMPLIES_EXEC across an execve() since this is the behaviour on diff --git a/arch/arm64/include/asm/processor.h b/arch/arm64/include/asm/processor.h index 5bdecb185f63..cf317203edc2 100644 --- a/arch/arm64/include/asm/processor.h +++ b/arch/arm64/include/asm/processor.h @@ -261,6 +261,22 @@ static inline void start_thread(struct pt_regs *regs, unsigned long pc, }
#ifdef CONFIG_COMPAT +#ifdef CONFIG_COMPAT64 + +static inline void compat_start_thread(struct pt_regs *regs, unsigned long pc, + unsigned long sp) +{ + start_thread_common(regs, pc); + regs->pstate = PSR_MODE_EL0t; + spectre_v4_enable_task_mitigation(current); + regs->sp = sp; + + if (system_supports_morello()) + morello_thread_start(regs, pc); +} + +#else /* CONFIG_COMPAT64 */ + static inline void compat_start_thread(struct pt_regs *regs, unsigned long pc, unsigned long sp) { @@ -276,7 +292,9 @@ static inline void compat_start_thread(struct pt_regs *regs, unsigned long pc, spectre_v4_enable_task_mitigation(current); regs->compat_sp = sp; } -#endif + +#endif /* !CONFIG_COMPAT64 */ +#endif /* CONFIG_COMPAT */
static inline bool is_ttbr0_addr(unsigned long addr) { diff --git a/fs/compat_binfmt_elf.c b/fs/compat_binfmt_elf.c index 247b9ebd8fbb..b6b453b35946 100644 --- a/fs/compat_binfmt_elf.c +++ b/fs/compat_binfmt_elf.c @@ -120,20 +120,6 @@ #define ELF_PLAT_INIT COMPAT_ELF_PLAT_INIT #endif
-#ifdef compat_start_thread -#define COMPAT_START_THREAD(ex, regs, new_ip, new_sp) \ - compat_start_thread(regs, new_ip, new_sp) -#endif - -#ifdef COMPAT_START_THREAD -#undef START_THREAD -#define START_THREAD(elf_ex, regs, elf_entry, bprm) \ -({ \ - COMPAT_START_THREAD(elf_ex, regs, elf_entry, bprm->p); \ - 0; /* binfmt_elf return value */ \ -}) -#endif - #ifdef compat_arch_setup_additional_pages #define COMPAT_ARCH_SETUP_ADDITIONAL_PAGES(bprm, ex, interpreter) \ compat_arch_setup_additional_pages(bprm, interpreter) @@ -177,6 +163,20 @@ #define SET_PERSONALITY COMPAT_SET_PERSONALITY #endif
+#ifdef compat_start_thread +#define COMPAT_START_THREAD(ex, regs, new_ip, new_sp) \ + compat_start_thread(regs, new_ip, new_sp) +#endif + +#ifdef COMPAT_START_THREAD +#undef START_THREAD +#define START_THREAD(elf_ex, regs, elf_entry, bprm) \ +({ \ + COMPAT_START_THREAD(elf_ex, regs, elf_entry, bprm->p); \ + 0; /* binfmt_elf return value */ \ +}) +#endif + /* * We share all the actual code with the native (64-bit) version. */
As per PCuABI specific registers c0-c3 are initialised for purecap applications to make use of. This is set in start_thread(). The macro START_THREAD is specialised to allow returning the value of argc to be placed in x0.
Signed-off-by: Sherwin da Cruz sherwin.dacruz@arm.com --- arch/arm64/include/asm/elf.h | 3 +++ arch/arm64/include/asm/processor.h | 35 ++++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/include/asm/elf.h b/arch/arm64/include/asm/elf.h index 5802ddd1d28a..6189c8614dd6 100644 --- a/arch/arm64/include/asm/elf.h +++ b/arch/arm64/include/asm/elf.h @@ -164,6 +164,9 @@ typedef unsigned long elf_greg_t; typedef elf_greg_t elf_gregset_t[ELF_NGREG]; typedef struct user_fpsimd_state elf_fpregset_t;
+#define START_THREAD(elf_ex, regs, elf_entry, bprm) \ + start_thread(regs, elf_entry, bprm->p, bprm->argc, bprm->envc) + #define SET_PERSONALITY_AARCH64() \ ({ \ current->personality &= ~READ_IMPLIES_EXEC; \ diff --git a/arch/arm64/include/asm/processor.h b/arch/arm64/include/asm/processor.h index cf317203edc2..08fe940fd2a0 100644 --- a/arch/arm64/include/asm/processor.h +++ b/arch/arm64/include/asm/processor.h @@ -238,6 +238,35 @@ void tls_preserve_current_state(void); .fpsimd_cpu = NR_CPUS, \ }
+static inline int init_gp_regs(struct pt_regs *regs, unsigned long sp, + int argc, int envc) +{ + int retval = 0; +#ifdef CONFIG_CHERI_PURECAP_UABI + unsigned long argv, envp, auxv; + /* + * TODO [PCuABI]: + * - When argv/envp/auxv is moved off the stack, update the registers x1-x3 + * with the new pointer values, and ensure c1-c3 contain appropriate + * capabilities (currently set to csp). + * - Restrict bounds/perms of c1-c3. + * + * In ret_to_user c regs are first loaded then merged with x regs if their values + * are different. Hence we load capabilities in c regs and the value in x regs. + */ + retval = argc; /* Placed in x0 */ + argv = sp + 1 * sizeof(user_uintptr_t); /* Increment past argc on stack */ + envp = argv + (argc + 1) * sizeof(user_uintptr_t); /* Go past arg vals + NULL */ + auxv = envp + (envc + 1) * sizeof(user_uintptr_t); /* Go past env vals + NULL */ + regs->cregs[1] = regs->cregs[2] = regs->cregs[3] = regs->csp; + regs->regs[1] = argv; + regs->regs[2] = envp; + regs->regs[3] = auxv; +#endif /* CONFIG_CHERI_PURECAP_UABI */ + + return retval; +} + static inline void start_thread_common(struct pt_regs *regs, unsigned long pc) { memset(regs, 0, sizeof(*regs)); @@ -248,8 +277,8 @@ static inline void start_thread_common(struct pt_regs *regs, unsigned long pc) regs->pmr_save = GIC_PRIO_IRQON; }
-static inline void start_thread(struct pt_regs *regs, unsigned long pc, - unsigned long sp) +static inline int start_thread(struct pt_regs *regs, unsigned long pc, + unsigned long sp, int argc, int envc) { start_thread_common(regs, pc); regs->pstate = PSR_MODE_EL0t; @@ -258,6 +287,8 @@ static inline void start_thread(struct pt_regs *regs, unsigned long pc,
if (system_supports_morello()) morello_thread_start(regs, pc); + + return init_gp_regs(regs, sp, argc, envc); }
#ifdef CONFIG_COMPAT
Amend an existing morello selftest to check that new capability aux entries are valid and unsealed, as well as check that registers c0-c3 match argc/argv/envp/auxv as viewed on the stack.
Signed-off-by: Sherwin da Cruz sherwin.dacruz@arm.com --- .../selftests/arm64/morello/bootstrap.c | 53 ++++++++++++++----- .../arm64/morello/freestanding_start.S | 12 ++++- 2 files changed, 51 insertions(+), 14 deletions(-)
diff --git a/tools/testing/selftests/arm64/morello/bootstrap.c b/tools/testing/selftests/arm64/morello/bootstrap.c index 92816a82f9e3..6a91ba39dd83 100644 --- a/tools/testing/selftests/arm64/morello/bootstrap.c +++ b/tools/testing/selftests/arm64/morello/bootstrap.c @@ -43,6 +43,15 @@ struct morello_auxv { uintcap_t a_val; };
+struct initial_data { + int argc; + char **argv; + char **envp; + struct morello_auxv *auxv; +}; + +static struct initial_data reg_data; + int clear_child_tid;
void verify_string(char *str) @@ -70,6 +79,9 @@ int verify_auxval(struct morello_auxv *auxv) switch (auxv->a_type) { case AT_NULL: return 0; + case AT_CHERI_EXEC_RW_CAP: + case AT_CHERI_INTERP_RW_CAP: + case AT_CHERI_INTERP_RX_CAP: case AT_BASE: /* Fall through if not null, abi allows it */ if ((void *)auxv->a_val == NULL) @@ -79,6 +91,12 @@ int verify_auxval(struct morello_auxv *auxv) case AT_PLATFORM: case AT_RANDOM: case AT_PHDR: + case AT_CHERI_EXEC_RX_CAP: + case AT_CHERI_STACK_CAP: + case AT_CHERI_SEAL_CAP: + case AT_CHERI_CID_CAP: + case AT_ARGV: + case AT_ENVP: /* valid and unsealed */ ASSERT_TRUE(cheri_tag_get(auxv->a_val)) TH_LOG("auxv (%ld) value is invalid", auxv->a_type); @@ -133,46 +151,50 @@ TEST(test_stack) { /* copy the pointer so we can modify it */ char **stack = stack_from_kernel; - int argc; + int argc_stack; /* start with an argc check */ VERIFY_CAP(stack, sizeof(void *), STACK_PERMS, "argc");
/* dereference and go past argc */ - argc = *(int *)stack; + argc_stack = *(int *)stack; + ASSERT_EQ(argc_stack, reg_data.argc); stack += 1;
/* argv + null */ - VERIFY_CAP(stack, sizeof(void *) * (argc + 1), STACK_PERMS, "argv"); + VERIFY_CAP(stack, sizeof(void *) * (argc_stack + 1), STACK_PERMS, "argv_stack"); + ASSERT_CAP_EQ(reg_data.argv, stack);
/* we are clear to dereference all argv */ - for (int i = 0; i < argc; i++) { + for (int i = 0; i < argc_stack; i++) { char *arg = *(stack + i); verify_string(arg); }
/* go past argv */ - stack += argc; - ASSERT_NULL(*stack) TH_LOG("argv was not null terminated"); + stack += argc_stack; + ASSERT_NULL(*stack) TH_LOG("argv was not null terminated on stack"); /* go past the null terminator */ stack += 1;
/* progressively check bounds for envp and dereference */ + ASSERT_CAP_EQ(reg_data.envp, stack); while (1) { - char *envp = *stack; + char *envp_stack = *stack;
VERIFY_CAP(stack, sizeof(void *), STACK_PERMS, "envp"); stack += 1; - if (envp == NULL) + if (envp_stack == NULL) break; - verify_string(envp); + verify_string(envp_stack); }
/* finally, go through auxv */ + ASSERT_CAP_EQ(reg_data.auxv, stack); while (1) { - struct morello_auxv *auxv = ((struct morello_auxv *) stack); + struct morello_auxv *auxv_stack = ((struct morello_auxv *) stack);
- VERIFY_CAP(auxv, sizeof(struct morello_auxv), STACK_PERMS, "auxv"); - if (verify_auxval(auxv) == 0) + VERIFY_CAP(auxv_stack, sizeof(struct morello_auxv), STACK_PERMS, "auxv"); + if (verify_auxval(auxv_stack) == 0) break; stack += 2; } @@ -192,8 +214,13 @@ TEST(test_set_tid_address_initial) * exit test. We assume exit() works and we progressively build a known working * test environment. */ -int main(void) +int main(int argc, char **argv, char **envp, struct morello_auxv *auxv) { + reg_data.argc = argc; + reg_data.argv = argv; + reg_data.envp = envp; + reg_data.auxv = auxv; + test_write(); /* from now on write and exit work, so go wild */ test_c64(); diff --git a/tools/testing/selftests/arm64/morello/freestanding_start.S b/tools/testing/selftests/arm64/morello/freestanding_start.S index 9eccac44d623..66dfe2bab676 100644 --- a/tools/testing/selftests/arm64/morello/freestanding_start.S +++ b/tools/testing/selftests/arm64/morello/freestanding_start.S @@ -66,6 +66,12 @@ VARIABLE(__cur_test, 16)
.text FUNCTION_START(_start) + /* save x0, c1-c3 which has argc/argv/envp/auxv */ + mov x20, x0 + mov c21, c1 + mov c22, c2 + mov c23, c3 + bl __morello_init_globals
/* save the initial kernel stack to a global variable */ @@ -84,7 +90,11 @@ FUNCTION_START(_start) mov csp, c0 #endif
- /* call int main(void) */ + /* call int main(int argc, char **argv, char **envp, char **auxv) */ + mov x0, x20 + mov c1, c21 + mov c2, c22 + mov c3, c23 bl main
/* exit. Argument is in x0 already */
On 12/09/2022 16:31, Sherwin da Cruz wrote:
v2..v3: patch 4: Added new compat_start_thread() for compat64 binaries. This change was made in a separate patch and inserted before the previous patch 4 that sets c0-c3.
The branch at [1] has been updated with the new commits
Looks all good now, thanks for your persistence! All patches are now in next.
Kevin
This patchset adds partial support for the new aux entries in the PCuABI, which mainly passes capabilites to the program. The registers c0-c3 are also set to argc/argv/envp/auxv which currently still resides on the stack for backwards compatability. The patches are available at [1]. The constants for the new aux entries can be found at [2]. For reference the transitional PCuABI can be found at [3].
The capabilities placed in the aux vector do not have restricted permissions or bounds but have the address set to the specified value, except for AT_CHERI_STACK_CAP and AT_CHERI_CID_CAP. The upper and lower bounds of the RX and RW regions as defined in the PCuABI spec for both the interpreter and program are also determined, though the upper bounds are left unused since the bounds are currently not set on the new aux entries.
A morello kselftest ensures that the register values of c0-c3 in startup code matches the corresponding values on the stack.
[1] https://git.morello-project.org/sherwin-dc/linux/-/commits/morello/next/ [2] https://git.morello-project.org/morello/kernel/linux/-/wikis/Morello-pure-ca... [3] https://git.morello-project.org/morello/kernel/linux/-/wikis/Transitional-Mo...
Sherwin da Cruz (6): arm64: elf.h: Remove register init macro fs/binfmt_elf: Add additional Aux vector entries elf: Return result from START_THREAD macro arm64/elf: Use separate compat_start_thread() for compat64 arm64: Set c0-c3 to argc/argv/envp/auxv in start_thread() kselftests/arm64/morello: Test for new aux entries and init registers
arch/arm64/include/asm/elf.h | 11 +- arch/arm64/include/asm/processor.h | 55 +++++++++- fs/binfmt_elf.c | 103 +++++++++++++++++- fs/compat_binfmt_elf.c | 24 ++-- fs/exec.c | 4 +- include/linux/auxvec.h | 2 +- include/linux/elf.h | 7 +- include/uapi/linux/auxvec.h | 13 +++ .../selftests/arm64/morello/bootstrap.c | 53 ++++++--- .../arm64/morello/freestanding_start.S | 12 +- 10 files changed, 239 insertions(+), 45 deletions(-)
linux-morello@op-lists.linaro.org