Linux内核如何在copy_from_user中临时禁用x86 SMAP?

YvG*_*vG3 2 security x86 operating-system kernel linux-kernel

我想知道Linux内核在执行该copy_from_user()函数时如何禁用x86 SMAP。我试图在源代码中找到一些东西,但失败了。

超级用户模式访问保护 (SMAP)是 x86 CPU 的一项安全功能,可防止内核访问非预期的用户空间内存,从而有助于抵御各种攻击。

Mar*_*lli 8

正如您链接的维基百科页面中所记录的:

当内存分页处于活动状态并且 CR4 控制寄存器中的 SMAP 位被置位时,SMAP 被启用。通过设置 EFLAGS.AC(对齐检查)标志,可以暂时禁用 SMAP 进行显式内存访问。(设置stacAC 标志)和clac(清除 AC 标志)指令可用于轻松设置或清除标志。

Linux 内核正是这样做来临时禁用 SMAP:它stac在复制数据之前设置 EFLAGS.AC,然后clac在完成后清除 EFLAGS.AC。

AC 标志自 486 年起就存在,作为用户空间加载/存储的对齐检查;SMAP 重载了该标志位的含义。 stac/clac是 SMAP 的新功能,仅允许在内核模式下使用 (CPL=0);它们在用户空间(以及没有 SMAP 的 CPU 上,以及在内核模式下)发生故障。


理论上它非常简单,但实际上 Linux 内核代码库是函数、宏、内联汇编模板等的丛林。要确切了解这是如何完成的,我们可以查看源代码,从以下位置开始copy_from_user()

  1. 调用时copy_from_user(),它会快速检查内存范围是否有效,然后调用_copy_from_user()...

  2. ...它又进行了几次检查,然后调用raw_copy_from_user()...

  3. ...在进行实际复制之前,调用__uaccess_begin_nospec()...

  4. ...这只是一个扩展为stac(); barrier_nospec().

  5. 关注stac(),这是一个简单的内联函数,我们有:

     alternative("", __ASM_STAC, X86_FEATURE_SMAP);
    
    Run Code Online (Sandbox Code Playgroud)

alternative()宏是一个相当复杂的宏,用于根据 CPU 支持在内核启动时选择指令的替代项。您可以检查定义它的源文件以获取更多信息。在这种情况下,它用于stac根据 CPU 支持来决定内核是否需要使用该指令(旧的 x86 CPU 没有可用的 SMAP,因此没有该指令:在这些 CPU 上,这只是一个不可用的指令) - 操作)。

看看__ASM_STAC宏观我们看到:

#define __ASM_STAC  ".byte 0x0f,0x01,0xcb"
Run Code Online (Sandbox Code Playgroud)

这是以stac字节为单位的汇编操作码。这是使用指令而不是助记符来定义的.byte,因为即使在 binutils 版本不知道这些指令的旧工具链上,也需要进行编译。

一旦启动,该cpuid指令用于检查(执行时的X86_FEATURE_SMAP第20位以获得扩展功能),这告诉内核SMAP是否可用(重写机器代码以使指令变为)或不可用(保持无操作)。ebxcpuideax=7, ecx=0stac

一旦完成所有这些疯狂的操作(实际上所有这些都只是归结为一条指令),就会执行来自用户内存的实际复制,__uaccess_end()然后使用宏来重新启用 SMAP。该宏的使用alternative()方式与我们刚刚看到的相同,并最终执行clac(或 a nop)。