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