0

I know there has been a question about this in the past, but I didn't find a solution.

I'm writing the next kernel module to trace do_exec calls. AFAIK every new process image (not creation) should be loaded like this, so I figure I can trace down all executions with this jprobe.

Unfortunately, the only outputs from this jprobe are these:

execve called /usr/lib/systemd/systemd-cgroups-agent by kworker/u8:3

My module code is as follow:

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

static long jdo_execve(struct filename *filename, 
        const char __user *const __user __argv, 
        const char __user *const __user __envp)
{
    const char *name = filename->name;
    printk("execve called %s by %s\n", name, current->comm);
    jprobe_return();
    return 0;
}

static struct jprobe execve_jprobe = {
    .entry          = jdo_execve,
    .kp = {
        .symbol_name    = "do_execve",
    },
};

static int __init jprobe_init(void)
{
    int ret;

    ret = register_jprobe(&execve_jprobe);
    if (ret < 0) {
        printk(KERN_INFO "register_jprobe failed, returned %d\n", ret);
        return -1;
    }
    return 0;
}

static void __exit jprobe_exit(void)
{
    unregister_jprobe(&execve_jprobe);
    printk(KERN_INFO "jprobe at %p unregistered\n", write_jprobe.kp.addr);
}

module_init(jprobe_init)
module_exit(jprobe_exit)
MODULE_LICENSE("GPL");

I'm using CentOS 7, kernel version 3.10.0-514.el7.x86_64

Any help is appreciated!

Ishay Peled
  • 2,783
  • 1
  • 23
  • 37
  • `AFAIK every new process image (not creation) should be loaded like this` - E.g., processes executed from 32-bit environment do not use this function, they use `compat_do_execve`. Try to hook `do_execve_common`, so you will intercept also 32-bit process creation. – Tsyvarev Jun 11 '17 at 21:55
  • Yes of course, forgot to mention I'm running a 64 bit kernel, there isn't `compat_do_execve` symbol in this kernel (see exact version in the post) – Ishay Peled Jun 12 '17 at 04:44
  • Symbol `compat_do_execve` (and other "compat" symbols) are **specifically** for *64-bit kernel* (yes, I noticed the kernel version in your post) when syscalls are issued by *32-bit application*. In other words, you have 64-bit kernel, 64-bit OS, but it can run 32-bit application, which will use "compat" syscalls. – Tsyvarev Jun 12 '17 at 08:08
  • Nope, no `compat_do_execve` in this kernel: `[root@localhost 3.10.0-514.el7.x86_64]# grep -R do_execve * include/linux/sched.h:extern int do_execve(struct filename *, System.map:ffffffff81205ac0 t do_execve_common.isra.25 System.map:ffffffff81206150 T do_execve [root@localhost 3.10.0-514.el7.x86_64]# grep -R compat_do_execve * [root@localhost 3.10.0-514.el7.x86_64]#` – Ishay Peled Jun 12 '17 at 10:26

1 Answers1

0

The code at hand is:

SYSCALL_DEFINE3(execve,
                const char __user *, filename,
                const char __user *const __user *, argv,
                const char __user *const __user *, envp)
{
        return do_execve(getname(filename), argv, envp);
}

and:

int do_execve(struct filename *filename,
        const char __user *const __user *__argv,
        const char __user *const __user *__envp)
{
        struct user_arg_ptr argv = { .ptr.native = __argv };
        struct user_arg_ptr envp = { .ptr.native = __envp };
        return do_execve_common(filename, argv, envp);
}

Given how short the function is the first obvious suspicion is that it got inlined in the execve entry point. Disassembling confirms the suspicion:

0xffffffff811e6e20 <sys_execve>:        nopl   0x0(%rax,%rax,1) [FTRACE NOP]
0xffffffff811e6e25 <sys_execve+0x5>:    push   %rbp
0xffffffff811e6e26 <sys_execve+0x6>:    mov    %rsp,%rbp
0xffffffff811e6e29 <sys_execve+0x9>:    push   %r12
0xffffffff811e6e2b <sys_execve+0xb>:    mov    %rdx,%r12
0xffffffff811e6e2e <sys_execve+0xe>:    push   %rbx
0xffffffff811e6e2f <sys_execve+0xf>:    mov    %rsi,%rbx
0xffffffff811e6e32 <sys_execve+0x12>:   callq  0xffffffff811ef380 <getname>
0xffffffff811e6e37 <sys_execve+0x17>:   mov    %r12,%r8
0xffffffff811e6e3a <sys_execve+0x1a>:   mov    %rbx,%rdx
0xffffffff811e6e3d <sys_execve+0x1d>:   xor    %ecx,%ecx
0xffffffff811e6e3f <sys_execve+0x1f>:   xor    %esi,%esi
0xffffffff811e6e41 <sys_execve+0x21>:   mov    %rax,%rdi
0xffffffff811e6e44 <sys_execve+0x24>:   callq  0xffffffff811e6520 <do_execve_common>
0xffffffff811e6e49 <sys_execve+0x29>:   pop    %rbx
0xffffffff811e6e4a <sys_execve+0x2a>:   pop    %r12
0xffffffff811e6e4c <sys_execve+0x2c>:   cltq   
0xffffffff811e6e4e <sys_execve+0x2e>:   pop    %rbp
0xffffffff811e6e4f <sys_execve+0x2f>:   retq   

Thus, you are not seeing most "calls" to do_execve because the are not any in the code path you are interested in.

Another approach to debug this would be try to put a probe on something deeper or higher in the stack.

I have to ask why are you playing with jprobes or the kernel in general. So far it seems you are a beginner programmer and should not play with it just yet. In particular it is unlikely you will be able to write kernel code (and this includes jprobes) which obeys all the rules and be able to explain why it does. It is too easy to create code which in insufficient testing appears to work, but is broken.