Search code examples
linux-security-module

Linux module: being notified about task creation and destruction


for Mach kernel API emulation on Linux, I need for my kernel module to get called when a task has been just created or is being terminated.

In my kernel module, this could most nicely be done via Linux Security Modules, but a couple of years ago, they prevented external modules from acting as a LSM by unexporting the needed symbols.

The only other way I could find was to make my module act like a rootkit. Find the syscall table and hook it in there.

Patching the kernel is out of the question. I need my app to be installed easily. Is there any other way?


Solution

  • You can use Kprobes, which enables you to dynamically hook into code in the kernel. You will need to find the right function among the ones involves in creating and destroying processes that give you the information you need. For instance, for tasks created, do_fork() in fork.c would be a good place to start. For tasks destroyed, do_exit. You would want to write a retprobe, which is a kind of kprobe that additionally gives you control at the end of the execution of the function, before it returns. The reason you want control before the function returns is to check if it succeeded in creating the process by checking the return value. If there was an error, then the function will return a negative value or in some cases possibly 0.

    You would do this by creating a kretprobe struct:

    static struct kretprobe do_fork_probe = {
        .entry_handler = (kprobe_opcode_t *) my_do_fork_entry,
        .handler = (kprobe_opcode_t *) my_do_fork_ret,
        .maxactive = 20,
        .data_size = sizeof(struct do_fork_ctx)
    };
    

    my_do_fork_entry gets executed when control enters the hooked function, and my_do_fork_ret gets executed just before it returns. You would hook it in as follows:

    do_fork_probe.kp.addr =
        (kprobe_opcode_t *) kallsyms_lookup_name("do_fork");
    
    if ((ret = register_kretprobe(&do_fork_probe)) <0) {
        // handle error
    }
    

    In the implementation of your hooks, it's a bit unwieldy to get the arguments and return value. You get these via the saved registers pt_regs data structure. Let's look at the return hook, where on x86 you get the return value via regs->ax.

    static int my_do_fork_ret(struct kretprobe_instance *ri, struct pt_regs *regs)
    {
        struct do_fork_ctx *ctx = (struct do_fork_ctx *) ri->data;
        int ret = regs->ax; // This is on x86
        if (ret > 0) {
            // It's not an error, probably a valid process
        }
    }
    

    In the entry point, you can get access to the arguments via the registers. e.g. on x86, regs->di is the first argument, regs->si is the second etc. You can google to get the full list. Note that you shouldn't rely on these registers for the arguments in the return hook as the registers may have been overwritten for other computations.

    You will surely have to jump many hoops in getting this working, but hopefully this note should set you off in the right direction.