| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * mm/mprotect.c | |
| * | |
| * (C) Copyright 1994 Linus Torvalds | |
| * (C) Copyright 2002 Christoph Hellwig | |
| * | |
| * Address space accounting code <alan@lxorguk.ukuu.org.uk> | |
| * (C) Copyright 2002 Red Hat Inc, All Rights Reserved | |
| */ | |
| #include <linux/pagewalk.h> | |
| #include <linux/hugetlb.h> | |
| #include <linux/shm.h> | |
| #include <linux/mman.h> | |
| #include <linux/fs.h> | |
| #include <linux/highmem.h> | |
| #include <linux/security.h> | |
| #include <linux/mempolicy.h> | |
| #include <linux/personality.h> | |
| #include <linux/syscalls.h> | |
| #include <linux/swap.h> | |
| #include <linux/swapops.h> | |
| #include <linux/mmu_notifier.h> | |
| #include <linux/migrate.h> | |
| #include <linux/perf_event.h> | |
| #include <linux/pkeys.h> | |
| #include <linux/ksm.h> | |
| #include <linux/uaccess.h> | |
| #include <linux/mm_inline.h> | |
| #include <linux/pgtable.h> | |
| #include <linux/userfaultfd_k.h> | |
| #include <uapi/linux/mman.h> | |
| #include <asm/cacheflush.h> | |
| #include <asm/mmu_context.h> | |
| #include <asm/tlbflush.h> | |
| #include <asm/tlb.h> | |
| #include "internal.h" | |
| static bool maybe_change_pte_writable(struct vm_area_struct *vma, pte_t pte) | |
| { | |
| if (WARN_ON_ONCE(!(vma->vm_flags & VM_WRITE))) | |
| return false; | |
| /* Don't touch entries that are not even readable. */ | |
| if (pte_protnone(pte)) | |
| return false; | |
| /* Do we need write faults for softdirty tracking? */ | |
| if (pte_needs_soft_dirty_wp(vma, pte)) | |
| return false; | |
| /* Do we need write faults for uffd-wp tracking? */ | |
| if (userfaultfd_pte_wp(vma, pte)) | |
| return false; | |
| return true; | |
| } | |
| static bool can_change_private_pte_writable(struct vm_area_struct *vma, | |
| unsigned long addr, pte_t pte) | |
| { | |
| struct page *page; | |
| if (!maybe_change_pte_writable(vma, pte)) | |
| return false; | |
| /* | |
| * Writable MAP_PRIVATE mapping: We can only special-case on | |
| * exclusive anonymous pages, because we know that our | |
| * write-fault handler similarly would map them writable without | |
| * any additional checks while holding the PT lock. | |
| */ | |
| page = vm_normal_page(vma, addr, pte); | |
| return page && PageAnon(page) && PageAnonExclusive(page); | |
| } | |
| static bool can_change_shared_pte_writable(struct vm_area_struct *vma, | |
| pte_t pte) | |
| { | |
| if (!maybe_change_pte_writable(vma, pte)) | |
| return false; | |
| VM_WARN_ON_ONCE(is_zero_pfn(pte_pfn(pte)) && pte_dirty(pte)); | |
| /* | |
| * Writable MAP_SHARED mapping: "clean" might indicate that the FS still | |
| * needs a real write-fault for writenotify | |
| * (see vma_wants_writenotify()). If "dirty", the assumption is that the | |
| * FS was already notified and we can simply mark the PTE writable | |
| * just like the write-fault handler would do. | |
| */ | |
| return pte_dirty(pte); | |
| } | |
| bool can_change_pte_writable(struct vm_area_struct *vma, unsigned long addr, | |
| pte_t pte) | |
| { | |
| if (!(vma->vm_flags & VM_SHARED)) | |
| return can_change_private_pte_writable(vma, addr, pte); | |
| return can_change_shared_pte_writable(vma, pte); | |
| } | |
| static int mprotect_folio_pte_batch(struct folio *folio, pte_t *ptep, | |
| pte_t pte, int max_nr_ptes, fpb_t flags) | |
| { | |
| /* No underlying folio, so cannot batch */ | |
| if (!folio) | |
| return 1; | |
| if (!folio_test_large(folio)) | |
| return 1; | |
| return folio_pte_batch_flags(folio, NULL, ptep, &pte, max_nr_ptes, flags); | |
| } | |
| /* Set nr_ptes number of ptes, starting from idx */ | |
| static __always_inline void prot_commit_flush_ptes(struct vm_area_struct *vma, | |
| unsigned long addr, pte_t *ptep, pte_t oldpte, pte_t ptent, | |
| int nr_ptes, int idx, bool set_write, struct mmu_gather *tlb) | |
| { | |
| /* | |
| * Advance the position in the batch by idx; note that if idx > 0, | |
| * then the nr_ptes passed here is <= batch size - idx. | |
| */ | |
| addr += idx * PAGE_SIZE; | |
| ptep += idx; | |
| oldpte = pte_advance_pfn(oldpte, idx); | |
| ptent = pte_advance_pfn(ptent, idx); | |
| if (set_write) | |
| ptent = pte_mkwrite(ptent, vma); | |
| modify_prot_commit_ptes(vma, addr, ptep, oldpte, ptent, nr_ptes); | |
| if (pte_needs_flush(oldpte, ptent)) | |
| tlb_flush_pte_range(tlb, addr, nr_ptes * PAGE_SIZE); | |
| } | |
| /* | |
| * Get max length of consecutive ptes pointing to PageAnonExclusive() pages or | |
| * !PageAnonExclusive() pages, starting from start_idx. Caller must enforce | |
| * that the ptes point to consecutive pages of the same anon large folio. | |
| */ | |
| static __always_inline int page_anon_exclusive_sub_batch(int start_idx, int max_len, | |
| struct page *first_page, bool expected_anon_exclusive) | |
| { | |
| int idx; | |
| for (idx = start_idx + 1; idx < start_idx + max_len; ++idx) { | |
| if (expected_anon_exclusive != PageAnonExclusive(first_page + idx)) | |
| break; | |
| } | |
| return idx - start_idx; | |
| } | |
| /* | |
| * This function is a result of trying our very best to retain the | |
| * "avoid the write-fault handler" optimization. In can_change_pte_writable(), | |
| * if the vma is a private vma, and we cannot determine whether to change | |
| * the pte to writable just from the vma and the pte, we then need to look | |
| * at the actual page pointed to by the pte. Unfortunately, if we have a | |
| * batch of ptes pointing to consecutive pages of the same anon large folio, | |
| * the anon-exclusivity (or the negation) of the first page does not guarantee | |
| * the anon-exclusivity (or the negation) of the other pages corresponding to | |
| * the pte batch; hence in this case it is incorrect to decide to change or | |
| * not change the ptes to writable just by using information from the first | |
| * pte of the batch. Therefore, we must individually check all pages and | |
| * retrieve sub-batches. | |
| */ | |
| static __always_inline void commit_anon_folio_batch(struct vm_area_struct *vma, | |
| struct folio *folio, struct page *first_page, unsigned long addr, pte_t *ptep, | |
| pte_t oldpte, pte_t ptent, int nr_ptes, struct mmu_gather *tlb) | |
| { | |
| bool expected_anon_exclusive; | |
| int sub_batch_idx = 0; | |
| int len; | |
| while (nr_ptes) { | |
| expected_anon_exclusive = PageAnonExclusive(first_page + sub_batch_idx); | |
| len = page_anon_exclusive_sub_batch(sub_batch_idx, nr_ptes, | |
| first_page, expected_anon_exclusive); | |
| prot_commit_flush_ptes(vma, addr, ptep, oldpte, ptent, len, | |
| sub_batch_idx, expected_anon_exclusive, tlb); | |
| sub_batch_idx += len; | |
| nr_ptes -= len; | |
| } | |
| } | |
| static __always_inline void set_write_prot_commit_flush_ptes(struct vm_area_struct *vma, | |
| struct folio *folio, struct page *page, unsigned long addr, pte_t *ptep, | |
| pte_t oldpte, pte_t ptent, int nr_ptes, struct mmu_gather *tlb) | |
| { | |
| bool set_write; | |
| if (vma->vm_flags & VM_SHARED) { | |
| set_write = can_change_shared_pte_writable(vma, ptent); | |
| prot_commit_flush_ptes(vma, addr, ptep, oldpte, ptent, nr_ptes, | |
| /* idx = */ 0, set_write, tlb); | |
| return; | |
| } | |
| set_write = maybe_change_pte_writable(vma, ptent) && | |
| (folio && folio_test_anon(folio)); | |
| if (!set_write) { | |
| prot_commit_flush_ptes(vma, addr, ptep, oldpte, ptent, nr_ptes, | |
| /* idx = */ 0, set_write, tlb); | |
| return; | |
| } | |
| commit_anon_folio_batch(vma, folio, page, addr, ptep, oldpte, ptent, nr_ptes, tlb); | |
| } | |
| static long change_softleaf_pte(struct vm_area_struct *vma, | |
| unsigned long addr, pte_t *pte, pte_t oldpte, unsigned long cp_flags) | |
| { | |
| const bool uffd_wp = cp_flags & MM_CP_UFFD_WP; | |
| const bool uffd_wp_resolve = cp_flags & MM_CP_UFFD_WP_RESOLVE; | |
| softleaf_t entry = softleaf_from_pte(oldpte); | |
| pte_t newpte; | |
| if (softleaf_is_migration_write(entry)) { | |
| const struct folio *folio = softleaf_to_folio(entry); | |
| /* | |
| * A protection check is difficult so | |
| * just be safe and disable write | |
| */ | |
| if (folio_test_anon(folio)) | |
| entry = make_readable_exclusive_migration_entry(swp_offset(entry)); | |
| else | |
| entry = make_readable_migration_entry(swp_offset(entry)); | |
| newpte = swp_entry_to_pte(entry); | |
| if (pte_swp_soft_dirty(oldpte)) | |
| newpte = pte_swp_mksoft_dirty(newpte); | |
| } else if (softleaf_is_device_private_write(entry)) { | |
| /* | |
| * We do not preserve soft-dirtiness. See | |
| * copy_nonpresent_pte() for explanation. | |
| */ | |
| entry = make_readable_device_private_entry(swp_offset(entry)); | |
| newpte = swp_entry_to_pte(entry); | |
| if (pte_swp_uffd_wp(oldpte)) | |
| newpte = pte_swp_mkuffd_wp(newpte); | |
| } else if (softleaf_is_marker(entry)) { | |
| /* | |
| * Ignore error swap entries unconditionally, | |
| * because any access should sigbus/sigsegv | |
| * anyway. | |
| */ | |
| if (softleaf_is_poison_marker(entry) || | |
| softleaf_is_guard_marker(entry)) | |
| return 0; | |
| /* | |
| * If this is uffd-wp pte marker and we'd like | |
| * to unprotect it, drop it; the next page | |
| * fault will trigger without uffd trapping. | |
| */ | |
| if (uffd_wp_resolve) { | |
| pte_clear(vma->vm_mm, addr, pte); | |
| return 1; | |
| } | |
| return 0; | |
| } else { | |
| newpte = oldpte; | |
| } | |
| if (uffd_wp) | |
| newpte = pte_swp_mkuffd_wp(newpte); | |
| else if (uffd_wp_resolve) | |
| newpte = pte_swp_clear_uffd_wp(newpte); | |
| if (!pte_same(oldpte, newpte)) { | |
| set_pte_at(vma->vm_mm, addr, pte, newpte); | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| static __always_inline void change_present_ptes(struct mmu_gather *tlb, | |
| struct vm_area_struct *vma, unsigned long addr, pte_t *ptep, | |
| int nr_ptes, unsigned long end, pgprot_t newprot, | |
| struct folio *folio, struct page *page, unsigned long cp_flags) | |
| { | |
| const bool uffd_wp_resolve = cp_flags & MM_CP_UFFD_WP_RESOLVE; | |
| const bool uffd_wp = cp_flags & MM_CP_UFFD_WP; | |
| pte_t ptent, oldpte; | |
| oldpte = modify_prot_start_ptes(vma, addr, ptep, nr_ptes); | |
| ptent = pte_modify(oldpte, newprot); | |
| if (uffd_wp) | |
| ptent = pte_mkuffd_wp(ptent); | |
| else if (uffd_wp_resolve) | |
| ptent = pte_clear_uffd_wp(ptent); | |
| /* | |
| * In some writable, shared mappings, we might want | |
| * to catch actual write access -- see | |
| * vma_wants_writenotify(). | |
| * | |
| * In all writable, private mappings, we have to | |
| * properly handle COW. | |
| * | |
| * In both cases, we can sometimes still change PTEs | |
| * writable and avoid the write-fault handler, for | |
| * example, if a PTE is already dirty and no other | |
| * COW or special handling is required. | |
| */ | |
| if ((cp_flags & MM_CP_TRY_CHANGE_WRITABLE) && | |
| !pte_write(ptent)) | |
| set_write_prot_commit_flush_ptes(vma, folio, page, | |
| addr, ptep, oldpte, ptent, nr_ptes, tlb); | |
| else | |
| prot_commit_flush_ptes(vma, addr, ptep, oldpte, ptent, | |
| nr_ptes, /* idx = */ 0, /* set_write = */ false, tlb); | |
| } | |
| static long change_pte_range(struct mmu_gather *tlb, | |
| struct vm_area_struct *vma, pmd_t *pmd, unsigned long addr, | |
| unsigned long end, pgprot_t newprot, unsigned long cp_flags) | |
| { | |
| pte_t *pte, oldpte; | |
| spinlock_t *ptl; | |
| long pages = 0; | |
| bool is_private_single_threaded; | |
| bool prot_numa = cp_flags & MM_CP_PROT_NUMA; | |
| bool uffd_wp = cp_flags & MM_CP_UFFD_WP; | |
| int nr_ptes; | |
| tlb_change_page_size(tlb, PAGE_SIZE); | |
| pte = pte_offset_map_lock(vma->vm_mm, pmd, addr, &ptl); | |
| if (!pte) | |
| return -EAGAIN; | |
| if (prot_numa) | |
| is_private_single_threaded = vma_is_single_threaded_private(vma); | |
| flush_tlb_batched_pending(vma->vm_mm); | |
| lazy_mmu_mode_enable(); | |
| do { | |
| nr_ptes = 1; | |
| oldpte = ptep_get(pte); | |
| if (pte_present(oldpte)) { | |
| const fpb_t flags = FPB_RESPECT_SOFT_DIRTY | FPB_RESPECT_WRITE; | |
| int max_nr_ptes = (end - addr) >> PAGE_SHIFT; | |
| struct folio *folio = NULL; | |
| struct page *page; | |
| /* Already in the desired state. */ | |
| if (prot_numa && pte_protnone(oldpte)) | |
| continue; | |
| page = vm_normal_page(vma, addr, oldpte); | |
| if (page) | |
| folio = page_folio(page); | |
| /* | |
| * Avoid trapping faults against the zero or KSM | |
| * pages. See similar comment in change_huge_pmd. | |
| */ | |
| if (prot_numa && | |
| !folio_can_map_prot_numa(folio, vma, | |
| is_private_single_threaded)) { | |
| /* determine batch to skip */ | |
| nr_ptes = mprotect_folio_pte_batch(folio, | |
| pte, oldpte, max_nr_ptes, /* flags = */ 0); | |
| continue; | |
| } | |
| nr_ptes = mprotect_folio_pte_batch(folio, pte, oldpte, max_nr_ptes, flags); | |
| /* | |
| * Optimize for the small-folio common case by | |
| * special-casing it here. Compiler constant propagation | |
| * plus copious amounts of __always_inline does wonders. | |
| */ | |
| if (likely(nr_ptes == 1)) { | |
| change_present_ptes(tlb, vma, addr, pte, 1, | |
| end, newprot, folio, page, cp_flags); | |
| } else { | |
| change_present_ptes(tlb, vma, addr, pte, | |
| nr_ptes, end, newprot, folio, page, | |
| cp_flags); | |
| } | |
| pages += nr_ptes; | |
| } else if (pte_none(oldpte)) { | |
| /* | |
| * Nobody plays with any none ptes besides | |
| * userfaultfd when applying the protections. | |
| */ | |
| if (likely(!uffd_wp)) | |
| continue; | |
| if (userfaultfd_wp_use_markers(vma)) { | |
| /* | |
| * For file-backed mem, we need to be able to | |
| * wr-protect a none pte, because even if the | |
| * pte is none, the page/swap cache could | |
| * exist. Doing that by install a marker. | |
| */ | |
| set_pte_at(vma->vm_mm, addr, pte, | |
| make_pte_marker(PTE_MARKER_UFFD_WP)); | |
| pages++; | |
| } | |
| } else { | |
| pages += change_softleaf_pte(vma, addr, pte, oldpte, cp_flags); | |
| } | |
| } while (pte += nr_ptes, addr += nr_ptes * PAGE_SIZE, addr != end); | |
| lazy_mmu_mode_disable(); | |
| pte_unmap_unlock(pte - 1, ptl); | |
| return pages; | |
| } | |
| /* | |
| * Return true if we want to split THPs into PTE mappings in change | |
| * protection procedure, false otherwise. | |
| */ | |
| static inline bool | |
| pgtable_split_needed(struct vm_area_struct *vma, unsigned long cp_flags) | |
| { | |
| /* | |
| * pte markers only resides in pte level, if we need pte markers, | |
| * we need to split. For example, we cannot wr-protect a file thp | |
| * (e.g. 2M shmem) because file thp is handled differently when | |
| * split by erasing the pmd so far. | |
| */ | |
| return (cp_flags & MM_CP_UFFD_WP) && !vma_is_anonymous(vma); | |
| } | |
| /* | |
| * Return true if we want to populate pgtables in change protection | |
| * procedure, false otherwise | |
| */ | |
| static inline bool | |
| pgtable_populate_needed(struct vm_area_struct *vma, unsigned long cp_flags) | |
| { | |
| /* If not within ioctl(UFFDIO_WRITEPROTECT), then don't bother */ | |
| if (!(cp_flags & MM_CP_UFFD_WP)) | |
| return false; | |
| /* Populate if the userfaultfd mode requires pte markers */ | |
| return userfaultfd_wp_use_markers(vma); | |
| } | |
| /* | |
| * Populate the pgtable underneath for whatever reason if requested. | |
| * When {pte|pmd|...}_alloc() failed we treat it the same way as pgtable | |
| * allocation failures during page faults by kicking OOM and returning | |
| * error. | |
| */ | |
| #define change_pmd_prepare(vma, pmd, cp_flags) \ | |
| ({ \ | |
| long err = 0; \ | |
| if (unlikely(pgtable_populate_needed(vma, cp_flags))) { \ | |
| if (pte_alloc(vma->vm_mm, pmd)) \ | |
| err = -ENOMEM; \ | |
| } \ | |
| err; \ | |
| }) | |
| /* | |
| * This is the general pud/p4d/pgd version of change_pmd_prepare(). We need to | |
| * have separate change_pmd_prepare() because pte_alloc() returns 0 on success, | |
| * while {pmd|pud|p4d}_alloc() returns the valid pointer on success. | |
| */ | |
| #define change_prepare(vma, high, low, addr, cp_flags) \ | |
| ({ \ | |
| long err = 0; \ | |
| if (unlikely(pgtable_populate_needed(vma, cp_flags))) { \ | |
| low##_t *p = low##_alloc(vma->vm_mm, high, addr); \ | |
| if (p == NULL) \ | |
| err = -ENOMEM; \ | |
| } \ | |
| err; \ | |
| }) | |
| static inline long change_pmd_range(struct mmu_gather *tlb, | |
| struct vm_area_struct *vma, pud_t *pud, unsigned long addr, | |
| unsigned long end, pgprot_t newprot, unsigned long cp_flags) | |
| { | |
| pmd_t *pmd; | |
| unsigned long next; | |
| long pages = 0; | |
| unsigned long nr_huge_updates = 0; | |
| pmd = pmd_offset(pud, addr); | |
| do { | |
| long ret; | |
| pmd_t _pmd; | |
| again: | |
| next = pmd_addr_end(addr, end); | |
| ret = change_pmd_prepare(vma, pmd, cp_flags); | |
| if (ret) { | |
| pages = ret; | |
| break; | |
| } | |
| if (pmd_none(*pmd)) | |
| goto next; | |
| _pmd = pmdp_get_lockless(pmd); | |
| if (pmd_is_huge(_pmd)) { | |
| if ((next - addr != HPAGE_PMD_SIZE) || | |
| pgtable_split_needed(vma, cp_flags)) { | |
| __split_huge_pmd(vma, pmd, addr, false); | |
| /* | |
| * For file-backed, the pmd could have been | |
| * cleared; make sure pmd populated if | |
| * necessary, then fall-through to pte level. | |
| */ | |
| ret = change_pmd_prepare(vma, pmd, cp_flags); | |
| if (ret) { | |
| pages = ret; | |
| break; | |
| } | |
| } else { | |
| ret = change_huge_pmd(tlb, vma, pmd, | |
| addr, newprot, cp_flags); | |
| if (ret) { | |
| if (ret == HPAGE_PMD_NR) { | |
| pages += HPAGE_PMD_NR; | |
| nr_huge_updates++; | |
| } | |
| /* huge pmd was handled */ | |
| goto next; | |
| } | |
| } | |
| /* fall through, the trans huge pmd just split */ | |
| } | |
| ret = change_pte_range(tlb, vma, pmd, addr, next, newprot, | |
| cp_flags); | |
| if (ret < 0) | |
| goto again; | |
| pages += ret; | |
| next: | |
| cond_resched(); | |
| } while (pmd++, addr = next, addr != end); | |
| if (nr_huge_updates) | |
| count_vm_numa_events(NUMA_HUGE_PTE_UPDATES, nr_huge_updates); | |
| return pages; | |
| } | |
| static inline long change_pud_range(struct mmu_gather *tlb, | |
| struct vm_area_struct *vma, p4d_t *p4d, unsigned long addr, | |
| unsigned long end, pgprot_t newprot, unsigned long cp_flags) | |
| { | |
| struct mmu_notifier_range range; | |
| pud_t *pudp, pud; | |
| unsigned long next; | |
| long pages = 0, ret; | |
| range.start = 0; | |
| pudp = pud_offset(p4d, addr); | |
| do { | |
| again: | |
| next = pud_addr_end(addr, end); | |
| ret = change_prepare(vma, pudp, pmd, addr, cp_flags); | |
| if (ret) { | |
| pages = ret; | |
| break; | |
| } | |
| pud = pudp_get(pudp); | |
| if (pud_none(pud)) | |
| continue; | |
| if (!range.start) { | |
| mmu_notifier_range_init(&range, | |
| MMU_NOTIFY_PROTECTION_VMA, 0, | |
| vma->vm_mm, addr, end); | |
| mmu_notifier_invalidate_range_start(&range); | |
| } | |
| if (pud_leaf(pud)) { | |
| if ((next - addr != PUD_SIZE) || | |
| pgtable_split_needed(vma, cp_flags)) { | |
| __split_huge_pud(vma, pudp, addr); | |
| goto again; | |
| } else { | |
| ret = change_huge_pud(tlb, vma, pudp, | |
| addr, newprot, cp_flags); | |
| if (ret == 0) | |
| goto again; | |
| /* huge pud was handled */ | |
| if (ret == HPAGE_PUD_NR) | |
| pages += HPAGE_PUD_NR; | |
| continue; | |
| } | |
| } | |
| pages += change_pmd_range(tlb, vma, pudp, addr, next, newprot, | |
| cp_flags); | |
| } while (pudp++, addr = next, addr != end); | |
| if (range.start) | |
| mmu_notifier_invalidate_range_end(&range); | |
| return pages; | |
| } | |
| static inline long change_p4d_range(struct mmu_gather *tlb, | |
| struct vm_area_struct *vma, pgd_t *pgd, unsigned long addr, | |
| unsigned long end, pgprot_t newprot, unsigned long cp_flags) | |
| { | |
| p4d_t *p4d; | |
| unsigned long next; | |
| long pages = 0, ret; | |
| p4d = p4d_offset(pgd, addr); | |
| do { | |
| next = p4d_addr_end(addr, end); | |
| ret = change_prepare(vma, p4d, pud, addr, cp_flags); | |
| if (ret) | |
| return ret; | |
| if (p4d_none_or_clear_bad(p4d)) | |
| continue; | |
| pages += change_pud_range(tlb, vma, p4d, addr, next, newprot, | |
| cp_flags); | |
| } while (p4d++, addr = next, addr != end); | |
| return pages; | |
| } | |
| static long change_protection_range(struct mmu_gather *tlb, | |
| struct vm_area_struct *vma, unsigned long addr, | |
| unsigned long end, pgprot_t newprot, unsigned long cp_flags) | |
| { | |
| struct mm_struct *mm = vma->vm_mm; | |
| pgd_t *pgd; | |
| unsigned long next; | |
| long pages = 0, ret; | |
| BUG_ON(addr >= end); | |
| pgd = pgd_offset(mm, addr); | |
| tlb_start_vma(tlb, vma); | |
| do { | |
| next = pgd_addr_end(addr, end); | |
| ret = change_prepare(vma, pgd, p4d, addr, cp_flags); | |
| if (ret) { | |
| pages = ret; | |
| break; | |
| } | |
| if (pgd_none_or_clear_bad(pgd)) | |
| continue; | |
| pages += change_p4d_range(tlb, vma, pgd, addr, next, newprot, | |
| cp_flags); | |
| } while (pgd++, addr = next, addr != end); | |
| tlb_end_vma(tlb, vma); | |
| return pages; | |
| } | |
| long change_protection(struct mmu_gather *tlb, | |
| struct vm_area_struct *vma, unsigned long start, | |
| unsigned long end, unsigned long cp_flags) | |
| { | |
| pgprot_t newprot = vma->vm_page_prot; | |
| long pages; | |
| BUG_ON((cp_flags & MM_CP_UFFD_WP_ALL) == MM_CP_UFFD_WP_ALL); | |
| #ifdef CONFIG_NUMA_BALANCING | |
| /* | |
| * Ordinary protection updates (mprotect, uffd-wp, softdirty tracking) | |
| * are expected to reflect their requirements via VMA flags such that | |
| * vma_set_page_prot() will adjust vma->vm_page_prot accordingly. | |
| */ | |
| if (cp_flags & MM_CP_PROT_NUMA) | |
| newprot = PAGE_NONE; | |
| #else | |
| WARN_ON_ONCE(cp_flags & MM_CP_PROT_NUMA); | |
| #endif | |
| if (is_vm_hugetlb_page(vma)) | |
| pages = hugetlb_change_protection(vma, start, end, newprot, | |
| cp_flags); | |
| else | |
| pages = change_protection_range(tlb, vma, start, end, newprot, | |
| cp_flags); | |
| return pages; | |
| } | |
| static int prot_none_pte_entry(pte_t *pte, unsigned long addr, | |
| unsigned long next, struct mm_walk *walk) | |
| { | |
| return pfn_modify_allowed(pte_pfn(ptep_get(pte)), | |
| *(pgprot_t *)(walk->private)) ? | |
| 0 : -EACCES; | |
| } | |
| static int prot_none_hugetlb_entry(pte_t *pte, unsigned long hmask, | |
| unsigned long addr, unsigned long next, | |
| struct mm_walk *walk) | |
| { | |
| return pfn_modify_allowed(pte_pfn(ptep_get(pte)), | |
| *(pgprot_t *)(walk->private)) ? | |
| 0 : -EACCES; | |
| } | |
| static int prot_none_test(unsigned long addr, unsigned long next, | |
| struct mm_walk *walk) | |
| { | |
| return 0; | |
| } | |
| static const struct mm_walk_ops prot_none_walk_ops = { | |
| .pte_entry = prot_none_pte_entry, | |
| .hugetlb_entry = prot_none_hugetlb_entry, | |
| .test_walk = prot_none_test, | |
| .walk_lock = PGWALK_WRLOCK, | |
| }; | |
| int | |
| mprotect_fixup(struct vma_iterator *vmi, struct mmu_gather *tlb, | |
| struct vm_area_struct *vma, struct vm_area_struct **pprev, | |
| unsigned long start, unsigned long end, vm_flags_t newflags) | |
| { | |
| struct mm_struct *mm = vma->vm_mm; | |
| const vma_flags_t old_vma_flags = READ_ONCE(vma->flags); | |
| vma_flags_t new_vma_flags = legacy_to_vma_flags(newflags); | |
| long nrpages = (end - start) >> PAGE_SHIFT; | |
| unsigned int mm_cp_flags = 0; | |
| unsigned long charged = 0; | |
| int error; | |
| if (vma_is_sealed(vma)) | |
| return -EPERM; | |
| if (vma_flags_same_pair(&old_vma_flags, &new_vma_flags)) { | |
| *pprev = vma; | |
| return 0; | |
| } | |
| /* | |
| * Do PROT_NONE PFN permission checks here when we can still | |
| * bail out without undoing a lot of state. This is a rather | |
| * uncommon case, so doesn't need to be very optimized. | |
| */ | |
| if (arch_has_pfn_modify_check() && | |
| vma_flags_test_any(&old_vma_flags, VMA_PFNMAP_BIT, | |
| VMA_MIXEDMAP_BIT) && | |
| !vma_flags_test_any_mask(&new_vma_flags, VMA_ACCESS_FLAGS)) { | |
| pgprot_t new_pgprot = vm_get_page_prot(newflags); | |
| error = walk_page_range(current->mm, start, end, | |
| &prot_none_walk_ops, &new_pgprot); | |
| if (error) | |
| return error; | |
| } | |
| /* | |
| * If we make a private mapping writable we increase our commit; | |
| * but (without finer accounting) cannot reduce our commit if we | |
| * make it unwritable again except in the anonymous case where no | |
| * anon_vma has yet to be assigned. | |
| * | |
| * hugetlb mapping were accounted for even if read-only so there is | |
| * no need to account for them here. | |
| */ | |
| if (vma_flags_test(&new_vma_flags, VMA_WRITE_BIT)) { | |
| /* Check space limits when area turns into data. */ | |
| if (!may_expand_vm(mm, &new_vma_flags, nrpages) && | |
| may_expand_vm(mm, &old_vma_flags, nrpages)) | |
| return -ENOMEM; | |
| if (!vma_flags_test_any(&old_vma_flags, | |
| VMA_ACCOUNT_BIT, VMA_WRITE_BIT, VMA_HUGETLB_BIT, | |
| VMA_SHARED_BIT, VMA_NORESERVE_BIT)) { | |
| charged = nrpages; | |
| if (security_vm_enough_memory_mm(mm, charged)) | |
| return -ENOMEM; | |
| vma_flags_set(&new_vma_flags, VMA_ACCOUNT_BIT); | |
| } | |
| } else if (vma_flags_test(&old_vma_flags, VMA_ACCOUNT_BIT) && | |
| vma_is_anonymous(vma) && !vma->anon_vma) { | |
| vma_flags_clear(&new_vma_flags, VMA_ACCOUNT_BIT); | |
| } | |
| vma = vma_modify_flags(vmi, *pprev, vma, start, end, &new_vma_flags); | |
| if (IS_ERR(vma)) { | |
| error = PTR_ERR(vma); | |
| goto fail; | |
| } | |
| *pprev = vma; | |
| /* | |
| * vm_flags and vm_page_prot are protected by the mmap_lock | |
| * held in write mode. | |
| */ | |
| vma_start_write(vma); | |
| vma_flags_reset_once(vma, &new_vma_flags); | |
| if (vma_wants_manual_pte_write_upgrade(vma)) | |
| mm_cp_flags |= MM_CP_TRY_CHANGE_WRITABLE; | |
| vma_set_page_prot(vma); | |
| change_protection(tlb, vma, start, end, mm_cp_flags); | |
| if (vma_flags_test(&old_vma_flags, VMA_ACCOUNT_BIT) && | |
| !vma_flags_test(&new_vma_flags, VMA_ACCOUNT_BIT)) | |
| vm_unacct_memory(nrpages); | |
| /* | |
| * Private VM_LOCKED VMA becoming writable: trigger COW to avoid major | |
| * fault on access. | |
| */ | |
| if (vma_flags_test(&new_vma_flags, VMA_WRITE_BIT) && | |
| vma_flags_test(&old_vma_flags, VMA_LOCKED_BIT) && | |
| !vma_flags_test_any(&old_vma_flags, VMA_WRITE_BIT, VMA_SHARED_BIT)) | |
| populate_vma_page_range(vma, start, end, NULL); | |
| vm_stat_account(mm, vma_flags_to_legacy(old_vma_flags), -nrpages); | |
| newflags = vma_flags_to_legacy(new_vma_flags); | |
| vm_stat_account(mm, newflags, nrpages); | |
| perf_event_mmap(vma); | |
| return 0; | |
| fail: | |
| vm_unacct_memory(charged); | |
| return error; | |
| } | |
| /* | |
| * pkey==-1 when doing a legacy mprotect() | |
| */ | |
| static int do_mprotect_pkey(unsigned long start, size_t len, | |
| unsigned long prot, int pkey) | |
| { | |
| unsigned long nstart, end, tmp, reqprot; | |
| struct vm_area_struct *vma, *prev; | |
| int error; | |
| const int grows = prot & (PROT_GROWSDOWN|PROT_GROWSUP); | |
| const bool rier = (current->personality & READ_IMPLIES_EXEC) && | |
| (prot & PROT_READ); | |
| struct mmu_gather tlb; | |
| struct vma_iterator vmi; | |
| start = untagged_addr(start); | |
| prot &= ~(PROT_GROWSDOWN|PROT_GROWSUP); | |
| if (grows == (PROT_GROWSDOWN|PROT_GROWSUP)) /* can't be both */ | |
| return -EINVAL; | |
| if (start & ~PAGE_MASK) | |
| return -EINVAL; | |
| if (!len) | |
| return 0; | |
| len = PAGE_ALIGN(len); | |
| end = start + len; | |
| if (end <= start) | |
| return -ENOMEM; | |
| if (!arch_validate_prot(prot, start)) | |
| return -EINVAL; | |
| reqprot = prot; | |
| if (mmap_write_lock_killable(current->mm)) | |
| return -EINTR; | |
| /* | |
| * If userspace did not allocate the pkey, do not let | |
| * them use it here. | |
| */ | |
| error = -EINVAL; | |
| if ((pkey != -1) && !mm_pkey_is_allocated(current->mm, pkey)) | |
| goto out; | |
| vma_iter_init(&vmi, current->mm, start); | |
| vma = vma_find(&vmi, end); | |
| error = -ENOMEM; | |
| if (!vma) | |
| goto out; | |
| if (unlikely(grows & PROT_GROWSDOWN)) { | |
| if (vma->vm_start >= end) | |
| goto out; | |
| start = vma->vm_start; | |
| error = -EINVAL; | |
| if (!(vma->vm_flags & VM_GROWSDOWN)) | |
| goto out; | |
| } else { | |
| if (vma->vm_start > start) | |
| goto out; | |
| if (unlikely(grows & PROT_GROWSUP)) { | |
| end = vma->vm_end; | |
| error = -EINVAL; | |
| if (!(vma->vm_flags & VM_GROWSUP)) | |
| goto out; | |
| } | |
| } | |
| prev = vma_prev(&vmi); | |
| if (start > vma->vm_start) | |
| prev = vma; | |
| tlb_gather_mmu(&tlb, current->mm); | |
| nstart = start; | |
| tmp = vma->vm_start; | |
| for_each_vma_range(vmi, vma, end) { | |
| vm_flags_t mask_off_old_flags; | |
| vma_flags_t new_vma_flags; | |
| vm_flags_t newflags; | |
| int new_vma_pkey; | |
| if (vma->vm_start != tmp) { | |
| error = -ENOMEM; | |
| break; | |
| } | |
| /* Does the application expect PROT_READ to imply PROT_EXEC */ | |
| if (rier && (vma->vm_flags & VM_MAYEXEC)) | |
| prot |= PROT_EXEC; | |
| /* | |
| * Each mprotect() call explicitly passes r/w/x permissions. | |
| * If a permission is not passed to mprotect(), it must be | |
| * cleared from the VMA. | |
| */ | |
| mask_off_old_flags = VM_ACCESS_FLAGS | VM_FLAGS_CLEAR; | |
| new_vma_pkey = arch_override_mprotect_pkey(vma, prot, pkey); | |
| newflags = calc_vm_prot_bits(prot, new_vma_pkey); | |
| newflags |= (vma->vm_flags & ~mask_off_old_flags); | |
| new_vma_flags = legacy_to_vma_flags(newflags); | |
| /* newflags >> 4 shift VM_MAY% in place of VM_% */ | |
| if ((newflags & ~(newflags >> 4)) & VM_ACCESS_FLAGS) { | |
| error = -EACCES; | |
| break; | |
| } | |
| if (map_deny_write_exec(&vma->flags, &new_vma_flags)) { | |
| error = -EACCES; | |
| break; | |
| } | |
| /* Allow architectures to sanity-check the new flags */ | |
| if (!arch_validate_flags(newflags)) { | |
| error = -EINVAL; | |
| break; | |
| } | |
| error = security_file_mprotect(vma, reqprot, prot); | |
| if (error) | |
| break; | |
| tmp = vma->vm_end; | |
| if (tmp > end) | |
| tmp = end; | |
| if (vma->vm_ops && vma->vm_ops->mprotect) { | |
| error = vma->vm_ops->mprotect(vma, nstart, tmp, newflags); | |
| if (error) | |
| break; | |
| } | |
| error = mprotect_fixup(&vmi, &tlb, vma, &prev, nstart, tmp, newflags); | |
| if (error) | |
| break; | |
| tmp = vma_iter_end(&vmi); | |
| nstart = tmp; | |
| prot = reqprot; | |
| } | |
| tlb_finish_mmu(&tlb); | |
| if (!error && tmp < end) | |
| error = -ENOMEM; | |
| out: | |
| mmap_write_unlock(current->mm); | |
| return error; | |
| } | |
| SYSCALL_DEFINE3(mprotect, unsigned long, start, size_t, len, | |
| unsigned long, prot) | |
| { | |
| return do_mprotect_pkey(start, len, prot, -1); | |
| } | |
| #ifdef CONFIG_ARCH_HAS_PKEYS | |
| SYSCALL_DEFINE4(pkey_mprotect, unsigned long, start, size_t, len, | |
| unsigned long, prot, int, pkey) | |
| { | |
| return do_mprotect_pkey(start, len, prot, pkey); | |
| } | |
| SYSCALL_DEFINE2(pkey_alloc, unsigned long, flags, unsigned long, init_val) | |
| { | |
| int pkey; | |
| int ret; | |
| /* No flags supported yet. */ | |
| if (flags) | |
| return -EINVAL; | |
| /* check for unsupported init values */ | |
| if (init_val & ~PKEY_ACCESS_MASK) | |
| return -EINVAL; | |
| mmap_write_lock(current->mm); | |
| pkey = mm_pkey_alloc(current->mm); | |
| ret = -ENOSPC; | |
| if (pkey == -1) | |
| goto out; | |
| ret = arch_set_user_pkey_access(pkey, init_val); | |
| if (ret) { | |
| mm_pkey_free(current->mm, pkey); | |
| goto out; | |
| } | |
| ret = pkey; | |
| out: | |
| mmap_write_unlock(current->mm); | |
| return ret; | |
| } | |
| SYSCALL_DEFINE1(pkey_free, int, pkey) | |
| { | |
| int ret; | |
| mmap_write_lock(current->mm); | |
| ret = mm_pkey_free(current->mm, pkey); | |
| mmap_write_unlock(current->mm); | |
| /* | |
| * We could provide warnings or errors if any VMA still | |
| * has the pkey set here. | |
| */ | |
| return ret; | |
| } | |
| #endif /* CONFIG_ARCH_HAS_PKEYS */ |