lists.openwall.net | lists / announce owl-users owl-dev john-users john-dev passwdqc-users yescrypt popa3d-users / oss-security kernel-hardening musl sabotage tlsify passwords / crypt-dev xvendor / Bugtraq Full-Disclosure linux-kernel linux-netdev linux-ext4 linux-hardening PHC | |
Open Source and information security mailing list archives
| ||
|
Date: Tue, 19 Apr 2022 17:42:35 -0700 From: joao@...rdrivepizza.com To: linux-kernel@...r.kernel.org, linux-hardening@...r.kernel.org Cc: joao@...rdrivepizza.com, peterz@...radead.org, jpoimboe@...hat.com, andrew.cooper3@...rix.com, keescook@...omium.org, samitolvanen@...gle.com, mark.rutland@....com, hjl.tools@...il.com, alyssa.milburn@...ux.intel.com, ndesaulniers@...gle.com, gabriel.gomes@...ux.intel.com, rick.p.edgecombe@...el.com Subject: [RFC PATCH 05/11] x86/text-patching: Support FineIBT text-patching From: Joao Moreira <joao@...rdrivepizza.com> When patching a direct branch into text, consider that the target may have a FineIBT hash check sequence and then sum the respective offset to the branch target address if this is the case. This is needed to support static calls. Signed-off-by: Joao Moreira <joao@...rdrivepizza.com> Tinkered-from-patches-by: Peter Zijlstra <peterz@...radead.org> --- arch/x86/include/asm/text-patching.h | 92 +++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/arch/x86/include/asm/text-patching.h b/arch/x86/include/asm/text-patching.h index d20ab0921480..a450761fae62 100644 --- a/arch/x86/include/asm/text-patching.h +++ b/arch/x86/include/asm/text-patching.h @@ -4,6 +4,7 @@ #include <linux/types.h> #include <linux/stddef.h> +#include <linux/uaccess.h> #include <asm/ptrace.h> struct paravirt_patch_site; @@ -66,6 +67,12 @@ extern void text_poke_finish(void); #define JMP8_INSN_SIZE 2 #define JMP8_INSN_OPCODE 0xEB +#define SUB_INSN_SIZE 7 +#define SUB_INSN_OPCODE 0x41 + +#define JE_INSN_SIZE 2 +#define JE_INSN_OPCODE 0x74 + #define DISP32_SIZE 4 static __always_inline int text_opcode_size(u8 opcode) @@ -96,6 +103,83 @@ union text_poke_insn { } __attribute__((packed)); }; +#ifdef CONFIG_X86_KERNEL_FINEIBT +#define FINEIBT_FIXUP 18 +// AFTER_FINEIBT = FINEIBT_FIXUP - ENDBR_LEN - XOR_LEN - JMP LEN +#define AFTER_FINEIBT FINEIBT_FIXUP - ENDBR_INSN_SIZE - 3 - 2 + +/// XXX: THIS IS *NOT* PROPERLY TESTED! +/// I did stumble on any scenario where this was needed while testing FineIBT, +/// Yet, I'm keeping this here for concept/future reference. - If we can't fix +/// the displacement, then the branch will always stumble on the FineIBT hash +/// check. To prevent that, patch the FineIBT hash check with nops. +static __always_inline +void bypass_fineibt_sequence(void *insn) { + static const char code[14] = { 0x4d, 0x31, 0xdb, 0xeb, AFTER_FINEIBT, + BYTES_NOP8, BYTES_NOP1 }; + if (unlikely(system_state == SYSTEM_BOOTING)) { + text_poke_early(insn + 4, code, 14); + text_poke_early(insn + 11, code, 14); + } + + text_poke_bp(insn + 4, code, 14, NULL); + text_poke_bp(insn + 11, code, 14, NULL); +} + +// Identify if the target address is a FineIBT instruction sequence, which +// should be: +// endbr +// sub $hash, %r11d +// je 1f +// call fineibt_handler (this will eventually be replaced with ud2) +// 1: +static __always_inline +bool __is_fineibt_sequence(const void *addr) { + union text_poke_insn text; + u32 insn; + + // the sequence starts with an endbr + if (get_kernel_nofault(insn, addr) || !(is_endbr(insn))) + return false; + + // then followed by a sub + if (get_kernel_nofault(text, addr+4) || text.opcode != SUB_INSN_OPCODE) + return false; + + // followed by a je + if (get_kernel_nofault(text, addr+11) || text.opcode != JE_INSN_OPCODE) + return false; + + // and finished with a call (which eventually will be an ud2) + if (get_kernel_nofault(text, addr+13) || + text.opcode != CALL_INSN_OPCODE) + return false; + + return true; +} + +// Verify if the branch target is a FineIBT sequence. If yes, fix the target +// to point right after the sequence, preventing crashes. +static __always_inline +void *__text_fix_fineibt_branch_target(const void *addr, void *dest, int size) { + bool fineibt; + s32 disp; + fineibt = __is_fineibt_sequence(dest); + if (!fineibt) + return dest; + + disp = (long) dest - (long) (addr + size) + FINEIBT_FIXUP; + + // if fineibt-fixed displacement doesn't fit as an operand, + // remove fineibt hash check from target. + if (size == 2 && ((disp >> 31) != (disp >> 7))) { + bypass_fineibt_sequence(dest); + return dest; + } + return dest + FINEIBT_FIXUP; +} +#endif + static __always_inline void __text_gen_insn(void *buf, u8 opcode, const void *addr, const void *dest, int size) { @@ -115,7 +199,13 @@ void __text_gen_insn(void *buf, u8 opcode, const void *addr, const void *dest, i insn->opcode = opcode; if (size > 1) { - insn->disp = (long)dest - (long)(addr + size); +#ifdef CONFIG_X86_KERNEL_FINEIBT + void *fineibt_dest = __text_fix_fineibt_branch_target(addr, + (void *) dest, size); + insn->disp = (long) fineibt_dest - (long) (addr + size); +#else + insn->disp = (long) dest - (long) (addr + size); +#endif if (size == 2) { /* * Ensure that for JMP8 the displacement -- 2.35.1
Powered by blists - more mailing lists