内存管理-22-KASLR

基于msm-5.4


一、简介

1. 什么是KASLR

KASLR是 kernel address space layout randomization 的缩写,直译过来就是内核地址空间布局随机化。KASLR技术允许将kernel image映射到 vmalloc 区域的任何位置(待确认哦)。

2. 引入KASLR的原因

没有KASLR的时候,kernel image都会映射到一个固定的链接地址,安全性得不到保证。KASLR技术可以让kernel image映射的地址相对于链接地址有个偏移。这个偏移可以通过dts设置,如果bootloader支持每次开机随机生成偏移数值,那么可以做到每次开机kernel image映射的虚拟地址都不一样。因此在安全性上有一定的提升。

3. 主要实现文件

arch/arm64/kernel/kaslr.c
arch/arm64/kernel/head.S


二、实现逻辑

1. 偏移对内核镜像映射的影响

//arch/arm64/kernel/head.S

__primary_switched:
    mov    x0, x21                //pass FDT address in x0
    bl    kaslr_early_init    //parse FDT for KASLR options
    cbz    x0, 0f                //KASLR disabled? just proceed 若返回0则跳转到下面标签0:处运行,若返回的offset非0则继续执行
    orr    x23, x23, x0            //record KASLR offset x23或上返回的offsets
    ret                            // to __primary_switch() 返回到上一级函数中
0:
    b    start_kernel        //若返回offset非0就不会从这里启动内核了
ENDPROC(__primary_switched)

这里调用 kaslr_early_init(), 将设备树做为参数,返回值保存在x0中,然后位或给x23, 由于x23初始值是0,因此x23=x0=返回值offset.

若返回值offset不为0,要在内核虚拟基地址上加上这个offset,重新映射一遍内核镜像:

__primary_switch:
    adrp    x1, init_pg_dir
    bl    __enable_mmu  //这里就使能了MMU
#ifdef CONFIG_RELOCATABLE
    bl    __relocate_kernel
#ifdef CONFIG_RANDOMIZE_BASE
    ldr    x8, =__primary_switched
    adrp    x0, __PHYS_OFFSET
    blr    x8  //若kaslr_early_init()返回的offset非0,才会返回到这里继续往下执行
    /* 若执行到这里,在 x23 中有一个 KASLR 偏移,需要丢弃当前内核映射并创建一个新的内核映射来考虑到它 */
    pre_disable_mmu_workaround
    bl    __create_page_tables        //recreate kernel mapping 重新创建内核映射页表
#endif
#endif
    ldr    x8, =__primary_switched //在这里执行 start_kernel()
    adrp    x0, __PHYS_OFFSET
    br    x8
ENDPROC(__primary_switch)

返回offset!=0会返回到生成函数,导致 blr x8 被执行,之后disable掉之前的映射,重新创建内核镜像的页表。

__create_page_tables:
    /* Map the kernel image (starting with PHYS_OFFSET). */
    adrp    x0, init_pg_dir
    mov_q    x5, KIMAGE_VADDR + TEXT_OFFSET    // compile time __va(_text)
    add    x5, x5, x23            //add KASLR displacement 在内核镜像虚拟地址上再加上保存在x23中的kaslr的offset
    mov    x4, PTRS_PER_PGD
    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)

    map_memory x0, x1, x5, x6, x7, x3, x4, x10, x11, x12, x13, x14
ENDPROC(__create_page_tables)
    .ltorg

再次创建内核镜像页表,要映射到的虚拟地址为 KIMAGE_VADDR + TEXT_OFFSET + offset.


2. 随机偏移获取方法

