| // SPDX-License-Identifier: GPL-2.0 |
| // Copyright (C) 2020 ARM Limited |
| |
| #include <fcntl.h> |
| #include <sched.h> |
| #include <signal.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| |
| #include <linux/auxvec.h> |
| #include <sys/auxv.h> |
| #include <sys/mman.h> |
| #include <sys/prctl.h> |
| |
| #include <asm/hwcap.h> |
| |
| #include "kselftest.h" |
| #include "mte_common_util.h" |
| #include "mte_def.h" |
| |
| #define INIT_BUFFER_SIZE 256 |
| |
| struct mte_fault_cxt cur_mte_cxt; |
| static unsigned int mte_cur_mode; |
| static unsigned int mte_cur_pstate_tco; |
| |
| void mte_default_handler(int signum, siginfo_t *si, void *uc) |
| { |
| unsigned long addr = (unsigned long)si->si_addr; |
| |
| if (signum == SIGSEGV) { |
| #ifdef DEBUG |
| ksft_print_msg("INFO: SIGSEGV signal at pc=%lx, fault addr=%lx, si_code=%lx\n", |
| ((ucontext_t *)uc)->uc_mcontext.pc, addr, si->si_code); |
| #endif |
| if (si->si_code == SEGV_MTEAERR) { |
| if (cur_mte_cxt.trig_si_code == si->si_code) |
| cur_mte_cxt.fault_valid = true; |
| else |
| ksft_print_msg("Got unexpected SEGV_MTEAERR at pc=$lx, fault addr=%lx\n", |
| ((ucontext_t *)uc)->uc_mcontext.pc, |
| addr); |
| return; |
| } |
| /* Compare the context for precise error */ |
| else if (si->si_code == SEGV_MTESERR) { |
| if (cur_mte_cxt.trig_si_code == si->si_code && |
| ((cur_mte_cxt.trig_range >= 0 && |
| addr >= MT_CLEAR_TAG(cur_mte_cxt.trig_addr) && |
| addr <= (MT_CLEAR_TAG(cur_mte_cxt.trig_addr) + cur_mte_cxt.trig_range)) || |
| (cur_mte_cxt.trig_range < 0 && |
| addr <= MT_CLEAR_TAG(cur_mte_cxt.trig_addr) && |
| addr >= (MT_CLEAR_TAG(cur_mte_cxt.trig_addr) + cur_mte_cxt.trig_range)))) { |
| cur_mte_cxt.fault_valid = true; |
| /* Adjust the pc by 4 */ |
| ((ucontext_t *)uc)->uc_mcontext.pc += 4; |
| } else { |
| ksft_print_msg("Invalid MTE synchronous exception caught!\n"); |
| exit(1); |
| } |
| } else { |
| ksft_print_msg("Unknown SIGSEGV exception caught!\n"); |
| exit(1); |
| } |
| } else if (signum == SIGBUS) { |
| ksft_print_msg("INFO: SIGBUS signal at pc=%lx, fault addr=%lx, si_code=%lx\n", |
| ((ucontext_t *)uc)->uc_mcontext.pc, addr, si->si_code); |
| if ((cur_mte_cxt.trig_range >= 0 && |
| addr >= MT_CLEAR_TAG(cur_mte_cxt.trig_addr) && |
| addr <= (MT_CLEAR_TAG(cur_mte_cxt.trig_addr) + cur_mte_cxt.trig_range)) || |
| (cur_mte_cxt.trig_range < 0 && |
| addr <= MT_CLEAR_TAG(cur_mte_cxt.trig_addr) && |
| addr >= (MT_CLEAR_TAG(cur_mte_cxt.trig_addr) + cur_mte_cxt.trig_range))) { |
| cur_mte_cxt.fault_valid = true; |
| /* Adjust the pc by 4 */ |
| ((ucontext_t *)uc)->uc_mcontext.pc += 4; |
| } |
| } |
| } |
| |
| void mte_register_signal(int signal, void (*handler)(int, siginfo_t *, void *)) |
| { |
| struct sigaction sa; |
| |
| sa.sa_sigaction = handler; |
| sa.sa_flags = SA_SIGINFO; |
| sigemptyset(&sa.sa_mask); |
| sigaction(signal, &sa, NULL); |
| } |
| |
| void mte_wait_after_trig(void) |
| { |
| sched_yield(); |
| } |
| |
| void *mte_insert_tags(void *ptr, size_t size) |
| { |
| void *tag_ptr; |
| int align_size; |
| |
| if (!ptr || (unsigned long)(ptr) & MT_ALIGN_GRANULE) { |
| ksft_print_msg("FAIL: Addr=%lx: invalid\n", ptr); |
| return NULL; |
| } |
| align_size = MT_ALIGN_UP(size); |
| tag_ptr = mte_insert_random_tag(ptr); |
| mte_set_tag_address_range(tag_ptr, align_size); |
| return tag_ptr; |
| } |
| |
| void mte_clear_tags(void *ptr, size_t size) |
| { |
| if (!ptr || (unsigned long)(ptr) & MT_ALIGN_GRANULE) { |
| ksft_print_msg("FAIL: Addr=%lx: invalid\n", ptr); |
| return; |
| } |
| size = MT_ALIGN_UP(size); |
| ptr = (void *)MT_CLEAR_TAG((unsigned long)ptr); |
| mte_clear_tag_address_range(ptr, size); |
| } |
| |
| static void *__mte_allocate_memory_range(size_t size, int mem_type, int mapping, |
| size_t range_before, size_t range_after, |
| bool tags, int fd) |
| { |
| void *ptr; |
| int prot_flag, map_flag; |
| size_t entire_size = size + range_before + range_after; |
| |
| switch (mem_type) { |
| case USE_MALLOC: |
| return malloc(entire_size) + range_before; |
| case USE_MMAP: |
| case USE_MPROTECT: |
| break; |
| default: |
| ksft_print_msg("FAIL: Invalid allocate request\n"); |
| return NULL; |
| } |
| |
| prot_flag = PROT_READ | PROT_WRITE; |
| if (mem_type == USE_MMAP) |
| prot_flag |= PROT_MTE; |
| |
| map_flag = mapping; |
| if (fd == -1) |
| map_flag = MAP_ANONYMOUS | map_flag; |
| if (!(mapping & MAP_SHARED)) |
| map_flag |= MAP_PRIVATE; |
| ptr = mmap(NULL, entire_size, prot_flag, map_flag, fd, 0); |
| if (ptr == MAP_FAILED) { |
| ksft_print_msg("FAIL: mmap allocation\n"); |
| return NULL; |
| } |
| if (mem_type == USE_MPROTECT) { |
| if (mprotect(ptr, entire_size, prot_flag | PROT_MTE)) { |
| munmap(ptr, size); |
| ksft_print_msg("FAIL: mprotect PROT_MTE property\n"); |
| return NULL; |
| } |
| } |
| if (tags) |
| ptr = mte_insert_tags(ptr + range_before, size); |
| return ptr; |
| } |
| |
| void *mte_allocate_memory_tag_range(size_t size, int mem_type, int mapping, |
| size_t range_before, size_t range_after) |
| { |
| return __mte_allocate_memory_range(size, mem_type, mapping, range_before, |
| range_after, true, -1); |
| } |
| |
| void *mte_allocate_memory(size_t size, int mem_type, int mapping, bool tags) |
| { |
| return __mte_allocate_memory_range(size, mem_type, mapping, 0, 0, tags, -1); |
| } |
| |
| void *mte_allocate_file_memory(size_t size, int mem_type, int mapping, bool tags, int fd) |
| { |
| int index; |
| char buffer[INIT_BUFFER_SIZE]; |
| |
| if (mem_type != USE_MPROTECT && mem_type != USE_MMAP) { |
| ksft_print_msg("FAIL: Invalid mmap file request\n"); |
| return NULL; |
| } |
| /* Initialize the file for mappable size */ |
| lseek(fd, 0, SEEK_SET); |
| for (index = INIT_BUFFER_SIZE; index < size; index += INIT_BUFFER_SIZE) { |
| if (write(fd, buffer, INIT_BUFFER_SIZE) != INIT_BUFFER_SIZE) { |
| perror("initialising buffer"); |
| return NULL; |
| } |
| } |
| index -= INIT_BUFFER_SIZE; |
| if (write(fd, buffer, size - index) != size - index) { |
| perror("initialising buffer"); |
| return NULL; |
| } |
| return __mte_allocate_memory_range(size, mem_type, mapping, 0, 0, tags, fd); |
| } |
| |
| void *mte_allocate_file_memory_tag_range(size_t size, int mem_type, int mapping, |
| size_t range_before, size_t range_after, int fd) |
| { |
| int index; |
| char buffer[INIT_BUFFER_SIZE]; |
| int map_size = size + range_before + range_after; |
| |
| if (mem_type != USE_MPROTECT && mem_type != USE_MMAP) { |
| ksft_print_msg("FAIL: Invalid mmap file request\n"); |
| return NULL; |
| } |
| /* Initialize the file for mappable size */ |
| lseek(fd, 0, SEEK_SET); |
| for (index = INIT_BUFFER_SIZE; index < map_size; index += INIT_BUFFER_SIZE) |
| if (write(fd, buffer, INIT_BUFFER_SIZE) != INIT_BUFFER_SIZE) { |
| perror("initialising buffer"); |
| return NULL; |
| } |
| index -= INIT_BUFFER_SIZE; |
| if (write(fd, buffer, map_size - index) != map_size - index) { |
| perror("initialising buffer"); |
| return NULL; |
| } |
| return __mte_allocate_memory_range(size, mem_type, mapping, range_before, |
| range_after, true, fd); |
| } |
| |
| static void __mte_free_memory_range(void *ptr, size_t size, int mem_type, |
| size_t range_before, size_t range_after, bool tags) |
| { |
| switch (mem_type) { |
| case USE_MALLOC: |
| free(ptr - range_before); |
| break; |
| case USE_MMAP: |
| case USE_MPROTECT: |
| if (tags) |
| mte_clear_tags(ptr, size); |
| munmap(ptr - range_before, size + range_before + range_after); |
| break; |
| default: |
| ksft_print_msg("FAIL: Invalid free request\n"); |
| break; |
| } |
| } |
| |
| void mte_free_memory_tag_range(void *ptr, size_t size, int mem_type, |
| size_t range_before, size_t range_after) |
| { |
| __mte_free_memory_range(ptr, size, mem_type, range_before, range_after, true); |
| } |
| |
| void mte_free_memory(void *ptr, size_t size, int mem_type, bool tags) |
| { |
| __mte_free_memory_range(ptr, size, mem_type, 0, 0, tags); |
| } |
| |
| void mte_initialize_current_context(int mode, uintptr_t ptr, ssize_t range) |
| { |
| cur_mte_cxt.fault_valid = false; |
| cur_mte_cxt.trig_addr = ptr; |
| cur_mte_cxt.trig_range = range; |
| if (mode == MTE_SYNC_ERR) |
| cur_mte_cxt.trig_si_code = SEGV_MTESERR; |
| else if (mode == MTE_ASYNC_ERR) |
| cur_mte_cxt.trig_si_code = SEGV_MTEAERR; |
| else |
| cur_mte_cxt.trig_si_code = 0; |
| } |
| |
| int mte_switch_mode(int mte_option, unsigned long incl_mask) |
| { |
| unsigned long en = 0; |
| |
| switch (mte_option) { |
| case MTE_NONE_ERR: |
| case MTE_SYNC_ERR: |
| case MTE_ASYNC_ERR: |
| break; |
| default: |
| ksft_print_msg("FAIL: Invalid MTE option %x\n", mte_option); |
| return -EINVAL; |
| } |
| |
| if (incl_mask & ~MT_INCLUDE_TAG_MASK) { |
| ksft_print_msg("FAIL: Invalid incl_mask %lx\n", incl_mask); |
| return -EINVAL; |
| } |
| |
| en = PR_TAGGED_ADDR_ENABLE; |
| switch (mte_option) { |
| case MTE_SYNC_ERR: |
| en |= PR_MTE_TCF_SYNC; |
| break; |
| case MTE_ASYNC_ERR: |
| en |= PR_MTE_TCF_ASYNC; |
| break; |
| case MTE_NONE_ERR: |
| en |= PR_MTE_TCF_NONE; |
| break; |
| } |
| |
| en |= (incl_mask << PR_MTE_TAG_SHIFT); |
| /* Enable address tagging ABI, mte error reporting mode and tag inclusion mask. */ |
| if (prctl(PR_SET_TAGGED_ADDR_CTRL, en, 0, 0, 0) != 0) { |
| ksft_print_msg("FAIL:prctl PR_SET_TAGGED_ADDR_CTRL for mte mode\n"); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| int mte_default_setup(void) |
| { |
| unsigned long hwcaps2 = getauxval(AT_HWCAP2); |
| unsigned long en = 0; |
| int ret; |
| |
| if (!(hwcaps2 & HWCAP2_MTE)) { |
| ksft_print_msg("SKIP: MTE features unavailable\n"); |
| return KSFT_SKIP; |
| } |
| /* Get current mte mode */ |
| ret = prctl(PR_GET_TAGGED_ADDR_CTRL, en, 0, 0, 0); |
| if (ret < 0) { |
| ksft_print_msg("FAIL:prctl PR_GET_TAGGED_ADDR_CTRL with error =%d\n", ret); |
| return KSFT_FAIL; |
| } |
| if (ret & PR_MTE_TCF_SYNC) |
| mte_cur_mode = MTE_SYNC_ERR; |
| else if (ret & PR_MTE_TCF_ASYNC) |
| mte_cur_mode = MTE_ASYNC_ERR; |
| else if (ret & PR_MTE_TCF_NONE) |
| mte_cur_mode = MTE_NONE_ERR; |
| |
| mte_cur_pstate_tco = mte_get_pstate_tco(); |
| /* Disable PSTATE.TCO */ |
| mte_disable_pstate_tco(); |
| return 0; |
| } |
| |
| void mte_restore_setup(void) |
| { |
| mte_switch_mode(mte_cur_mode, MTE_ALLOW_NON_ZERO_TAG); |
| if (mte_cur_pstate_tco == MT_PSTATE_TCO_EN) |
| mte_enable_pstate_tco(); |
| else if (mte_cur_pstate_tco == MT_PSTATE_TCO_DIS) |
| mte_disable_pstate_tco(); |
| } |
| |
| int create_temp_file(void) |
| { |
| int fd; |
| char filename[] = "/dev/shm/tmp_XXXXXX"; |
| |
| /* Create a file in the tmpfs filesystem */ |
| fd = mkstemp(&filename[0]); |
| if (fd == -1) { |
| perror(filename); |
| ksft_print_msg("FAIL: Unable to open temporary file\n"); |
| return 0; |
| } |
| unlink(&filename[0]); |
| return fd; |
| } |