蜗窝科技

KASLR

smcdef · · 内存管理 · 阅读 40492 · 2018年5月存档

KASLR

引言

什么是KASLR?KASLR是kernel address space layout randomization的缩写,直译过来就是内核地址空间布局随机化。KASLR技术允许kernel image加载到VMALLOC区域的任何位置。当KASLR关闭的时候,kernel image都会映射到一个固定的链接地址。对于黑客来说是透明的,因此安全性得不到保证。KASLR技术可以让kernel image映射的地址相对于链接地址有个偏移。偏移地址可以通过dts设置。如果bootloader支持每次开机随机生成偏移数值,那么可以做到每次开机kernel image映射的虚拟地址都不一样。因此,对于开启KASLR的kernel来说,不同的产品的kernel image映射的地址几乎都不一样。因此在安全性上有一定的提升。

注:文章代码分析基于linux-4.15,架构基于aarch64(ARM64)。

如何使用

打开KASLR功能非常简单,在支持KASLR的内核配置选项添加选项CONFIG_RANDOMIZE_BASE=y。同时还需要告知kernel映射的偏移地址,通过dts传递。在chosen节点下添加kaslr-seed属性。属性值就是偏移地址。另外command line不要带nokaslr,否则KASLR还是关闭。dts信息举例如下。顺便说一下,在dts中<>符号中是一个32 bit的值。但是在ARM64平台,这里的kaslr-seed属性是一个特例,他就是一个64 bit的值。

如何获取偏移

kaslr-seed属性的解析在kaslr_early_init函数完成。该函数根据输入参数dtb首地址(物理地址)解析dtb,找到偏移地址,最后返回。kaslr_early_init实现如下。

  1. 由于dtb的地址是物理地址,因此第一步先为dtb区域建立映射。
  2. 从dtb文件获取kaslr-seed属性的值。
  3. 确保command line没有传递nokaslr参数,如果传递nokaslr则关闭KASLR。
  4. 保证传递的偏移地址2M地址对齐,并且保证kernel位于VMALLOC区域大小的一半地址空间以下 (VA_BITS - 2)。当VA_BITS=48时,mask=0x0000_3fff_ffe0_0000。
  5. 线性映射区地址也会随机化。
  6. kernel启动初期只有一个PUD页表,因此希望kernel映射在1G(1 « SWAPPER_TABLE_SHIFT)大小范围内,这样就不用两个PUD页表。如果kernel加上偏移offset后不满足这点,自然要重新对齐。

如何创建映射

kernel启动初期在汇编阶段创建映射关系。代码位于head.S文件。在__primary_switched函数中会调用kaslr_early_init得到偏移地址。保存在x23寄存器中。然后重新创建kernel image的映射。

创建映射的函数是__create_page_tables。

 

__create_page_tables:

    /*

     * Map the kernel image.

     */

    adrp	x0, swapper_pg_dir

    mov_q	x5, KIMAGE_VADDR + TEXT_OFFSET  // compile time __va(_text)

    add	x5, x5, x23                         // add KASLR displacement

    create_pgd_entry x0, x5, x3, x6

    adrp	x6, _end                        // runtime __pa(_end)

    adrp	x3, _text                       // runtime __pa(_text)

    sub	x6, x6, x3                          // _end - _text

    add	x6, x6, x5                          // runtime __va(_end)

    create_block_map x0, x7, x3, x5, x6

这段代码在我的另一篇文章《ARM64 Kernel Image Mapping的变化》已经有分析过,这里就略过了。注意第7行,kernel image映射的虚拟地址加上了一个偏移地址x23。还有一点需要说明,就是对重定位段进行重定位。这部分代码在arch/arm64/kernel/head.S文件__relocate_kernel函数实现。大概说下__relocate_kernel有什么用呢!例如链接脚本中常见的几个变量_text、_etext、_end。这几个你应该很熟悉,他们是一个地址并且他们的值是链接的时候确定下来,那么现在使能kaslr的情况下,代码中再访问_text的值就很明显不是运行时的虚拟地址,而是链接时候的值。因此,__relocate_kernel函数可以负责重定位这些变量。保证访问这些变量的值依然是正确的值。这部分涉及编译和链接,有兴趣的可以研究一下《程序员的自我修养》这本书(我不太熟悉)。

