筑起Linux Kernel 安全堡垒

原创:  ZenonXiu(修志龙) 微信公众号  MindShare思享   1周前

本文旨在帮助工程师理解可使用的kernel安全选项,以加固其使用的kernel安全性。



Linux Kernel安全预览


虽然随着安全的重要性的提升,Linux Kernel 也在安全性方面也做了很多事情,比如SELinux, 但是因为Linux kernel的庞大,还是有很多存在kernel很久的安全问题被发现或是还在隐匿中。

 

Google统计2014-2016年的kernel bugs的统计,

可以看出,有诸多安全性问题

·     不正确的安全边界检查,甚至缺失

·     信息泄露

·     权限访问检查的缺失

·     Stack overflow ROP攻击


有关Linux kernel 的安全问题list可以在这个网站找到,

https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=linux+kernel  

比如

CVE-2018-1068

A flaw was found in the Linux 4.x kernel's  implementation of 32-bit syscall interface for bridging. This allowed a  privileged user to arbitrarily write to a limited range of kernel memory.

CVE-2018-10675

The do_get_mempolicy function in mm/mempolicy.c  in the Linux kernel before 4.12.9 allows local users to cause a denial of  service (use-after-free) or possibly have unspecified other impact via  crafted system calls.

本文试图提供几个可以加固Linux Kernel的选项和对其的分析,以帮助大家理解。

 

这些方法包含,


限制可执行代码的空间


CONFIG_DEBUG_RODATA

 

一般来说Linux kernel会将内核空间设置RWX属性,所以

  • kernel代码可以被修改,

  • Const数据(RO data)可以修改,

  • CPU可以在数据空间执行代码。

CONFIG_DEBUG_RODATA功能将内核内存划分为多个逻辑区段,并限定每个区段的页访问权限。将代码标记为只读可执行。将数据区段标记为不可执行,并进一步将其细分为只读区段和读写区段。该功能通过配置选项 CONFIG_DEBUG_RODATA 启用。


CONFIG_DEBUG_RODATA

   arm

   prompt: Make kernel text and rodata read-only

   type: bool

   depends on: ( CONFIG_MMU && ! CONFIG_XIP_KERNEL ) && (CONFIG_CPU_V7 )

   defined in arch/arm/mm/Kconfig

   found in Linux kernels: 3.19, 4.0–4.6, 4.6+HEAD

   Help text:

   If this is set, kernel text and rodata memory will be made read-only,and non-text kernel memory will be made non-executable. The tradeoff is thateach region is padded to section-size (1MiB) boundaries (because theirpermissions are different and splitting the 1M pages into 4K ones causes TLBperformance problems), which can waste memory.

 

   arm64

   prompt: Make kernel text and rodata read-only

   type: bool

   depends on: (none)

   defined in arch/arm64/Kconfig.debug

   found in Linux kernels: 4.0–4.6, 4.6+HEAD

   Help text:

   If this is set, kernel text and rodata will be made read-only. This isto help catch accidental or malicious attempts to change the kernel'sexecutable code.

   If in doubt, say Y


在kernel 4.6以后版本里CONFIG_DEBUG_RODATA是默认选项。虽然这个选项名字看起来像是给kernel debug用的,其实是加强(hardening)kernel的。

 

在kernel启动过程中(init/main.c),kernel_init调用mark_rodata_ro将RO data 设置Execute Never (XN)的属性,使CPU不能跳到RO data里面去把一个RO data当成指令来执行,避免hacker找到kernel的漏洞迫使CPU跳到RO data中执行,把某一个RO data当作程序的跳板。


staticint __ref kernel_init(void *unused)
{
   kernel_init_freeable();
...
    mark_rodata_ro();
...
}

 在Arm32 Linux kernel中,对于kernel的.text, .rodata段,会设置APX=1, AP[1:0]=0b01 (Readonly), 相关代码在 arch/arm/mm/init.c中


#ifdefCONFIG_DEBUG_RODATA
staticstructsection_permro_perms[] = {
       /* Make kernelcode and rodata RX (set RO). */
       {
              .start  = (unsignedlong)_stext,
               .end    = (unsignedlong)__init_begin,
...
              .mask   = ~(PMD_SECT_APX |PMD_SECT_AP_WRITE),
              .prot   = PMD_SECT_APX |PMD_SECT_AP_WRITE,
              .clear  = PMD_SECT_AP_WRITE,
#endif
       },
};
#endif


在Arm64 Linux kernel,相关代码在arch/arm64/mm/mmu.c


#ifdefCONFIG_DEBUG_RODATA
voidmark_rodata_ro(void)
{
       create_mapping_late(__pa(_stext), (unsignedlong)_stext,
                               (unsignedlong)_etext - (unsigned )_stext,
                               PAGE_KERNEL_EXEC | PTE_RDONLY);
}
#endif


虽然在armv7-a和armv8-a构架里面有定义,

·      SCTLR_EL1.WXN: 当一块memory在页表属性里定义成是writable的,CPU硬件会把它当成是不可执行XN的。

·      SCTLR_EL1.UWXN: 当一块memory在页表属性里定义成是user非特权级writable的,CPU运行在EL1 Linux kernel硬件会把它当成是不可执行XN的。