u64 __init kaslr_early_init(u64 dt_phys) //kaslr.c
{
    void *fdt;
    u64 seed, offset, mask, module_range;
    const u8 *cmdline, *str;
    int size;

    /* 为 module_alloc_base 设置一个合理的默认值,以防我们最终在禁用模块随机化的情况下运行 */
    module_alloc_base = (u64)_etext - MODULES_VSIZE;

    /*
     * 尝试尽早映射 FDT。如果失败,我们只需放弃,然后禁用 KASLR。我们将在 setup_machine() 中
     * 再次尝试映射 FDT. msm-5.4上没有这个函数,msm-4.14在 /external/u-boot/common/board_f.c 中
     * 此时MMU还远没有开启。
     */
    early_fixmap_init();
    fdt = fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL);
    if (!fdt)
        return 0;

    /* 解析设备树 "/chosen" 节点的 "kaslr-seed" 属性的值,得到随机种子,若没有配置则直接返回0 */
    seed = get_kaslr_seed(fdt);
    if (!seed)
        return 0 //实验:替换为 seed = 0x800000;后,内核起不来

    /*  若是在“/chosen”的"bootargs"属性或cmdline中指定了“ nokaslr” 标签,则返回0,表示kaslr不生效。*/
    cmdline = kaslr_get_cmdline(fdt);
    str = strstr(cmdline, "nokaslr");
    if (str == cmdline || (str > cmdline && *(str - 1) == ' '))
        return 0;

    /*
     * 好的,我们继续启用 KASLR。计算与种子相距合适的内核映像偏移量。让我们将
     * 内核放置在 VMALLOC 区域的中间一半(VA_BITS_MIN - 2),并避开下四分之一
     * 和上四分之一以避免与其他分配发生冲突。
     * 即使我们可以在 16k 和 64k 页面的页面粒度上随机化,我们也要始终四舍五入
     * 到 2 MB,这样我们就不会干扰使用连续 PTE 进行映射的能力。
     *
     * 补充: 启动阶段内核镜像映射粒度是2M,4k宵页映射一个PMD页表也映射2M.
     */
    /* mask = (1<<37 - 1) & ~(2M-1) = 0x1fffe00000 */
    mask = ((1UL << (VA_BITS_MIN - 2)) - 1) & ~(SZ_2M - 1);
    /* 偏移是2M对齐,且要落在虚拟地址空间的[1/4, 3/8] 之间 */
    offset = BIT(VA_BITS_MIN - 3) + (seed & mask);

    /* 随机种子是64bit的,这里保留高16bit的值,来随机化线性映射区的地址 */
    memstart_offset_seed = seed >> 48;

    if (IS_ENABLED(CONFIG_KASAN)) //默认关闭
        /*
         * KASAN 不希望模块区域与 vmalloc 区域相交,因为影子内存是在加载时为每个模块分配的,
         * 而 vmalloc 区域则被 KASAN 零页所遮蔽。因此,如果启用了 KASAN,请将模块置于 vmalloc 
         * 区域之外,并将内核置于模块区域的 4 GB 以内。
         */
        return offset % SZ_2G;

    if (IS_ENABLED(CONFIG_RANDOMIZE_MODULE_REGION_FULL)) { //默认使能
        /*
         * 在覆盖内核的 2 GB 窗口上随机化模块区域(启动汇编中内核段映射最大支持2GB)。
         * 这降低了模块泄露有关内核本身地址信息的风险,但会导致模块和核心内核之间的分支通过 PLT 解析。
         */
        module_range = SZ_2G - (u64)(_end - _stext); //delta=0x3707000
        /* _end + offset - SZ_2G = 0xffffffbf937be000 + offset 与 MODULES_VADDR=0xffffffc008000000 取较大值 */
        module_alloc_base = max((u64)_end + offset - SZ_2G, (u64)MODULES_VADDR);
    } else {
        module_range = MODULES_VSIZE - (u64)(_etext - _stext);
        module_alloc_base = (u64)_etext + offset - MODULES_VSIZE;
    }

    /* 使用低21位随机化模块区域的基地址 */
    module_alloc_base += (module_range * (seed & ((1 << 21) - 1))) >> 21;
    module_alloc_base &= PAGE_MASK; //4k对齐

    pr_info("HAM: %s: offset=0x%lx", offset); //调用过早,这里无法打印出来

    return offset;
}

可以看到,这个随机化除了内核镜像虚拟基地址随机化外,还对线性映射区的虚拟地址、module_alloc区的虚拟基地址进行了随机化。返回的offset用于随机内核镜像的虚拟基地址,返回的offset偏移是2M对齐,且要落在虚拟地址空间的[1/4, 3/8]之间。

KASLR使能开关:CONFIG_RANDOMIZE_BASE,它选中编译 kaslr.c, 从head.S中看它还依赖 CONFIG_RELOCATABLE, 所以这两个都需要使能。

使能后生效的条件(不是返回0)是在/chosen节点中配置了kaslr-seed随机种子,这个随机种子虽然看似在设备树中静态配置的,但是可以由bootloader进行填充,并且可以做到每次开机都不一样。其次是在 /chosen 节点的 bootargs 和 default_cmdline 中没有没有指定 “ nokaslr” 标签。

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


三、实验

将 seed = 0x800000; 后内核起不来,若要使能,还需要进一步调试。


四、总结

1. KASLR就是在映射内核镜像的时候,在虚拟基地址上加上一个偏移。利用随机种子,可以让每次启动偏移都不一样。

2. 通过 CONFIG_RANDOMIZE_BASE 和 CONFIG_RELOCATABLE 使能此功能,若是想生效,还需要在 /chosen 节点中配置 kaslr-seed 随机种子,并且不能在 bootargs 和 cmdline 中指定 “ nokaslr”标签。

3. 算KASLR加的这个偏移,对内核镜像的映射一共有两个加偏移的位置了。另一个是 _text 要映射的虚拟基地址和Makefile中指定的 TEXT_OFFSET 决定的。

#define __PHYS_OFFSET    (KERNEL_START - TEXT_OFFSET) //_text - TEXT_OFFSET
ENTRY(stext)
    adrp    x23, __PHYS_OFFSET
ENDPROC(stext)

 

五、补充

1. kaslr还会影响逻辑物理内存起始地址

arm64_memblock_init //init.c
    if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {
        u64 range = linear_region_size - (bootloader_memory_limit - memblock_start_of_DRAM());
        if (memstart_offset_seed > 0 && range >= ARM64_MEMSTART_ALIGN) {
            range /= ARM64_MEMSTART_ALIGN; //1G
            memstart_addr -= ARM64_MEMSTART_ALIGN * ((range * memstart_offset_seed) >> 16);
        }
    }

 

 

参考:
KASLR: http://www.wowotech.net/memory_management/441.html //蜗蜗

 

posted on 2024-07-24 14:41  Hello-World3  阅读(108)  评论(0编辑  收藏  举报

导航