反复探究工控SCADA技术中的accessok函数内幕解析
一、问题探究
access_ok函数背后的工作原理是什么?
问题
二、分析解析
在进行内核空间与用户空间之间的数据拷贝操作时,我们需要确保用户空间地址的合法性。主要是通过access_ok函数来实现这一检查。
Linux用户空间与内核地址区隔
Linux操作系统及其驱动程序运行于内核空间,而应用程序则运行于用户空间,两者不能直接通过指针进行数据交换,因为Linux采用虚拟内存机制,使得用户空间的数据可能会被交换出内存,当内核试图使用用户指针时,对应的数据可能不再存在于物理内存中。
通常情况下,32位Linux系统将其4GB虚拟地址空間划分为0-3GB作为用户区域和3-4GB作为核心区域。值得注意的是,这里的讨论基于32位环境,64位环境下的划分方式有所不同。
进程寻址范围从0到4G
进程在用户态只能访问0-3G区域,而只有进入核心态才能访问3-4G区域
每个进程共享同样的虚拟地址空間中的3-4G部分(即相同的核心区)
当进程从用户态转入核心态时,它不会改变CR3寄存器,但会改变堆栈。
access_ok深入解读
原型:
access_ok (type, addr, size);
功能:
access_ok - 检查指定类型是否可以对指定大小范围内的 用户模式指针进行读取或写入。
参数说明:
type 访问类型:VERIFY_READ 或 VERIFY_WRITE 注意,VERIFY_WRITE 是 VERIFY_READ 的超集 —— 如果一个块可以安全地被写入,那么它也必然能够安全地被读取。addr 要检查的块开始位置在 用户模式下的指针size 需要检查大小
返回值:
该函数用于验证位于 用户模式 中的一个 内存块 是否可用。如果可用,则返回真(非零),否则返回假(零)。
源码细节分析 #define access_ok(type, addr, size) (_range_ok(addr, size) != 0)
我们使用33-bit算术。在这个宏中,我们首先调用了_range_ok宏,然后将其结果与零比较。如果_result 为非零,则表明_user_ptr_check成功,并且_access_ok也成功,因此整个表达式为非零;如果_result 为 零,则表示_range_check失败,所以整个表达式也是 零。
define _range_ok(addr, size) ({ \ unsigned long flag; \
_range_check_user_ptr(addr);
_asmlinkage long __user *_chk_user_ptr(const volatile void *p, size_t size) { assert(p >= _user_addr_min && p + size <= _user_addr_max); }
static inline void _chk_user_ptr(const volatile void p, size_t s) { assert(p >= _user_addr_min && p + s <= _user_addr_max); }
flag = 1; / 假设flag初始值为1 */ \
_roksum = addr + size; \
asm("adds %1,%2,%3;setcc %0,%1" : "=r" (flag), "+r" (_roksum), "Ir" (addr), "0" (current_thread_info()->addr_limit)); \
flag; })
其中_range-ok详解如下:参数对应:
flag —— 未初始化变量 —— %{[}01]
roksum —— 计算出的总和 —— %{[}01]
addr —— 指定的起始位置 —— %{[}01]
.size — 指定的长度 — %{[}01]
汇编代码详解:
adds %{[\]01},{[\]02},{[\]03}
等价于:
roksum = addr + length;
这个操作影响状态标志C。
接下来两个条件跳转语句带有条件CC,也就是当C=0的时候才执行;如果上面的加法指令产生了进位(C=1),则以下两个命令都不执行,并且设置flag为初始值current_thread_info()->addr_limit(非零)并返回。如果没有产生进位(C=0),就执行下面的命令:
sbcccs %{[\]01},{[\]01},{\[00}
这条指令等效于:
roksum = roksum - flag - 1;
也就是 (addr+length) - (``current_thread_info()->addr_limit``) - 1, 操作符号位。
如果 (addr+length) >= (current_thread_info()->addr_limit) - 1, 则 C=1 如果 (addr+length) < ( ``current_thread_info()->addr_limit `` ), 则 C=0 当 C==O 时 执行以下命令否则跳过(Flag 非 零)
最后一条命令:
movcc %{[}{]}%{{{\}}%{%{}%}, {%{}%
等效于:
Flag = O;
给 Flag 赋予初级值 O.
综上所述: _range-ok' 宏 等效于: 如果 (add+len)>=(curthread->addresslimit)-l', 返回非O 值,如果`(add+len)<(curthread->addresslimit)', 返回O 值
而 'Access-ok' 就是验证将要操作 的User-space 地址范围是否在当前 进程 的User-address-limit 范围之中。这两个 函数由于频繁使用,就通过汇编 来实现一些功能以增加效率。
三、实例应用
在拷贝数据到User-space或者从User-space 拷贝到Kernel-space 时,都需要判断 User-space 地址是否合法。
静态 inline 不透明长整数类型 mustcheck_copy_from_user(void *to , const void *from , unsigned long n){
if(access_ok(VERIFY_READ , from , n))
n = copy_from_user(to , from , n);
else security hole -- plug it
memset(to , O,n);
return n;
}
静态 inline 不透明长整数类型 mustcheck_copy_to_user(void user to , const void to, unsigned long n){
if(access_ok(VERIFY_WRITE,to,n))
n = copy_to_ouser(to,*from,n);
return n;
}
以上便是关于 Linux 系统中的 Access-ok 函数的一些基本介绍及相关源码分析。