linux kerneのmemory protectionをARM用コードで確認してみる


# 参考文献
http://ijcsit.com/docs/Volume%205/vol5issue04/ijcsit20140504225.pdf

# linux kerneのmemory protectionをコードで確認してみる

- [Kernel Self-Protection (1/2)](https://qiita.com/hon_no_mushi/items/22de230b61bb15859fa2)
- [Kernel Self-Protection (2/2)](https://qiita.com/hon_no_mushi/items/da7ef87ca53ab3e1db63)

で確認した、メモリー保護について、[Attack Surface Reduction](https://qiita.com/hon_no_mushi/items/22de230b61bb15859fa2#attack-surface-reduction)を、実際のソースコードでも確認してみる。(主にARMで）

# KCONFIG

- CONFIG_STRICT_KERNEL_RWX
- CONFIG_STRICT_MODULE_RWX
- CONFIG_ARCH_OPTIONAL_KERNEL_RWX
- CONFIG_ARCH_OPTIONAL_KERNEL_RWX_DEFAULT

これらがキーワードっと・・・

これらが定義されているのでは、src/arch/Kconfig 。うん、普段はあまり触らない箇所にあるなー。

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/Kconfig

```plaintext:arch/Kconfig
config ARCH_OPTIONAL_KERNEL_RWX
    def_bool n

config ARCH_OPTIONAL_KERNEL_RWX_DEFAULT
    def_bool n

config ARCH_HAS_STRICT_KERNEL_RWX
    def_bool n

config STRICT_KERNEL_RWX
    bool "Make kernel text and rodata read-only" if ARCH_OPTIONAL_KERNEL_RWX
    depends on ARCH_HAS_STRICT_KERNEL_RWX
    default !ARCH_OPTIONAL_KERNEL_RWX || ARCH_OPTIONAL_KERNEL_RWX_DEFAULT
    help
      If this is set, kernel text and rodata memory will be made read-only,
      and non-text memory will be made non-executable. This provides
      protection against certain security exploits (e.g. executing the heap
      or modifying text)

      These features are considered standard security practice these days.
      You should say Y here in almost all cases.

config ARCH_HAS_STRICT_MODULE_RWX
    def_bool n

config STRICT_MODULE_RWX
    bool "Set loadable kernel module data as NX and text as RO" if ARCH_OPTIONAL_KERNEL_RWX
    depends on ARCH_HAS_STRICT_MODULE_RWX && MODULES
    default !ARCH_OPTIONAL_KERNEL_RWX || ARCH_OPTIONAL_KERNEL_RWX_DEFAULT
    help
      If this is set, module text and rodata memory will be made read-only,
      and non-text memory will be made non-executable. This provides
      protection against certain security exploits (e.g. writing to text)

```

ということで。`ARCH_HAS_STRICT_KERNEL_RWX`がdependになっていて、これが無ければ無効ですと。

# ARMのscatterファイル

まずは、リンク時に使うscatter。長いのでその中の一部だけ。

```c:arch/arm/kernel/vmlinux.lds.S

＜略＞
#ifdef CONFIG_STRICT_KERNEL_RWX
    . = ALIGN(1<<SECTION_SHIFT);
#endif

#ifdef CONFIG_ARM_MPU
    . = ALIGN(PMSAv8_MINALIGN);
#endif
    .text : {           /* Real text segment        */
        _stext = .;     /* Text and read-only data  */
        ARM_TEXT
    }

#ifdef CONFIG_DEBUG_ALIGN_RODATA
    . = ALIGN(1<<SECTION_SHIFT);
#endif
    _etext = .;         /* End of text section */

    RO_DATA(PAGE_SIZE)
＜略＞

```

SECTION_SHIFTの定義はこちら。(LPAE非対応ならば2Level、LPAE対応なら3Level)

```c:arch/arm/include/asm/pgtable-2level.h

/*
 * section address mask and size definitions.
 */
#define SECTION_SHIFT       20
#define SECTION_SIZE        (1UL << SECTION_SHIFT)
#define SECTION_MASK        (~(SECTION_SIZE-1))

```

1 << 20 だから、 1,048,576 = 1MBバウンダリですね。

これで、text/ro/rw/bss なんかの領域は、1MB単位でスタートするようになった、はず。

# それぞれの属性管理

それぞれの属性を管理しているのは、下記コード。

- "nx_perms"がNon-execute permission。
- "ro_perms"がRead Only permission。　と思われる。

```c:arch/arm/mm/init.c
static struct section_perm nx_perms[] = {
    /* Make pages tables, etc before _stext RW (set NX). */
    {
        .name   = "pre-text NX",
        .start  = PAGE_OFFSET,
        .end    = (unsigned long)_stext,
        .mask   = ~PMD_SECT_XN,
        .prot   = PMD_SECT_XN,
    },
    /* Make init RW (set NX). */
    {
        .name   = "init NX",
        .start  = (unsigned long)__init_begin,
        .end    = (unsigned long)_sdata,
        .mask   = ~PMD_SECT_XN,
        .prot   = PMD_SECT_XN,
    },
    /* Make rodata NX (set RO in ro_perms below). */
    {
        .name   = "rodata NX",
        .start  = (unsigned long)__start_rodata_section_aligned,
        .end    = (unsigned long)__init_begin,
        .mask   = ~PMD_SECT_XN,
        .prot   = PMD_SECT_XN,
    },
};

static struct section_perm ro_perms[] = {
    /* Make kernel code and rodata RX (set RO). */
    {
        .name   = "text/rodata RO",
        .start  = (unsigned long)_stext,
        .end    = (unsigned long)__init_begin,
#ifdef CONFIG_ARM_LPAE
        .mask   = ~(L_PMD_SECT_RDONLY | PMD_SECT_AP2),
        .prot   = L_PMD_SECT_RDONLY | PMD_SECT_AP2,
#else
        .mask   = ~(PMD_SECT_APX | PMD_SECT_AP_WRITE),
        .prot   = PMD_SECT_APX | PMD_SECT_AP_WRITE,
        .clear  = PMD_SECT_AP_WRITE,
#endif
    },
};
```

```c:arch/arm/include/asm/pgtable-2level-hwdef.h
/*
 *   - section
 */
#define PMD_SECT_PXN    (_AT(pmdval_t, 1) << 0)     /* v7 */
#define PMD_SECT_BUFFERABLE (_AT(pmdval_t, 1) << 2)
#define PMD_SECT_CACHEABLE  (_AT(pmdval_t, 1) << 3)
#define PMD_SECT_XN     (_AT(pmdval_t, 1) << 4)     /* v6 */
#define PMD_SECT_AP_WRITE   (_AT(pmdval_t, 1) << 10)
#define PMD_SECT_AP_READ    (_AT(pmdval_t, 1) << 11)
#define PMD_SECT_TEX(x)     (_AT(pmdval_t, (x)) << 12)  /* v5 */
#define PMD_SECT_APX        (_AT(pmdval_t, 1) << 15)    /* v6 */
#define PMD_SECT_S      (_AT(pmdval_t, 1) << 16)    /* v6 */
#define PMD_SECT_nG     (_AT(pmdval_t, 1) << 17)    /* v6 */
#define PMD_SECT_SUPER      (_AT(pmdval_t, 1) << 18)    /* v6 */
#define PMD_SECT_AF     (_AT(pmdval_t, 0))

```

例えば、先ほどの例だと、

| Start | End | NX/RO/RW | 目的 |
|:-:|:-:|:-:|:-:|
| PAGE_OFFSET  | _stext | NX |  pre-text |
| _stext  | _etext | RO | text |
| \_start_rodata_section_aligned  | __init_begin  | RO + NX | rodata  |
| __init_begin  | _sdata  | NX | init  |

ただし、rodataに関しては、1MBに境界があうようにマクロが組まれています、と。

# 呼び出しまで

sectionのupdateまで到達するコールシーケンスはこんな感じ。
(kexec関係はとりあえず見なかったことにする）。

- init/main.c::start_kernel()
- → init/main.c::arch_call_rest_init()
- → → init/main.c::rest_init()
- → → → init/main.c::kernel_init() 
- → → → → arch/arm/mm/init.c::free_initmem() 
- → → → → → arch/arm/mm/init.c::fix_kernmem_perms()
- → → → → → → arch/arm/mm/init.c::update_sections_early()
- → → → → init/main.c::mark_readonly()
- → → → → → arch/arm/mm/init.c::mark_rodata_ro()
- → → → → → → arch/arm/mm/init.c::update_sections_early()

## update_sections_early()

ここでは、全processor の、全thread(除くkthread) と、現状のactieve_mmおよびinit_mmに対して、セクションのパーミッション設定変更を要求。

```c:arch/arm/mm/init.c::update_sections_early()

/**
 * update_sections_early intended to be called only through stop_machine
 * framework and executed by only one CPU while all other CPUs will spin and
 * wait, so no locking is required in this function.
 */
static void update_sections_early(struct section_perm perms[], int n)
{
    struct task_struct *t, *s;

    for_each_process(t) {
        if (t->flags & PF_KTHREAD)
            continue;
        for_each_thread(t, s)
            if (s->mm)
                set_section_perms(perms, n, true, s->mm);
    }
    set_section_perms(perms, n, true, current->active_mm);
    set_section_perms(perms, n, true, &init_mm);
}

```

## set_section_perms()

ここでは、1MB単位でsectionの属性変更を要求。setのフラグによって、permissionのprotを使うか、clearを使うかが変わる。

```c:arch/arm/mm/init.c::set_section_perms()
static void set_section_perms(struct section_perm *perms, int n, bool set,
                  struct mm_struct *mm)
{
    size_t i;
    unsigned long addr;

    if (!arch_has_strict_perms())
        return;

    for (i = 0; i < n; i++) {
        if (!IS_ALIGNED(perms[i].start, SECTION_SIZE) ||
            !IS_ALIGNED(perms[i].end, SECTION_SIZE)) {
            pr_err("BUG: %s section %lx-%lx not aligned to %lx\n",
                perms[i].name, perms[i].start, perms[i].end,
                SECTION_SIZE);
            continue;
        }

        for (addr = perms[i].start;
             addr < perms[i].end;
             addr += SECTION_SIZE)
            section_update(addr, perms[i].mask,
                set ? perms[i].prot : perms[i].clear, mm);
    }

}

```

## section_update()

メモリアドレスから、pgdに変換し、pudに変換し、pmdに変換し、そこにmaskとprot(あるいはclear)をかけて、更新しますと。

```c:arch/arm/mm/init.c::section_update()
/*
 * Updates section permissions only for the current mm (sections are
 * copied into each mm). During startup, this is the init_mm. Is only
 * safe to be called with preemption disabled, as under stop_machine().
 */
static inline void section_update(unsigned long addr, pmdval_t mask,
                  pmdval_t prot, struct mm_struct *mm)
{
    pmd_t *pmd;

    pmd = pmd_offset(pud_offset(pgd_offset(mm, addr), addr), addr);

#ifdef CONFIG_ARM_LPAE
    pmd[0] = __pmd((pmd_val(pmd[0]) & mask) | prot);
#else
    if (addr & SECTION_SIZE)
        pmd[1] = __pmd((pmd_val(pmd[1]) & mask) | prot);
    else
        pmd[0] = __pmd((pmd_val(pmd[0]) & mask) | prot);
#endif
    flush_pmd_entry(pmd);
    local_flush_tlb_kernel_range(addr, addr + SECTION_SIZE);
}
```

## flush_pmd_entry()

pmdの内容が書き換わったので、ここでpmd entryをflushしますと。

```c:arch/arm/include/asm/tlbflush.h
/*
 *  flush_pmd_entry
 *
 *  Flush a PMD entry (word aligned, or double-word aligned) to
 *  RAM if the TLB for the CPU we are running on requires this.
 *  This is typically used when we are creating PMD entries.
 *
 *  clean_pmd_entry
 *
 *  Clean (but don't drain the write buffer) if the CPU requires
 *  these operations.  This is typically used when we are removing
 *  PMD entries.
 */
static inline void flush_pmd_entry(void *pmd)
{
    const unsigned int __tlb_flag = __cpu_tlb_flags;

    tlb_op(TLB_DCLEAN, "c7, c10, 1  @ flush_pmd", pmd);
    tlb_l2_op(TLB_L2CLEAN_FR, "c15, c9, 1  @ L2 flush_pmd", pmd);

    if (tlb_flag(TLB_WB))
        dsb(ishst);
}

#define tlb_op(f, regs, arg)    __tlb_op(f, "p15, 0, %0, " regs, arg)
#define tlb_l2_op(f, regs, arg) __tlb_op(f, "p15, 1, %0, " regs, arg)

#define __tlb_op(f, insnarg, arg)                   \
    do {                                \
        if (always_tlb_flags & (f))             \
            asm("mcr " insnarg              \
                : : "r" (arg) : "cc");          \
        else if (possible_tlb_flags & (f))          \
            asm("tst %1, %2\n\t"                \
                "mcrne " insnarg                \
                : : "r" (arg), "r" (__tlb_flag), "Ir" (f)   \
                : "cc");                    \
    } while (0)

```

## なんでこれで動くのか、正直わからんとです。

で、ここまで記載して、4時間以上経過していますが、ここから進まない。
topper projectのドキュメントを読んで、理解はできたが…うーん。
というところまでにしておきます。

```c
asm("mcr p15, 0, %0, c7, c10, 1" : : "r"(pmd) : "cc" ); 
```

https://www.aps-web.jp/academy/ca/229/

> MCR{ cond } coproc,#opcode1,Rd,CRn,CRm {, # opcode2 }
> opcode1	3ビットコプロセッサ固有のオペコードを指定します。
> CRn	コプロセッサレジスタを指定します。
> CRm	コプロセッサレジスタを指定します。

→ CRn = c7, CRm = c10, opcode=0, opcode2 = 1

http://infocenter.arm.com/help/topic/com.arm.doc.ddi0388fj/CIHGJFEH.html

> 1	DCCVAC	WO	

で、この命令の詳細については、ARM Reference　Manual見ろとのことですで、とりあえずここまで。

多分、これでSECTIONレベルでのAP bitとかを操作した結果が反映されることで、1MB単位で権限設定ができる、という話になると思います。


以上になります。