addr2line怎么办

KASLR在技术上增加了OS安全性,但是对于调试或许增加了些难度。何以见得呢?首先,我们知道编译kernel的时候链接地址和最终运行地址是不一样的,因此如果发生oops,栈的回溯信息中的函数地址其实都是运行地址。如果你使用过addr2line工具的话,应该不会陌生addr2line -e vmlinux 0xffffff8000080000这条命令获取某个地址在代码中的哪一行。那么现在问题是oops中的地址和链接地址有一个偏差,并且这个偏差随着bootloader传递的值而变化。现在摆在我们眼前的是addr2line工具还怎么用?下面就为你答疑解惑。kernel开机log中会打印Virtual kernel memory layout。举例如下。

 

Virtual kernel memory layout:

  modules : 0xffffff8000000000 - 0xffffff8008000000   (   128 MB)

  vmalloc : 0xffffff8008000000 - 0xffffffbebfff0000   (   250 GB)

    .text : 0xffffff80ae280000 - 0xffffff80af2e0000   ( 16768 KB)

  .rodata : 0xffffff80af300000 - 0xffffff80afb60000   (  8576 KB)

    .init : 0xffffff80afb60000 - 0xffffff80b01e0000   (  6656 KB)

    .data : 0xffffff80b01e0000 - 0xffffff80b044f200   (  2493 KB)

     .bss : 0xffffff80b044f200 - 0xffffff80b0e18538   ( 10021 KB)

  fixed   : 0xffffffbefe7fb000 - 0xffffffbefec00000   (  4116 KB)

  PCI I/O : 0xffffffbefee00000 - 0xffffffbeffe00000   (    16 MB)

  vmemmap : 0xffffffbf00000000 - 0xffffffc000000000   (     4 GB maximum)

            0xffffffbf00000000 - 0xffffffbf03000000   (    48 MB actual)

  memory  : 0xffffffc000000000 - 0xffffffc0c0000000   (  3072 MB)

注意看以上.text区域(kernel代码段)起始地址和结束地址是不是位于VMALLOC区域。如果发生oops,log中函数的地址必然是一个位于.text段的地址,假设是addr_run,但是链接地址应该是KIMAGE_VADDR + TEXT_OFFSET,这两个宏定义参考这篇文章《ARM64 Kernel Image Mapping的变化》。在这个例子中,KIMAGE_VADDR = 0xffffff8008000000,TEXT_OFFSET = 0x80000。addr2line工具使用的必须是链接地址,因此需要将addr_run转换成链接地址。公式很容易推导出来,addr_link = addr_run - .text_start + vmalloc_start + TEXT_OFFSET。在这个例子中就是addr_link = addr_run - 0xffffff80ae280000 + 0xffffff8008000000 + 0x80000。计算的addr_link就可以使用addr2line工具解析了。Have fun!

标签:kaslr

继续讨论

新讨论请用 GitHub 账号在下方发言——评论会保存在本仓库的 Discussions 里,与站点同寿; 再下方的「历史评论」是原站的只读存档。

评论(29 条,只读存档)

