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