Copy structure with included user pointers from user space to kernel space (copy_from_user)

时间:2019-04-08 13:03:50

标签: gcc arm embedded-linux

I want to transfer a transaction structure, which contains an user space pointer to an array, to kernel by using copy_from_user. The goal is, to get access to the array elements in kernel space.

User space side: I allocate an array of _sg_param structures in user space. Now i put the address of this array in a transaction structure (line (*)). Then i transfer the transaction structure to the kernel via ioctl().

Kernel space side: On executing this ioctl, the complete transaction structure is copied to kernel space (line ()). Now kernel space is allocated for holding the array (line (*)). Then i try to copy the array from user space to the new allocated kernel space (line (****)), and here start my problems: The kernel is corrupted during execution of this copy. dmesg shows following output:

[   54.443106] Unhandled fault: page domain fault (0x01b) at 0xb6f09738
[   54.448067] pgd = ee5ec000
[   54.449465] [b6f09738] *pgd=2e9d7831, *pte=2d56875f, *ppte=2d568c7f
[   54.454411] Internal error: : 1b [#1] PREEMPT SMP ARM

Any ideas ???

Following an simplified extract of my code:

// structure declaration

typedef struct _sg_param {
    void *seg_buf;
    int seg_len;
    int received;
} sg_param_t;

struct transaction {
    ...
    int num_of_elements;
    sg_param_t *pbuf_list;    // Array of sg_param structure
    ...
} trans;


// user space side:

    if ((pParam = (sg_param_t *) malloc(NR_OF_STRUCTS * sizeof(sg_param_t))) == NULL) {
        return -ENOMEM;
    }
    else {
        trans.num_of_elements = NR_OF_STRUCTS;
        trans.pbuf_list = pParam;    // (*)
    }

    rc = ioctl(dev->fd, MY_CMD, &trans);
    if (rc < 0) {
        return rc;
    }


// kernel space side

static long ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    arg_ptr = (void __user *)arg;

    // Perform the specified command
    switch (cmd) {
        case MY_CMD:
        {
            struct transaction *__user user_trans;
            user_trans = (struct transaction *__user)arg_ptr;

           if (copy_from_user(&trans, arg_ptr, sizeof(trans)) != 0) { // (**)
                k_err("Unable to copy transfer info from userspace for "
                     "AXIDMA_DMA_START_DMA.\n");       
                return -EFAULT;
            }
            int size = trans.num_of_elements * sizeof(sg_param_t);

            if (trans.pbuf_list != NULL) {
                // Allocate kernel memory for buf_list
                trans.pbuf_list = (sg_param_t *) kmalloc(size, GFP_KERNEL); // (***)
                if (trans.pbuf_list == NULL) {
                    k_err("Unable to allocate array for buffers.\n");
                    return -ENOMEM;
                }
                // Now copy pbuf_list from user space to kernel space
                if (copy_from_user(trans.pbuf_list, user_trans->pbuf_list, size) != 0) { // (****)
                    kfree(trans.pbuf_list);
                    return -EFAULT;
                }
            }
            break;
        }
    }

1 个答案:

答案 0 :(得分:0)

You're directly accessing userspace data (user_trans->pbuf_list). You should use the one that you've already copied to kernel (trans.pbuf_list).

Code for this would normally be something like:

sg_param_t *local_copy = kmalloc(size, ...);
// TODO check it succeeded
if (copy_from_user(local_copy, trans.pbuf_list, size) ...)
trans.pbuf_list = local_copy;
// use trans.pbuf_list

Note that you also need to check trans.num_of_elements to be valid (0 would make kmalloc return ZERO_SIZE_PTR, and too big value might be a way for DoS).