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