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
CodePudding user response:
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.)