Linux kernel syscall return not given pointer

1k views Asked by At

I am trying to implement a new syscall as an experiment, but I always get a segfault. I think the problem is that I try to return a pointer to a char array that is not in the user space. I tried to allocate the memory in the user space by using the GFP_USER as flag for kmalloc, but the problem remains the same.

This is how I call my syscall and I can't change it at the moment (normally would allocate memory for return and provide it as parameter):

#include <errno.h>
#include <linux/unistd.h>
#include <linux/mysyscall.h>

main() {
  printf("Returned: %s\n", mycall("user string arg"));

}

The definition of the syscall function looks like this:

#include <linux/mysyscall.h>
#include <linux/kernel.h>
#include <linux/unistd.h>
#include <linux/slab.h>
#include <asm/uaccess.h>

asmlinkage char* sys_mycall (char * str_in) {
    int size = 0;

    printk(KERN_INFO "Starting mycall.\n");
    size = strnlen_user(str_in, 255);
    printk(KERN_INFO "size = %d\n", size);
    char str[size];
    char *ret;
    ret = kmalloc(size, GFP_ATOMIC | GFP_USER); 

    if(str != NULL){
        copy_from_user(str, str_in, size);
        printk(KERN_INFO "Copied string.\n");
    }else{
        printk(KERN_ERR "str is NULL!!!\n");
        return -EFAULT;
    }

    // Doing operations on str

    int acc = access_ok(VERIFY_WRITE, ret, size);
    if(acc){
        printk(KERN_ERR "Not accessible!\n");
        return -EFAULT; 
    }

    int len = copy_to_user(ret, str, size);
    if(len){
        printk(KERN_ERR "Could not copy! len = %d\n", len);
        return -EFAULT;
    }

    return ret;
} 

I have tested it a lot and I'm sure the syscall is called correctly, but it has problems with copy_to_user. copy_to_user always returns a positive value (equal to size, so does not copy at all).
By the way the address of ret is like f74ce400 and str_in 080484ac.

Does anybody have an idea what's wrong?

2

There are 2 answers

5
Oren Kishon On

The destination of the copy should be a user space pointer, while the pointer ret is in Kernel space. You should have the destination pointer be provided from the user as an argument. See for example:

0
tekkk On

You should do copy_from_user before , calling strlen(). Typically system calls gets an extra argument with length of the argument passed.

For ex; mysyscall(str,strlen)