Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

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?

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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.)

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading