KASLR
作者:smcdef 发布于:2018-5-6 10:00 分类:内存管理
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的值。
/ { chosen { kaslr-seed = <0x10000000>; }; };
如何获取偏移
kaslr-seed属性的解析在kaslr_early_init函数完成。该函数根据输入参数dtb首地址(物理地址)解析dtb,找到偏移地址,最后返回。kaslr_early_init实现如下。u64 __init kaslr_early_init(u64 dt_phys) { void *fdt; u64 seed, offset, mask, module_range; const u8 *cmdline, *str; int size; early_fixmap_init(); /* 1 */ fdt = __fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL); /* 1 */ seed = get_kaslr_seed(fdt); /* 2 */ if (!seed) return 0; cmdline = get_cmdline(fdt); str = strstr(cmdline, "nokaslr"); /* 3 */ if (str == cmdline || (str > cmdline && *(str - 1) == ' ')) return 0; mask = ((1UL << (VA_BITS - 2)) - 1) & ~(SZ_2M - 1); /* 4 */ offset = seed & mask; /* use the top 16 bits to randomize the linear region */ memstart_offset_seed = seed >> 48; /* 5 */ if ((((u64)_text + offset) >> SWAPPER_TABLE_SHIFT) != (((u64)_end + offset) >> SWAPPER_TABLE_SHIFT)) offset = round_down(offset, 1 << SWAPPER_TABLE_SHIFT); /* 6 */ return offset; }
- 由于dtb的地址是物理地址,因此第一步先为dtb区域建立映射。
- 从dtb文件获取kaslr-seed属性的值。
- 确保command line没有传递nokaslr参数,如果传递nokaslr则关闭KASLR。
- 保证传递的偏移地址2M地址对齐,并且保证kernel位于VMALLOC区域大小的一半地址空间以下 (VA_BITS - 2)。当VA_BITS=48时,mask=0x0000_3fff_ffe0_0000。
- 线性映射区地址也会随机化。
- kernel启动初期只有一个PUD页表,因此希望kernel映射在1G(1 << SWAPPER_TABLE_SHIFT)大小范围内,这样就不用两个PUD页表。如果kernel加上偏移offset后不满足这点,自然要重新对齐。
如何创建映射
kernel启动初期在汇编阶段创建映射关系。代码位于head.S文件。在__primary_switched函数中会调用kaslr_early_init得到偏移地址。保存在x23寄存器中。然后重新创建kernel image的映射。__primary_switched: tst x23, ~(MIN_KIMG_ALIGN - 1) // already running randomized? b.ne 0f mov x0, x21 // pass FDT address in x0 bl kaslr_early_init // parse FDT for KASLR options cbz x0, 0f // KASLR disabled? just proceed orr x23, x23, x0 // record KASLR offset ldp x29, x30, [sp], #16 // we must enable KASLR, return ret // to __primary_switch()
创建映射的函数是__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

评论:
2021-10-14 11:24
关于线性映射区地址也会随机化。当前调试kernel-4.14 arm64, 驱动里面kmalloc申请的buf, buf前后连续地址一直是随机的, 关闭CONFIG_RANDOMIZE_BASE之后还是随机的, 如何分析这个随机化, 感谢
2020-05-28 16:23
请教一个问题,就算开启了KASLR(没有设置kaslr_seed。偏移应该为0)和内核代码段被映射到vmalloc区域,内核代码段的hash值应该还是不会改变的吧,我现在通过sha256去计算[_stext, _etext]范围的hash值,在内核的加载过程中会一直变化,难道内核代码段也会发生缺页中断么,我的内核版本和博主您的是一样的:4.14 ARM64
2020-05-28 17:13
1. aarch64会有一些CPU的feature,这些feature会在系统boot的时候动态监测。如果某些feature在当前CPU是支持的,此时会有指令替换的过程,把新feature的指令替换编译期的指令。
2. 类似kprobe技术,会修改代码段达到hook函数的目的。
这些都可能导致代码段的hash值变化。
2020-05-29 16:24
2020-05-29 21:15
2020-06-01 11:05
2020-06-01 14:32
另外你在arm32上测试有什么意义呢?CPU feature检测替换这块是arm64实现的,arm32是否实现了类似机制都不确定,你的测试能说明什么呢?
你应该去看看这个宏定义的作用
#define ALTERNATIVE(oldinstr, newinstr, ...)
2019-09-25 16:02
kaslr_early_init返回了offset,__create_page_tables根据offset去重新创建映射。我没搞明白,如何实现的随机化。
多谢!
2019-02-19 19:20
这样看来即使配置了CONFIG_RANDOMIZE_BASE也没有实现kernel image会被随机copy到不同的位置。 反倒时我觉得配置了CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET才能实现kernel image被随机copy到不同的位置。 因CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET会决定TEXT_OFFSET的值。
2019-02-21 11:58
你配置CONFIG_RANDOMIZE_BASE后,kaslr 没有起效。可能原因文章提到的dts kaslr-seed没有。
2019-04-27 19:14
2019-06-11 15:29
请问一下内核如果配置成了可重定位内核,那么内核是如何通过vmlinux.relocs这个重定位表进行重定位的?
我的理解是内核会遍历vmlinux.relocs ,在每个需要修订的位置,加上内核实际加载地址和理论加载地址的差值,实现重定位;但是这只是改变了vmlinux.relocs,并没有改变位置相关码呀?
请大师指点一二,谢谢。
2019-06-14 09:48
__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)中可以找到。你可以看下这部分实现。
2018-11-03 21:52
使用nm app1查看的符号表和app1在运行时的符号表地址信息不一致(应该是链接脚本重定位地址)
如何才能看到程序的符号表和运行时一致呢??
2018-08-06 16:15
addr_link = addr_run - (0xffffff80ae280000 - 0xffffff8008000000 - 0x80000)
而(0xffffff80ae280000 - 0xffffff8008000000 - 0x80000)实际就是karsl-seed 偏移地址。
功能
最新评论
- wangjing
写得太好了 - wangjing
写得太好了! - DRAM
圖面都沒辦法顯示出來好像掛點了。 - Simbr
bus至少是不是还有个subsystem? - troy
@testtest:只要ldrex-modify-strex... - gh
Linux 内核在 sparse 内存模型基础上实现了vme...
文章分类
随机文章
文章存档
- 2025年4月(5)
- 2024年2月(1)
- 2023年5月(1)
- 2022年10月(1)
- 2022年8月(1)
- 2022年6月(1)
- 2022年5月(1)
- 2022年4月(2)
- 2022年2月(2)
- 2021年12月(1)
- 2021年11月(5)
- 2021年7月(1)
- 2021年6月(1)
- 2021年5月(3)
- 2020年3月(3)
- 2020年2月(2)
- 2020年1月(3)
- 2019年12月(3)
- 2019年5月(4)
- 2019年3月(1)
- 2019年1月(3)
- 2018年12月(2)
- 2018年11月(1)
- 2018年10月(2)
- 2018年8月(1)
- 2018年6月(1)
- 2018年5月(1)
- 2018年4月(7)
- 2018年2月(4)
- 2018年1月(5)
- 2017年12月(2)
- 2017年11月(2)
- 2017年10月(1)
- 2017年9月(5)
- 2017年8月(4)
- 2017年7月(4)
- 2017年6月(3)
- 2017年5月(3)
- 2017年4月(1)
- 2017年3月(8)
- 2017年2月(6)
- 2017年1月(5)
- 2016年12月(6)
- 2016年11月(11)
- 2016年10月(9)
- 2016年9月(6)
- 2016年8月(9)
- 2016年7月(5)
- 2016年6月(8)
- 2016年5月(8)
- 2016年4月(7)
- 2016年3月(5)
- 2016年2月(5)
- 2016年1月(6)
- 2015年12月(6)
- 2015年11月(9)
- 2015年10月(9)
- 2015年9月(4)
- 2015年8月(3)
- 2015年7月(7)
- 2015年6月(3)
- 2015年5月(6)
- 2015年4月(9)
- 2015年3月(9)
- 2015年2月(6)
- 2015年1月(6)
- 2014年12月(17)
- 2014年11月(8)
- 2014年10月(9)
- 2014年9月(7)
- 2014年8月(12)
- 2014年7月(6)
- 2014年6月(6)
- 2014年5月(9)
- 2014年4月(9)
- 2014年3月(7)
- 2014年2月(3)
- 2014年1月(4)
2023-11-14 18:38
因为下面有如下的判断:
if (!prop || len != sizeof(u64))
节点长度不够,直接return 0;