Linux kernel module read from process VMA

Viewed 826

I'm trying to build a small demonstration kernel module for Linux which finds a specific process and reads a value from that process' memory.

I have put together the following code:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/init.h>
#include <linux/highmem.h>
#include <asm/uaccess.h>

static void read_process_memory(struct task_struct *task) {
    char buf[1024];
    unsigned long size;
    unsigned long ret;
    void __user *addr;
    struct mm_struct *mm = get_task_mm(task);
    struct vm_area_struct *vma;

    if (!mm) {
        // Abort
        return;
    }

    // Lock for reading
    down_read(&mm->mmap_sem);

    vma = mm->mmap;
    memset(buf, 0, 1024);

    // Make sure read is enabled for this region
    if (vma && vma->vm_flags & VM_READ) {

        // Read without overflowing
        size = vma->vm_end - vma->vm_start;
        if (size > 1023) {
            size = 1023;
        }

        // Attempt to get the data from the start of the vma
        addr = (void __user *)vma->vm_start;

        if (access_ok(VERIFY_READ, addr, size)) {
            ret = copy_from_user(buf, addr, size);

            if (ret == 0) {
                // Probably doesn't contain anything relevent
                printk(KERN_ALERT "mymodule: Read '%s'\n", buf);
            } else {
                printk(KERN_ALERT "mymodule: Failed to copy %lu bytes from userspace\n", ret);
            }
        } else {
            printk(KERN_ALERT "mymodule: access_ok check failed\n");
        }

        // Release the lock
        up_read(&mm->mmap_sem);
        mmput(mm);
    }
}

static int __init mymodule_init(void) {
    struct task_struct *task;
    bool found = false;

    printk(KERN_ALERT "mymodule: Starting\n");

    // Find the process
    rcu_read_lock();

    for_each_process(task) {
        if (strcmp(task->comm, "example-process") == 0) {
            printk(KERN_ALERT "mymodule: Found pid %d for process %s\n", task->pid, task->comm);
            found = true;
            break;
        }
    }

    rcu_read_unlock();

    if (!found) {
        printk(KERN_ALERT "mymodule: Process not found, aborting\n");
        return -1;
    }

    read_process_memory(task);

    return 0;
}

static void __exit mymodule_exit(void) {
    printk(KERN_ALERT "mymodule: Stopped\n");
}

module_init(mymodule_init);
module_exit(mymodule_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("user");

Everything works fine until the copy_from_user call, which returns a non-zero count (actually, it returns size, meaning it didn't ready any data), regardless of the process and vma I try to read from.

Is there a misunderstanding on my part, or anything I'm doing wrong?

EDIT: Here's a working version of read_process_memory:

static void read_process_memory(struct task_struct *task) {
    char buf[1024];
    unsigned long size;
    int ret;
    void *addr;
    struct mm_struct *mm = get_task_mm(task);
    struct vm_area_struct *vma;
    struct page *pages[1];

    if (!mm) {
        // Abort
        return;
    }

    // Lock for reading
    down_read(&mm->mmap_sem);

    vma = mm->mmap;
    memset(buf, 0, 1024);

    // Make sure read is enabled for this region
    if (vma && vma->vm_flags & VM_READ) {

        // Get the first page
        ret = get_user_pages_remote(task, mm, vma->vm_start, 1, FOLL_FORCE, pages, NULL);

        if (ret > 0) {
            // We got our page, we should now be able to map it and read directly
            addr = kmap(*pages);

            // Read without overflowing
            size = vma->vm_end - vma->vm_start;
            if (size > 1023) {
                size = 1023;
            }
            strncpy(buf, addr, size);

            // Probably doesn't contain anything relevent
            printk(KERN_ALERT "mymodule: Read '%s'\n", buf);

            // Make sure to release our page
            kunmap(*pages);
            put_page(*pages);
        } else {
            printk(KERN_ALERT "mymodule: Failed to read page at %p (errno=%d)\n", (void *)vma->vm_start, ret);
        }

        // Release the lock
        up_read(&mm->mmap_sem);
        mmput(mm);
    }
}
1 Answers

I'd say your code is wrong. Please be noted you are accessing memory of remote process (not current). To do that correctly you must use one of the GUP APIs(e.g. get_user_pages_remote()) to grab the pages first. The uaccess APIs (e.g. copy_from_user) only works for current process.

Here is an example in iouring: https://elixir.bootlin.com/linux/latest/source/fs/io_uring.c#L4839

Related