/* * Adjust the displacement if the instruction uses the %rip-relative * addressing mode. * If it does, Return the address of the 32-bit displacement word. * If not, return null. * Only applicable to 64-bit x86. */ static void __kprobes fix_riprel(struct kprobe *p) { #ifdef CONFIG_X86_64 struct insn insn; kernel_insn_init(&insn, p->ainsn.insn); if (insn_rip_relative(&insn)) { s64 newdisp; u8 *disp; insn_get_displacement(&insn); /* * The copied instruction uses the %rip-relative addressing * mode. Adjust the displacement for the difference between * the original location of this instruction and the location * of the copy that will actually be run. The tricky bit here * is making sure that the sign extension happens correctly in * this calculation, since we need a signed 32-bit result to * be sign-extended to 64 bits when it's added to the %rip * value and yield the same 64-bit result that the sign- * extension of the original signed 32-bit displacement would * have given. */ newdisp = (u8 *) p->addr + (s64) insn.displacement.value - (u8 *) p->ainsn.insn; BUG_ON((s64) (s32) newdisp != newdisp); /* Sanity check. */ disp = (u8 *) p->ainsn.insn + insn_offset_displacement(&insn); *(s32 *) disp = (s32) newdisp; } #endif }
/* * Copy an instruction and adjust the displacement if the instruction * uses the %rip-relative addressing mode. * If it does, Return the address of the 32-bit displacement word. * If not, return null. * Only applicable to 64-bit x86. */ int __copy_instruction(u8 *dest, u8 *src) { struct insn insn; kprobe_opcode_t buf[MAX_INSN_SIZE]; int length; unsigned long recovered_insn = recover_probed_instruction(buf, (unsigned long)src); if (!recovered_insn) return 0; kernel_insn_init(&insn, (void *)recovered_insn, MAX_INSN_SIZE); insn_get_length(&insn); length = insn.length; /* Another subsystem puts a breakpoint, failed to recover */ if (insn.opcode.bytes[0] == BREAKPOINT_INSTRUCTION) return 0; pax_open_kernel(); memcpy(dest, insn.kaddr, length); pax_close_kernel(); #ifdef CONFIG_X86_64 if (insn_rip_relative(&insn)) { s64 newdisp; u8 *disp; kernel_insn_init(&insn, dest, length); insn_get_displacement(&insn); /* * The copied instruction uses the %rip-relative addressing * mode. Adjust the displacement for the difference between * the original location of this instruction and the location * of the copy that will actually be run. The tricky bit here * is making sure that the sign extension happens correctly in * this calculation, since we need a signed 32-bit result to * be sign-extended to 64 bits when it's added to the %rip * value and yield the same 64-bit result that the sign- * extension of the original signed 32-bit displacement would * have given. */ newdisp = (u8 *) src + (s64) insn.displacement.value - (u8 *) dest; if ((s64) (s32) newdisp != newdisp) { pr_err("Kprobes error: new displacement does not fit into s32 (%llx)\n", newdisp); pr_err("\tSrc: %p, Dest: %p, old disp: %x\n", src, dest, insn.displacement.value); return 0; } disp = (u8 *) dest + insn_offset_displacement(&insn); pax_open_kernel(); *(s32 *) disp = (s32) newdisp; pax_close_kernel(); } #endif return length; }
/* * Copy an instruction with recovering modified instruction by kprobes * and adjust the displacement if the instruction uses the %rip-relative * addressing mode. Note that since @real will be the final place of copied * instruction, displacement must be adjust by @real, not @dest. * This returns the length of copied instruction, or 0 if it has an error. */ int __copy_instruction(u8 *dest, u8 *src, u8 *real, struct insn *insn) { kprobe_opcode_t buf[MAX_INSN_SIZE]; unsigned long recovered_insn = recover_probed_instruction(buf, (unsigned long)src); if (!recovered_insn || !insn) return 0; /* This can access kernel text if given address is not recovered */ if (probe_kernel_read(dest, (void *)recovered_insn, MAX_INSN_SIZE)) return 0; kernel_insn_init(insn, dest, MAX_INSN_SIZE); insn_get_length(insn); /* Another subsystem puts a breakpoint, failed to recover */ if (insn->opcode.bytes[0] == BREAKPOINT_INSTRUCTION) return 0; /* We should not singlestep on the exception masking instructions */ if (insn_masking_exception(insn)) return 0; #ifdef CONFIG_X86_64 /* Only x86_64 has RIP relative instructions */ if (insn_rip_relative(insn)) { s64 newdisp; u8 *disp; /* * The copied instruction uses the %rip-relative addressing * mode. Adjust the displacement for the difference between * the original location of this instruction and the location * of the copy that will actually be run. The tricky bit here * is making sure that the sign extension happens correctly in * this calculation, since we need a signed 32-bit result to * be sign-extended to 64 bits when it's added to the %rip * value and yield the same 64-bit result that the sign- * extension of the original signed 32-bit displacement would * have given. */ newdisp = (u8 *) src + (s64) insn->displacement.value - (u8 *) real; if ((s64) (s32) newdisp != newdisp) { pr_err("Kprobes error: new displacement does not fit into s32 (%llx)\n", newdisp); return 0; } disp = (u8 *) dest + insn_offset_displacement(insn); *(s32 *) disp = (s32) newdisp; } #endif return insn->length; }
void copy_and_fixup_insn(struct insn *src_insn, void *dest, const struct kernsym *func) { u32 *to_fixup; unsigned long addr; BUG_ON(src_insn->length == 0); memcpy((void *)dest, (const void *)src_insn->kaddr, src_insn->length); if (src_insn->opcode.bytes[0] == OP_CALL_REL32 || src_insn->opcode.bytes[0] == OP_JMP_REL32) { addr = (unsigned long)CODE_ADDR_FROM_OFFSET( src_insn->kaddr, src_insn->length, src_insn->immediate.value); if (addr >= (unsigned long)func->addr && addr < (unsigned long)func->addr + func->size) return; to_fixup = (u32 *)((unsigned long)dest + insn_offset_immediate(src_insn)); *to_fixup = CODE_OFFSET_FROM_ADDR(dest, src_insn->length, (void *)addr); return; } #ifdef CONFIG_X86_64 if (!insn_rip_relative(src_insn)) return; addr = (unsigned long)CODE_ADDR_FROM_OFFSET( src_insn->kaddr, src_insn->length, src_insn->displacement.value); if (addr >= (unsigned long)func->addr && addr < (unsigned long)func->addr + func->size) return; to_fixup = (u32 *)((unsigned long)dest + insn_offset_displacement(src_insn)); *to_fixup = CODE_OFFSET_FROM_ADDR(dest, src_insn->length, (void *)addr); #endif return; }
/* * Copy an instruction and adjust the displacement if the instruction * uses the %rip-relative addressing mode. * If it does, Return the address of the 32-bit displacement word. * If not, return null. * Only applicable to 64-bit x86. */ static int __kprobes __copy_instruction(u8 *dest, u8 *src, int recover) { struct insn insn; int ret; kprobe_opcode_t buf[MAX_INSN_SIZE]; kernel_insn_init(&insn, src); if (recover) { insn_get_opcode(&insn); if (insn.opcode.bytes[0] == BREAKPOINT_INSTRUCTION) { ret = recover_probed_instruction(buf, (unsigned long)src); if (ret) return 0; kernel_insn_init(&insn, buf); } } insn_get_length(&insn); memcpy(dest, insn.kaddr, insn.length); #ifdef CONFIG_X86_64 if (insn_rip_relative(&insn)) { s64 newdisp; u8 *disp; kernel_insn_init(&insn, dest); insn_get_displacement(&insn); /* * The copied instruction uses the %rip-relative addressing * mode. Adjust the displacement for the difference between * the original location of this instruction and the location * of the copy that will actually be run. The tricky bit here * is making sure that the sign extension happens correctly in * this calculation, since we need a signed 32-bit result to * be sign-extended to 64 bits when it's added to the %rip * value and yield the same 64-bit result that the sign- * extension of the original signed 32-bit displacement would * have given. */ newdisp = (u8 *) src + (s64) insn.displacement.value - (u8 *) dest; BUG_ON((s64) (s32) newdisp != newdisp); /* Sanity check. */ disp = (u8 *) dest + insn_offset_displacement(&insn); *(s32 *) disp = (s32) newdisp; } #endif return insn.length; }
/* * If arch_uprobe->insn doesn't use rip-relative addressing, return * immediately. Otherwise, rewrite the instruction so that it accesses * its memory operand indirectly through a scratch register. Set * arch_uprobe->fixups and arch_uprobe->rip_rela_target_address * accordingly. (The contents of the scratch register will be saved * before we single-step the modified instruction, and restored * afterward.) * * We do this because a rip-relative instruction can access only a * relatively small area (+/- 2 GB from the instruction), and the XOL * area typically lies beyond that area. At least for instructions * that store to memory, we can't execute the original instruction * and "fix things up" later, because the misdirected store could be * disastrous. * * Some useful facts about rip-relative instructions: * * - There's always a modrm byte. * - There's never a SIB byte. * - The displacement is always 4 bytes. */ static void handle_riprel_insn(struct arch_uprobe *auprobe, struct mm_struct *mm, struct insn *insn) { u8 *cursor; u8 reg; if (mm->context.ia32_compat) return; auprobe->rip_rela_target_address = 0x0; if (!insn_rip_relative(insn)) return; /* * insn_rip_relative() would have decoded rex_prefix, modrm. * Clear REX.b bit (extension of MODRM.rm field): * we want to encode rax/rcx, not r8/r9. */ if (insn->rex_prefix.nbytes) { cursor = auprobe->insn + insn_offset_rex_prefix(insn); *cursor &= 0xfe; /* Clearing REX.B bit */ } /* * Point cursor at the modrm byte. The next 4 bytes are the * displacement. Beyond the displacement, for some instructions, * is the immediate operand. */ cursor = auprobe->insn + insn_offset_modrm(insn); insn_get_length(insn); /* * Convert from rip-relative addressing to indirect addressing * via a scratch register. Change the r/m field from 0x5 (%rip) * to 0x0 (%rax) or 0x1 (%rcx), and squeeze out the offset field. */ reg = MODRM_REG(insn); if (reg == 0) { /* * The register operand (if any) is either the A register * (%rax, %eax, etc.) or (if the 0x4 bit is set in the * REX prefix) %r8. In any case, we know the C register * is NOT the register operand, so we use %rcx (register * #1) for the scratch register. */ auprobe->fixups = UPROBE_FIX_RIP_CX; /* Change modrm from 00 000 101 to 00 000 001. */ *cursor = 0x1; } else { /* Use %rax (register #0) for the scratch register. */ auprobe->fixups = UPROBE_FIX_RIP_AX; /* Change modrm from 00 xxx 101 to 00 xxx 000 */ *cursor = (reg << 3); } /* Target address = address of next instruction + (signed) offset */ auprobe->rip_rela_target_address = (long)insn->length + insn->displacement.value; /* Displacement field is gone; slide immediate field (if any) over. */ if (insn->immediate.nbytes) { cursor++; memmove(cursor, cursor + insn->displacement.nbytes, insn->immediate.nbytes); } return; }