#define pr_fmt(fmt) "kprobes: " fmt
#include <linux/kprobes.h>
#include <linux/ptrace.h>
#include <linux/preempt.h>
#include <linux/stop_machine.h>
#include <linux/cpufeature.h>
#include <linux/kdebug.h>
#include <linux/uaccess.h>
#include <linux/extable.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/hardirq.h>
#include <linux/ftrace.h>
#include <linux/execmem.h>
#include <asm/text-patching.h>
#include <asm/set_memory.h>
#include <asm/sections.h>
#include <asm/dis.h>
#include "entry.h"
DEFINE_PER_CPU(struct kprobe *, current_kprobe);
DEFINE_PER_CPU(struct kprobe_ctlblk, kprobe_ctlblk);
struct kretprobe_blackpoint kretprobe_blacklist[] = { };
void *alloc_insn_page(void)
{
void *page;
page = execmem_alloc(EXECMEM_KPROBES, PAGE_SIZE);
if (!page)
return NULL;
set_memory_rox((unsigned long)page, 1);
return page;
}
static void copy_instruction(struct kprobe *p)
{
kprobe_opcode_t insn[MAX_INSN_SIZE];
s64 disp, new_disp;
u64 addr, new_addr;
unsigned int len;
len = insn_length(*p->addr >> 8);
memcpy(&insn, p->addr, len);
p->opcode = insn[0];
if (probe_is_insn_relative_long(&insn[0])) {
disp = *(s32 *)&insn[1];
addr = (u64)(unsigned long)p->addr;
new_addr = (u64)(unsigned long)p->ainsn.insn;
new_disp = ((addr + (disp * 2)) - new_addr) / 2;
*(s32 *)&insn[1] = new_disp;
}
s390_kernel_write(p->ainsn.insn, &insn, len);
}
NOKPROBE_SYMBOL(copy_instruction);
static bool can_probe(unsigned long paddr)
{
unsigned long addr, offset = 0;
kprobe_opcode_t insn;
struct kprobe *kp;
if (paddr & 0x01)
return false;
if (!kallsyms_lookup_size_offset(paddr, NULL, &offset))
return false;
addr = paddr - offset;
while (addr < paddr) {
if (copy_from_kernel_nofault(&insn, (void *)addr, sizeof(insn)))
return false;
if (insn >> 8 == 0) {
if (insn != BREAKPOINT_INSTRUCTION) {
return false;
}
kp = get_kprobe((void *)addr);
if (!kp) {
return false;
}
insn = kp->opcode;
}
addr += insn_length(insn >> 8);
}
return addr == paddr;
}
int arch_prepare_kprobe(struct kprobe *p)
{
if (!can_probe((unsigned long)p->addr))
return -EINVAL;
if (probe_is_prohibited_opcode(p->addr))
return -EINVAL;
p->ainsn.insn = get_insn_slot();
if (!p->ainsn.insn)
return -ENOMEM;
copy_instruction(p);
return 0;
}
NOKPROBE_SYMBOL(arch_prepare_kprobe);
struct swap_insn_args {
struct kprobe *p;
unsigned int arm_kprobe : 1;
};
static int swap_instruction(void *data)
{
struct swap_insn_args *args = data;
struct kprobe *p = args->p;
u16 opc;
opc = args->arm_kprobe ? BREAKPOINT_INSTRUCTION : p->opcode;
s390_kernel_write(p->addr, &opc, sizeof(opc));
return 0;
}
NOKPROBE_SYMBOL(swap_instruction);
void arch_arm_kprobe(struct kprobe *p)
{
struct swap_insn_args args = {.p = p, .arm_kprobe = 1};
if (cpu_has_seq_insn()) {
swap_instruction(&args);
text_poke_sync();
} else {
stop_machine_cpuslocked(swap_instruction, &args, NULL);
}
}
NOKPROBE_SYMBOL(arch_arm_kprobe);
void arch_disarm_kprobe(struct kprobe *p)
{
struct swap_insn_args args = {.p = p, .arm_kprobe = 0};
if (cpu_has_seq_insn()) {
swap_instruction(&args);
text_poke_sync();
} else {
stop_machine_cpuslocked(swap_instruction, &args, NULL);
}
}
NOKPROBE_SYMBOL(arch_disarm_kprobe);
void arch_remove_kprobe(struct kprobe *p)
{
if (!p->ainsn.insn)
return;
free_insn_slot(p->ainsn.insn, 0);
p->ainsn.insn = NULL;
}
NOKPROBE_SYMBOL(arch_remove_kprobe);
static void enable_singlestep(struct kprobe_ctlblk *kcb,
struct pt_regs *regs,
unsigned long ip)
{
union {
struct ctlreg regs[3];
struct {
struct ctlreg control;
struct ctlreg start;
struct ctlreg end;
};
} per_kprobe;
per_kprobe.control.val = PER_EVENT_IFETCH;
per_kprobe.start.val = ip;
per_kprobe.end.val = ip;
__local_ctl_store(9, 11, kcb->kprobe_saved_ctl);
kcb->kprobe_saved_imask = regs->psw.mask &
(PSW_MASK_PER | PSW_MASK_IO | PSW_MASK_EXT);
__local_ctl_load(9, 11, per_kprobe.regs);
regs->psw.mask |= PSW_MASK_PER;
regs->psw.mask &= ~(PSW_MASK_IO | PSW_MASK_EXT);
regs->psw.addr = ip;
}
NOKPROBE_SYMBOL(enable_singlestep);
static void disable_singlestep(struct kprobe_ctlblk *kcb,
struct pt_regs *regs,
unsigned long ip)
{
__local_ctl_load(9, 11, kcb->kprobe_saved_ctl);
regs->psw.mask &= ~PSW_MASK_PER;
regs->psw.mask |= kcb->kprobe_saved_imask;
regs->psw.addr = ip;
}
NOKPROBE_SYMBOL(disable_singlestep);
static void push_kprobe(struct kprobe_ctlblk *kcb, struct kprobe *p)
{
kcb->prev_kprobe.kp = __this_cpu_read(current_kprobe);
kcb->prev_kprobe.status = kcb->kprobe_status;
__this_cpu_write(current_kprobe, p);
}
NOKPROBE_SYMBOL(push_kprobe);
static void pop_kprobe(struct kprobe_ctlblk *kcb)
{
__this_cpu_write(current_kprobe, kcb->prev_kprobe.kp);
kcb->kprobe_status = kcb->prev_kprobe.status;
kcb->prev_kprobe.kp = NULL;
}
NOKPROBE_SYMBOL(pop_kprobe);
static void kprobe_reenter_check(struct kprobe_ctlblk *kcb, struct kprobe *p)
{
switch (kcb->kprobe_status) {
case KPROBE_HIT_SSDONE:
case KPROBE_HIT_ACTIVE:
kprobes_inc_nmissed_count(p);
break;
case KPROBE_HIT_SS:
case KPROBE_REENTER:
default:
pr_err("Failed to recover from reentered kprobes.\n");
dump_kprobe(p);
BUG();
}
}
NOKPROBE_SYMBOL(kprobe_reenter_check);
static int kprobe_handler(struct pt_regs *regs)
{
struct kprobe_ctlblk *kcb;
struct kprobe *p;
preempt_disable();
kcb = get_kprobe_ctlblk();
p = get_kprobe((void *)(regs->psw.addr - 2));
if (p) {
if (kprobe_running()) {
kprobe_reenter_check(kcb, p);
push_kprobe(kcb, p);
kcb->kprobe_status = KPROBE_REENTER;
} else {
push_kprobe(kcb, p);
kcb->kprobe_status = KPROBE_HIT_ACTIVE;
if (p->pre_handler && p->pre_handler(p, regs)) {
pop_kprobe(kcb);
preempt_enable_no_resched();
return 1;
}
kcb->kprobe_status = KPROBE_HIT_SS;
}
enable_singlestep(kcb, regs, (unsigned long) p->ainsn.insn);
return 1;
}
preempt_enable_no_resched();
return 0;
}
NOKPROBE_SYMBOL(kprobe_handler);
static void resume_execution(struct kprobe *p, struct pt_regs *regs)
{
struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();
unsigned long ip = regs->psw.addr;
int fixup = probe_get_fixup_type(p->ainsn.insn);
if (fixup & FIXUP_PSW_NORMAL)
ip += (unsigned long) p->addr - (unsigned long) p->ainsn.insn;
if (fixup & FIXUP_BRANCH_NOT_TAKEN) {
int ilen = insn_length(p->ainsn.insn[0] >> 8);
if (ip - (unsigned long) p->ainsn.insn == ilen)
ip = (unsigned long) p->addr + ilen;
}
if (fixup & FIXUP_RETURN_REGISTER) {
int reg = (p->ainsn.insn[0] & 0xf0) >> 4;
regs->gprs[reg] += (unsigned long) p->addr -
(unsigned long) p->ainsn.insn;
}
disable_singlestep(kcb, regs, ip);
}
NOKPROBE_SYMBOL(resume_execution);
static int post_kprobe_handler(struct pt_regs *regs)
{
struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();
struct kprobe *p = kprobe_running();
if (!p)
return 0;
resume_execution(p, regs);
if (kcb->kprobe_status != KPROBE_REENTER && p->post_handler) {
kcb->kprobe_status = KPROBE_HIT_SSDONE;
p->post_handler(p, regs, 0);
}
pop_kprobe(kcb);
preempt_enable_no_resched();
if (regs->psw.mask & PSW_MASK_PER)
return 0;
return 1;
}
NOKPROBE_SYMBOL(post_kprobe_handler);
static int kprobe_trap_handler(struct pt_regs *regs, int trapnr)
{
struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();
struct kprobe *p = kprobe_running();
switch(kcb->kprobe_status) {
case KPROBE_HIT_SS:
case KPROBE_REENTER:
disable_singlestep(kcb, regs, (unsigned long) p->addr);
pop_kprobe(kcb);
preempt_enable_no_resched();
break;
case KPROBE_HIT_ACTIVE:
case KPROBE_HIT_SSDONE:
if (fixup_exception(regs))
return 1;
break;
default:
break;
}
return 0;
}
NOKPROBE_SYMBOL(kprobe_trap_handler);
int kprobe_fault_handler(struct pt_regs *regs, int trapnr)
{
int ret;
if (regs->psw.mask & (PSW_MASK_IO | PSW_MASK_EXT))
local_irq_disable();
ret = kprobe_trap_handler(regs, trapnr);
if (regs->psw.mask & (PSW_MASK_IO | PSW_MASK_EXT))
local_irq_restore(regs->psw.mask & ~PSW_MASK_PER);
return ret;
}
NOKPROBE_SYMBOL(kprobe_fault_handler);
int kprobe_exceptions_notify(struct notifier_block *self,
unsigned long val, void *data)
{
struct die_args *args = (struct die_args *) data;
struct pt_regs *regs = args->regs;
int ret = NOTIFY_DONE;
if (regs->psw.mask & (PSW_MASK_IO | PSW_MASK_EXT))
local_irq_disable();
switch (val) {
case DIE_BPT:
if (kprobe_handler(regs))
ret = NOTIFY_STOP;
break;
case DIE_SSTEP:
if (post_kprobe_handler(regs))
ret = NOTIFY_STOP;
break;
case DIE_TRAP:
if (!preemptible() && kprobe_running() &&
kprobe_trap_handler(regs, args->trapnr))
ret = NOTIFY_STOP;
break;
default:
break;
}
if (regs->psw.mask & (PSW_MASK_IO | PSW_MASK_EXT))
local_irq_restore(regs->psw.mask & ~PSW_MASK_PER);
return ret;
}
NOKPROBE_SYMBOL(kprobe_exceptions_notify);
int __init arch_init_kprobes(void)
{
return 0;
}
int __init arch_populate_kprobe_blacklist(void)
{
return kprobe_add_area_blacklist((unsigned long)__irqentry_text_start,
(unsigned long)__irqentry_text_end);
}
int arch_trampoline_kprobe(struct kprobe *p)
{
return 0;
}
NOKPROBE_SYMBOL(arch_trampoline_kprobe);