copy_to_user包含数组(指针)的结构

时间:2022-09-06 11:19:15

Disclosure: I'm fairly new to C. If you could explain any answers verbosely, I would appreciate it.

披露:我对C很新。如果你能详细解释任何答案,我将不胜感激。

I am writing a linux kernel module, and in one of the functions I am writing I need to copy a structure to userspace that looks like this:

我正在编写一个linux内核模块,在我写的一个函数中,我需要将一个结构复制到用户空间,如下所示:

typedef struct
{
    uint32_t  someProperty;
    uint32_t  numOfFruits;
    uint32_t  *arrayOfFruits;
} ObjectCapabilities;

The API I'm implementing has documentation that describes the arrayOfFruits member as "an array of size numOfFruits where each element is a FRUIT_TYPE constant." I am confused how to do this, given that the arrayOfFruits is a pointer. When I copy_to_user the ObjectCapabilities structure, it will only copy the pointer arrayOfFruits to userspace.

我正在实现的API有一些文档,它将arrayOfFruits成员描述为“一个大小为numOfFruits的数组,其中每个元素都是一个FRUIT_TYPE常量。”鉴于arrayOfFruits是一个指针,我很困惑如何做到这一点。当我copy_to_user的ObjectCapabilities结构时,它只会将指针arrayOfFruits复制到用户空间。

How can userspace continuously access the elements of the array? Here is my attempt:

用户空间如何连续访问数组的元素?这是我的尝试:

ObjectCapabilities caps;
caps.someProperty = 1024;
caps.numOfFruits  = 3;
uint32_t localArray[] = {
        FRUIT_TYPE_APPLE,
        FRUIT_TYPE_ORANGE,
        FRUIT_TYPE_BANANA
};
caps.arrayOfFruits = localArray;

And then for the copy... can I just do this?

然后复制......我可以这样做吗?

copy_to_user((void *)destination, &caps, (sizeof(caps) + (sizeof(localArray) / sizeof((localArray)[0]))));

3 个解决方案

#1


2  

The user needs to provide enough space for all the data being copied out. Ideally he'll tell you how much space he provided, and you check that everything fits.

用户需要为所有被复制的数据提供足够的空间。理想情况下,他会告诉你他提供了多少空间,并检查一切是否合适。

The copied-out data should (in general) not include any pointers, since they're "local" to a different "process" (the kernel can be viewed as a separate process, as it were, and kernel / user interactions involve process-to-process IPC, similar to sending stuff over local or even Internet-connected sockets).

复制出来的数据应该(通常)不包含任何指针,因为它们对于不同的“进程”是“本地的”(内核可以被视为一个单独的进程,就像它一样,内核/用户交互涉及进程)处理IPC,类似于通过本地甚至互联网连接的套接字发送内容。

Since the kernel has pretty intimate knowledge of a process, you can skirt these rules somewhat, e.g., you could compute what the user's pointer will be, and copy out a copy of the original data, with the pointer modified appropriately. But that's kind of wasteful. Or, you can copy a kernel pointer and just not use it in the user code, but now you're "leaking data" that "bad guys" can sometimes leverage in various ways. In security-people-speak you've left a wide-open "covert channel".

由于内核对进程有非常深入的了解,因此您可以稍微考虑这些规则,例如,您可以计算用户指针的内容,并复制出原始数据的副本,并适当地修改指针。但这有点浪费。或者,您可以复制内核指针,而不是在用户代码中使用它,但现在您正在“泄漏数据”,“坏人”有时可以通过各种方式利用它。在安全 - 人们说话中,你已经离开了一个开放的“隐蔽通道”。

In the end, then, the "right" way to do this tends to be something like this:

最后,“正确”的方式往往是这样的:

struct user_interface_version_of_struct {
    int property;
    int count;
    int data[]; /* of size "count" */
};

The user code mallocs (or otherwise arranges to have sufficient space) the "user interface version" and makes some system call to the kernel (read, receive, rcvmsg, ioctl, whatever, as long as it involves doing a "read"-type operation) and tells the kernel: "here's the memory holding the struct, and here's how big it is" (in bytes, or the maximum count value, or whatever: user and kernel simply need to agree on the protocol). The kernel-side code then verifies the user's values in some appropriate manner, and either does the copy-out however is most convenient, or returns an error.

用户代码mallocs(或以其他方式安排有足够的空间)“用户界面版本”并对内核进行一些系统调用(读取,接收,rcvmsg,ioctl等等,只要它涉及执行“读取”类型操作)并告诉内核:“这是持有结构的内存,这里有多大”(以字节为单位,或最大计数值,或者其他:用户和内核只需要在协议上达成一致)。然后,内核端代码以某种适当的方式验证用户的值,然而复制输出最方便,或者返回错误。

"Most convenient" is sometimes two separate copy ops, or some put_user calls, e.g., if the kernel side has the data structure you showed, you might do:

“最方便”有时是两个单独的复制操作,或者一些put_user调用,例如,如果内核端具有您显示的数据结构,您可能会这样做:

/* let's say ulen is the user supplied length in bytes,
   and uaddr is the user-supplied address */
