The mappings now do perfect kernel pte mappings even when the kernel is relocated. This patch refactors create_physical_mapping() and mark_rodata_ro(). create_physical_mapping() is now largely done with a helper called __create_physical_mapping(), which is defined differently for when CONFIG_STRICT_KERNEL_RWX is enabled and when its not.
The goal of the patchset is to provide minimal changes when the CONFIG_STRICT_KERNEL_RWX is disabled, when enabled however, we do split the linear mapping so that permissions are strictly adherent to expectations from the user. Signed-off-by: Balbir Singh <bsinghar...@gmail.com> --- arch/powerpc/mm/pgtable-radix.c | 225 ++++++++++++++++++++++++++++++++-------- 1 file changed, 179 insertions(+), 46 deletions(-) diff --git a/arch/powerpc/mm/pgtable-radix.c b/arch/powerpc/mm/pgtable-radix.c index d2fd34a..5aaf886 100644 --- a/arch/powerpc/mm/pgtable-radix.c +++ b/arch/powerpc/mm/pgtable-radix.c @@ -112,26 +112,16 @@ int radix__map_kernel_page(unsigned long ea, unsigned long pa, } #ifdef CONFIG_STRICT_KERNEL_RWX -void radix__mark_rodata_ro(void) +static void remove_page_permission_range(unsigned long start, + unsigned long end, + unsigned long clr) { - unsigned long start = (unsigned long)_stext; - unsigned long end = (unsigned long)__init_begin; unsigned long idx; pgd_t *pgdp; pud_t *pudp; pmd_t *pmdp; pte_t *ptep; - if (!mmu_has_feature(MMU_FTR_KERNEL_RO)) { - pr_info("R/O rodata not supported\n"); - return; - } - - start = ALIGN_DOWN(start, PAGE_SIZE); - end = PAGE_ALIGN(end); // aligns up - - pr_devel("marking ro start %lx, end %lx\n", start, end); - for (idx = start; idx < end; idx += PAGE_SIZE) { pgdp = pgd_offset_k(idx); pudp = pud_alloc(&init_mm, pgdp, idx); @@ -152,10 +142,41 @@ void radix__mark_rodata_ro(void) if (!ptep) continue; update_the_pte: - radix__pte_update(&init_mm, idx, ptep, _PAGE_WRITE, 0, 0); + radix__pte_update(&init_mm, idx, ptep, clr, 0, 0); } radix__flush_tlb_kernel_range(start, end); +} + +void radix__mark_rodata_ro(void) +{ + unsigned long start = (unsigned long)_stext; + unsigned long end = (unsigned long)__init_begin; + if (!mmu_has_feature(MMU_FTR_KERNEL_RO)) { + pr_info("R/O rodata not supported\n"); + return; + } + + start = ALIGN_DOWN(start, PAGE_SIZE); + end = PAGE_ALIGN(end); // aligns up + + pr_devel("marking ro start %lx, end %lx\n", start, end); + remove_page_permission_range(start, end, _PAGE_WRITE); + + start = (unsigned long)__init_begin; + end = (unsigned long)__init_end; + start = ALIGN_DOWN(start, PAGE_SIZE); + end = PAGE_ALIGN(end); + + pr_devel("marking no exec start %lx, end %lx\n", start, end); + remove_page_permission_range(start, end, _PAGE_EXEC); + + start = (unsigned long)__start_interrupts - PHYSICAL_START; + end = (unsigned long)__end_interrupts - PHYSICAL_START; + start = ALIGN_DOWN(start, PAGE_SIZE); + end = PAGE_ALIGN(end); + pr_devel("marking ro start %lx, end %lx\n", start, end); + remove_page_permission_range(start, end, _PAGE_WRITE); } #endif @@ -169,31 +190,36 @@ static inline void __meminit print_mapping(unsigned long start, pr_info("Mapped range 0x%lx - 0x%lx with 0x%lx\n", start, end, size); } -static int __meminit create_physical_mapping(unsigned long start, - unsigned long end) +/* + * Create physical mapping and return the last mapping size + * If the call is successful, end_of_mapping will return the + * last address mapped via this call, if not, it will leave + * the value untouched. + */ +static int __meminit __create_physical_mapping(unsigned long vstart, + unsigned long vend, pgprot_t prot, + unsigned long *end_of_mapping) { - unsigned long vaddr, addr, mapping_size = 0; - pgprot_t prot; - unsigned long max_mapping_size; -#ifdef CONFIG_STRICT_KERNEL_RWX - int split_text_mapping = 1; -#else - int split_text_mapping = 0; -#endif + unsigned long mapping_size = 0; + static unsigned long previous_size; + unsigned long addr, start, end; + start = __pa(vstart); + end = __pa(vend); start = _ALIGN_UP(start, PAGE_SIZE); + + pr_devel("physical_mapping start %lx->%lx, prot %lx\n", + vstart, vend, pgprot_val(prot)); + for (addr = start; addr < end; addr += mapping_size) { - unsigned long gap, previous_size; + unsigned long gap; int rc; gap = end - addr; previous_size = mapping_size; - max_mapping_size = PUD_SIZE; -retry: if (IS_ALIGNED(addr, PUD_SIZE) && gap >= PUD_SIZE && - mmu_psize_defs[MMU_PAGE_1G].shift && - PUD_SIZE <= max_mapping_size) + mmu_psize_defs[MMU_PAGE_1G].shift) mapping_size = PUD_SIZE; else if (IS_ALIGNED(addr, PMD_SIZE) && gap >= PMD_SIZE && mmu_psize_defs[MMU_PAGE_2M].shift) @@ -201,40 +227,147 @@ static int __meminit create_physical_mapping(unsigned long start, else mapping_size = PAGE_SIZE; - if (split_text_mapping && (mapping_size == PUD_SIZE) && - (addr <= __pa_symbol(__init_begin)) && - (addr + mapping_size) >= __pa_symbol(_stext)) { - max_mapping_size = PMD_SIZE; - goto retry; + if (previous_size != mapping_size) { + print_mapping(start, addr, previous_size); + start = addr; + previous_size = mapping_size; } - if (split_text_mapping && (mapping_size == PMD_SIZE) && - (addr <= __pa_symbol(__init_begin)) && - (addr + mapping_size) >= __pa_symbol(_stext)) - mapping_size = PAGE_SIZE; + rc = radix__map_kernel_page((unsigned long)__va(addr), addr, + prot, mapping_size); + if (rc) + return rc; + } - if (mapping_size != previous_size) { - print_mapping(start, addr, previous_size); - start = addr; + print_mapping(start, addr, mapping_size); + *end_of_mapping = (unsigned long)__va(addr); + return 0; +} + +#ifdef CONFIG_STRICT_KERNEL_RWX +static int __meminit create_physical_mapping(unsigned long start, + unsigned long end) +{ + pgprot_t prot; + unsigned long rc; + unsigned long vstart, vend; + unsigned long gap; + unsigned long st = (unsigned long)_stext; + unsigned long ie = (unsigned long)__init_end; + unsigned long ib = (unsigned long)__init_begin; + unsigned long si = (unsigned long)__start_interrupts - PHYSICAL_START; + unsigned long ei = (unsigned long)__end_interrupts - PHYSICAL_START; + + + start = _ALIGN_UP(start, PAGE_SIZE); + vstart = (unsigned long)__va(start); + vend = (unsigned long)__va(end); + + while (vstart < vend) { + if ((PHYSICAL_START > MEMORY_START) && + (overlaps_interrupt_vector_text(vstart, vend))) { + /* + * Is there a gap between start and start of interrupts. + * We need to care for PHYSICAL_START here since we need + * to nail down __start_interrupts..__end_interrupts as + * physical offsets from 0. + */ + gap = _ALIGN_DOWN(si, PAGE_SIZE) - vstart; + if (gap > PAGE_SIZE) { + prot = PAGE_KERNEL; + rc = __create_physical_mapping(vstart, si, prot, + &vstart); + if (rc) + return rc; + } + + prot = PAGE_KERNEL_X; + rc = __create_physical_mapping(vstart, ei, prot, + &vstart); + if (rc) + return rc; } - vaddr = (unsigned long)__va(addr); + if (overlaps_kernel_text(vstart, vend)) { + + gap = _ALIGN_DOWN(st, PAGE_SIZE) - vstart; + if (gap > PAGE_SIZE) { + prot = PAGE_KERNEL; + rc = __create_physical_mapping(vstart, st, + prot, &vstart); + if (rc) + return rc; + } - if (overlaps_kernel_text(vaddr, vaddr + mapping_size) || - overlaps_interrupt_vector_text(vaddr, vaddr + mapping_size)) + /* + * __init_begin/end are special,they are marked + * executable but we'll turn rw off until __init_begin + * and if the mapping is not split here, it will spill + * over up to * __init_end and allocations from that + * region will find read-only permissions + */ + prot = PAGE_KERNEL_X; + rc = __create_physical_mapping(vstart, ib, prot, + &vstart); + if (rc) + return rc; + + rc = __create_physical_mapping(vstart, ie, prot, + &vstart); + if (rc) + return rc; + } + + prot = PAGE_KERNEL; + rc = __create_physical_mapping(vstart, vend, prot, &vstart); + if (rc) + return rc; + } + + return 0; +} + +#else /* !CONFIG_STRICT_KERNEL_RWX */ + +static int __meminit create_physical_mapping(unsigned long start, + unsigned long end) +{ + pgprot_t prot; + unsigned long rc; + unsigned long vstart, vend; + unsigned long mapping_size; + + + start = _ALIGN_UP(start, PAGE_SIZE); + vstart = (unsigned long)__va(start); + vend = (unsigned long)__va(end); + + while (vstart < vend) { + /* + * STRICT_KERNEL_RWX is off, but we can't map all of + * vstart--vend as * executable, lets split vend into + * mapping_size and try + */ + mapping_size = min(vend - vstart, PUD_SIZE); + + if (overlaps_kernel_text(vstart, vstart + mapping_size) || + overlaps_interrupt_vector_text(vstart, + vstart + mapping_size)) prot = PAGE_KERNEL_X; else prot = PAGE_KERNEL; - rc = radix__map_kernel_page(vaddr, addr, prot, mapping_size); + rc = __create_physical_mapping(vstart, vstart + mapping_size, + prot, &vstart); if (rc) return rc; } - print_mapping(start, addr, mapping_size); return 0; } +#endif /* CONFIG_STRICT_KERNEL_RWX */ + static void __init radix_init_pgtable(void) { unsigned long rts_field; -- 2.9.4