我一直在做一些研究,对这个宏我有点困惑。希望有人可以给我一些指导。我有一些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的返回描述:
所以这意味着它真的什么都没做,那么确保我们检查的指针在用户空间中可能初始化?既然我们知道除了用户空间调用之外我们不能进入这个功能,除非我们向这个设备打开一个有效的文件描述符,否则它不会发生,这真的需要吗?它确实比确保我们没有得到NULL指针更安全吗?
第二个问题来自这个描述:
这是否意味着我的代码中的第一次检查是多余的?如果我们要检查可写区域,我们可以作为免费赠品阅读吗?
我正在使用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; \
})
答案 0 :(得分:11)
如果__lddk_copy_from_user()
只是调用copy_from_user()
,则access_ok()
检查是多余的,因为copy_from_user()
会自行执行这些检查。
access_ok()
检查确保用户空间应用程序不要求内核读取或写入内核地址(它们是完整性/安全性检查)。仅仅因为用户空间提供的指针并不意味着它肯定是用户空间指针 - 在许多情况下“内核指针”只是意味着它指向虚拟地址空间的特定区域。
此外,使用access_ok()
调用VERIFY_WRITE
隐含VERIFY_READ
,因此如果您检查前者,则无需再检查后者。
<小时/> 截至this commit in 2019,
access_ok()
不久就有type
个参数,因此VERIFY_WRITE
与VERIFY_READ
点之间没有实际意义。
答案 1 :(得分:1)
access_ok
宏只是快速检查指针的可能有效性。例如,它会捕获带有NULL或只读参数的错误调用。
理想情况下,即使稍后访问功能失败,驱动程序也应该恢复。然而,这可能仅在一些昂贵的硬件操作之后发生。因此,提前检查可以使驱动程序更加强大,以防止最常见的用户空间程序员错误。
编辑:是的,如果您的唯一输入调用是copy_from_user(),则检查是多余的。但是,如果稍后写入,则在开头检查可写性是很有用的。
你应该检查copy_from_user()的返回值。
答案 2 :(得分:1)
这不是多余的。 access_ok()验证地址,尝试从该区域读取。如果地址有效并且确实存在于用户空间中,则它会尝试读取,否则该函数将返回EFAULT,表示地址失败。这是一种更安全的方法来验证区域上的访问,而不是检查NULL(在您遇到可能导致内核崩溃的任何分段错误之前)。
此外,您可以使用access_ok()验证读/写访问权限。第二个调用只是验证该区域的“写入”访问权限。