0

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
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • "I lost access to my account" the linked question says "This question was voluntarily removed by its author.". How did you remove it if you lost access to the account? – DavidPostill Jul 12 '22 at 15:15
  • 1
    You access the user space memory of **non-current** process. (Once again you forgot to highlight that in the question post). Like `copy_to_user` and many other functions, `get_user_pages` works only with the **current process**: it doesn't accept any argument describing a target process or address space. You may try to use [get_user_pages_remote](https://elixir.bootlin.com/linux/latest/source/mm/gup.c#L1999) instead, but make sure to read and understand its description. – Tsyvarev Jul 12 '22 at 22:30

1 Answers1

3

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

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • Yes, `kmap()` will create a temporary mapping of a physical memory page to kernel-space and it returns a kernel space address of the page, so it cannot be used with `copy_to_user`. Oops! – justtoaskaquestion Jul 12 '22 at 14:34
  • Though when I comment out the `get_user_pages` and related stuff, and do `memcpy` instead, it refuses to write to `args.start_addr` directly. _unable to handle page fault for address: 00007fff7aade293, supervisor write access in kernel mode, error_code(0x0002) - not-present page_. Looks like mapping the pages into kernel space is necessary. – justtoaskaquestion Jul 12 '22 at 14:41
  • 1
    @justtoaskaquestion: I thought you said in the question: *I **pin** the user space pages of the process*. "Pin" means locking them into memory, like `mlock`, so they're definitely paged in and can't be paged out by memory pressure. ([example usage](https://eric-lo.gitbook.io/memory-mapped-io/pin-the-page) or search on "What does it mean to pin memory?"). Maybe you actually meant "mapped", as in created a logical mapping with `kmap`, but haven't made sure the page was populated / wired into the hardware page tables. – Peter Cordes Jul 12 '22 at 14:46
  • As I said, After your answer I commented out the `kmap` which maps, and `get_user_pages` which pins meaning I don't do anything with the pages. Just `memcpy` directly to my user space address. The kernel error makes me think I need to map the user space pages into my kernel memory before `memcpy`ing to address in the page: _not-present page_. I'm a bit confused though, so if I don't get your point that's now my fault. – justtoaskaquestion Jul 12 '22 at 14:54
  • I will accept your answer, as I'm dealing with a different problem. In my opinion Kernel documentation of `get_user_pages` is really poor, hopefully they will be more explict in the future regarding the kernel memory functions, it really confuses. – justtoaskaquestion Jul 12 '22 at 14:59
  • Oh, you were trying to "*Just `memcpy` directly to my user space address*"? That obviously can't work; this isn't the `current` task so as discussed on [your earlier question](https://stackoverflow.com/questions/72929460/what-is-needed-to-be-able-to-access-a-user-pointer-from-kernel-space), that address is in a different virtual address space, not the one that the CPU is currently using to interpret virtual addresses. And it's not even close to what my answer says: I said to memcpy to the *kernel* address for mapped + pinned pages. – Peter Cordes Jul 12 '22 at 14:59
  • @justtoaskaquestion: Even if the task was the `current` one, Linux uses hardware features (like x86-64 [SMAP](https://en.wikipedia.org/wiki/Supervisor_Mode_Access_Prevention)) to prevent reading/writing user-space addresses, outside of `copy_to/from_user`. Those functions set/clear `AC` in `RFLAGS`, as well as address checking and making sure it's paged in. – Peter Cordes Jul 12 '22 at 15:02