我想知道Linux内核在执行copy_from_user()
函数时如何禁用x86 SMAP。我试图在源代码中找到某些内容,但失败了。
[Supervisor Mode Access Prevention (SMAP)是x86 CPU的一项安全功能,可以防止内核访问意外的用户空间内存,这有助于抵御各种攻击。
如您链接的Wikipedia页面中所述:
当内存分页处于活动状态并且CR4控制寄存器中的SMAP位置1时,启用SMAP。通过设置EFLAGS.AC(对齐检查)标志,可以暂时禁用SMAP进行显式内存访问。
stac
(设置AC标志)和clac
(清除AC标志)指令可用于轻松设置或清除标志。
Linux内核确实这样做是为了暂时禁用SMAP:它在复制数据之前使用stac
设置EFLAGS.AC,然后在完成后使用clac
清除EFLAGS.AC。
理论上很简单,但实际上Linux内核代码库是函数,宏,内联汇编模板等的丛林。要确切地了解如何完成操作,我们可以从copy_from_user()
开始看源代码。 :
copy_from_user()
被调用时,它会快速检查内存范围是否有效,然后调用copy_from_user()
...
...这会再进行两次检查,然后调用_copy_from_user()
...
...,在进行实际复制之前,调用_copy_from_user()
...
...这只是一个扩展为raw_copy_from_user()
的宏。
关注raw_copy_from_user()
,这是一个简单的内联函数,我们有:
__uaccess_begin_nospec()
__uaccess_begin_nospec()
宏是一个非常复杂的宏,用于基于CPU支持在运行时为指令选择替代方案。您可以检查在其中定义了源文件的更多信息。在这种情况下,它用于基于CPU支持来有条件地决定是否执行stac(); barrier_nospec()
指令(旧的x86 CPU没有SMAP,因此没有该指令:在那些CPU上,这变成了无操作)。
查看stac()
宏,我们看到:
stac()
哪个是汇编的alternative("", __ASM_STAC, X86_FEATURE_SMAP);
操作码,以字节为单位。这是用alternative()
指令而不是助记符定义的,因为同样,即使在不存在该指令的旧CPU上,也需要编译该指令。在运行时,alternative()
指令用于检查stac
(当用__ASM_STAC
执行__ASM_STAC
以获取#define __ASM_STAC ".byte 0x0f,0x01,0xcb"
时,stac
的第20位),这告诉内核SMAP是否可用(使指令变为.byte
)(或保持无操作状态)。
一旦完成所有这些疯狂操作(实际上全部都归结为一条指令),就会执行来自用户内存的实际复制,然后使用cpuid
宏重新启用SMAP。此宏以与我们刚看到的相同的方式使用X86_FEATURE_SMAP
,并最终执行ebx
如果需要。