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