[PATCH 6/7] riscv: batch write-protect contiguous PTE ranges
Yunhui Cui <[email protected]>
| Newsgroups | org.kernel.vger.linux-efi,org.infradead.lists.kvm-riscv,org.infradead.lists.linux-riscv,org.kernel.vger.kvm,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
Hook wrprotect_ptes() into the Svnapot contpte helpers so write protection can preserve fully covered NAPOT blocks and only unfold partial ranges at the edges. Signed-off-by: Yunhui Cui <[email protected]> --- arch/riscv/include/asm/pgtable.h | 38 +++++++++++++++++++++++++++-- arch/riscv/mm/contpte.c | 42 ++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/arch/riscv/include/asm/pgtable.h b/arch/riscv/include/asm/pgtable.h index 3e6516b5a4587..db82253efb218 100644 --- a/arch/riscv/include/asm/pgtable.h +++ b/arch/riscv/include/asm/pgtable.h @@ -813,13 +813,30 @@ __ptep_set_wrprotect(struct mm_struct *mm, unsigned long address, pte_t *ptep) * shadow stack memory is XWR = 010 and thus clearing _PAGE_WRITE will lead to * encoding 000b which is wrong encoding with V = 1. This should lead to page fault * but we dont want this wrong configuration to be set in page tables. + * Keep the entry readable when clearing write permissions so we don't create + * an invalid present encoding. */ atomic_long_set((atomic_long_t *)ptep, - ((pte_val(read_pte) & ~(unsigned long)_PAGE_WRITE) | _PAGE_READ)); + (pte_val(read_pte) & ~(unsigned long)_PAGE_WRITE) | + _PAGE_READ); } #define __ptep_set_wrprotect __ptep_set_wrprotect +static inline void __wrprotect_ptes(struct mm_struct *mm, + unsigned long address, + pte_t *ptep, unsigned int nr) +{ + for (;;) { + __ptep_set_wrprotect(mm, address, ptep); + if (--nr == 0) + break; + ptep++; + address += PAGE_SIZE; + } +} + +#define __wrprotect_ptes __wrprotect_ptes static inline pte_t __ptep_clear_flush(struct vm_area_struct *vma, unsigned long address, pte_t *ptep) @@ -879,6 +896,8 @@ pte_t napotpte_get_and_clear_full_ptes(struct mm_struct *mm, void napotpte_clear_young_dirty_ptes(struct vm_area_struct *vma, unsigned long addr, pte_t *ptep, unsigned int nr, cydp_t flags); +void napotpte_wrprotect_ptes(struct mm_struct *mm, unsigned long addr, + pte_t *ptep, unsigned int nr); bool napotpte_ptep_set_access_flags(struct vm_area_struct *vma, unsigned long address, pte_t *ptep, pte_t entry, int dirty); @@ -1004,11 +1023,25 @@ static inline pte_t get_and_clear_full_ptes(struct mm_struct *mm, return napotpte_get_and_clear_full_ptes(mm, addr, ptep, nr, full); } +#define wrprotect_ptes wrprotect_ptes +static inline void wrprotect_ptes(struct mm_struct *mm, + unsigned long address, pte_t *ptep, + unsigned int nr) +{ + if (likely(nr == 1)) { + napotpte_try_unfold(mm, address, ptep, __ptep_get(ptep)); + __ptep_set_wrprotect(mm, address, ptep); + return; + } + + napotpte_wrprotect_ptes(mm, address, ptep, nr); +} + #define __HAVE_ARCH_PTEP_SET_WRPROTECT static inline void ptep_set_wrprotect(struct mm_struct *mm, unsigned long address, pte_t *ptep) { - __ptep_set_wrprotect(mm, address, ptep); + wrprotect_ptes(mm, address, ptep, 1); } #define __HAVE_ARCH_PTEP_CLEAR_YOUNG_FLUSH @@ -1062,6 +1095,7 @@ napotpte_ptep_clear_flush_young(struct vm_area_struct *vma, #define clear_young_dirty_ptes __clear_young_dirty_ptes #define clear_full_ptes __clear_full_ptes #define get_and_clear_full_ptes __get_and_clear_full_ptes +#define wrprotect_ptes __wrprotect_ptes #define __HAVE_ARCH_PTEP_SET_WRPROTECT #define ptep_set_wrprotect __ptep_set_wrprotect #define __HAVE_ARCH_PTEP_CLEAR_YOUNG_FLUSH diff --git a/arch/riscv/mm/contpte.c b/arch/riscv/mm/contpte.c index 77c2a4dbd3dda..077ffa49e89d9 100644 --- a/arch/riscv/mm/contpte.c +++ b/arch/riscv/mm/contpte.c @@ -261,6 +261,30 @@ static void napotpte_try_unfold_range(struct mm_struct *mm, } } +static void napotpte_try_unfold_partial(struct mm_struct *mm, + unsigned long addr, pte_t *ptep, + unsigned int nr) +{ + pte_t pte; + + if (ptep != napot_align_ptep(ptep) || nr < napotpte_pte_num()) { + pte = READ_ONCE(*ptep); + if (pte_present_napot(pte)) + __napotpte_try_unfold(mm, addr, ptep, pte); + } + + if (ptep + nr != napot_align_ptep(ptep + nr)) { + unsigned long last_addr; + pte_t *last_ptep; + + last_addr = addr + PAGE_SIZE * (nr - 1); + last_ptep = ptep + nr - 1; + pte = READ_ONCE(*last_ptep); + if (pte_present_napot(pte)) + __napotpte_try_unfold(mm, last_addr, last_ptep, pte); + } +} + void __napotpte_try_unfold(struct mm_struct *mm, unsigned long addr, pte_t *ptep, pte_t pte) { @@ -485,6 +509,24 @@ void napotpte_clear_young_dirty_ptes(struct vm_area_struct *vma, } EXPORT_SYMBOL(napotpte_clear_young_dirty_ptes); +void napotpte_wrprotect_ptes(struct mm_struct *mm, unsigned long addr, + pte_t *ptep, unsigned int nr) +{ + unsigned int i; + + if (!napot_hw_supported() || !mm_is_user(mm)) { + for (i = 0; i < nr; i++, ptep++, addr += PAGE_SIZE) + __ptep_set_wrprotect(mm, addr, ptep); + return; + } + + napotpte_try_unfold_partial(mm, addr, ptep, nr); + + for (i = 0; i < nr; i++, ptep++, addr += PAGE_SIZE) + __ptep_set_wrprotect(mm, addr, ptep); +} +EXPORT_SYMBOL(napotpte_wrprotect_ptes); + bool napotpte_ptep_set_access_flags(struct vm_area_struct *vma, unsigned long address, pte_t *ptep, pte_t entry, int dirty) -- 2.39.5