clinuxlinux-kernelkprobe

Cannot read syscall arguments from a kprobe handler


I'm installing a kprobe on sys_kill and I want to monitor PIDs and signal numbers.

While I can install the kprobe and get some data in dmesg, the data looks wrong and I cannot make sense of it.

Below is the code that I have:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kprobes.h>
#include <linux/ptrace.h>
#include <linux/signal.h>
#include <linux/sched.h>

static struct kprobe kp;


static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
    pid_t pid = (pid_t)regs->di;
    int sig = (int)regs->si; 

    printk(KERN_INFO "kprobe pre_handler: kill syscall - PID: %d, Signal: %d\n", pid, sig);

    return 0;
}

static void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags)
{
    pid_t pid = (pid_t)regs->di;
    int sig = (int)regs->si;  

    printk(KERN_INFO "kprobe post_handler: kill syscall - PID: %d, Signal: %d\n", pid, sig);
}


static int __init kprobe_init(void)
{
    kp.symbol_name = "__x64_sys_kill";
    kp.pre_handler = handler_pre;
    kp.post_handler = handler_post;

    int ret = register_kprobe(&kp);
    if (ret < 0) {
        printk(KERN_INFO "register_kprobe failed, returned %d\n", ret);
        return ret;
    }
    printk(KERN_INFO "kprobe registered for __x64_sys_kill\n");
    return 0;
}

static void __exit kprobe_exit(void)
{
    unregister_kprobe(&kp);
    printk(KERN_INFO "kprobe unregistered\n");
}

module_init(kprobe_init)
module_exit(kprobe_exit)
MODULE_LICENSE("GPL");

It gives me the following output:

kernel: [ 2605.007657] kprobe pre_handler: kill syscall - PID: 6029144, Signal: 62
kernel: [ 2605.007658] kprobe post_handler: kill syscall - PID: 6029144, Signal: 62
kernel: [ 2605.007660] kprobe pre_handler: kill syscall - PID: 6029144, Signal: 62
kernel: [ 2605.007661] kprobe post_handler: kill syscall - PID: 6029144, Signal: 62
kernel: [ 2605.007662] kprobe pre_handler: kill syscall - PID: 6029144, Signal: 62
kernel: [ 2605.007663] kprobe post_handler: kill syscall - PID: 6029144, Signal: 62
kernel: [ 2605.007666] kprobe pre_handler: kill syscall - PID: 6029144, Signal: 62
kernel: [ 2605.007667] kprobe post_handler: kill syscall - PID: 6029144, Signal: 62
kernel: [ 2605.007668] kprobe pre_handler: kill syscall - PID: 6029144, Signal: 62
kernel: [ 2605.007669] kprobe post_handler: kill syscall - PID: 6029144, Signal: 62
kernel: [ 2605.007671] kprobe pre_handler: kill syscall - PID: 6029144, Signal: 62
kernel: [ 2605.007672] kprobe post_handler: kill syscall - PID: 6029144, Signal: 62
kernel: [ 2605.007674] kprobe pre_handler: kill syscall - PID: 6029144, Signal: 62
kernel: [ 2605.007675] kprobe post_handler: kill syscall - PID: 6029144, Signal: 62
kernel: [ 2605.007676] kprobe pre_handler: kill syscall - PID: 6029144, Signal: 62
kernel: [ 2605.007677] kprobe post_handler: kill syscall - PID: 6029144, Signal: 62

I tried different approaches:

    long syscall_nr = regs->orig_ax;

    // Check if it is the kill syscall (syscall number 62 on x86_64)
    if (syscall_nr == __NR_kill) {
        // NEVER GOT HERE
    }
}

So I thought I ask for help here. I do not do this as part of a project, my goal is to learn kernel programming and Linux kernel internals.


Solution

  • Syscall wrappers (__x64_sys_xxx) created by the standard SYSCALL_DEFINEn(...) macros only take a single argument: a pointer to a struct pt_regs holding the user registers. See also this other answer of mine where I explain this.

    Your kprobe handlers get the kernel pt_regs, but you want the user pt_regs so you need to dereference regs->di to get ahold of it:

    static int handler_pre(struct kprobe *p, struct pt_regs *regs)
    {
        struct pt_regs *user_regs = (struct pt_regs *)regs->di;
        pid_t pid = (pid_t)user_regs->di;
        int sig = (int)user_regs->si; 
    
        printk(KERN_INFO "kprobe pre_handler: kill syscall - PID: %d, Signal: %d\n", pid, sig);
    
        return 0;
    }