以下为原站的历史评论(只读存档);继续讨论请用上方的评论区。

  • hzm

    请问下,开了KASLR后, kernel image 加载到VMALLOC是随机变化的, 加载到物理地址还是固定的吗? 因为如果不是固定,bootloader那边加载image也需要偏移的信息 谢谢。
    • smcdef

      @hzm:物理地址还是以前一样,改变的只是虚拟地址而已,物理地址在哪其实没什么影响,创建的是虚拟地址到物理地址的映射关系
      • hzm

        @smcdef:多谢解答。。另外这个过程可以理解成静态地址重定位? 还有的是不大理解 为什么要0x80000这个偏移,没KASLR的版本是为了预留一段空间用来fix mapping吗? 那打开了KASLR似乎不需要这个偏移?
        • smcdef

          @hzm:针对ARM32架构,0x80000偏移之前的物理地址是特殊用途,例如页表。但是针对ARM64架构,好像没什么作用。你说的fix mapping,可以参考另一篇fix mapping的文章。
          • hzm

            @smcdef:TEXT_OFFSET放的是哪个页表?看了窝窝内存初始化【上】这篇,说swapper进程放在bss段以后
            • smcdef

              @hzm:注意区分ARM32和ARM64的区别
  • litao6169

    addr_link = addr_run - 0xffffff80ae280000 + 0xffffff8008000000 + 0x80000 等同于: addr_link = addr_run - (0xffffff80ae280000 - 0xffffff8008000000 - 0x80000) 而(0xffffff80ae280000 - 0xffffff8008000000 - 0x80000)实际就是karsl-seed 偏移地址。
    • hymmsx

      @litao6169:你这个结果计算出来是A6200000和他设的A6200000不一样?
    • hymmsx

      @litao6169:你这个结果计算出来是A6200000和他设的A6200000不一样?
  • 天津网站建设

    感谢博主,写得真好
  • zhangge0409

    请教下博主,我想查看一个程序在运行时的符号表信息,比如程序为app1, 使用nm app1查看的符号表和app1在运行时的符号表地址信息不一致(应该是链接脚本重定位地址) 如何才能看到程序的符号表和运行时一致呢??
  • yiyeguzhou100 主页

    感谢作者写了这么好的文档, 我在们的产品上用的arm64架构, 我发现CONFIG_RANDOMIZE_BASE配置了(CONFIG_RANDOMIZE_BASE=y),CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET没有配置(is not set), 我编译个若干次内核进行测试, 发现kernel image会被load到固定的位置(0xffff000010080000~0xffff000010c95000), 其中vmalloc区域的地址是0xffff000010000000~0xffff7bffbfff0000。 这样看来即使配置了CONFIG_RANDOMIZE_BASE也没有实现kernel image会被随机copy到不同的位置。 反倒时我觉得配置了CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET才能实现kernel image被随机copy到不同的位置。 因CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET会决定TEXT_OFFSET的值。
    • smcdef

      @yiyeguzhou100:当你配置CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET后,你就会发现每次开机后,kernel image 映射开始地址是固定不变的。你看到现象虽然变了,但是这并不是kaslr ,kaslr 的目的是每次开机都不一样。实际上这个选项是配置编译时期使TEXT_OFFSET随机值。不配置该选项时,默认TEXT_OFFSET = 0x00080000。所以这个选项其实和kaslr 无关。该配置选项实际上还是链接地址等于最终的运行地址。但是kaslr 却不想等。 你配置CONFIG_RANDOMIZE_BASE后,kaslr 没有起效。可能原因文章提到的dts kaslr-seed没有。
      • qifeifly

        @smcdef:请问开启kaslr后,kernel image被随机映射在vmalloc区,链接地址和运行地址不相等,若遇到位置相关码如何处理?
        • smcdef

          @qifeifly:所以打开kaslr后,会做重定位操作,否则不能运行啊
          • nicole

            @smcdef:大师您好 请问一下内核如果配置成了可重定位内核,那么内核是如何通过vmlinux.relocs这个重定位表进行重定位的? 我的理解是内核会遍历vmlinux.relocs ,在每个需要修订的位置,加上内核实际加载地址和理论加载地址的差值,实现重定位;但是这只是改变了vmlinux.relocs,并没有改变位置相关码呀? 请大师指点一二,谢谢。
            • smcdef

              @nicole:以ARM64架构为例,relocate 实现代码位于:arch/arm64/kernel/head.S __relocate_kernel() 函数。 __relocate_kernel: /* * Iterate over each entry in the relocation table, and apply the * relocations in place. */ ldr w9, =__rela_offset // offset to reloc table ldr w10, =__rela_size // size of reloc table mov_q x11, KIMAGE_VADDR // default virtual offset add x11, x11, x23 // actual virtual offset add x9, x9, x11 // __va(.rela) add x10, x9, x10 // __va(.rela) + sizeof(.rela) 0: cmp x9, x10 b.hs 1f ldp x11, x12, [x9], #24 ldr x13, [x9, #-8] cmp w12, #R_AARCH64_RELATIVE b.ne 0b add x13, x13, x23 // relocate str x13, [x11, x23] b 0b 1: ret ENDPROC(__relocate_kernel) __rela_offset和__rela_size在链接脚本(arch/arm64/kernel/vmlinux.lds.S)中可以找到。你可以看下这部分实现。
  • TTTT

    @smcdef,你好,感谢分享。有问题请教: kaslr_early_init返回了offset,__create_page_tables根据offset去重新创建映射。我没搞明白,如何实现的随机化。 多谢!
    • smcdef

      @TTTT:kaslr_early_init() 返回的 offset 得值是 BootLoader 通过 dts 传递过来的。所以,随机产生 offset 的工作由 BootLoader 完成。
  • chenlifu2015

    博主,您好 请教一个问题,就算开启了KASLR(没有设置kaslr_seed。偏移应该为0)和内核代码段被映射到vmalloc区域,内核代码段的hash值应该还是不会改变的吧,我现在通过sha256去计算[_stext, _etext]范围的hash值,在内核的加载过程中会一直变化,难道内核代码段也会发生缺页中断么,我的内核版本和博主您的是一样的:4.14 ARM64
    • smcdef

      @chenlifu2015:我不明白你提出缺页异常和代码段变化有什么关系。当然内核的代码也不会发生缺页异常。你好奇的问题应该是代码段为什么hash值变化了?代码段发生变化,也和KASLR没有关系。我这里给你提供一点思路证明代码段确实可以发生变化。 1. aarch64会有一些CPU的feature,这些feature会在系统boot的时候动态监测。如果某些feature在当前CPU是支持的,此时会有指令替换的过程,把新feature的指令替换编译期的指令。 2. 类似kprobe技术,会修改代码段达到hook函数的目的。 这些都可能导致代码段的hash值变化。
      • chenlifu2015

        @smcdef:感谢博主的回复,我今天去了解了一下kprobe机制,确实可能会导致内核代码段被篡改,但您说的第一点我确实没找到相关资料,能方便一点一下么,比如说一个具体的机制我能够去熟悉,或许这个动态监测的机制能够通过配置宏关闭
        • smcdef

          @chenlifu2015:例如atomic实现有2种方式,LSE和LL/SC。印象中LSE是armv8.2以后支持的特性。LL/SC是老的实现方式。你看看atomic实现的2个文件吧。
          • chenlifu2015

            @smcdef:我看了下我使用的SDK中内核关于atomic的代码实现,可以确定这部分是通过编译宏来决定的,并且kprobe特性在编译时也没有被打开,因此可以排除kprobe和LSE的原因导致内核代码段hash发生变化,博主,还有其它的思路么,因为我在4.12的arm32位上计算内核hash是不会发生变化的,也就是说是可以关闭掉cpu feature带来的影响,博主,我猜测了一下,这种动态检测的特性能否通过配置宏来关闭呢
            • smcdef

              @chenlifu2015:我让你看的目的不是为了让你确认是不是宏确认编译。而是让你看怎么根据CPU feature检测,动态替换代码的。默认情况下本身就是宏唯一确定了使用LL/SC的方式,只不过boot的时候会检测feature,如果支持LSE,就会把LSE的指令替换了LL/SC的方式。你通过编译是无法确定的。 另外你在arm32上测试有什么意义呢?CPU feature检测替换这块是arm64实现的,arm32是否实现了类似机制都不确定,你的测试能说明什么呢? 你应该去看看这个宏定义的作用 #define ALTERNATIVE(oldinstr, newinstr, ...)
  • 蜗牛 主页

    您好,窝窝 请问arm平台有对kaslr机制的处理的,kernel414上好像没有
  • 悟实

    请教wowo大佬: 关于线性映射区地址也会随机化。当前调试kernel-4.14 arm64, 驱动里面kmalloc申请的buf, buf前后连续地址一直是随机的, 关闭CONFIG_RANDOMIZE_BASE之后还是随机的, 如何分析这个随机化, 感谢
    • zxq

      @悟实:你这个主要原因是因为slub使能了CONFIG_SLAB_FREELIST_RANDOM,跟CONFIG_RANDOMIZE_BASE没关系。
  • Enlin

    在4.15中,我觉得节点信息应该是:kaslr-seed = <0x10000000 0x10000000>;这类格式的。 因为下面有如下的判断: if (!prop || len != sizeof(u64)) 节点长度不够,直接return 0;