argv and envp strings can be large enough for their bounds to not be exactly representable. During allocation on the stack, align the strings that need an adjusted base and pad if needed for alignment or exact bounds. Handle this eventual padding in fs/binfmt_elf by detecting unexpected 0s.
Add a a helper function to put already created capabilities on the stack during elf setup.
Signed-off-by: Teo Couprie Diaz teo.coupriediaz@arm.com --- fs/binfmt_elf.c | 110 +++++++++++++++++++++++++++++++++++++++++++- fs/exec.c | 66 ++++++++++++++++++++++++++ include/linux/elf.h | 4 ++ 3 files changed, 178 insertions(+), 2 deletions(-)
diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c index 1d82465cb9e9..e5f9c8622c32 100644 --- a/fs/binfmt_elf.c +++ b/fs/binfmt_elf.c @@ -386,11 +386,74 @@ create_elf_tables(struct linux_binprm *bprm, const struct elfhdr *exec, p = mm->arg_end = mm->arg_start; while (argc-- > 0) { size_t len; - if (elf_stack_put_user_ptr(p, sp++)) +#if defined(CONFIG_CHERI_PURECAP_UABI) && (ELF_COMPAT == 0) + uintcap_t for_stack; + char __user *access = uaddr_to_user_ptr_safe(p); + char checked_char; + int adjusted_string = 0; + + if (copy_from_user(&checked_char, access, sizeof(char))) + return -EFAULT; + + /* + * If right after the end of the previous argument we have a zero, + * that means the string start has been adjusted to allow exact bounds + * for the controlling capability. + * Find the real start of the string and go from there. + * It should already be properly aligned for exact bounds. + */ + if (checked_char == '\0') { + size_t pad_len = 0; + adjusted_string = 1; + + while (pad_len < MAX_ARG_STRLEN && checked_char == '\0') { + access++; + pad_len++; + if (copy_from_user(&checked_char, access, sizeof(char))) + return -EFAULT; + } + p += pad_len; + } + + len = strnlen_user(uaddr_to_user_ptr_safe(p), MAX_ARG_STRLEN); + if (!len || len > MAX_ARG_STRLEN) + return -EINVAL; + + if (!adjusted_string && + copy_from_user(&checked_char, access+len, sizeof(char))) + return -EFAULT; + /* + * Check if the next arg string starts right after the current one. + * If not, the len was not exactly representable and there is padding. + */ + if (!adjusted_string && checked_char == '\0') + adjusted_string = 1; + + /* + * If the string has been adjusted for exact bounds representation, + * its start should already be properly aligned. Get the representable + * length by using the same length that was used during allocation: + * the length of the original string. + * This takes into account the padding due to length change after + * the string, but not that for alignment. Thus we might not end up + * at the start of the next arg. If not, it will take the slow path + * above. + */ + if (adjusted_string) + len = __builtin_cheri_round_representable_length(len); + + for_stack = cheri_build_user_cap(p, len, + (CHERI_PERM_GLOBAL | CHERI_PERM_STORE | CHERI_PERM_LOAD)); + if (elf_stack_put_user_cap(for_stack, sp++)) return -EFAULT; +#else len = strnlen_user(uaddr_to_user_ptr_safe(p), MAX_ARG_STRLEN); if (!len || len > MAX_ARG_STRLEN) return -EINVAL; + + if (elf_stack_put_user_ptr(p, sp++)) + return -EFAULT; +#endif p += len; } if (elf_stack_put_user(0, sp++)) @@ -404,11 +467,54 @@ create_elf_tables(struct linux_binprm *bprm, const struct elfhdr *exec, mm->env_end = mm->env_start = p; while (envc-- > 0) { size_t len; - if (elf_stack_put_user_ptr(p, sp++)) +#if defined(CONFIG_CHERI_PURECAP_UABI) && (ELF_COMPAT == 0) + uintcap_t for_stack; + char __user *access = uaddr_to_user_ptr_safe(p); + char checked_char; + int adjusted_string = 0; + + if (copy_from_user(&checked_char, access, sizeof(char))) + return -EFAULT; + + if (checked_char == '\0') { + size_t pad_len = 0; + adjusted_string = 1; + + while (pad_len < MAX_ARG_STRLEN && checked_char == '\0') { + access++; + pad_len++; + if (copy_from_user(&checked_char, access, sizeof(char))) + return -EFAULT; + } + p += pad_len; + } + + len = strnlen_user(uaddr_to_user_ptr_safe(p), MAX_ARG_STRLEN); + if (!len || len > MAX_ARG_STRLEN) + return -EINVAL; + + if (!adjusted_string && + copy_from_user(&checked_char, access+len, sizeof(char))) + return -EFAULT; + + if (!adjusted_string && checked_char == '\0') + adjusted_string = 1; + + if (adjusted_string) + len = __builtin_cheri_round_representable_length(len); + + for_stack = cheri_build_user_cap(p, len, + (CHERI_PERM_GLOBAL | CHERI_PERM_STORE | CHERI_PERM_LOAD)); + if (elf_stack_put_user_cap(for_stack, sp++)) return -EFAULT; +#else len = strnlen_user(uaddr_to_user_ptr_safe(p), MAX_ARG_STRLEN); if (!len || len > MAX_ARG_STRLEN) return -EINVAL; + + if (elf_stack_put_user_ptr(p, sp++)) + return -EFAULT; +#endif p += len; } if (elf_stack_put_user(0, sp++)) diff --git a/fs/exec.c b/fs/exec.c index f5782fd96fcb..cf69e7798083 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -530,15 +530,48 @@ static int copy_strings(int argc, struct user_arg_ptr argv, const char __user *str; int len; unsigned long pos; +#if defined(CONFIG_CHERI_PURECAP_UABI) + size_t arg_len, str_len, pad_len = 0; + size_t repr_align; +#endif
ret = -EFAULT; str = get_user_arg_ptr(argv, argc); if (USER_PTR_IS_ERR(str)) goto out;
+#if defined(CONFIG_CHERI_PURECAP_UABI) + str_len = strnlen_user(str, MAX_ARG_STRLEN); + if (!str_len) + goto out; + + arg_len = __builtin_cheri_round_representable_length(str_len); + if (arg_len < str_len) { + pr_warn_once("exec %s: Argument overflowed CHERI representable length, not changing it.", + bprm->filename); + /* This should be bigger than MAX_ARG_STRLEN anyway, fail later. */ + arg_len = str_len; + } + if (arg_len > str_len) { + repr_align = ~__builtin_cheri_representable_alignment_mask(str_len) + 1; + len = bprm->p - ALIGN_DOWN(bprm->p - arg_len, repr_align); + /* + * We want the capability base to be aligned. As we work from the + * last to the first argument, we can place the start of the string + * to be aligned for representability. + * All padding then goes after it, both for the representable + * length and for the alignment, to fill the space we left before + * the previous argument we put on the stack. + */ + pad_len = len - str_len; + } else { + len = str_len; + } +#else len = strnlen_user(str, MAX_ARG_STRLEN); if (!len) goto out; +#endif
ret = -E2BIG; if (!valid_arg_len(bprm, len)) @@ -546,7 +579,11 @@ static int copy_strings(int argc, struct user_arg_ptr argv,
/* We're going to work our way backwards. */ pos = bprm->p; +#if defined(CONFIG_CHERI_PURECAP_UABI) + str += str_len; +#else str += len; +#endif bprm->p -= len; #ifdef CONFIG_MMU if (bprm->p < bprm->argmin) @@ -555,6 +592,9 @@ static int copy_strings(int argc, struct user_arg_ptr argv,
while (len > 0) { int offset, bytes_to_copy; +#if defined(CONFIG_CHERI_PURECAP_UABI) + int doing_padding = 0; +#endif
if (fatal_signal_pending(current)) { ret = -ERESTARTNOHAND; @@ -566,13 +606,30 @@ static int copy_strings(int argc, struct user_arg_ptr argv, if (offset == 0) offset = PAGE_SIZE;
+#if defined(CONFIG_CHERI_PURECAP_UABI) bytes_to_copy = offset; + /* Don't complicate things: only put padding, then only the arg. */ + if (pad_len > 0 && bytes_to_copy > pad_len) + bytes_to_copy = pad_len; + if (pad_len == 0 && str_len > 0 && bytes_to_copy > str_len) + bytes_to_copy = str_len; +#endif if (bytes_to_copy > len) bytes_to_copy = len;
offset -= bytes_to_copy; pos -= bytes_to_copy; +#if defined(CONFIG_CHERI_PURECAP_UABI) + if (pad_len > 0) { + doing_padding = 1; + pad_len -= bytes_to_copy; + } else if (str_len > 0) { + str_len -= bytes_to_copy; + str -= bytes_to_copy; + } +#else str -= bytes_to_copy; +#endif len -= bytes_to_copy;
if (!kmapped_page || kpos != (pos & PAGE_MASK)) { @@ -594,10 +651,19 @@ static int copy_strings(int argc, struct user_arg_ptr argv, kpos = pos & PAGE_MASK; flush_arg_page(bprm, kpos, kmapped_page); } +#if defined(CONFIG_CHERI_PURECAP_UABI) + if (!doing_padding) { + if (copy_from_user(kaddr+offset, str, bytes_to_copy)) { + ret = -EFAULT; + goto out; + } + } +#else if (copy_from_user(kaddr+offset, str, bytes_to_copy)) { ret = -EFAULT; goto out; } +#endif } } ret = 0; diff --git a/include/linux/elf.h b/include/linux/elf.h index 039ad1867045..7bb2b39944bd 100644 --- a/include/linux/elf.h +++ b/include/linux/elf.h @@ -68,6 +68,10 @@ extern Elf64_Dyn _DYNAMIC []; #define elf_uaddr_to_user_ptr(addr) uaddr_to_user_ptr_safe(addr) #define elf_copy_to_user_stack(to, from, len) copy_to_user_with_ptr(to, from, len) #ifdef CONFIG_CHERI_PURECAP_UABI +#define elf_stack_put_user_cap(cap, ptr) \ + morello_put_user_cap(cap, \ + (void * __capability * __capability)ptr) + #define elf_stack_put_user_ptr(val, ptr) \ put_user_ptr(elf_uaddr_to_user_ptr(val), \ (void * __capability * __capability)ptr)