#define pr_fmt(fmt) "rethook: " fmt
#include <linux/bug.h>
#include <linux/kallsyms.h>
#include <linux/kprobes.h>
#include <linux/preempt.h>
#include <linux/rethook.h>
#include <linux/slab.h>
void rethook_flush_task(struct task_struct *tk)
{
struct rethook_node *rhn;
struct llist_node *node;
node = __llist_del_all(&tk->rethooks);
while (node) {
rhn = container_of(node, struct rethook_node, llist);
node = node->next;
preempt_disable();
rethook_recycle(rhn);
preempt_enable();
}
}
static void rethook_free_rcu(struct rcu_head *head)
{
struct rethook *rh = container_of(head, struct rethook, rcu);
objpool_fini(&rh->pool);
}
void rethook_stop(struct rethook *rh)
{
rcu_assign_pointer(rh->handler, NULL);
}
void rethook_free(struct rethook *rh)
{
rethook_stop(rh);
call_rcu(&rh->rcu, rethook_free_rcu);
}
static int rethook_init_node(void *nod, void *context)
{
struct rethook_node *node = nod;
node->rethook = context;
return 0;
}
static int rethook_fini_pool(struct objpool_head *head, void *context)
{
kfree(context);
return 0;
}
static inline rethook_handler_t rethook_get_handler(struct rethook *rh)
{
return (rethook_handler_t)rcu_dereference_check(rh->handler,
rcu_read_lock_any_held());
}
struct rethook *rethook_alloc(void *data, rethook_handler_t handler,
int size, int num)
{
struct rethook *rh;
if (!handler || num <= 0 || size < sizeof(struct rethook_node))
return ERR_PTR(-EINVAL);
rh = kzalloc_obj(struct rethook);
if (!rh)
return ERR_PTR(-ENOMEM);
rh->data = data;
rcu_assign_pointer(rh->handler, handler);
if (objpool_init(&rh->pool, num, size, GFP_KERNEL, rh,
rethook_init_node, rethook_fini_pool)) {
kfree(rh);
return ERR_PTR(-ENOMEM);
}
return rh;
}
static void free_rethook_node_rcu(struct rcu_head *head)
{
struct rethook_node *node = container_of(head, struct rethook_node, rcu);
struct rethook *rh = node->rethook;
objpool_drop(node, &rh->pool);
}
void rethook_recycle(struct rethook_node *node)
{
rethook_handler_t handler;
handler = rethook_get_handler(node->rethook);
if (likely(handler))
objpool_push(node, &node->rethook->pool);
else
call_rcu(&node->rcu, free_rethook_node_rcu);
}
NOKPROBE_SYMBOL(rethook_recycle);
struct rethook_node *rethook_try_get(struct rethook *rh)
{
rethook_handler_t handler = rethook_get_handler(rh);
if (unlikely(!handler))
return NULL;
#if defined(CONFIG_FTRACE_VALIDATE_RCU_IS_WATCHING) || defined(CONFIG_KPROBE_EVENTS_ON_NOTRACE)
if (unlikely(!rcu_is_watching()))
return NULL;
#endif
return (struct rethook_node *)objpool_pop(&rh->pool);
}
NOKPROBE_SYMBOL(rethook_try_get);
void rethook_hook(struct rethook_node *node, struct pt_regs *regs, bool mcount)
{
arch_rethook_prepare(node, regs, mcount);
__llist_add(&node->llist, ¤t->rethooks);
}
NOKPROBE_SYMBOL(rethook_hook);
static unsigned long __rethook_find_ret_addr(struct task_struct *tsk,
struct llist_node **cur)
{
struct rethook_node *rh = NULL;
struct llist_node *node = *cur;
if (!node)
node = tsk->rethooks.first;
else
node = node->next;
while (node) {
rh = container_of(node, struct rethook_node, llist);
if (rh->ret_addr != (unsigned long)arch_rethook_trampoline) {
*cur = node;
return rh->ret_addr;
}
node = node->next;
}
return 0;
}
NOKPROBE_SYMBOL(__rethook_find_ret_addr);
unsigned long rethook_find_ret_addr(struct task_struct *tsk, unsigned long frame,
struct llist_node **cur)
{
struct rethook_node *rhn = NULL;
unsigned long ret;
if (WARN_ON_ONCE(!cur))
return 0;
if (tsk != current && task_is_running(tsk))
return 0;
do {
ret = __rethook_find_ret_addr(tsk, cur);
if (!ret)
break;
rhn = container_of(*cur, struct rethook_node, llist);
} while (rhn->frame != frame);
return ret;
}
NOKPROBE_SYMBOL(rethook_find_ret_addr);
void __weak arch_rethook_fixup_return(struct pt_regs *regs,
unsigned long correct_ret_addr)
{
}
unsigned long rethook_trampoline_handler(struct pt_regs *regs,
unsigned long frame)
{
struct llist_node *first, *node = NULL;
unsigned long correct_ret_addr;
rethook_handler_t handler;
struct rethook_node *rhn;
correct_ret_addr = __rethook_find_ret_addr(current, &node);
if (!correct_ret_addr) {
pr_err("rethook: Return address not found! Maybe there is a bug in the kernel\n");
BUG_ON(1);
}
instruction_pointer_set(regs, correct_ret_addr);
preempt_disable_notrace();
first = current->rethooks.first;
while (first) {
rhn = container_of(first, struct rethook_node, llist);
if (WARN_ON_ONCE(rhn->frame != frame))
break;
handler = rethook_get_handler(rhn->rethook);
if (handler)
handler(rhn, rhn->rethook->data,
correct_ret_addr, regs);
if (first == node)
break;
first = first->next;
}
arch_rethook_fixup_return(regs, correct_ret_addr);
first = current->rethooks.first;
current->rethooks.first = node->next;
node->next = NULL;
while (first) {
rhn = container_of(first, struct rethook_node, llist);
first = first->next;
rethook_recycle(rhn);
}
preempt_enable_notrace();
return correct_ret_addr;
}
NOKPROBE_SYMBOL(rethook_trampoline_handler);