linux/mm/mprotect.c
<<
>>
Prefs
   1/*
   2 *  mm/mprotect.c
   3 *
   4 *  (C) Copyright 1994 Linus Torvalds
   5 *  (C) Copyright 2002 Christoph Hellwig
   6 *
   7 *  Address space accounting code       <alan@lxorguk.ukuu.org.uk>
   8 *  (C) Copyright 2002 Red Hat Inc, All Rights Reserved
   9 */
  10
  11#include <linux/mm.h>
  12#include <linux/hugetlb.h>
  13#include <linux/slab.h>
  14#include <linux/shm.h>
  15#include <linux/mman.h>
  16#include <linux/fs.h>
  17#include <linux/highmem.h>
  18#include <linux/security.h>
  19#include <linux/mempolicy.h>
  20#include <linux/personality.h>
  21#include <linux/syscalls.h>
  22#include <linux/swap.h>
  23#include <linux/swapops.h>
  24#include <linux/mmu_notifier.h>
  25#include <linux/migrate.h>
  26#include <linux/perf_event.h>
  27#include <asm/uaccess.h>
  28#include <asm/pgtable.h>
  29#include <asm/cacheflush.h>
  30#include <asm/tlbflush.h>
  31
  32#ifndef pgprot_modify
  33static inline pgprot_t pgprot_modify(pgprot_t oldprot, pgprot_t newprot)
  34{
  35        return newprot;
  36}
  37#endif
  38
  39static void change_pte_range(struct mm_struct *mm, pmd_t *pmd,
  40                unsigned long addr, unsigned long end, pgprot_t newprot,
  41                int dirty_accountable)
  42{
  43        pte_t *pte, oldpte;
  44        spinlock_t *ptl;
  45
  46        pte = pte_offset_map_lock(mm, pmd, addr, &ptl);
  47        arch_enter_lazy_mmu_mode();
  48        do {
  49                oldpte = *pte;
  50                if (pte_present(oldpte)) {
  51                        pte_t ptent;
  52
  53                        ptent = ptep_modify_prot_start(mm, addr, pte);
  54                        ptent = pte_modify(ptent, newprot);
  55
  56                        /*
  57                         * Avoid taking write faults for pages we know to be
  58                         * dirty.
  59                         */
  60                        if (dirty_accountable && pte_dirty(ptent))
  61                                ptent = pte_mkwrite(ptent);
  62
  63                        ptep_modify_prot_commit(mm, addr, pte, ptent);
  64                } else if (PAGE_MIGRATION && !pte_file(oldpte)) {
  65                        swp_entry_t entry = pte_to_swp_entry(oldpte);
  66
  67                        if (is_write_migration_entry(entry)) {
  68                                /*
  69                                 * A protection check is difficult so
  70                                 * just be safe and disable write
  71                                 */
  72                                make_migration_entry_read(&entry);
  73                                set_pte_at(mm, addr, pte,
  74                                        swp_entry_to_pte(entry));
  75                        }
  76                }
  77        } while (pte++, addr += PAGE_SIZE, addr != end);
  78        arch_leave_lazy_mmu_mode();
  79        pte_unmap_unlock(pte - 1, ptl);
  80}
  81
  82static inline void change_pmd_range(struct mm_struct *mm, pud_t *pud,
  83                unsigned long addr, unsigned long end, pgprot_t newprot,
  84                int dirty_accountable)
  85{
  86        pmd_t *pmd;
  87        unsigned long next;
  88
  89        pmd = pmd_offset(pud, addr);
  90        do {
  91                next = pmd_addr_end(addr, end);
  92                if (pmd_none_or_clear_bad(pmd))
  93                        continue;
  94                change_pte_range(mm, pmd, addr, next, newprot, dirty_accountable);
  95        } while (pmd++, addr = next, addr != end);
  96}
  97
  98static inline void change_pud_range(struct mm_struct *mm, pgd_t *pgd,
  99                unsigned long addr, unsigned long end, pgprot_t newprot,
 100                int dirty_accountable)
 101{
 102        pud_t *pud;
 103        unsigned long next;
 104
 105        pud = pud_offset(pgd, addr);
 106        do {
 107                next = pud_addr_end(addr, end);
 108                if (pud_none_or_clear_bad(pud))
 109                        continue;
 110                change_pmd_range(mm, pud, addr, next, newprot, dirty_accountable);
 111        } while (pud++, addr = next, addr != end);
 112}
 113
 114static void change_protection(struct vm_area_struct *vma,
 115                unsigned long addr, unsigned long end, pgprot_t newprot,
 116                int dirty_accountable)
 117{
 118        struct mm_struct *mm = vma->vm_mm;
 119        pgd_t *pgd;
 120        unsigned long next;
 121        unsigned long start = addr;
 122
 123        BUG_ON(addr >= end);
 124        pgd = pgd_offset(mm, addr);
 125        flush_cache_range(vma, addr, end);
 126        do {
 127                next = pgd_addr_end(addr, end);
 128                if (pgd_none_or_clear_bad(pgd))
 129                        continue;
 130                change_pud_range(mm, pgd, addr, next, newprot, dirty_accountable);
 131        } while (pgd++, addr = next, addr != end);
 132        flush_tlb_range(vma, start, end);
 133}
 134
 135int
 136mprotect_fixup(struct vm_area_struct *vma, struct vm_area_struct **pprev,
 137        unsigned long start, unsigned long end, unsigned long newflags)
 138{
 139        struct mm_struct *mm = vma->vm_mm;
 140        unsigned long oldflags = vma->vm_flags;
 141        long nrpages = (end - start) >> PAGE_SHIFT;
 142        unsigned long charged = 0;
 143        pgoff_t pgoff;
 144        int error;
 145        int dirty_accountable = 0;
 146
 147        if (newflags == oldflags) {
 148                *pprev = vma;
 149                return 0;
 150        }
 151
 152        /*
 153         * If we make a private mapping writable we increase our commit;
 154         * but (without finer accounting) cannot reduce our commit if we
 155         * make it unwritable again. hugetlb mapping were accounted for
 156         * even if read-only so there is no need to account for them here
 157         */
 158        if (newflags & VM_WRITE) {
 159                if (!(oldflags & (VM_ACCOUNT|VM_WRITE|VM_HUGETLB|
 160                                                VM_SHARED|VM_NORESERVE))) {
 161                        charged = nrpages;
 162                        if (security_vm_enough_memory(charged))
 163                                return -ENOMEM;
 164                        newflags |= VM_ACCOUNT;
 165                }
 166        }
 167
 168        /*
 169         * First try to merge with previous and/or next vma.
 170         */
 171        pgoff = vma->vm_pgoff + ((start - vma->vm_start) >> PAGE_SHIFT);
 172        *pprev = vma_merge(mm, *pprev, start, end, newflags,
 173                        vma->anon_vma, vma->vm_file, pgoff, vma_policy(vma));
 174        if (*pprev) {
 175                vma = *pprev;
 176                goto success;
 177        }
 178
 179        *pprev = vma;
 180
 181        if (start != vma->vm_start) {
 182                error = split_vma(mm, vma, start, 1);
 183                if (error)
 184                        goto fail;
 185        }
 186
 187        if (end != vma->vm_end) {
 188                error = split_vma(mm, vma, end, 0);
 189                if (error)
 190                        goto fail;
 191        }
 192
 193success:
 194        /*
 195         * vm_flags and vm_page_prot are protected by the mmap_sem
 196         * held in write mode.
 197         */
 198        vma->vm_flags = newflags;
 199        vma->vm_page_prot = pgprot_modify(vma->vm_page_prot,
 200                                          vm_get_page_prot(newflags));
 201
 202        if (vma_wants_writenotify(vma)) {
 203                vma->vm_page_prot = vm_get_page_prot(newflags & ~VM_SHARED);
 204                dirty_accountable = 1;
 205        }
 206
 207        mmu_notifier_invalidate_range_start(mm, start, end);
 208        if (is_vm_hugetlb_page(vma))
 209                hugetlb_change_protection(vma, start, end, vma->vm_page_prot);
 210        else
 211                change_protection(vma, start, end, vma->vm_page_prot, dirty_accountable);
 212        mmu_notifier_invalidate_range_end(mm, start, end);
 213        vm_stat_account(mm, oldflags, vma->vm_file, -nrpages);
 214        vm_stat_account(mm, newflags, vma->vm_file, nrpages);
 215        return 0;
 216
 217fail:
 218        vm_unacct_memory(charged);
 219        return error;
 220}
 221
 222SYSCALL_DEFINE3(mprotect, unsigned long, start, size_t, len,
 223                unsigned long, prot)
 224{
 225        unsigned long vm_flags, nstart, end, tmp, reqprot;
 226        struct vm_area_struct *vma, *prev;
 227        int error = -EINVAL;
 228        const int grows = prot & (PROT_GROWSDOWN|PROT_GROWSUP);
 229        prot &= ~(PROT_GROWSDOWN|PROT_GROWSUP);
 230        if (grows == (PROT_GROWSDOWN|PROT_GROWSUP)) /* can't be both */
 231                return -EINVAL;
 232
 233        if (start & ~PAGE_MASK)
 234                return -EINVAL;
 235        if (!len)
 236                return 0;
 237        len = PAGE_ALIGN(len);
 238        end = start + len;
 239        if (end <= start)
 240                return -ENOMEM;
 241        if (!arch_validate_prot(prot))
 242                return -EINVAL;
 243
 244        reqprot = prot;
 245        /*
 246         * Does the application expect PROT_READ to imply PROT_EXEC:
 247         */
 248        if ((prot & PROT_READ) && (current->personality & READ_IMPLIES_EXEC))
 249                prot |= PROT_EXEC;
 250
 251        vm_flags = calc_vm_prot_bits(prot);
 252
 253        down_write(&current->mm->mmap_sem);
 254
 255        vma = find_vma_prev(current->mm, start, &prev);
 256        error = -ENOMEM;
 257        if (!vma)
 258                goto out;
 259        if (unlikely(grows & PROT_GROWSDOWN)) {
 260                if (vma->vm_start >= end)
 261                        goto out;
 262                start = vma->vm_start;
 263                error = -EINVAL;
 264                if (!(vma->vm_flags & VM_GROWSDOWN))
 265                        goto out;
 266        }
 267        else {
 268                if (vma->vm_start > start)
 269                        goto out;
 270                if (unlikely(grows & PROT_GROWSUP)) {
 271                        end = vma->vm_end;
 272                        error = -EINVAL;
 273                        if (!(vma->vm_flags & VM_GROWSUP))
 274                                goto out;
 275                }
 276        }
 277        if (start > vma->vm_start)
 278                prev = vma;
 279
 280        for (nstart = start ; ; ) {
 281                unsigned long newflags;
 282
 283                /* Here we know that  vma->vm_start <= nstart < vma->vm_end. */
 284
 285                newflags = vm_flags | (vma->vm_flags & ~(VM_READ | VM_WRITE | VM_EXEC));
 286
 287                /* newflags >> 4 shift VM_MAY% in place of VM_% */
 288                if ((newflags & ~(newflags >> 4)) & (VM_READ | VM_WRITE | VM_EXEC)) {
 289                        error = -EACCES;
 290                        goto out;
 291                }
 292
 293                error = security_file_mprotect(vma, reqprot, prot);
 294                if (error)
 295                        goto out;
 296
 297                tmp = vma->vm_end;
 298                if (tmp > end)
 299                        tmp = end;
 300                error = mprotect_fixup(vma, &prev, nstart, tmp, newflags);
 301                if (error)
 302                        goto out;
 303                perf_event_mmap(vma);
 304                nstart = tmp;
 305
 306                if (nstart < prev->vm_end)
 307                        nstart = prev->vm_end;
 308                if (nstart >= end)
 309                        goto out;
 310
 311                vma = prev->vm_next;
 312                if (!vma || vma->vm_start != nstart) {
 313                        error = -ENOMEM;
 314                        goto out;
 315                }
 316        }
 317out:
 318        up_write(&current->mm->mmap_sem);
 319        return error;
 320}
 321