但是看起来现在的kernel暂时还没有利用这一功能(arch/arm64/include/asm/sysreg.h)

#define SCTLR_EL1_SET   (SCTLR_ELx_M | SCTLR_ELx_C | SCTLR_ELx_SA |\SCTLR_EL1_SA0 | SCTLR_EL1_SED | SCTLR_ELx_I |\SCTLR_EL1_DZE | SCTLR_EL1_UCT | SCTLR_EL1_NTWI |\SCTLR_EL1_NTWE | SCTLR_ELx_IESB | SCTLR_EL1_SPAN |\ENDIAN_SET_EL1 | SCTLR_EL1_UCI | SCTLR_EL1_RES1)



#define SCTLR_EL1_CLEAR (SCTLR_ELx_A | SCTLR_EL1_CP15BEN | SCTLR_EL1_ITD |\SCTLR_EL1_UMA | SCTLR_ELx_WXN | ENDIAN_CLEAR_EL1 |\SCTLR_EL1_RES0)


CONFIG_DEBUG_SET_MODULE_RONX

另外一个和CONFIG_DEBUG_RODATA 类似的配置,但是是给kernel module来用的。

 

Privilege execute only

在armv8的构架里还有定义PXN (privilege execute only), 其用法是


On AArch64,the meaning of the XN bit has changed to UXN (user). The PXN (privileged) bit must be set to prevent kernel execution. Without the PXN bit set, the CPU may speculatively access device memory.  All the mappings that the kernel must not execute from (including user mappings) have the PXN bit set.


+#define PAGE_NONE        _MOD_PROT(pgprot_default, PTE_NG | PTE_PXN | PTE_UXN | PTE_RDONLY)
    +#define PAGE_SHARED        _MOD_PROT(pgprot_default, PTE_USER | PTE_NG | PTE_PXN | PTE_UXN)
    +#define PAGE_SHARED_EXEC    _MOD_PROT(pgprot_default, PTE_USER | PTE_NG | PTE_PXN)
    ...
    +#define PAGE_KERNEL_EXEC    _MOD_PROT(pgprot_default, PTE_UXN | PTE_DIRTY)
 
    -#define __PAGE_NONE        __pgprot(_PAGE_DEFAULT | PTE_NG | PTE_XN | PTE_RDONLY)
    -#define __PAGE_SHARED        __pgprot(_PAGE_DEFAULT | PTE_USER | PTE_NG | PTE_XN)


限制内核对应用空间的访问

如文章开头所说,很多kernel安全问题都是因为在kernel运行时不当user指针引用。

虽然我们可以要求kernel developer或是driver developer小心编程,更好的方式是通过CPU硬件的机制来保护,这样的话即使代码检查不严格,硬件也可以帮助阻止攻击。

通过配置硬件阻止内核直接访问用户空间内存来增强对内核的保护。这样可以增大许多攻击的难度,这是因为攻击者对于可执行的内核内存(特别是启用 CONFIG_DEBUG_RODATA 的内存)的控制力要弱得多。

一般来说,用户和内核内存使用同一地址空间,虽然如以前我的一篇文章所说,他们可能会使用不同的页表(TTBR0/TTBR1)来做MMU地址转换,这意味着:

  •      他们都可以被同一个load/store访问

  •     如果权限没问题,kernel可以访问user 的内存地址,不会产生异常

  •      也可以跳转到用户空间的内存执行代码

逻辑上,user和kernel的memory 应该是分开的,

  •   用户空间不能访问kernel空间

  •    kernel空间只能通过特定的interface来访问用户空间的内存,因为这个接口是kernel代码显式调用的,程序员很清楚怎么访问用户空间的,所以应该不会留下安全问题。这些接口一般是,

  1.  get_user

  2. copy_from_user

  3.  copy_to_user

  •  但是在kernel空间的地址引用,比如user传给kernel的参数,是需要小心使用的,虽然kernel提供了检查一个地址是否是user地址的函数,但是要求每次对指针做这个检查是困难的。

在arm构架和Linux kernel引进了通过硬件阻止kernel空间程序对user space的内存的不经意(或是Hacker诱导)的访问,同时用允许在显式访问接口访问(get_user etc)。

在不同的构架版本里实现的方式有所不同,请参照以下表格,


地址随机化KASLR

这个安全特性引入了每次设备引导时产生的随机“偏移”,通过该偏移,内核的基地址被移位。通常,内核被加载到固定的物理地址,其对应于内核的VAS中的固定虚拟地址。通过引入KASLR,所有内核的内存,包括其代码,被这个随机偏移量移动。

ARM64平台上的KASLR也已经从Linux kernel 4.6版本开始支持(CONFIG_RANDOMIZE_BASE=y)。安卓8.0使得KASLR在安卓内核4.4上和更新的版本开始受到支持。

KASLR通过在每次启动时随机化内核代码的地址来防御内核漏洞。例如,在ARM64平台上,它根据设备的内存配置,添加了13-25位的熵,这会使得代码重用攻击更加困难。

这个随机数需要在kernel之外产生,再由bootloader传给kernel, 比如用device tree

  /{
     chosen {
          kaslr-seed =<0x10000000>;
      };
  };


猜你喜欢

转载自blog.csdn.net/weixin_39366778/article/details/80491166
今日推荐