Search code examples
clinux-kernelx86-64userspace

What prevents from copy_to_user to succeed on kmap'ed pages from a non-current task?


This is a new and modified question for: What is needed to be able to access a user pointer from kernel-space as I lost access to my account.

I want to write to start_arg. start_arg is a pointer to a user-space process argv[0], i.e int main(int argc, char **argv). I start by associated a process id to struct task_struct, pull out the start_arg and arg_end from task_struct->mm->arg_(end/start).

After that, I pin the user space pages of the process (starting from arg_start) to kernel address space, map them, and then try to write to the virtual address I have from kmap. Is that the correct way to accomplish my task? If so, why does copy_to_user always fail?

EDIT: Looks like kmap returns a kernel space address of the page, so the problem has been solved, however I can't try the kernel code because looks like get_user_pages always fail as well with -14 (Bad Address)

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/sched.h>
#include <linux/highmem.h>
#include <linux/slab.h>
#include <asm/io.h>
#include <linux/pid.h>
#include <linux/mm.h>

static int PID;
module_param(PID, int, 0);

#define NR_PAGES    (1)
#define BUF_SIZE    (256) /* IDK why */

/* A struct to store the start address of the process args, and the end address */
struct args {
    u64 start_addr;
    u64 end_addr;
};

/* 
  * Overwrite the argument address with our local buffer \ 
  * The argument address will most probably contain the first variable in argv \
  * That is, possibly, we overwrite argv[0]
*/
int exploit(struct mm_struct *mm, struct args args)
{
    long res;
    struct page *p;
    void __user *vaddr;
    const char *buf = "Hello!";

    /* Make sure we can write stuff */
    if (!(mm->mmap->vm_flags & (VM_WRITE | VM_MAYWRITE)))
        return -EPERM;

    /* Pin pages in memory */
    mmap_read_lock(mm);
    res = get_user_pages(args.start_addr, NR_PAGES, FOLL_WRITE, &p, NULL);
    if (res < 0)
        goto err;
    mmap_read_unlock(mm);

    vaddr = kmap(p);

    /* TODO: FIX copy_to_user always fails */
    if (copy_to_user(vaddr, buf, strlen(buf) + 1))
        goto err_copy;

    kunmap(p);
    set_page_dirty_lock(p);
    put_page(p);

    pr_info("[Y] argv[0] has been successfully overwritten!\n");
    return 0;

err:
    pr_err("[X] FAILURE. ERROR CODE: %ld\n", res);
    return -EFAULT;
err_copy:
    kunmap(p);
    put_page(p);
    pr_err("[X] FAILURE. ERROR CODE: %ld\n", res);
    return -EFAULT;
}

/* Copy the user-space program arguments addresses into our variables */
void psmem(struct mm_struct *mm, u64 *arg_start, u64 *arg_end)
{
    spin_lock(&mm->arg_lock);
    *arg_start = mm->arg_start;
    *arg_end = mm->arg_end;
    spin_unlock(&mm->arg_lock);
}

int view_values(struct task_struct *ts, struct mm_struct *mm, struct args args)
{
    char *kbuf;
    size_t bytes_read;
    unsigned long length = args.end_addr - args.start_addr;

    if (!(kbuf = kmalloc(BUF_SIZE, GFP_KERNEL)))
        return -EFAULT;

    /* Read length bytes starting from args.start_addr */
    bytes_read = access_process_vm(ts, args.start_addr, kbuf, length, FOLL_FORCE);
    pr_info("[I] Number of bytes read: %zu of %ld; Data: '%s'\n",
                    bytes_read, length, kbuf);
    kfree(kbuf);

    return 0;
}

static int __init init_mod(void) 
{
    struct args args;
    struct mm_struct *pid_mm;
    struct task_struct *pid_task_struct;
    
    if (PID <= 0 || PID > PID_MAX_LIMIT)
        return -EINVAL;

    pid_task_struct = pid_task(find_vpid(PID), PIDTYPE_PID);
    if (!pid_task_struct)
        return -EINVAL;
    
    pid_mm = pid_task_struct->mm;

    psmem(pid_mm, &args.start_addr, &args.end_addr);
    view_values(pid_task_struct, pid_mm, args);

    if (exploit(pid_mm, args) < 0)
        return -EFAULT;

    pr_info("%s: Successfully written to process' memory!\n", THIS_MODULE->name);
    return 0;
}

static void __exit exit_mod(void) 
{

}

MODULE_LICENSE("GPL");

module_init(init_mod);
module_exit(exit_mod);
obj-m := mymodule.o
CFLAGS_EXTRA += -pedantic-errors -Wall -Werror -Wextra -g -DDEBUG

all: build

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

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

Solution

  • copy_to_user expects a user-space virtual address. I think it would check to make sure the high bit isn't set (or a compare on 32-bit with a 3:1 split), so user-space can't pass in a wild pointer and make the kernel overwrite its own memory.

    Your destination is not a user-space virtual address, so copy_to_user will correctly refuse to write to it. It's a kernel virtual address that happens to point at some physical pages which are also mapped by a user-space process.

    If you've already pinned the memory and found a kernel virtual address to access the pages through, I think you can just memcpy. But I'm much less confident of that; I understand enough general stuff about how kernels work (and Linux specifically) to read kernel code and have things make sense, but I don't know enough to say for sure you're not leaving out anything important.
    (And I haven't even read your code; your text description was sufficient for me to be pretty sure what the problem is.)