连云港做网站设计,稿定设计官网入口,外贸单证,wordpress的数据库文章目录 1 UAO/PAN 特性由来2 硬件PAN的支持3 UAO 的支持 1 UAO/PAN 特性由来
linux 内核空间与用户空间通过 copy_from/to_user 进行数据拷贝交换#xff0c;而不是通过简单的 memcpy/strcpy 进行拷贝复制#xff0c;原因是安全问题#xff08;这里不详细展开#xff09… 文章目录 1 UAO/PAN 特性由来2 硬件PAN的支持3 UAO 的支持 1 UAO/PAN 特性由来
linux 内核空间与用户空间通过 copy_from/to_user 进行数据拷贝交换而不是通过简单的 memcpy/strcpy 进行拷贝复制原因是安全问题这里不详细展开。
而对应内核有 __probe_kernel_read/__probe_user_read读取用户空间数据该类接口在用户空间地址存在问题时内核读取用户空间不会导致内核系统出现问题。
那么 __probe_kernel_read和__probe_user_read 具体区别在哪里呢通过源码可以看到具体区别只有一处 __probe_kenel_read中设置 addr_limit 为 set_fs(KERNEL_DS)而 __probe_user_read 中设置 addr_limit 为 set_fs(USER_DS)首先需要说明每个任务的 thread 结构中都有一个 addr_limit 变量该变量标记了该任务能访问的内核空间代码范围比如内核线程都为addr_limit KERNEL_DS 表明可以访问任意内核和用户空间地址而用户态程序 addr_limit USER_DS 表明只能访问用户空间地址不能访问内核空间地址当然在执行一些特殊操作时可以通过 set_fs(KERNEL_DS) 让用户态程序进入内核后可以访问内核地址空间不过在访问完后会恢复原来的值对于 arm64 还会在 set_fs 中会设置 set_thread_flag(TIF_FSCHECK)标记作用是在返回用户空间时检查 addr_limit 是否恢复为 USER_DS。关于 USER_DS 和 KERNEL_DS 可以看内核中 asm/uaccess.h 中相关定义。
总之在这里两个函数会限制访问的地址空间范围这里再看看 arm64 对 set_fs 的实现:
static inline void set_fs(mm_segment_t fs)
{current_thread_info()-addr_limit fs;/** Prevent a mispredicted conditional call to set_fs from forwarding* the wrong address limit to access_ok under speculation.*/spec_bar();/* On user-mode return, check fs is correct */set_thread_flag(TIF_FSCHECK);/** Enable/disable UAO so that copy_to_user() etc can access* kernel memory with the unprivileged instructions.*/if (IS_ENABLED(CONFIG_ARM64_UAO) fs KERNEL_DS) --------------------------------------------(1)asm(ALTERNATIVE(nop, SET_PSTATE_UAO(1), ARM64_HAS_UAO));elseasm(ALTERNATIVE(nop, SET_PSTATE_UAO(0), ARM64_HAS_UAO,CONFIG_ARM64_UAO));
}除了常规设置外arm64 还多了 UAO 相关arm cpu 特性的设置这里需要分析相关特性原理及使用 首先看一下 arm64 页表属性中关于读写权限的描述 以及在用户态不具有执行权限下的执行权限描述 可以看到无论用户空间拥有什么读写权限内核都至少拥有相同的权限比如 AP[2:1] 01 时用户态具有读写权限内核也具有读写权限AP[2:1] 11 时用户态拥有仅读权限而内核态也拥有仅读权限。 再来时 UXN 0 时用户态不具有执行权限时内核态也至少是拥有相同权限的SCTLR_ELx.WXN 在 linux arm64 中为 0所以比如 PXN 0 时 AP[2:1] 11 时用户态具有仅读不可执行权限而内核态具有仅读和可执行权限。 综上看起来一切没有问题在UXN 0 的一组配置中内核的权限总是比用户态拥有更多权限并且内核如果访问用户空间使用封装的 copy_{from/to}_userget_user/put_user … 等变体访问用户空间即便发生异常也可以通过 __ex_table 表来返回 EFAULT防止内核错误。但是实际情况是只要 user 非特权可以访问的内存地址kernel 特权级都有相等或是更高的访问权限。恶意的/有问题的应用可能传入一个落在 kernel 空间的 buf 地址从而达到破坏 kernel 数据获取 kernel 数据以及执行用户空间代码的目的。 因此早期内核就提供了接口仔细检查这些地址是否是用户空间地址代码比如 access_ok()同时设计了 set_fs 接口可以临时改变传入的地址允许的空间根据 USER_DS 和 KERNEL_DS 来改变以便其通过 access_ok 的检查。 当 set_fs(KERNEL_DS) 时addr_limit 允许访问内核和用户空间的所有地址。 当 set_fs(USER_DS) 时addr_limit 只允许访问用户空间的地址。
access_ok 的实现也很简单就是通过对应架构实现的 __range_ok 来判断地址范围是否越界。然而即便如此还是有些驱动会有漏洞或者内核的其他地方不经意的访问到用户空间从而导致漏洞有一个例子这里贴出他们对应的标题感兴趣的可以自己去研究
An issue where a provided address with access_ok() is not checked was discovered in i915_gem_execbuffer2_ioctl in drivers/gpu/drm/i915/i915_gem_execbuffer.c in the Linux kernel through 4.19.13所以为了进一步增强安全除了这种显式的地址空间检测外硬件架构上还引入了一系列的硬件访问权限控制来帮助阻挡不经意的或者恶意的访问就是 PAN 和 UAO 特性。
armv8 上有一类特殊的 load store 指令sttr*ldtr*它们是非特权指令即在用户态使用的加载和存储指令以及str*ldr*它们是特权指令在特权模式使用加载和存储指令。
2 硬件PAN的支持
Armv8.1-A引入了硬件Privilege Access NeverPAN的支持。软件可以设置PSTATE.PAN1使能这个功能。当这个功能使能时对于任何具有user可读或可写权限的内存在CPU运行在特权模式时对这些内存都不能访问Access Never不管内存的特权访问权限是什么。PAN提供一个硬件控制门实现运行在kernel态时任何不小心的如https://www.cvedetails.com/cve/CVE-2018-20669/ An issue where a provided address with access_ok() is not checked或故意的对user space memory的访问都会被PAN阻止。只有在copy_from/to_user或get/put_user这些预知的地方设置PAN允许对user space内存的访问。 如果启用了 armv8.1 的 PAN 特性那么str*,ldr* 这些特权指令访问访问用户空间时会产生 data abort 异常如果禁用 PAN 特性str*/ldr*则可以正常访问用户空间。因此在 {get/put}user, copy{from/to}_user 的对应架构实现中会暂时禁用 PAN如下
ENTRY(__arch_copy_from_user)uaccess_enable_not_uao x3, x4, x5 ----------------------------------------(1)add end, x0, x2
#include copy_template.Suaccess_disable_not_uao x3, x4 ----------------------------------------(2)mov x0, #0 // Nothing to copyret
ENDPROC(__arch_copy_from_user)
EXPORT_SYMBOL(__arch_copy_from_user).macro uaccess_enable_not_uao, tmp1, tmp2, tmp3uaccess_ttbr0_enable \tmp1, \tmp2, \tmp3
alternative_if ARM64_ALT_PAN_NOT_UAOSET_PSTATE_PAN(0) --------------------------------------------------------(3)
alternative_else_nop_endif.endm.macro uaccess_disable_not_uao, tmp1, tmp2uaccess_ttbr0_disable \tmp1, \tmp2
alternative_if ARM64_ALT_PAN_NOT_UAOSET_PSTATE_PAN(1) --------------------------------------------------------(3)
alternative_else_nop_endif.endm#define SET_PSTATE_PAN(x) __emit_inst(0xd500401f | PSTATE_PAN | ((!!x) PSTATE_Imm_shift))(1)23在调用从用户空间拷贝的 api 时首先会调用 uaccess_enable_not_uao 来激活访问用户空间这里其实调用的就是 SET_PSTATE_PAN(0) 先暂时不看 UAO 特性假设这里没有启用 UAO 特性禁用了 PAN 那么此时使用 ldr/str 指令访问用户空间不会出现问题当完成访问后调用 SET_PSTATE_PAN(1)再次开启 PAN 特性所以这里就是在特殊的访问 api 中临时禁用 PAN以便顺利访问用户空间。
3 UAO 的支持
Armv8.2-a引入了User Access Override UAO功能。当软件通过设置PSTATE.UAO1 使能UAO功能时sttr*/ldtr* 这些利用非特权权限访问指令会变成正常的Load/store指令执行在特权级时利用特权级的访问权限执行在非特权级时利用非特权级的访问权限。当软件设置PSTATE.UAO0时sttr*/ldtr* 这些利用非特权权限访问指令还是使用非特权级访问权限即使执行在特权级。什么意思呢就是当启用了 UAO 后 sttr*/ldtr* 这些指令就具有了特性模式 str*/ldr* 指令相同的行为一旦访问用户空间就会触发 data abort而可以正常访问内核空间。 所以当支持 UAO 时上面的 uaccess_ttbr0_enable 和 uaccess_ttbr0_disable将变为空操作此时 PAN 为默认启用状态。而在 copy_xx_user.S 的源码中定义了如下宏 .macro ldrb1 ptr, regB, valuao_user_alternative 9998f, ldrb, ldtrb, \ptr, \regB, \val.endm.macro strb1 ptr, regB, valstrb \ptr, [\regB], \val.endm
...
....macro uao_user_alternative l, inst, alt_inst, reg, addr, post_incalternative_if_not ARM64_HAS_UAO
8888: \inst \reg, [\addr], \post_inc;nop;alternative_else\alt_inst \reg, [\addr];add \addr, \addr, \post_inc;alternative_endif_asm_extable 8888b,\l;.endm也就是说当支持 UAO 时加载指令使用的 ldtr*不支持 UAO 时使用的是 ldr*PAN 生效。 所以此时的加载存储指令是非特权指令当我们在 set_fs 中设置为 KERNEL_DS 时UAO 生效非特权指令行为和特权指令一样因此我们可以正常访问内核空间但是一旦访问用户空间将会触发 data abort 异常在 pagefault 流程中我们会进入 __do_kernel_fault - fixup_exception 从而返回 EFAULT。而当我们 set_fs 为 USER_DS 后会清除 UAO那么此时使用 ldtr 可以正常访问用户空间但是不能访问内核空间可以有效的控制意外的非法空间访问又可以正确的读取用户空间。 综上当使用了 PAN 和 UAO 特性后可以弥补 arm64 对用户空间读写访问上的设计缺陷并且提供更全面的保护机制。所以看起来 PAN 和 UAO 更像是一种对架构的补丁。 通过上述分析我们可以知道为什么我们在 bcc 中使用 bpf_probe_read_kernel 无法读取到 filename 的真实数据以及需要切换为 bpf_probe_read_user 的原因。
备注linux-5.11 后又去掉了 CONFIG_ARM64_UAO 选项原因是后续 uaccess 不考虑通过 set_fs 来改变 addr_limit。 相关 patch 可以参考
[11/13] arm64: uaccess: remove set_fs() [PATCH v5 09/10] ARM: uaccess: remove set_fs() implementation