:p
atchew
Login
The kernel abi for this was merged in v5.8, just as the qemu 5.1 merge window was closing, so this slipped to the next dev cycle. Changes from v9: * Split what is now patch 7 into 3 more (pmm). * All prerequisites are now upstream. r~ Richard Henderson (8): linux-user/aarch64: Reset btype for signals linux-user: Set PAGE_TARGET_1 for TARGET_PROT_BTI include/elf: Add defines related to GNU property notes for AArch64 linux-user/elfload: Fix coding style in load_elf_image linux-user/elfload: Adjust iteration over phdr linux-user/elfload: Move PT_INTERP detection to first loop linux-user/elfload: Parse NT_GNU_PROPERTY_TYPE_0 notes tests/tcg/aarch64: Add bti smoke test include/elf.h | 22 +++++ include/exec/cpu-all.h | 2 + linux-user/qemu.h | 4 + linux-user/syscall_defs.h | 4 + target/arm/cpu.h | 5 + linux-user/aarch64/signal.c | 10 +- linux-user/elfload.c | 147 ++++++++++++++++++++++-------- linux-user/mmap.c | 16 ++++ target/arm/translate-a64.c | 6 +- tests/tcg/aarch64/bti-1.c | 62 +++++++++++++ tests/tcg/aarch64/bti-crt.inc.c | 51 +++++++++++ tests/tcg/aarch64/Makefile.target | 7 ++ tests/tcg/configure.sh | 4 + 13 files changed, 298 insertions(+), 42 deletions(-) create mode 100644 tests/tcg/aarch64/bti-1.c create mode 100644 tests/tcg/aarch64/bti-crt.inc.c -- 2.25.1
The kernel sets btype for the signal handler as if for a call. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> --- linux-user/aarch64/signal.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/linux-user/aarch64/signal.c b/linux-user/aarch64/signal.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/aarch64/signal.c +++ b/linux-user/aarch64/signal.c @@ -XXX,XX +XXX,XX @@ static void target_setup_frame(int usig, struct target_sigaction *ka, + offsetof(struct target_rt_frame_record, tramp); } env->xregs[0] = usig; - env->xregs[31] = frame_addr; env->xregs[29] = frame_addr + fr_ofs; - env->pc = ka->_sa_handler; env->xregs[30] = return_addr; + env->xregs[31] = frame_addr; + env->pc = ka->_sa_handler; + + /* Invoke the signal handler as if by indirect call. */ + if (cpu_isar_feature(aa64_bti, env_archcpu(env))) { + env->btype = 2; + } + if (info) { tswap_siginfo(&frame->info, info); env->xregs[1] = frame_addr + offsetof(struct target_rt_sigframe, info); -- 2.25.1
Transform the prot bit to a qemu internal page bit, and save it in the page tables. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> --- v10: Add PAGE_BTI define (pmm). --- include/exec/cpu-all.h | 2 ++ linux-user/syscall_defs.h | 4 ++++ target/arm/cpu.h | 5 +++++ linux-user/mmap.c | 16 ++++++++++++++++ target/arm/translate-a64.c | 6 +++--- 5 files changed, 30 insertions(+), 3 deletions(-) diff --git a/include/exec/cpu-all.h b/include/exec/cpu-all.h index XXXXXXX..XXXXXXX 100644 --- a/include/exec/cpu-all.h +++ b/include/exec/cpu-all.h @@ -XXX,XX +XXX,XX @@ extern intptr_t qemu_host_page_mask; /* FIXME: Code that sets/uses this is broken and needs to go away. */ #define PAGE_RESERVED 0x0020 #endif +/* Target-specific bits that will be used via page_get_flags(). */ +#define PAGE_TARGET_1 0x0080 #if defined(CONFIG_USER_ONLY) void page_dump(FILE *f); diff --git a/linux-user/syscall_defs.h b/linux-user/syscall_defs.h index XXXXXXX..XXXXXXX 100644 --- a/linux-user/syscall_defs.h +++ b/linux-user/syscall_defs.h @@ -XXX,XX +XXX,XX @@ struct target_winsize { #define TARGET_PROT_SEM 0x08 #endif +#ifdef TARGET_AARCH64 +#define TARGET_PROT_BTI 0x10 +#endif + /* Common */ #define TARGET_MAP_SHARED 0x01 /* Share changes */ #define TARGET_MAP_PRIVATE 0x02 /* Changes are private */ diff --git a/target/arm/cpu.h b/target/arm/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -XXX,XX +XXX,XX @@ static inline MemTxAttrs *typecheck_memtxattrs(MemTxAttrs *x) #define arm_tlb_bti_gp(x) (typecheck_memtxattrs(x)->target_tlb_bit0) #define arm_tlb_mte_tagged(x) (typecheck_memtxattrs(x)->target_tlb_bit1) +/* + * AArch64 usage of the PAGE_TARGET_* bits for linux-user. + */ +#define PAGE_BTI PAGE_TARGET_1 + /* * Naming convention for isar_feature functions: * Functions which test 32-bit ID registers should have _aa32_ in diff --git a/linux-user/mmap.c b/linux-user/mmap.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/mmap.c +++ b/linux-user/mmap.c @@ -XXX,XX +XXX,XX @@ static int validate_prot_to_pageflags(int *host_prot, int prot) *host_prot = (prot & (PROT_READ | PROT_WRITE)) | (prot & PROT_EXEC ? PROT_READ : 0); +#ifdef TARGET_AARCH64 + /* + * The PROT_BTI bit is only accepted if the cpu supports the feature. + * Since this is the unusual case, don't bother checking unless + * the bit has been requested. If set and valid, record the bit + * within QEMU's page_flags. + */ + if (prot & TARGET_PROT_BTI) { + ARMCPU *cpu = ARM_CPU(thread_cpu); + if (cpu_isar_feature(aa64_bti, cpu)) { + valid |= TARGET_PROT_BTI; + page_flags |= PAGE_BTI; + } + } +#endif + return prot & ~valid ? 0 : page_flags; } diff --git a/target/arm/translate-a64.c b/target/arm/translate-a64.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/translate-a64.c +++ b/target/arm/translate-a64.c @@ -XXX,XX +XXX,XX @@ static void disas_data_proc_simd_fp(DisasContext *s, uint32_t insn) */ static bool is_guarded_page(CPUARMState *env, DisasContext *s) { -#ifdef CONFIG_USER_ONLY - return false; /* FIXME */ -#else uint64_t addr = s->base.pc_first; +#ifdef CONFIG_USER_ONLY + return page_get_flags(addr) & PAGE_BTI; +#else int mmu_idx = arm_to_core_mmu_idx(s->mmu_idx); unsigned int index = tlb_index(env, mmu_idx, addr); CPUTLBEntry *entry = tlb_entry(env, mmu_idx, addr); -- 2.25.1
These are all of the defines required to parse GNU_PROPERTY_AARCH64_FEATURE_1_AND, copied from binutils. Other missing defines related to other GNU program headers and notes are elided for now. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> --- include/elf.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/include/elf.h b/include/elf.h index XXXXXXX..XXXXXXX 100644 --- a/include/elf.h +++ b/include/elf.h @@ -XXX,XX +XXX,XX @@ typedef int64_t Elf64_Sxword; #define PT_NOTE 4 #define PT_SHLIB 5 #define PT_PHDR 6 +#define PT_LOOS 0x60000000 +#define PT_HIOS 0x6fffffff #define PT_LOPROC 0x70000000 #define PT_HIPROC 0x7fffffff +#define PT_GNU_PROPERTY (PT_LOOS + 0x474e553) + #define PT_MIPS_REGINFO 0x70000000 #define PT_MIPS_RTPROC 0x70000001 #define PT_MIPS_OPTIONS 0x70000002 @@ -XXX,XX +XXX,XX @@ typedef struct elf64_shdr { #define NT_ARM_SYSTEM_CALL 0x404 /* ARM system call number */ #define NT_ARM_SVE 0x405 /* ARM Scalable Vector Extension regs */ +/* Defined note types for GNU systems. */ + +#define NT_GNU_PROPERTY_TYPE_0 5 /* Program property */ + +/* Values used in GNU .note.gnu.property notes (NT_GNU_PROPERTY_TYPE_0). */ + +#define GNU_PROPERTY_STACK_SIZE 1 +#define GNU_PROPERTY_NO_COPY_ON_PROTECTED 2 + +#define GNU_PROPERTY_LOPROC 0xc0000000 +#define GNU_PROPERTY_HIPROC 0xdfffffff +#define GNU_PROPERTY_LOUSER 0xe0000000 +#define GNU_PROPERTY_HIUSER 0xffffffff + +#define GNU_PROPERTY_AARCH64_FEATURE_1_AND 0xc0000000 +#define GNU_PROPERTY_AARCH64_FEATURE_1_BTI (1u << 0) +#define GNU_PROPERTY_AARCH64_FEATURE_1_PAC (1u << 1) + /* * Physical entry point into the kernel. * -- 2.25.1
Fixing this now will clarify following patches. Signed-off-by: Richard Henderson <richard.henderson@linaro.org> --- linux-user/elfload.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/linux-user/elfload.c b/linux-user/elfload.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, abi_ulong vaddr, vaddr_po, vaddr_ps, vaddr_ef, vaddr_em, vaddr_len; int elf_prot = 0; - if (eppnt->p_flags & PF_R) elf_prot = PROT_READ; - if (eppnt->p_flags & PF_W) elf_prot |= PROT_WRITE; - if (eppnt->p_flags & PF_X) elf_prot |= PROT_EXEC; + if (eppnt->p_flags & PF_R) { + elf_prot |= PROT_READ; + } + if (eppnt->p_flags & PF_W) { + elf_prot |= PROT_WRITE; + } + if (eppnt->p_flags & PF_X) { + elf_prot |= PROT_EXEC; + } vaddr = load_bias + eppnt->p_vaddr; vaddr_po = TARGET_ELF_PAGEOFFSET(vaddr); -- 2.25.1
The second loop uses a loop induction variable, and the first does not. Transform the first to match the second, to simplify a following patch moving code between them. Signed-off-by: Richard Henderson <richard.henderson@linaro.org> --- linux-user/elfload.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/linux-user/elfload.c b/linux-user/elfload.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, loaddr = -1, hiaddr = 0; info->alignment = 0; for (i = 0; i < ehdr->e_phnum; ++i) { - if (phdr[i].p_type == PT_LOAD) { - abi_ulong a = phdr[i].p_vaddr - phdr[i].p_offset; + struct elf_phdr *eppnt = phdr + i; + if (eppnt->p_type == PT_LOAD) { + abi_ulong a = eppnt->p_vaddr - eppnt->p_offset; if (a < loaddr) { loaddr = a; } - a = phdr[i].p_vaddr + phdr[i].p_memsz; + a = eppnt->p_vaddr + eppnt->p_memsz; if (a > hiaddr) { hiaddr = a; } ++info->nsegs; - info->alignment |= phdr[i].p_align; + info->alignment |= eppnt->p_align; } } -- 2.25.1
For BTI, we need to know if the executable is static or dynamic, which means looking for PT_INTERP earlier. Signed-off-by: Richard Henderson <richard.henderson@linaro.org> --- linux-user/elfload.c | 60 +++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/linux-user/elfload.c b/linux-user/elfload.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, mmap_lock(); - /* Find the maximum size of the image and allocate an appropriate - amount of memory to handle that. */ + /* + * Find the maximum size of the image and allocate an appropriate + * amount of memory to handle that. Locate the interpreter, if any. + */ loaddr = -1, hiaddr = 0; info->alignment = 0; for (i = 0; i < ehdr->e_phnum; ++i) { @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, } ++info->nsegs; info->alignment |= eppnt->p_align; + } else if (eppnt->p_type == PT_INTERP && pinterp_name) { + char *interp_name; + + if (*pinterp_name) { + errmsg = "Multiple PT_INTERP entries"; + goto exit_errmsg; + } + interp_name = malloc(eppnt->p_filesz); + if (!interp_name) { + goto exit_perror; + } + + if (eppnt->p_offset + eppnt->p_filesz <= BPRM_BUF_SIZE) { + memcpy(interp_name, bprm_buf + eppnt->p_offset, + eppnt->p_filesz); + } else { + retval = pread(image_fd, interp_name, eppnt->p_filesz, + eppnt->p_offset); + if (retval != eppnt->p_filesz) { + goto exit_perror; + } + } + if (interp_name[eppnt->p_filesz - 1] != 0) { + errmsg = "Invalid PT_INTERP entry"; + goto exit_errmsg; + } + *pinterp_name = interp_name; } } @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, if (vaddr_em > info->brk) { info->brk = vaddr_em; } - } else if (eppnt->p_type == PT_INTERP && pinterp_name) { - char *interp_name; - - if (*pinterp_name) { - errmsg = "Multiple PT_INTERP entries"; - goto exit_errmsg; - } - interp_name = malloc(eppnt->p_filesz); - if (!interp_name) { - goto exit_perror; - } - - if (eppnt->p_offset + eppnt->p_filesz <= BPRM_BUF_SIZE) { - memcpy(interp_name, bprm_buf + eppnt->p_offset, - eppnt->p_filesz); - } else { - retval = pread(image_fd, interp_name, eppnt->p_filesz, - eppnt->p_offset); - if (retval != eppnt->p_filesz) { - goto exit_perror; - } - } - if (interp_name[eppnt->p_filesz - 1] != 0) { - errmsg = "Invalid PT_INTERP entry"; - goto exit_errmsg; - } - *pinterp_name = interp_name; #ifdef TARGET_MIPS } else if (eppnt->p_type == PT_MIPS_ABIFLAGS) { Mips_elf_abiflags_v0 abiflags; -- 2.25.1
For aarch64, this includes the GNU_PROPERTY_AARCH64_FEATURE_1_BTI bit, which indicates that the image should be mapped with guarded pages. Signed-off-by: Richard Henderson <richard.henderson@linaro.org> --- v9: Only map the startup executable with BTI; anything else must be handled by the interpreter. v10: Split out preparatory patches (pmm). --- linux-user/qemu.h | 4 +++ linux-user/elfload.c | 68 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/linux-user/qemu.h b/linux-user/qemu.h index XXXXXXX..XXXXXXX 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -XXX,XX +XXX,XX @@ struct image_info { abi_ulong interpreter_loadmap_addr; abi_ulong interpreter_pt_dynamic_addr; struct image_info *other_info; + + /* For target-specific processing of NT_GNU_PROPERTY_TYPE_0. */ + uint32_t note_flags; + #ifdef TARGET_MIPS int fp_abi; int interp_fp_abi; diff --git a/linux-user/elfload.c b/linux-user/elfload.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, struct elfhdr *ehdr = (struct elfhdr *)bprm_buf; struct elf_phdr *phdr; abi_ulong load_addr, load_bias, loaddr, hiaddr, error; - int i, retval; + int i, retval, prot_exec; const char *errmsg; /* First of all, some simple consistency checks */ @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, goto exit_errmsg; } *pinterp_name = interp_name; + } else if (eppnt->p_type == PT_GNU_PROPERTY) { + /* Process NT_GNU_PROPERTY_TYPE_0. */ + const uint32_t gnu0_magic = const_le32('G' | 'N' << 8 | 'U' << 16); + uint32_t note[7]; + + /* + * The note contents are 7 words, but depending on LP64 vs ILP32 + * there may be an 8th padding word at the end. Check for and + * read the minimum size. Further checks below will validate + * that the sizes of everything involved are as we expect. + */ + if (eppnt->p_filesz < sizeof(note)) { + continue; + } + if (eppnt->p_offset + eppnt->p_filesz <= BPRM_BUF_SIZE) { + memcpy(note, bprm_buf + eppnt->p_offset, sizeof(note)); + } else { + retval = pread(image_fd, note, sizeof(note), eppnt->p_offset); + if (retval != sizeof(note)) { + goto exit_perror; + } + } +#ifdef BSWAP_NEEDED + for (i = 0; i < ARRAY_SIZE(note); ++i) { + bswap32s(note + i); + } +#endif + /* + * Check that this is a NT_GNU_PROPERTY_TYPE_0 note. + * Again, descsz includes padding. Full size validation + * awaits checking the final payload. + */ + if (note[0] != 4 || /* namesz */ + note[1] < 12 || /* descsz */ + note[2] != NT_GNU_PROPERTY_TYPE_0 || /* type */ + note[3] != gnu0_magic) { /* name */ + continue; + } +#ifdef TARGET_AARCH64 + if (note[4] == GNU_PROPERTY_AARCH64_FEATURE_1_AND && + note[5] == 4) { + info->note_flags = note[6]; + } +#endif /* TARGET_AARCH64 */ } } @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, info->brk = 0; info->elf_flags = ehdr->e_flags; + prot_exec = PROT_EXEC; +#ifdef TARGET_AARCH64 + /* + * If the BTI feature is present, this indicates that the executable + * pages of the startup binary should be mapped with PROT_BTI, so that + * branch targets are enforced. + * + * The startup binary is either the interpreter or the static executable. + * The interpreter is responsible for all pages of a dynamic executable. + * + * Elf notes are backward compatible to older cpus. + * Do not enable BTI unless it is supported. + */ + if ((info->note_flags & GNU_PROPERTY_AARCH64_FEATURE_1_BTI) + && (pinterp_name == NULL || *pinterp_name == 0) + && cpu_isar_feature(aa64_bti, ARM_CPU(thread_cpu))) { + prot_exec |= TARGET_PROT_BTI; + } +#endif + for (i = 0; i < ehdr->e_phnum; i++) { struct elf_phdr *eppnt = phdr + i; if (eppnt->p_type == PT_LOAD) { @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, elf_prot |= PROT_WRITE; } if (eppnt->p_flags & PF_X) { - elf_prot |= PROT_EXEC; + elf_prot |= prot_exec; } vaddr = load_bias + eppnt->p_vaddr; -- 2.25.1
This test requires gcc 10 for -mbranch-protection=standard. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> --- v9: Expect and require gcc 10. --- tests/tcg/aarch64/bti-1.c | 62 +++++++++++++++++++++++++++++++ tests/tcg/aarch64/bti-crt.inc.c | 51 +++++++++++++++++++++++++ tests/tcg/aarch64/Makefile.target | 7 ++++ tests/tcg/configure.sh | 4 ++ 4 files changed, 124 insertions(+) create mode 100644 tests/tcg/aarch64/bti-1.c create mode 100644 tests/tcg/aarch64/bti-crt.inc.c diff --git a/tests/tcg/aarch64/bti-1.c b/tests/tcg/aarch64/bti-1.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/tcg/aarch64/bti-1.c @@ -XXX,XX +XXX,XX @@ +/* + * Branch target identification, basic notskip cases. + */ + +#include "bti-crt.inc.c" + +static void skip2_sigill(int sig, siginfo_t *info, ucontext_t *uc) +{ + uc->uc_mcontext.pc += 8; + uc->uc_mcontext.pstate = 1; +} + +#define NOP "nop" +#define BTI_N "hint #32" +#define BTI_C "hint #34" +#define BTI_J "hint #36" +#define BTI_JC "hint #38" + +#define BTYPE_1(DEST) \ + asm("mov %0,#1; adr x16, 1f; br x16; 1: " DEST "; mov %0,#0" \ + : "=r"(skipped) : : "x16") + +#define BTYPE_2(DEST) \ + asm("mov %0,#1; adr x16, 1f; blr x16; 1: " DEST "; mov %0,#0" \ + : "=r"(skipped) : : "x16", "x30") + +#define BTYPE_3(DEST) \ + asm("mov %0,#1; adr x15, 1f; br x15; 1: " DEST "; mov %0,#0" \ + : "=r"(skipped) : : "x15") + +#define TEST(WHICH, DEST, EXPECT) \ + do { WHICH(DEST); fail += skipped ^ EXPECT; } while (0) + + +int main() +{ + int fail = 0; + int skipped; + + /* Signal-like with SA_SIGINFO. */ + signal_info(SIGILL, skip2_sigill); + + TEST(BTYPE_1, NOP, 1); + TEST(BTYPE_1, BTI_N, 1); + TEST(BTYPE_1, BTI_C, 0); + TEST(BTYPE_1, BTI_J, 0); + TEST(BTYPE_1, BTI_JC, 0); + + TEST(BTYPE_2, NOP, 1); + TEST(BTYPE_2, BTI_N, 1); + TEST(BTYPE_2, BTI_C, 0); + TEST(BTYPE_2, BTI_J, 1); + TEST(BTYPE_2, BTI_JC, 0); + + TEST(BTYPE_3, NOP, 1); + TEST(BTYPE_3, BTI_N, 1); + TEST(BTYPE_3, BTI_C, 1); + TEST(BTYPE_3, BTI_J, 0); + TEST(BTYPE_3, BTI_JC, 0); + + return fail; +} diff --git a/tests/tcg/aarch64/bti-crt.inc.c b/tests/tcg/aarch64/bti-crt.inc.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/tcg/aarch64/bti-crt.inc.c @@ -XXX,XX +XXX,XX @@ +/* + * Minimal user-environment for testing BTI. + * + * Normal libc is not (yet) built with BTI support enabled, + * and so could generate a BTI TRAP before ever reaching main. + */ + +#include <stdlib.h> +#include <signal.h> +#include <ucontext.h> +#include <asm/unistd.h> + +int main(void); + +void _start(void) +{ + exit(main()); +} + +void exit(int ret) +{ + register int x0 __asm__("x0") = ret; + register int x8 __asm__("x8") = __NR_exit; + + asm volatile("svc #0" : : "r"(x0), "r"(x8)); + __builtin_unreachable(); +} + +/* + * Irritatingly, the user API struct sigaction does not match the + * kernel API struct sigaction. So for simplicity, isolate the + * kernel ABI here, and make this act like signal. + */ +void signal_info(int sig, void (*fn)(int, siginfo_t *, ucontext_t *)) +{ + struct kernel_sigaction { + void (*handler)(int, siginfo_t *, ucontext_t *); + unsigned long flags; + unsigned long restorer; + unsigned long mask; + } sa = { fn, SA_SIGINFO, 0, 0 }; + + register int x0 __asm__("x0") = sig; + register void *x1 __asm__("x1") = &sa; + register void *x2 __asm__("x2") = 0; + register int x3 __asm__("x3") = sizeof(unsigned long); + register int x8 __asm__("x8") = __NR_rt_sigaction; + + asm volatile("svc #0" + : : "r"(x0), "r"(x1), "r"(x2), "r"(x3), "r"(x8) : "memory"); +} diff --git a/tests/tcg/aarch64/Makefile.target b/tests/tcg/aarch64/Makefile.target index XXXXXXX..XXXXXXX 100644 --- a/tests/tcg/aarch64/Makefile.target +++ b/tests/tcg/aarch64/Makefile.target @@ -XXX,XX +XXX,XX @@ run-pauth-%: QEMU_OPTS += -cpu max run-plugin-pauth-%: QEMU_OPTS += -cpu max endif +# BTI Tests +ifneq ($(DOCKER_IMAGE)$(CROSS_CC_HAS_ARMV8_BTI),) +AARCH64_TESTS += bti-1 +bti-%: CFLAGS += -mbranch-protection=standard +bti-%: LDFLAGS += -nostdlib +endif + # Semihosting smoke test for linux-user AARCH64_TESTS += semihosting run-semihosting: semihosting diff --git a/tests/tcg/configure.sh b/tests/tcg/configure.sh index XXXXXXX..XXXXXXX 100755 --- a/tests/tcg/configure.sh +++ b/tests/tcg/configure.sh @@ -XXX,XX +XXX,XX @@ for target in $target_list; do -march=armv8.3-a -o $TMPE $TMPC; then echo "CROSS_CC_HAS_ARMV8_3=y" >> $config_target_mak fi + if do_compiler "$target_compiler" $target_compiler_cflags \ + -mbranch-protection=standard -o $TMPE $TMPC; then + echo "CROSS_CC_HAS_ARMV8_BTI=y" >> $config_target_mak + fi ;; esac -- 2.25.1
The kernel abi for this was merged in v5.8, just as the qemu 5.1 merge window was closing, so this slipped to the next dev cycle. Changes from v10: * Include Phil's plug of interp_name memory leak. * Convert error reporting to Error api. * Mirror the kernel's code structure for parsing notes (though Error means that it's not exactly the same). * Split aarch64 stuff from basic note parsing patch. Changes from v9: * Split what is now patch 7 into 3 more (pmm). * All prerequisites are now upstream. r~ Philippe Mathieu-Daudé (1): linux-user/elfload: Avoid leaking interp_name using GLib memory API Richard Henderson (11): linux-user/aarch64: Reset btype for signals linux-user: Set PAGE_TARGET_1 for TARGET_PROT_BTI include/elf: Add defines related to GNU property notes for AArch64 linux-user/elfload: Fix coding style in load_elf_image linux-user/elfload: Adjust iteration over phdr linux-user/elfload: Move PT_INTERP detection to first loop linux-user/elfload: Use Error for load_elf_image linux-user/elfload: Use Error for load_elf_interp linux-user/elfload: Parse NT_GNU_PROPERTY_TYPE_0 notes linux-user/elfload: Parse GNU_PROPERTY_AARCH64_FEATURE_1_AND tests/tcg/aarch64: Add bti smoke tests include/elf.h | 22 ++ include/exec/cpu-all.h | 2 + linux-user/qemu.h | 4 + linux-user/syscall_defs.h | 4 + target/arm/cpu.h | 5 + linux-user/aarch64/signal.c | 10 +- linux-user/elfload.c | 326 +++++++++++++++++++++++++----- linux-user/mmap.c | 16 ++ target/arm/translate-a64.c | 6 +- tests/tcg/aarch64/bti-1.c | 62 ++++++ tests/tcg/aarch64/bti-2.c | 108 ++++++++++ tests/tcg/aarch64/bti-crt.inc.c | 51 +++++ tests/tcg/aarch64/Makefile.target | 10 + tests/tcg/configure.sh | 4 + 14 files changed, 569 insertions(+), 61 deletions(-) create mode 100644 tests/tcg/aarch64/bti-1.c create mode 100644 tests/tcg/aarch64/bti-2.c create mode 100644 tests/tcg/aarch64/bti-crt.inc.c -- 2.25.1
The kernel sets btype for the signal handler as if for a call. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> --- linux-user/aarch64/signal.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/linux-user/aarch64/signal.c b/linux-user/aarch64/signal.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/aarch64/signal.c +++ b/linux-user/aarch64/signal.c @@ -XXX,XX +XXX,XX @@ static void target_setup_frame(int usig, struct target_sigaction *ka, + offsetof(struct target_rt_frame_record, tramp); } env->xregs[0] = usig; - env->xregs[31] = frame_addr; env->xregs[29] = frame_addr + fr_ofs; - env->pc = ka->_sa_handler; env->xregs[30] = return_addr; + env->xregs[31] = frame_addr; + env->pc = ka->_sa_handler; + + /* Invoke the signal handler as if by indirect call. */ + if (cpu_isar_feature(aa64_bti, env_archcpu(env))) { + env->btype = 2; + } + if (info) { tswap_siginfo(&frame->info, info); env->xregs[1] = frame_addr + offsetof(struct target_rt_sigframe, info); -- 2.25.1
Transform the prot bit to a qemu internal page bit, and save it in the page tables. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> --- v10: Add PAGE_BTI define (pmm). --- include/exec/cpu-all.h | 2 ++ linux-user/syscall_defs.h | 4 ++++ target/arm/cpu.h | 5 +++++ linux-user/mmap.c | 16 ++++++++++++++++ target/arm/translate-a64.c | 6 +++--- 5 files changed, 30 insertions(+), 3 deletions(-) diff --git a/include/exec/cpu-all.h b/include/exec/cpu-all.h index XXXXXXX..XXXXXXX 100644 --- a/include/exec/cpu-all.h +++ b/include/exec/cpu-all.h @@ -XXX,XX +XXX,XX @@ extern intptr_t qemu_host_page_mask; /* FIXME: Code that sets/uses this is broken and needs to go away. */ #define PAGE_RESERVED 0x0020 #endif +/* Target-specific bits that will be used via page_get_flags(). */ +#define PAGE_TARGET_1 0x0080 #if defined(CONFIG_USER_ONLY) void page_dump(FILE *f); diff --git a/linux-user/syscall_defs.h b/linux-user/syscall_defs.h index XXXXXXX..XXXXXXX 100644 --- a/linux-user/syscall_defs.h +++ b/linux-user/syscall_defs.h @@ -XXX,XX +XXX,XX @@ struct target_winsize { #define TARGET_PROT_SEM 0x08 #endif +#ifdef TARGET_AARCH64 +#define TARGET_PROT_BTI 0x10 +#endif + /* Common */ #define TARGET_MAP_SHARED 0x01 /* Share changes */ #define TARGET_MAP_PRIVATE 0x02 /* Changes are private */ diff --git a/target/arm/cpu.h b/target/arm/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -XXX,XX +XXX,XX @@ static inline MemTxAttrs *typecheck_memtxattrs(MemTxAttrs *x) #define arm_tlb_bti_gp(x) (typecheck_memtxattrs(x)->target_tlb_bit0) #define arm_tlb_mte_tagged(x) (typecheck_memtxattrs(x)->target_tlb_bit1) +/* + * AArch64 usage of the PAGE_TARGET_* bits for linux-user. + */ +#define PAGE_BTI PAGE_TARGET_1 + /* * Naming convention for isar_feature functions: * Functions which test 32-bit ID registers should have _aa32_ in diff --git a/linux-user/mmap.c b/linux-user/mmap.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/mmap.c +++ b/linux-user/mmap.c @@ -XXX,XX +XXX,XX @@ static int validate_prot_to_pageflags(int *host_prot, int prot) *host_prot = (prot & (PROT_READ | PROT_WRITE)) | (prot & PROT_EXEC ? PROT_READ : 0); +#ifdef TARGET_AARCH64 + /* + * The PROT_BTI bit is only accepted if the cpu supports the feature. + * Since this is the unusual case, don't bother checking unless + * the bit has been requested. If set and valid, record the bit + * within QEMU's page_flags. + */ + if (prot & TARGET_PROT_BTI) { + ARMCPU *cpu = ARM_CPU(thread_cpu); + if (cpu_isar_feature(aa64_bti, cpu)) { + valid |= TARGET_PROT_BTI; + page_flags |= PAGE_BTI; + } + } +#endif + return prot & ~valid ? 0 : page_flags; } diff --git a/target/arm/translate-a64.c b/target/arm/translate-a64.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/translate-a64.c +++ b/target/arm/translate-a64.c @@ -XXX,XX +XXX,XX @@ static void disas_data_proc_simd_fp(DisasContext *s, uint32_t insn) */ static bool is_guarded_page(CPUARMState *env, DisasContext *s) { -#ifdef CONFIG_USER_ONLY - return false; /* FIXME */ -#else uint64_t addr = s->base.pc_first; +#ifdef CONFIG_USER_ONLY + return page_get_flags(addr) & PAGE_BTI; +#else int mmu_idx = arm_to_core_mmu_idx(s->mmu_idx); unsigned int index = tlb_index(env, mmu_idx, addr); CPUTLBEntry *entry = tlb_entry(env, mmu_idx, addr); -- 2.25.1
These are all of the defines required to parse GNU_PROPERTY_AARCH64_FEATURE_1_AND, copied from binutils. Other missing defines related to other GNU program headers and notes are elided for now. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> --- include/elf.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/include/elf.h b/include/elf.h index XXXXXXX..XXXXXXX 100644 --- a/include/elf.h +++ b/include/elf.h @@ -XXX,XX +XXX,XX @@ typedef int64_t Elf64_Sxword; #define PT_NOTE 4 #define PT_SHLIB 5 #define PT_PHDR 6 +#define PT_LOOS 0x60000000 +#define PT_HIOS 0x6fffffff #define PT_LOPROC 0x70000000 #define PT_HIPROC 0x7fffffff +#define PT_GNU_PROPERTY (PT_LOOS + 0x474e553) + #define PT_MIPS_REGINFO 0x70000000 #define PT_MIPS_RTPROC 0x70000001 #define PT_MIPS_OPTIONS 0x70000002 @@ -XXX,XX +XXX,XX @@ typedef struct elf64_shdr { #define NT_ARM_SYSTEM_CALL 0x404 /* ARM system call number */ #define NT_ARM_SVE 0x405 /* ARM Scalable Vector Extension regs */ +/* Defined note types for GNU systems. */ + +#define NT_GNU_PROPERTY_TYPE_0 5 /* Program property */ + +/* Values used in GNU .note.gnu.property notes (NT_GNU_PROPERTY_TYPE_0). */ + +#define GNU_PROPERTY_STACK_SIZE 1 +#define GNU_PROPERTY_NO_COPY_ON_PROTECTED 2 + +#define GNU_PROPERTY_LOPROC 0xc0000000 +#define GNU_PROPERTY_HIPROC 0xdfffffff +#define GNU_PROPERTY_LOUSER 0xe0000000 +#define GNU_PROPERTY_HIUSER 0xffffffff + +#define GNU_PROPERTY_AARCH64_FEATURE_1_AND 0xc0000000 +#define GNU_PROPERTY_AARCH64_FEATURE_1_BTI (1u << 0) +#define GNU_PROPERTY_AARCH64_FEATURE_1_PAC (1u << 1) + /* * Physical entry point into the kernel. * -- 2.25.1
From: Philippe Mathieu-Daudé <f4bug@amsat.org> Fix an unlikely memory leak in load_elf_image(). Fixes: bf858897b7 ("linux-user: Re-use load_elf_image for the main binary.") Signed-off-by: Philippe Mathieu-Daudé <f4bug@amsat.org> Message-Id: <20201003174944.1972444-1-f4bug@amsat.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> --- linux-user/elfload.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/linux-user/elfload.c b/linux-user/elfload.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, info->brk = vaddr_em; } } else if (eppnt->p_type == PT_INTERP && pinterp_name) { - char *interp_name; + g_autofree char *interp_name = NULL; if (*pinterp_name) { errmsg = "Multiple PT_INTERP entries"; goto exit_errmsg; } - interp_name = malloc(eppnt->p_filesz); + interp_name = g_malloc(eppnt->p_filesz); if (!interp_name) { goto exit_perror; } @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, errmsg = "Invalid PT_INTERP entry"; goto exit_errmsg; } - *pinterp_name = interp_name; + *pinterp_name = g_steal_pointer(&interp_name); #ifdef TARGET_MIPS } else if (eppnt->p_type == PT_MIPS_ABIFLAGS) { Mips_elf_abiflags_v0 abiflags; @@ -XXX,XX +XXX,XX @@ int load_elf_binary(struct linux_binprm *bprm, struct image_info *info) if (elf_interpreter) { info->load_bias = interp_info.load_bias; info->entry = interp_info.entry; - free(elf_interpreter); + g_free(elf_interpreter); } #ifdef USE_ELF_CORE_DUMP -- 2.25.1
Fixing this now will clarify following patches. Signed-off-by: Richard Henderson <richard.henderson@linaro.org> --- linux-user/elfload.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/linux-user/elfload.c b/linux-user/elfload.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, abi_ulong vaddr, vaddr_po, vaddr_ps, vaddr_ef, vaddr_em, vaddr_len; int elf_prot = 0; - if (eppnt->p_flags & PF_R) elf_prot = PROT_READ; - if (eppnt->p_flags & PF_W) elf_prot |= PROT_WRITE; - if (eppnt->p_flags & PF_X) elf_prot |= PROT_EXEC; + if (eppnt->p_flags & PF_R) { + elf_prot |= PROT_READ; + } + if (eppnt->p_flags & PF_W) { + elf_prot |= PROT_WRITE; + } + if (eppnt->p_flags & PF_X) { + elf_prot |= PROT_EXEC; + } vaddr = load_bias + eppnt->p_vaddr; vaddr_po = TARGET_ELF_PAGEOFFSET(vaddr); -- 2.25.1
The second loop uses a loop induction variable, and the first does not. Transform the first to match the second, to simplify a following patch moving code between them. Signed-off-by: Richard Henderson <richard.henderson@linaro.org> --- linux-user/elfload.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/linux-user/elfload.c b/linux-user/elfload.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, loaddr = -1, hiaddr = 0; info->alignment = 0; for (i = 0; i < ehdr->e_phnum; ++i) { - if (phdr[i].p_type == PT_LOAD) { - abi_ulong a = phdr[i].p_vaddr - phdr[i].p_offset; + struct elf_phdr *eppnt = phdr + i; + if (eppnt->p_type == PT_LOAD) { + abi_ulong a = eppnt->p_vaddr - eppnt->p_offset; if (a < loaddr) { loaddr = a; } - a = phdr[i].p_vaddr + phdr[i].p_memsz; + a = eppnt->p_vaddr + eppnt->p_memsz; if (a > hiaddr) { hiaddr = a; } ++info->nsegs; - info->alignment |= phdr[i].p_align; + info->alignment |= eppnt->p_align; } } -- 2.25.1
For BTI, we need to know if the executable is static or dynamic, which means looking for PT_INTERP earlier. Signed-off-by: Richard Henderson <richard.henderson@linaro.org> --- linux-user/elfload.c | 60 +++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/linux-user/elfload.c b/linux-user/elfload.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, mmap_lock(); - /* Find the maximum size of the image and allocate an appropriate - amount of memory to handle that. */ + /* + * Find the maximum size of the image and allocate an appropriate + * amount of memory to handle that. Locate the interpreter, if any. + */ loaddr = -1, hiaddr = 0; info->alignment = 0; for (i = 0; i < ehdr->e_phnum; ++i) { @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, } ++info->nsegs; info->alignment |= eppnt->p_align; + } else if (eppnt->p_type == PT_INTERP && pinterp_name) { + g_autofree char *interp_name = NULL; + + if (*pinterp_name) { + errmsg = "Multiple PT_INTERP entries"; + goto exit_errmsg; + } + interp_name = g_malloc(eppnt->p_filesz); + if (!interp_name) { + goto exit_perror; + } + + if (eppnt->p_offset + eppnt->p_filesz <= BPRM_BUF_SIZE) { + memcpy(interp_name, bprm_buf + eppnt->p_offset, + eppnt->p_filesz); + } else { + retval = pread(image_fd, interp_name, eppnt->p_filesz, + eppnt->p_offset); + if (retval != eppnt->p_filesz) { + goto exit_perror; + } + } + if (interp_name[eppnt->p_filesz - 1] != 0) { + errmsg = "Invalid PT_INTERP entry"; + goto exit_errmsg; + } + *pinterp_name = g_steal_pointer(&interp_name); } } @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, if (vaddr_em > info->brk) { info->brk = vaddr_em; } - } else if (eppnt->p_type == PT_INTERP && pinterp_name) { - g_autofree char *interp_name = NULL; - - if (*pinterp_name) { - errmsg = "Multiple PT_INTERP entries"; - goto exit_errmsg; - } - interp_name = g_malloc(eppnt->p_filesz); - if (!interp_name) { - goto exit_perror; - } - - if (eppnt->p_offset + eppnt->p_filesz <= BPRM_BUF_SIZE) { - memcpy(interp_name, bprm_buf + eppnt->p_offset, - eppnt->p_filesz); - } else { - retval = pread(image_fd, interp_name, eppnt->p_filesz, - eppnt->p_offset); - if (retval != eppnt->p_filesz) { - goto exit_perror; - } - } - if (interp_name[eppnt->p_filesz - 1] != 0) { - errmsg = "Invalid PT_INTERP entry"; - goto exit_errmsg; - } - *pinterp_name = g_steal_pointer(&interp_name); #ifdef TARGET_MIPS } else if (eppnt->p_type == PT_MIPS_ABIFLAGS) { Mips_elf_abiflags_v0 abiflags; -- 2.25.1
This is a bit clearer than open-coding some of this with a bare c string. Signed-off-by: Richard Henderson <richard.henderson@linaro.org> --- linux-user/elfload.c | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/linux-user/elfload.c b/linux-user/elfload.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -XXX,XX +XXX,XX @@ #include "qemu/guest-random.h" #include "qemu/units.h" #include "qemu/selfmap.h" +#include "qapi/error.h" #ifdef _ARCH_PPC64 #undef ARCH_DLINFO @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, struct elf_phdr *phdr; abi_ulong load_addr, load_bias, loaddr, hiaddr, error; int i, retval; - const char *errmsg; + Error *err = NULL; /* First of all, some simple consistency checks */ - errmsg = "Invalid ELF image for this architecture"; if (!elf_check_ident(ehdr)) { + error_setg(&err, "Invalid ELF image for this architecture"); goto exit_errmsg; } bswap_ehdr(ehdr); if (!elf_check_ehdr(ehdr)) { + error_setg(&err, "Invalid ELF image for this architecture"); goto exit_errmsg; } @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, g_autofree char *interp_name = NULL; if (*pinterp_name) { - errmsg = "Multiple PT_INTERP entries"; + error_setg(&err, "Multiple PT_INTERP entries"); goto exit_errmsg; } + interp_name = g_malloc(eppnt->p_filesz); - if (!interp_name) { - goto exit_perror; - } if (eppnt->p_offset + eppnt->p_filesz <= BPRM_BUF_SIZE) { memcpy(interp_name, bprm_buf + eppnt->p_offset, @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, retval = pread(image_fd, interp_name, eppnt->p_filesz, eppnt->p_offset); if (retval != eppnt->p_filesz) { - goto exit_perror; + goto exit_read; } } if (interp_name[eppnt->p_filesz - 1] != 0) { - errmsg = "Invalid PT_INTERP entry"; + error_setg(&err, "Invalid PT_INTERP entry"); goto exit_errmsg; } *pinterp_name = g_steal_pointer(&interp_name); @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, (ehdr->e_type == ET_EXEC ? MAP_FIXED : 0), -1, 0); if (load_addr == -1) { - goto exit_perror; + goto exit_mmap; } load_bias = load_addr - loaddr; @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, image_fd, eppnt->p_offset - vaddr_po); if (error == -1) { - goto exit_perror; + goto exit_mmap; } } @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, } else if (eppnt->p_type == PT_MIPS_ABIFLAGS) { Mips_elf_abiflags_v0 abiflags; if (eppnt->p_filesz < sizeof(Mips_elf_abiflags_v0)) { - errmsg = "Invalid PT_MIPS_ABIFLAGS entry"; + error_setg(&err, "Invalid PT_MIPS_ABIFLAGS entry"); goto exit_errmsg; } if (eppnt->p_offset + eppnt->p_filesz <= BPRM_BUF_SIZE) { @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, retval = pread(image_fd, &abiflags, sizeof(Mips_elf_abiflags_v0), eppnt->p_offset); if (retval != sizeof(Mips_elf_abiflags_v0)) { - goto exit_perror; + goto exit_read; } } bswap_mips_abiflags(&abiflags); @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, exit_read: if (retval >= 0) { - errmsg = "Incomplete read of file header"; - goto exit_errmsg; + error_setg(&err, "Incomplete read of file header"); + } else { + error_setg_errno(&err, errno, "Error reading file header"); } - exit_perror: - errmsg = strerror(errno); + goto exit_errmsg; + exit_mmap: + error_setg_errno(&err, errno, "Error mapping file"); + goto exit_errmsg; exit_errmsg: - fprintf(stderr, "%s: %s\n", image_name, errmsg); + error_reportf_err(err, "%s: ", image_name); exit(-1); } -- 2.25.1
This is slightly clearer than just using strerror, though the different forms produced by error_setg_file_open and error_setg_errno isn't entirely convenient. Signed-off-by: Richard Henderson <richard.henderson@linaro.org> --- linux-user/elfload.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/linux-user/elfload.c b/linux-user/elfload.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -XXX,XX +XXX,XX @@ static void load_elf_interp(const char *filename, struct image_info *info, char bprm_buf[BPRM_BUF_SIZE]) { int fd, retval; + Error *err = NULL; fd = open(path(filename), O_RDONLY); if (fd < 0) { - goto exit_perror; + error_setg_file_open(&err, errno, filename); + error_report_err(err); + exit(-1); } retval = read(fd, bprm_buf, BPRM_BUF_SIZE); if (retval < 0) { - goto exit_perror; + error_setg_errno(&err, errno, "Error reading file header"); + error_reportf_err(err, "%s: ", filename); + exit(-1); } + if (retval < BPRM_BUF_SIZE) { memset(bprm_buf + retval, 0, BPRM_BUF_SIZE - retval); } load_elf_image(filename, fd, info, NULL, bprm_buf); - return; - - exit_perror: - fprintf(stderr, "%s: %s\n", filename, strerror(errno)); - exit(-1); } static int symfind(const void *s0, const void *s1) -- 2.25.1
This is generic support, with the code disabled for all targets. Signed-off-by: Richard Henderson <richard.henderson@linaro.org> --- v9: Only map the startup executable with BTI; anything else must be handled by the interpreter. v10: Split out preparatory patches (pmm). v11: Mirror(-ish) the kernel's code structure (pmm). --- linux-user/qemu.h | 4 ++ linux-user/elfload.c | 157 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) diff --git a/linux-user/qemu.h b/linux-user/qemu.h index XXXXXXX..XXXXXXX 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -XXX,XX +XXX,XX @@ struct image_info { abi_ulong interpreter_loadmap_addr; abi_ulong interpreter_pt_dynamic_addr; struct image_info *other_info; + + /* For target-specific processing of NT_GNU_PROPERTY_TYPE_0. */ + uint32_t note_flags; + #ifdef TARGET_MIPS int fp_abi; int interp_fp_abi; diff --git a/linux-user/elfload.c b/linux-user/elfload.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -XXX,XX +XXX,XX @@ static void elf_core_copy_regs(target_elf_gregset_t *regs, #include "elf.h" +static bool arch_parse_elf_property(uint32_t pr_type, uint32_t pr_datasz, + const uint32_t *data, + struct image_info *info, + Error **errp) +{ + g_assert_not_reached(); +} +#define ARCH_USE_GNU_PROPERTY 0 + struct exec { unsigned int a_info; /* Use macros N_MAGIC, etc for access */ @@ -XXX,XX +XXX,XX @@ void probe_guest_base(const char *image_name, abi_ulong guest_loaddr, "@ 0x%" PRIx64 "\n", (uint64_t)guest_base); } +enum { + /* The string "GNU\0" as a magic number. */ + GNU0_MAGIC = const_le32('G' | 'N' << 8 | 'U' << 16), + NOTE_DATA_SZ = 1 * KiB, + NOTE_NAME_SZ = 4, + ELF_GNU_PROPERTY_ALIGN = ELF_CLASS == ELFCLASS32 ? 4 : 8, +}; + +/* + * Process a single gnu_property entry. + * Return false for error. + */ +static bool parse_elf_property(const uint32_t *data, int *off, int datasz, + struct image_info *info, bool have_prev_type, + uint32_t *prev_type, Error **errp) +{ + uint32_t pr_type, pr_datasz, step; + + if (*off > datasz || !QEMU_IS_ALIGNED(*off, ELF_GNU_PROPERTY_ALIGN)) { + goto error_data; + } + datasz -= *off; + data += *off / sizeof(uint32_t); + + if (datasz < 2 * sizeof(uint32_t)) { + goto error_data; + } + pr_type = data[0]; + pr_datasz = data[1]; + data += 2; + datasz -= 2 * sizeof(uint32_t); + step = ROUND_UP(pr_datasz, ELF_GNU_PROPERTY_ALIGN); + if (step > datasz) { + goto error_data; + } + + /* Properties are supposed to be unique and sorted on pr_type. */ + if (have_prev_type && pr_type <= *prev_type) { + if (pr_type == *prev_type) { + error_setg(errp, "Duplicate property in PT_GNU_PROPERTY"); + } else { + error_setg(errp, "Unsorted property in PT_GNU_PROPERTY"); + } + return false; + } + *prev_type = pr_type; + + if (!arch_parse_elf_property(pr_type, pr_datasz, data, info, errp)) { + return false; + } + + *off += 2 * sizeof(uint32_t) + step; + return true; + + error_data: + error_setg(errp, "Ill-formed property in PT_GNU_PROPERTY"); + return false; +} + +/* Process NT_GNU_PROPERTY_TYPE_0. */ +static bool parse_elf_properties(int image_fd, + struct image_info *info, + const struct elf_phdr *phdr, + char bprm_buf[BPRM_BUF_SIZE], + Error **errp) +{ + union { + struct elf_note nhdr; + uint32_t data[NOTE_DATA_SZ / sizeof(uint32_t)]; + } note; + + int n, off, datasz; + bool have_prev_type; + uint32_t prev_type; + + /* Unless the arch requires properties, ignore them. */ + if (!ARCH_USE_GNU_PROPERTY) { + return true; + } + + /* If the properties are crazy large, that's too bad. */ + n = phdr->p_filesz; + if (n > sizeof(note)) { + error_setg(errp, "PT_GNU_PROPERTY too large"); + return false; + } + if (n < sizeof(note.nhdr)) { + error_setg(errp, "PT_GNU_PROPERTY too small"); + return false; + } + + if (phdr->p_offset + n <= BPRM_BUF_SIZE) { + memcpy(¬e, bprm_buf + phdr->p_offset, n); + } else { + ssize_t len = pread(image_fd, ¬e, n, phdr->p_offset); + if (len != n) { + error_setg_errno(errp, errno, "Error reading file header"); + return false; + } + } + + /* + * The contents of a valid PT_GNU_PROPERTY is a sequence + * of uint32_t -- swap them all now. + */ +#ifdef BSWAP_NEEDED + for (int i = 0; i < n / 4; i++) { + bswap32s(note.data + i); + } +#endif + + /* + * Note that nhdr is 3 words, and that the "name" described by namesz + * immediately follows nhdr and is thus at the 4th word. Further, all + * of the inputs to the kernel's round_up are multiples of 4. + */ + if (note.nhdr.n_type != NT_GNU_PROPERTY_TYPE_0 || + note.nhdr.n_namesz != NOTE_NAME_SZ || + note.data[3] != GNU0_MAGIC) { + error_setg(errp, "Invalid note in PT_GNU_PROPERTY"); + return false; + } + off = sizeof(note.nhdr) + NOTE_NAME_SZ; + + datasz = note.nhdr.n_descsz + off; + if (datasz > n) { + error_setg(errp, "Invalid note size in PT_GNU_PROPERTY"); + return false; + } + + have_prev_type = false; + prev_type = 0; + while (1) { + if (off == datasz) { + return true; /* end, exit ok */ + } + if (!parse_elf_property(note.data, &off, datasz, info, + have_prev_type, &prev_type, errp)) { + return false; + } + have_prev_type = true; + } +} + /* Load an ELF image into the address space. IMAGE_NAME is the filename of the image, to use in error messages. @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, goto exit_errmsg; } *pinterp_name = g_steal_pointer(&interp_name); + } else if (eppnt->p_type == PT_GNU_PROPERTY) { + if (!parse_elf_properties(image_fd, info, eppnt, bprm_buf, &err)) { + goto exit_errmsg; + } } } -- 2.25.1
Use the new generic support for NT_GNU_PROPERTY_TYPE_0. Signed-off-by: Richard Henderson <richard.henderson@linaro.org> --- v11: Split out aarch64 bits from generic patch. --- linux-user/elfload.c | 48 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/linux-user/elfload.c b/linux-user/elfload.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -XXX,XX +XXX,XX @@ static void elf_core_copy_regs(target_elf_gregset_t *regs, #include "elf.h" +/* We must delay the following stanzas until after "elf.h". */ +#if defined(TARGET_AARCH64) + +static bool arch_parse_elf_property(uint32_t pr_type, uint32_t pr_datasz, + const uint32_t *data, + struct image_info *info, + Error **errp) +{ + if (pr_type == GNU_PROPERTY_AARCH64_FEATURE_1_AND) { + if (pr_datasz != sizeof(uint32_t)) { + error_setg(errp, "Ill-formed GNU_PROPERTY_AARCH64_FEATURE_1_AND"); + return false; + } + /* We will extract GNU_PROPERTY_AARCH64_FEATURE_1_BTI later. */ + info->note_flags = *data; + } + return true; +} +#define ARCH_USE_GNU_PROPERTY 1 + +#else + static bool arch_parse_elf_property(uint32_t pr_type, uint32_t pr_datasz, const uint32_t *data, struct image_info *info, @@ -XXX,XX +XXX,XX @@ static bool arch_parse_elf_property(uint32_t pr_type, uint32_t pr_datasz, } #define ARCH_USE_GNU_PROPERTY 0 +#endif + struct exec { unsigned int a_info; /* Use macros N_MAGIC, etc for access */ @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, struct elfhdr *ehdr = (struct elfhdr *)bprm_buf; struct elf_phdr *phdr; abi_ulong load_addr, load_bias, loaddr, hiaddr, error; - int i, retval; + int i, retval, prot_exec; Error *err = NULL; /* First of all, some simple consistency checks */ @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, info->brk = 0; info->elf_flags = ehdr->e_flags; + prot_exec = PROT_EXEC; +#ifdef TARGET_AARCH64 + /* + * If the BTI feature is present, this indicates that the executable + * pages of the startup binary should be mapped with PROT_BTI, so that + * branch targets are enforced. + * + * The startup binary is either the interpreter or the static executable. + * The interpreter is responsible for all pages of a dynamic executable. + * + * Elf notes are backward compatible to older cpus. + * Do not enable BTI unless it is supported. + */ + if ((info->note_flags & GNU_PROPERTY_AARCH64_FEATURE_1_BTI) + && (pinterp_name == NULL || *pinterp_name == 0) + && cpu_isar_feature(aa64_bti, ARM_CPU(thread_cpu))) { + prot_exec |= TARGET_PROT_BTI; + } +#endif + for (i = 0; i < ehdr->e_phnum; i++) { struct elf_phdr *eppnt = phdr + i; if (eppnt->p_type == PT_LOAD) { @@ -XXX,XX +XXX,XX @@ static void load_elf_image(const char *image_name, int image_fd, elf_prot |= PROT_WRITE; } if (eppnt->p_flags & PF_X) { - elf_prot |= PROT_EXEC; + elf_prot |= prot_exec; } vaddr = load_bias + eppnt->p_vaddr; -- 2.25.1
The note test requires gcc 10 for -mbranch-protection=standard. The mmap test uses PROT_BTI and does not require special compiler support. Acked-by: Alex Bennée <alex.bennee@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> --- v9: Expect and require gcc 10. v11: Squash mmap smoke test. --- tests/tcg/aarch64/bti-1.c | 62 +++++++++++++++++ tests/tcg/aarch64/bti-2.c | 108 ++++++++++++++++++++++++++++++ tests/tcg/aarch64/bti-crt.inc.c | 51 ++++++++++++++ tests/tcg/aarch64/Makefile.target | 10 +++ tests/tcg/configure.sh | 4 ++ 5 files changed, 235 insertions(+) create mode 100644 tests/tcg/aarch64/bti-1.c create mode 100644 tests/tcg/aarch64/bti-2.c create mode 100644 tests/tcg/aarch64/bti-crt.inc.c diff --git a/tests/tcg/aarch64/bti-1.c b/tests/tcg/aarch64/bti-1.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/tcg/aarch64/bti-1.c @@ -XXX,XX +XXX,XX @@ +/* + * Branch target identification, basic notskip cases. + */ + +#include "bti-crt.inc.c" + +static void skip2_sigill(int sig, siginfo_t *info, ucontext_t *uc) +{ + uc->uc_mcontext.pc += 8; + uc->uc_mcontext.pstate = 1; +} + +#define NOP "nop" +#define BTI_N "hint #32" +#define BTI_C "hint #34" +#define BTI_J "hint #36" +#define BTI_JC "hint #38" + +#define BTYPE_1(DEST) \ + asm("mov %0,#1; adr x16, 1f; br x16; 1: " DEST "; mov %0,#0" \ + : "=r"(skipped) : : "x16") + +#define BTYPE_2(DEST) \ + asm("mov %0,#1; adr x16, 1f; blr x16; 1: " DEST "; mov %0,#0" \ + : "=r"(skipped) : : "x16", "x30") + +#define BTYPE_3(DEST) \ + asm("mov %0,#1; adr x15, 1f; br x15; 1: " DEST "; mov %0,#0" \ + : "=r"(skipped) : : "x15") + +#define TEST(WHICH, DEST, EXPECT) \ + do { WHICH(DEST); fail += skipped ^ EXPECT; } while (0) + + +int main() +{ + int fail = 0; + int skipped; + + /* Signal-like with SA_SIGINFO. */ + signal_info(SIGILL, skip2_sigill); + + TEST(BTYPE_1, NOP, 1); + TEST(BTYPE_1, BTI_N, 1); + TEST(BTYPE_1, BTI_C, 0); + TEST(BTYPE_1, BTI_J, 0); + TEST(BTYPE_1, BTI_JC, 0); + + TEST(BTYPE_2, NOP, 1); + TEST(BTYPE_2, BTI_N, 1); + TEST(BTYPE_2, BTI_C, 0); + TEST(BTYPE_2, BTI_J, 1); + TEST(BTYPE_2, BTI_JC, 0); + + TEST(BTYPE_3, NOP, 1); + TEST(BTYPE_3, BTI_N, 1); + TEST(BTYPE_3, BTI_C, 1); + TEST(BTYPE_3, BTI_J, 0); + TEST(BTYPE_3, BTI_JC, 0); + + return fail; +} diff --git a/tests/tcg/aarch64/bti-2.c b/tests/tcg/aarch64/bti-2.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/tcg/aarch64/bti-2.c @@ -XXX,XX +XXX,XX @@ +/* + * Branch target identification, basic notskip cases. + */ + +#include <stdio.h> +#include <signal.h> +#include <string.h> +#include <unistd.h> +#include <sys/mman.h> + +#ifndef PROT_BTI +#define PROT_BTI 0x10 +#endif + +static void skip2_sigill(int sig, siginfo_t *info, void *vuc) +{ + ucontext_t *uc = vuc; + uc->uc_mcontext.pc += 8; + uc->uc_mcontext.pstate = 1; +} + +#define NOP "nop" +#define BTI_N "hint #32" +#define BTI_C "hint #34" +#define BTI_J "hint #36" +#define BTI_JC "hint #38" + +#define BTYPE_1(DEST) \ + "mov x1, #1\n\t" \ + "adr x16, 1f\n\t" \ + "br x16\n" \ +"1: " DEST "\n\t" \ + "mov x1, #0" + +#define BTYPE_2(DEST) \ + "mov x1, #1\n\t" \ + "adr x16, 1f\n\t" \ + "blr x16\n" \ +"1: " DEST "\n\t" \ + "mov x1, #0" + +#define BTYPE_3(DEST) \ + "mov x1, #1\n\t" \ + "adr x15, 1f\n\t" \ + "br x15\n" \ +"1: " DEST "\n\t" \ + "mov x1, #0" + +#define TEST(WHICH, DEST, EXPECT) \ + WHICH(DEST) "\n" \ + ".if " #EXPECT "\n\t" \ + "eor x1, x1," #EXPECT "\n" \ + ".endif\n\t" \ + "add x0, x0, x1\n\t" + +extern char test_begin[], test_end[]; + +asm("\n" +"test_begin:\n\t" + BTI_C "\n\t" + "mov x2, x30\n\t" + "mov x0, #0\n\t" + + TEST(BTYPE_1, NOP, 1) + TEST(BTYPE_1, BTI_N, 1) + TEST(BTYPE_1, BTI_C, 0) + TEST(BTYPE_1, BTI_J, 0) + TEST(BTYPE_1, BTI_JC, 0) + + TEST(BTYPE_2, NOP, 1) + TEST(BTYPE_2, BTI_N, 1) + TEST(BTYPE_2, BTI_C, 0) + TEST(BTYPE_2, BTI_J, 1) + TEST(BTYPE_2, BTI_JC, 0) + + TEST(BTYPE_3, NOP, 1) + TEST(BTYPE_3, BTI_N, 1) + TEST(BTYPE_3, BTI_C, 1) + TEST(BTYPE_3, BTI_J, 0) + TEST(BTYPE_3, BTI_JC, 0) + + "ret x2\n" +"test_end:" +); + +int main() +{ + struct sigaction sa; + + void *p = mmap(0, getpagesize(), + PROT_EXEC | PROT_READ | PROT_WRITE | PROT_BTI, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (p == MAP_FAILED) { + perror("mmap"); + return 1; + } + + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = skip2_sigill; + sa.sa_flags = SA_SIGINFO; + if (sigaction(SIGILL, &sa, NULL) < 0) { + perror("sigaction"); + return 1; + } + + memcpy(p, test_begin, test_end - test_begin); + return ((int (*)(void))p)(); +} diff --git a/tests/tcg/aarch64/bti-crt.inc.c b/tests/tcg/aarch64/bti-crt.inc.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/tcg/aarch64/bti-crt.inc.c @@ -XXX,XX +XXX,XX @@ +/* + * Minimal user-environment for testing BTI. + * + * Normal libc is not (yet) built with BTI support enabled, + * and so could generate a BTI TRAP before ever reaching main. + */ + +#include <stdlib.h> +#include <signal.h> +#include <ucontext.h> +#include <asm/unistd.h> + +int main(void); + +void _start(void) +{ + exit(main()); +} + +void exit(int ret) +{ + register int x0 __asm__("x0") = ret; + register int x8 __asm__("x8") = __NR_exit; + + asm volatile("svc #0" : : "r"(x0), "r"(x8)); + __builtin_unreachable(); +} + +/* + * Irritatingly, the user API struct sigaction does not match the + * kernel API struct sigaction. So for simplicity, isolate the + * kernel ABI here, and make this act like signal. + */ +void signal_info(int sig, void (*fn)(int, siginfo_t *, ucontext_t *)) +{ + struct kernel_sigaction { + void (*handler)(int, siginfo_t *, ucontext_t *); + unsigned long flags; + unsigned long restorer; + unsigned long mask; + } sa = { fn, SA_SIGINFO, 0, 0 }; + + register int x0 __asm__("x0") = sig; + register void *x1 __asm__("x1") = &sa; + register void *x2 __asm__("x2") = 0; + register int x3 __asm__("x3") = sizeof(unsigned long); + register int x8 __asm__("x8") = __NR_rt_sigaction; + + asm volatile("svc #0" + : : "r"(x0), "r"(x1), "r"(x2), "r"(x3), "r"(x8) : "memory"); +} diff --git a/tests/tcg/aarch64/Makefile.target b/tests/tcg/aarch64/Makefile.target index XXXXXXX..XXXXXXX 100644 --- a/tests/tcg/aarch64/Makefile.target +++ b/tests/tcg/aarch64/Makefile.target @@ -XXX,XX +XXX,XX @@ run-pauth-%: QEMU_OPTS += -cpu max run-plugin-pauth-%: QEMU_OPTS += -cpu max endif +# BTI Tests +# bti-1 tests the elf notes, so we require special compiler support. +ifneq ($(DOCKER_IMAGE)$(CROSS_CC_HAS_ARMV8_BTI),) +AARCH64_TESTS += bti-1 +bti-1: CFLAGS += -mbranch-protection=standard +bti-1: LDFLAGS += -nostdlib +endif +# bti-2 tests PROT_BTI, so no special compiler support required. +AARCH64_TESTS += bti-2 + # Semihosting smoke test for linux-user AARCH64_TESTS += semihosting run-semihosting: semihosting diff --git a/tests/tcg/configure.sh b/tests/tcg/configure.sh index XXXXXXX..XXXXXXX 100755 --- a/tests/tcg/configure.sh +++ b/tests/tcg/configure.sh @@ -XXX,XX +XXX,XX @@ for target in $target_list; do -march=armv8.3-a -o $TMPE $TMPC; then echo "CROSS_CC_HAS_ARMV8_3=y" >> $config_target_mak fi + if do_compiler "$target_compiler" $target_compiler_cflags \ + -mbranch-protection=standard -o $TMPE $TMPC; then + echo "CROSS_CC_HAS_ARMV8_BTI=y" >> $config_target_mak + fi ;; esac -- 2.25.1