| // SPDX-License-Identifier: GPL-2.0 | |
| #ifndef pr_fmt | |
| #define pr_fmt(fmt) "stackprot: " fmt | |
| #endif | |
| #include <linux/export.h> | |
| #include <linux/hex.h> | |
| #include <linux/uaccess.h> | |
| #include <linux/printk.h> | |
| #include <asm/abs_lowcore.h> | |
| #include <asm/sections.h> | |
| #include <asm/machine.h> | |
| #include <asm/asm-offsets.h> | |
| #include <asm/arch-stackprotector.h> | |
| #ifdef __DECOMPRESSOR | |
| #define DEBUGP boot_debug | |
| #define EMERGP boot_emerg | |
| #define PANIC boot_panic | |
| #else /* __DECOMPRESSOR */ | |
| #define DEBUGP pr_debug | |
| #define EMERGP pr_emerg | |
| #define PANIC panic | |
| #endif /* __DECOMPRESSOR */ | |
| int __bootdata_preserved(stack_protector_debug); | |
| unsigned long __stack_chk_guard; | |
| EXPORT_SYMBOL(__stack_chk_guard); | |
| struct insn_ril { | |
| u8 opc1 : 8; | |
| u8 r1 : 4; | |
| u8 opc2 : 4; | |
| u32 imm; | |
| } __packed; | |
| /* | |
| * Convert a virtual instruction address to a real instruction address. The | |
| * decompressor needs to patch instructions within the kernel image based on | |
| * their virtual addresses, while dynamic address translation is still | |
| * disabled. Therefore a translation from virtual kernel image addresses to | |
| * the corresponding physical addresses is required. | |
| * | |
| * After dynamic address translation is enabled and when the kernel needs to | |
| * patch instructions such a translation is not required since the addresses | |
| * are identical. | |
| */ | |
| static struct insn_ril *vaddress_to_insn(unsigned long vaddress) | |
| { | |
| #ifdef __DECOMPRESSOR | |
| return (struct insn_ril *)__kernel_pa(vaddress); | |
| #else | |
| return (struct insn_ril *)vaddress; | |
| #endif | |
| } | |
| static unsigned long insn_to_vaddress(struct insn_ril *insn) | |
| { | |
| #ifdef __DECOMPRESSOR | |
| return (unsigned long)__kernel_va(insn); | |
| #else | |
| return (unsigned long)insn; | |
| #endif | |
| } | |
| #define INSN_RIL_STRING_SIZE (sizeof(struct insn_ril) * 2 + 1) | |
| static void insn_ril_to_string(char *str, struct insn_ril *insn) | |
| { | |
| u8 *ptr = (u8 *)insn; | |
| int i; | |
| for (i = 0; i < sizeof(*insn); i++) | |
| hex_byte_pack(&str[2 * i], ptr[i]); | |
| str[2 * i] = 0; | |
| } | |
| static void stack_protector_dump(struct insn_ril *old, struct insn_ril *new) | |
| { | |
| char ostr[INSN_RIL_STRING_SIZE]; | |
| char nstr[INSN_RIL_STRING_SIZE]; | |
| insn_ril_to_string(ostr, old); | |
| insn_ril_to_string(nstr, new); | |
| DEBUGP("%016lx: %s -> %s\n", insn_to_vaddress(old), ostr, nstr); | |
| } | |
| static int stack_protector_verify(struct insn_ril *insn, unsigned long kernel_start) | |
| { | |
| char istr[INSN_RIL_STRING_SIZE]; | |
| unsigned long vaddress, offset; | |
| /* larl */ | |
| if (insn->opc1 == 0xc0 && insn->opc2 == 0x0) | |
| return 0; | |
| /* lgrl */ | |
| if (insn->opc1 == 0xc4 && insn->opc2 == 0x8) | |
| return 0; | |
| insn_ril_to_string(istr, insn); | |
| vaddress = insn_to_vaddress(insn); | |
| if (__is_defined(__DECOMPRESSOR)) { | |
| offset = (unsigned long)insn - kernel_start + TEXT_OFFSET; | |
| EMERGP("Unexpected instruction at %016lx/%016lx: %s\n", vaddress, offset, istr); | |
| PANIC("Stackprotector error\n"); | |
| } else { | |
| EMERGP("Unexpected instruction at %016lx: %s\n", vaddress, istr); | |
| } | |
| return -EINVAL; | |
| } | |
| int __stack_protector_apply(unsigned long *start, unsigned long *end, unsigned long kernel_start) | |
| { | |
| unsigned long canary, *loc; | |
| struct insn_ril *insn, new; | |
| int rc; | |
| /* | |
| * Convert LARL/LGRL instructions to LLILF so register R1 contains the | |
| * address of the per-cpu / per-process stack canary: | |
| * | |
| * LARL/LGRL R1,__stack_chk_guard => LLILF R1,__lc_stack_canary | |
| */ | |
| canary = __LC_STACK_CANARY; | |
| if (machine_has_relocated_lowcore()) | |
| canary += LOWCORE_ALT_ADDRESS; | |
| for (loc = start; loc < end; loc++) { | |
| insn = vaddress_to_insn(*loc); | |
| rc = stack_protector_verify(insn, kernel_start); | |
| if (rc) | |
| return rc; | |
| new = *insn; | |
| new.opc1 = 0xc0; | |
| new.opc2 = 0xf; | |
| new.imm = canary; | |
| if (stack_protector_debug) | |
| stack_protector_dump(insn, &new); | |
| s390_kernel_write(insn, &new, sizeof(*insn)); | |
| } | |
| return 0; | |
| } | |
| #ifdef __DECOMPRESSOR | |
| void __stack_protector_apply_early(unsigned long kernel_start) | |
| { | |
| unsigned long *start, *end; | |
| start = (unsigned long *)vmlinux.stack_prot_start; | |
| end = (unsigned long *)vmlinux.stack_prot_end; | |
| __stack_protector_apply(start, end, kernel_start); | |
| } | |
| #endif |