struct user_interface_version_of_struct *p;

needed = sizeof(*p) + 3 * sizeof(int);
if (needed > ulen)
    return -ENOMEM; /* user did not supply enough space */
p = uaddr;
error = put_user(1024, &p->property);
if (error == 0)
    error = put_user(3, &p->count);
if (error == 0 && copy_to_user(&p->data, localArray, 3 * sizeof(int))
    error = -EFAULT;

You may have a situation where you must conform to some not-very-nice interface, though.

但是,您可能会遇到一些必须符合某些不太好的界面的情况。


Edit: if you're adding your own system call (rather than tying in to read or ioctl for instance), you can separate the header and data, as in Adam Rosenfield's answer.

编辑:如果您要添加自己的系统调用(而不是插入读取或ioctl),您可以分隔标题和数据,如Adam Rosenfield的答案。

#2


2  

You can't copy raw pointers, since a pointer into kernel space is meaningless to userspace (and will segfault if dereferenced).

您无法复制原始指针,因为指向内核空间的指针对用户空间没有意义(如果取消引用,则会出现段错误)。

The typical way of doing something like this is to ask the userspace code to allocate the memory and pass in a pointer to that memory into a system call. If the program doesn't pass in a large enough buffer, then fail with an error (e.g. EFAULT). If there's no way for the program to know in advance a priori how much memory it will need, then typically you'd return the amount of data needed when passed a NULL pointer.

执行此类操作的典型方法是请求用户空间代码分配内存并将指向该内存的指针传入系统调用。如果程序没有传入足够大的缓冲区,则会因错误而失败(例如EFAULT)。如果程序无法事先知道它需要多少内存,那么通常你会返回传递NULL指针所需的数据量。

Example usage from userspace:

用户空间的使用示例:

// Fixed-size data
typedef struct
{
    uint32_t  someProperty;
    uint32_t  numOfFruits;
} ObjectCapabilities;

// First query the number of fruits we need
ObjectCapabilities caps;
int r = sys_get_fruit(&caps, NULL, 0);
if (r != 0) { /* Handle error */ }

// Now allocate memory and query the fruit
uint32_t *arrayOfFruits = malloc(caps.numOfFruits * sizeof(uint32_t));
r = sys_get_fruit(&caps, arrayOfFruits, caps.numOfFruits);
if (r != 0) { /* Handle error */ }

And here's how the corresponding code would look in kernel space on the other side of the system call:

以下是相应代码在系统调用另一端的内核空间中的外观:

int sys_get_fruit(ObjectCapabilities __user *userCaps, uint32_t __user *userFruit, uint32_t numFruits)
{
    ObjectCapabilities caps;
    caps.someProperty = 1024;
    caps.numOfFruits  = 3;

    // Copy out fixed-size data
    int r = copy_to_user(userCaps, &caps, sizeof(caps));
    if (r != 0)
        return r;

    uint32_t localArray[] = {
        FRUIT_TYPE_APPLE,
        FRUIT_TYPE_ORANGE,
        FRUIT_TYPE_BANANA
    };

    // Attempt to copy variable-sized data.  Check the size first.
    if (numFruits * sizeof(uint32_t) < sizeof(localArray))
        return -EFAULT;
    return copy_to_user(userFruit, localArray, sizeof(localArray));
}

#3


2  

With copy_to_user you would do two copy to users.

使用copy_to_user,您可以对用户执行两次复制。

//copy the struct
copy_to_user((void *)destination, &caps, sizeof(caps));
//copy the array.
copy_to_user((void *)destination->array, localArray, sizeof(localArray);

#1


2  

The user needs to provide enough space for all the data being copied out. Ideally he'll tell you how much space he provided, and you check that everything fits.

用户需要为所有被复制的数据提供足够的空间。理想情况下,他会告诉你他提供了多少空间,并检查一切是否合适。

The copied-out data should (in general) not include any pointers, since they're "local" to a different "process" (the kernel can be viewed as a separate process, as it were, and kernel / user interactions involve process-to-process IPC, similar to sending stuff over local or even Internet-connected sockets).

复制出来的数据应该(通常)不包含任何指针,因为它们对于不同的“进程”是“本地的”(内核可以被视为一个单独的进程,就像它一样,内核/用户交互涉及进程)处理IPC,类似于通过本地甚至互联网连接的套接字发送内容。

Since the kernel has pretty intimate knowledge of a process, you can skirt these rules somewhat, e.g., you could compute what the user's pointer will be, and copy out a copy of the original data, with the pointer modified appropriately. But that's kind of wasteful. Or, you can copy a kernel pointer and just not use it in the user code, but now you're "leaking data" that "bad guys" can sometimes leverage in various ways. In security-people-speak you've left a wide-open "covert channel".

由于内核对进程有非常深入的了解,因此您可以稍微考虑这些规则,例如,您可以计算用户指针的内容,并复制出原始数据的副本,并适当地修改指针。但这有点浪费。或者,您可以复制内核指针,而不是在用户代码中使用它,但现在您正在“泄漏数据”,“坏人”有时可以通过各种方式利用它。在安全 - 人们说话中,你已经离开了一个开放的“隐蔽通道”。

In the end, then, the "right" way to do this tends to be something like this:

最后,“正确”的方式往往是这样的:

struct user_interface_version_of_struct {
    int property;
    int count;
    int data[]; /* of size "count" */
};

The user code mallocs (or otherwise arranges to have sufficient space) the "user interface version" and makes some system call to the kernel (read, receive, rcvmsg, ioctl, whatever, as long as it involves doing a "read"-type operation) and tells the kernel: "here's the memory holding the struct, and here's how big it is" (in bytes, or the maximum count value, or whatever: user and kernel simply need to agree on the protocol). The kernel-side code then verifies the user's values in some appropriate manner, and either does the copy-out however is most convenient, or returns an error.

用户代码mallocs(或以其他方式安排有足够的空间)“用户界面版本”并对内核进行一些系统调用(读取,接收,rcvmsg,ioctl等等,只要它涉及执行“读取”类型操作)并告诉内核:“这是持有结构的内存,这里有多大”(以字节为单位,或最大计数值,或者其他:用户和内核只需要在协议上达成一致)。然后,内核端代码以某种适当的方式验证用户的值,然而复制输出最方便,或者返回错误。

"Most convenient" is sometimes two separate copy ops, or some put_user calls, e.g., if the kernel side has the data structure you showed, you might do:

“最方便”有时是两个单独的复制操作,或者一些put_user调用,例如,如果内核端具有您显示的数据结构,您可能会这样做:

/* let's say ulen is the user supplied length in bytes,
   and uaddr is the user-supplied address */
struct user_interface_version_of_struct *p;

needed = sizeof(*p) + 3 * sizeof(int);
if (needed > ulen)
    return -ENOMEM; /* user did not supply enough space */
p = uaddr;
error = put_user(1024, &p->property);
if (error == 0)
    error = put_user(3, &p->count);
if (error == 0 && copy_to_user(&p->data, localArray, 3 * sizeof(int))
    error = -EFAULT;

You may have a situation where you must conform to some not-very-nice interface, though.

但是,您可能会遇到一些必须符合某些不太好的界面的情况。


Edit: if you're adding your own system call (rather than tying in to read or ioctl for instance), you can separate the header and data, as in Adam Rosenfield's answer.

编辑:如果您要添加自己的系统调用(而不是插入读取或ioctl),您可以分隔标题和数据,如Adam Rosenfield的答案。

#2


2  

You can't copy raw pointers, since a pointer into kernel space is meaningless to userspace (and will segfault if dereferenced).

您无法复制原始指针,因为指向内核空间的指针对用户空间没有意义(如果取消引用,则会出现段错误)。

The typical way of doing something like this is to ask the userspace code to allocate the memory and pass in a pointer to that memory into a system call. If the program doesn't pass in a large enough buffer, then fail with an error (e.g. EFAULT). If there's no way for the program to know in advance a priori how much memory it will need, then typically you'd return the amount of data needed when passed a NULL pointer.

执行此类操作的典型方法是请求用户空间代码分配内存并将指向该内存的指针传入系统调用。如果程序没有传入足够大的缓冲区,则会因错误而失败(例如EFAULT)。如果程序无法事先知道它需要多少内存,那么通常你会返回传递NULL指针所需的数据量。

Example usage from userspace:

用户空间的使用示例:

// Fixed-size data
typedef struct
{
    uint32_t  someProperty;
    uint32_t  numOfFruits;
} ObjectCapabilities;

// First query the number of fruits we need
ObjectCapabilities caps;
int r = sys_get_fruit(&caps, NULL, 0);
if (r != 0) { /* Handle error */ }

// Now allocate memory and query the fruit
uint32_t *arrayOfFruits = malloc(caps.numOfFruits * sizeof(uint32_t));
r = sys_get_fruit(&caps, arrayOfFruits, caps.numOfFruits);
if (r != 0) { /* Handle error */ }

And here's how the corresponding code would look in kernel space on the other side of the system call:

以下是相应代码在系统调用另一端的内核空间中的外观:

int sys_get_fruit(ObjectCapabilities __user *userCaps, uint32_t __user *userFruit, uint32_t numFruits)
{
    ObjectCapabilities caps;
    caps.someProperty = 1024;
    caps.numOfFruits  = 3;

    // Copy out fixed-size data
    int r = copy_to_user(userCaps, &caps, sizeof(caps));
    if (r != 0)
        return r;

    uint32_t localArray[] = {
        FRUIT_TYPE_APPLE,
        FRUIT_TYPE_ORANGE,
        FRUIT_TYPE_BANANA
    };

    // Attempt to copy variable-sized data.  Check the size first.
    if (numFruits * sizeof(uint32_t) < sizeof(localArray))
        return -EFAULT;
    return copy_to_user(userFruit, localArray, sizeof(localArray));
}

#3


2  

With copy_to_user you would do two copy to users.

使用copy_to_user,您可以对用户执行两次复制。

//copy the struct
copy_to_user((void *)destination, &caps, sizeof(caps));
//copy the array.
copy_to_user((void *)destination->array, localArray, sizeof(localArray);