5

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

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

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

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

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

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

然后对于副本...我可以这样做吗?

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

3 回答 3

3

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

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

由于内核对进程有相当深入的了解,因此您可以稍微绕过这些规则,例如,您可以计算用户的指针将是什么,并复制出原始数据的副本,并适当修改指针。但这有点浪费。或者,您可以复制内核指针,只是不在用户代码中使用它,但现在您正在“泄露数据”,“坏人”有时可以通过各种方式加以利用。在安全人员讲话中,您留下了一个敞开的“秘密通道”。

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

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

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

“最方便”有时是两个单独的复制操作,或一些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;

不过,您可能会遇到必须遵守一些不太好的界面的情况。


编辑:如果您要添加自己的系统调用(而不是绑定readioctl例如),您可以将标题和数据分开,如Adam Rosenfield 的回答

于 2013-08-29T21:02:55.487 回答
3

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

做这样的事情的典型方法是要求用户空间代码分配内存并将指向该内存的指针传递给系统调用。如果程序没有传入足够大的缓冲区,则失败并出现错误(例如EFAULT)。如果程序无法事先知道它需要多少内存,那么通常您会在传递NULL指针时返回所需的数据量。

来自用户空间的示例用法:

// 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 */ }

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

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));
}
于 2013-08-29T20:58:49.133 回答
2

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);
于 2013-08-29T20:14:06.963 回答