linux-kernelkernel-modulekprobe

kprobe on getdents64() fails


I'm learning Linux Kernel programming and I'm learning kernel probes. I tried to install kprobe on getdents64() syscall and read it's arguments but I always get -1 in the third argument, count. I tried different codes and searched and read several sources but couldn't find the solution.

Here is the prototype of getdents64() syscall,

// https://elixir.bootlin.com/linux/latest/source/tools/include/nolibc/sys.h#L366
int getdents64(int fd, struct linux_dirent64 *dirp, int count)

Below is the output of /proc/kallsyms,

user@xubun2204:~$ sudo grep getdents /proc/kallsyms 
[sudo] password for user: 
ffffffff955b7c70 T __ia32_compat_sys_getdents
ffffffff955b83c0 T __ia32_sys_getdents64
ffffffff955b84e0 T __ia32_sys_getdents
ffffffff955b8920 T __x64_compat_sys_getdents
ffffffff955b8a40 T __x64_sys_getdents64
ffffffff955b8b60 T __x64_sys_getdents
ffffffff9723f620 d event_exit__getdents64
ffffffff9723f6c0 d event_enter__getdents64
ffffffff9723f760 d __syscall_meta__getdents64
ffffffff9723f7a0 d args__getdents64
ffffffff9723f7c0 d types__getdents64
ffffffff9723f7e0 d event_exit__getdents
ffffffff9723f880 d event_enter__getdents
ffffffff9723f920 d __syscall_meta__getdents
ffffffff9723f960 d args__getdents
ffffffff9723f980 d types__getdents
ffffffff97702f00 d __event_exit__getdents64
ffffffff97702f08 d __event_enter__getdents64
ffffffff97702f10 d __event_exit__getdents
ffffffff97702f18 d __event_enter__getdents
ffffffff977059a8 d __p_syscall_meta__getdents64
ffffffff977059b0 d __p_syscall_meta__getdents
ffffffff97708fb0 d _eil_addr___x64_compat_sys_getdents
ffffffff97708fc0 d _eil_addr___ia32_compat_sys_getdents
ffffffff97708ff0 d _eil_addr___ia32_sys_getdents64
ffffffff97709000 d _eil_addr___x64_sys_getdents64
ffffffff97709010 d _eil_addr___ia32_sys_getdents
ffffffff97709020 d _eil_addr___x64_sys_getdents

and below is part of my code,

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kprobes.h>
#include <linux/unistd.h>

static struct kprobe kp;

static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
    // Access the third argument of the getdents64 syscall on x86_64
    unsigned long count = regs->dx;
    printk("kprobe: getdents64 called with count = %ld\n", count);
    return 0;
}


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

    if (register_kprobe(&kp) < 0) {
        printk("register_kprobe failed\n");
        return -1;
    }
    printk("kprobe registered\n");
    return 0;
}


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

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

I'm building it with the following Makefile,

obj-m += hidproc.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

After building the project I load it with insmod and then check the kernel logs,

# insmod hidproc.ko
# tail -f /var/log/kern.log
 kprobe registered
 kprobe: getdents64 called with count = -1
 kprobe: getdents64 called with count = -1
 kprobe: getdents64 called with count = -1
 kprobe: getdents64 called with count = -1
 kprobe: getdents64 called with count = -1
 kprobe: getdents64 called with count = -1
 kprobe: getdents64 called with count = -1
 kprobe: getdents64 called with count = -1
 kprobe unregistered

Finally I'm running XUbuntu 22.04 running Linux xubun2204 5.15.0-107-generic #117-Ubuntu SMP Fri Apr 26 12:26:49 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux.

How can I hook into a Linux syscall with kprobe and successfully read the arguments? The third argument is an integer and should be passed by value.

I tried to think of anything I could provide to have a good an acceptable question by the community and up to stackoverflow standards, if there is any shortcoming please let me know.

Thank you


Solution

  • @Luke's answer was correct. You need to dereference the second pt_struct that is passed to the probe handler for syscall arguments.

    unsigned long count = ((struct pt_regs*)regs->di)->dx;
    

    I tried this on Ubuntu 22.04 and 24.04 with kernel 5.x and 6.x and worked fine.

    Thanks