| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * Copyright 2018, Michael Ellerman, IBM Corp. | |
| * | |
| * Test that an out-of-bounds branch to counter behaves as expected. | |
| */ | |
| #include <setjmp.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <sys/mman.h> | |
| #include <sys/types.h> | |
| #include <sys/wait.h> | |
| #include <ucontext.h> | |
| #include <unistd.h> | |
| #include "utils.h" | |
| #define BAD_NIP 0x788c545a18000000ull | |
| static struct pt_regs signal_regs; | |
| static jmp_buf setjmp_env; | |
| static void save_regs(ucontext_t *ctxt) | |
| { | |
| struct pt_regs *regs = ctxt->uc_mcontext.regs; | |
| memcpy(&signal_regs, regs, sizeof(signal_regs)); | |
| } | |
| static void segv_handler(int signum, siginfo_t *info, void *ctxt_v) | |
| { | |
| save_regs(ctxt_v); | |
| longjmp(setjmp_env, 1); | |
| } | |
| static void usr2_handler(int signum, siginfo_t *info, void *ctxt_v) | |
| { | |
| save_regs(ctxt_v); | |
| } | |
| static int ok(void) | |
| { | |
| printf("Everything is OK in here.\n"); | |
| return 0; | |
| } | |
| #define REG_POISON 0x5a5a | |
| #define POISONED_REG(n) ((((unsigned long)REG_POISON) << 48) | ((n) << 32) | \ | |
| (((unsigned long)REG_POISON) << 16) | (n)) | |
| static inline void poison_regs(void) | |
| { | |
| #define POISON_REG(n) \ | |
| "lis " __stringify(n) "," __stringify(REG_POISON) ";" \ | |
| "addi " __stringify(n) "," __stringify(n) "," __stringify(n) ";" \ | |
| "sldi " __stringify(n) "," __stringify(n) ", 32 ;" \ | |
| "oris " __stringify(n) "," __stringify(n) "," __stringify(REG_POISON) ";" \ | |
| "addi " __stringify(n) "," __stringify(n) "," __stringify(n) ";" | |
| asm (POISON_REG(15) | |
| POISON_REG(16) | |
| POISON_REG(17) | |
| POISON_REG(18) | |
| POISON_REG(19) | |
| POISON_REG(20) | |
| POISON_REG(21) | |
| POISON_REG(22) | |
| POISON_REG(23) | |
| POISON_REG(24) | |
| POISON_REG(25) | |
| POISON_REG(26) | |
| POISON_REG(27) | |
| POISON_REG(28) | |
| POISON_REG(29) | |
| : // inputs | |
| : // outputs | |
| : "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", | |
| "26", "27", "28", "29" | |
| ); | |
| #undef POISON_REG | |
| } | |
| static int check_regs(void) | |
| { | |
| unsigned long i; | |
| for (i = 15; i <= 29; i++) | |
| FAIL_IF(signal_regs.gpr[i] != POISONED_REG(i)); | |
| printf("Regs OK\n"); | |
| return 0; | |
| } | |
| static void dump_regs(void) | |
| { | |
| for (int i = 0; i < 32; i += 4) { | |
| printf("r%02d 0x%016lx r%02d 0x%016lx " \ | |
| "r%02d 0x%016lx r%02d 0x%016lx\n", | |
| i, signal_regs.gpr[i], | |
| i+1, signal_regs.gpr[i+1], | |
| i+2, signal_regs.gpr[i+2], | |
| i+3, signal_regs.gpr[i+3]); | |
| } | |
| } | |
| #ifdef _CALL_AIXDESC | |
| struct opd { | |
| unsigned long ip; | |
| unsigned long toc; | |
| unsigned long env; | |
| }; | |
| static struct opd bad_opd = { | |
| .ip = BAD_NIP, | |
| }; | |
| #define BAD_FUNC (&bad_opd) | |
| #else | |
| #define BAD_FUNC BAD_NIP | |
| #endif | |
| int test_wild_bctr(void) | |
| { | |
| int (*func_ptr)(void); | |
| struct sigaction segv = { | |
| .sa_sigaction = segv_handler, | |
| .sa_flags = SA_SIGINFO | |
| }; | |
| struct sigaction usr2 = { | |
| .sa_sigaction = usr2_handler, | |
| .sa_flags = SA_SIGINFO | |
| }; | |
| FAIL_IF(sigaction(SIGSEGV, &segv, NULL)); | |
| FAIL_IF(sigaction(SIGUSR2, &usr2, NULL)); | |
| bzero(&signal_regs, sizeof(signal_regs)); | |
| if (setjmp(setjmp_env) == 0) { | |
| func_ptr = ok; | |
| func_ptr(); | |
| kill(getpid(), SIGUSR2); | |
| printf("Regs before:\n"); | |
| dump_regs(); | |
| bzero(&signal_regs, sizeof(signal_regs)); | |
| poison_regs(); | |
| func_ptr = (int (*)(void))BAD_FUNC; | |
| func_ptr(); | |
| FAIL_IF(1); /* we didn't segv? */ | |
| } | |
| FAIL_IF(signal_regs.nip != BAD_NIP); | |
| printf("All good - took SEGV as expected branching to 0x%llx\n", BAD_NIP); | |
| dump_regs(); | |
| FAIL_IF(check_regs()); | |
| return 0; | |
| } | |
| int main(void) | |
| { | |
| return test_harness(test_wild_bctr, "wild_bctr"); | |
| } |