使用 linux 宏 access_ok() 有啥意义

Posted

技术标签:

【中文标题】使用 linux 宏 access_ok() 有啥意义【英文标题】:What is the point of using the linux macro access_ok()使用 linux 宏 access_ok() 有什么意义 【发布时间】:2012-09-10 18:53:33 【问题描述】:

我一直在做一些研究,我对这个宏有点困惑。希望有人能给我一些指导。我有一些 ioctl 代码(我是继承的,而不是编写的),如果在继续从用户空间复制数据之前检查 access_ok(),它会做的第一件事:

#define __lddk_copy_from_user(a,b,c) copy_from_user(a,b,c)
#define __lddk_copy_to_user(a,b,c) copy_to_user(a,b,c)

long can_ioctl(struct file *file, unsigned int cmd, unsigned long arg)

  switch(cmd) 
    case COMMAND:
      if(! access_ok(VERIFY_READ, (void *)arg, sizeof(Message_par_t)))
        return(retval); 

      if(! access_ok(VERIFY_WRITE, (void *)arg, sizeof(Message_par_t)))
        return(retval); 

      argp = &Command;
      __lddk_copy_from_user( (void *) argp,(Command_par_t *) arg, sizeof(Command_par_t));

所以代码工作得很好,但我不确定它是否需要。第一个问题来自对access_ok返回的这个描述:

如果该区域可能可访问,则该函数返回非零值(尽管访问仍可能导致 -EFAULT)。此函数仅检查该地址是否可能在用户空间中,而不是在内核中。

所以这意味着除了确保我们正在检查的指针可能在用户空间中初始化之外,它真的什么也不做?因为我们知道除了用户空间调用之外我们不能进入这个函数,而且除非我们打开这个设备的有效文件描述符,否则它不会发生,这真的需要吗?真的比确保我们没有得到 NULL 指针更安全吗?

第二个问题来自这个描述:

类型参数可以指定为 VERIFY_READ 或 VERIFY_WRITE。 VERIFY_WRITE 符号还标识内存区域是否可读和可写。

这是否意味着我的代码中的第一次检查是多余的?如果我们要检查一个可写区域,我们可以作为免费赠品读取吗?

我使用的是 x86 架构,因此 access_ok() 和 __range_no_ok() 的定义来自 /usr/src/linux-3.1.10-1.16/arch/x86/include/asm/uaccess.h 如下:

#define access_ok(type, addr, size) (likely(__range_not_ok(addr, size) == 0))

#define __range_not_ok(addr, size)                  \
(                                  \
    unsigned long flag, roksum;                 \
    __chk_user_ptr(addr);                       \
    asm("add %3,%1 ; sbb %0,%0 ; cmp %1,%4 ; sbb $0,%0"     \
        : "=&r" (flag), "=r" (roksum)               \
        : "1" (addr), "g" ((long)(size)),               \
          "rm" (current_thread_info()->addr_limit.seg));        \
    flag;                               \
)

【问题讨论】:

我不是 Linux 内核专家,但根据经验,通常我认为最好只是尝试操作并让函数失败而不是提前执行这些检查仍然 必须为失败做好准备(除非出于某种性能原因,提前执行检查可以节省大量时间);此外,检查源代码看起来像copy_from_user 已经执行了读取检查,所以它是多余的。只需检查它的返回值(=无法复制的字节数),如果它不为零,则失败并且您必须返回错误。 这个问题的答案取决于__lddk_copy_from_user() 函数/宏的作用——这不在主流内核中。你能用这个函数的定义更新你的问题吗? @caf - 好点,这是“标准”的 uCLinux can 驱动程序,并且该包装器在 can_defs.h 中定义,但是,是的,我之前不得不查找它,也不知道它是什么. 【参考方案1】:

如果__lddk_copy_from_user() 只是调用copy_from_user(),那么access_ok() 检查是多余的,因为copy_from_user() 自己执行这些检查。

access_ok() 检查确保用户空间应用程序不会要求内核读取或写入内核地址(它们是完整性/安全性检查)。仅仅因为指针是由用户空间提供的,并不意味着它肯定是用户空间指针——在许多情况下,“内核指针”仅仅意味着它指向虚拟地址空间的特定区域。

此外,用VERIFY_WRITE 调用access_ok() 意味着VERIFY_READ,因此如果您检查前者,则无需同时检查后者。


从this commit in 2019 开始,access_ok() 不再有type 参数,所以VERIFY_WRITEVERIFY_READ 的区别是没有意义的。

【讨论】:

【参考方案2】:

access_ok 宏只是快速检查指针的可能有效性。例如,它会捕获带有 NULL 或只读参数的错误调用。

理想情况下,即使稍后访问功能失败,驱动程序也应该恢复。然而,这只有在一些昂贵的硬件操作之后才会发生。因此,及早检查可能会使驱动程序对最常见的用户空间程序员错误更加健壮。

编辑:是的,如果您唯一的输入调用是那里的 copy_from_user(),则检查是多余的。但是,如果稍后有写入,则最好在开始时检查可写性。

你真的应该检查 copy_from_user() 的返回值。

【讨论】:

你能解释一下它是如何用 NULL 指针“捕获”调用的吗?我看到 __range_not_ok() 调用 __chk_user_ptr() 所以它可能在那里,但我找不到它的定义。 我不相信这是真的,它只检查它是否在 x86 的用户空间地址范围内。空指针在该范围内,因此会通过。【参考方案3】:

这不是多余的。 access_ok() 验证地址,尝试从该区域读取。如果地址有效并且确实存在于用户空间,则尝试读取,否则函数返回 EFAULT,表示地址失败。这是一种更安全的方式来验证对区域的访问,而不是检查 NULL(在您遇到任何可能导致内核崩溃的分段错误之前)。

此外,您可以使用 access_ok() 验证读/写访问权限。第二个调用只是验证对该区域的“写”访问。

【讨论】:

所以你的评论是我读到的,即VERIFY_WRITE symbolic also identifies whether the memory region is readable as well as writable. 是一个不准确的陈述?因此,即使将调用 access_ok(write),access_ok(read) 仍然有用吗? 这不是不准确的。您必须确定您是否拥有访问内存区域的“权限”。有可能您只是读取权限或写入权限,甚至两者兼而有之。

以上是关于使用 linux 宏 access_ok() 有啥意义的主要内容,如果未能解决你的问题,请参考以下文章

access_ok()函数介绍

clickhouse 中的“宏”是啥,clickhouse 中的“宏”有啥用?

Excel里的宏有啥作用?

WOW里有啥FS 有用的宏么

当宏用作变量名时,有啥方法可以跳过宏替换(在预处理期间)?

在表格中啥叫宏?宏有啥做用