mprotect.c 6.27 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1
/*
Andrew Morton's avatar
Andrew Morton committed
2
 *  mm/mprotect.c
Linus Torvalds's avatar
Linus Torvalds committed
3 4
 *
 *  (C) Copyright 1994 Linus Torvalds
5
 *  (C) Copyright 2002 Christoph Hellwig
Andrew Morton's avatar
Andrew Morton committed
6 7 8
 *
 *  Address space accounting code	<alan@redhat.com>
 *  (C) Copyright 2002 Red Hat Inc, All Rights Reserved
Linus Torvalds's avatar
Linus Torvalds committed
9
 */
10

Linus Torvalds's avatar
Linus Torvalds committed
11
#include <linux/mm.h>
12
#include <linux/hugetlb.h>
Linus Torvalds's avatar
Linus Torvalds committed
13 14 15
#include <linux/slab.h>
#include <linux/shm.h>
#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>
Linus Torvalds's avatar
Linus Torvalds committed
21 22 23

#include <asm/uaccess.h>
#include <asm/pgtable.h>
24 25
#include <asm/cacheflush.h>
#include <asm/tlbflush.h>
Linus Torvalds's avatar
Linus Torvalds committed
26

27 28 29
static inline void
change_pte_range(pmd_t *pmd, unsigned long address,
		unsigned long size, pgprot_t newprot)
Linus Torvalds's avatar
Linus Torvalds committed
30 31 32 33 34 35 36 37 38 39 40
{
	pte_t * pte;
	unsigned long end;

	if (pmd_none(*pmd))
		return;
	if (pmd_bad(*pmd)) {
		pmd_ERROR(*pmd);
		pmd_clear(pmd);
		return;
	}
41
	pte = pte_offset_map(pmd, address);
Linus Torvalds's avatar
Linus Torvalds committed
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
	address &= ~PMD_MASK;
	end = address + size;
	if (end > PMD_SIZE)
		end = PMD_SIZE;
	do {
		if (pte_present(*pte)) {
			pte_t entry;

			/* Avoid an SMP race with hardware updated dirty/clean
			 * bits by wiping the pte and then setting the new pte
			 * into place.
			 */
			entry = ptep_get_and_clear(pte);
			set_pte(pte, pte_modify(entry, newprot));
		}
		address += PAGE_SIZE;
		pte++;
	} while (address && (address < end));
60
	pte_unmap(pte - 1);
Linus Torvalds's avatar
Linus Torvalds committed
61 62
}

63 64 65
static inline void
change_pmd_range(pgd_t *pgd, unsigned long address,
		unsigned long size, pgprot_t newprot)
Linus Torvalds's avatar
Linus Torvalds committed
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
{
	pmd_t * pmd;
	unsigned long end;

	if (pgd_none(*pgd))
		return;
	if (pgd_bad(*pgd)) {
		pgd_ERROR(*pgd);
		pgd_clear(pgd);
		return;
	}
	pmd = pmd_offset(pgd, address);
	address &= ~PGDIR_MASK;
	end = address + size;
	if (end > PGDIR_SIZE)
		end = PGDIR_SIZE;
	do {
		change_pte_range(pmd, address, end - address, newprot);
		address = (address + PMD_SIZE) & PMD_MASK;
		pmd++;
	} while (address && (address < end));
}

89 90 91
static void
change_protection(struct vm_area_struct *vma, unsigned long start,
		unsigned long end, pgprot_t newprot)
Linus Torvalds's avatar
Linus Torvalds committed
92 93 94 95 96
{
	pgd_t *dir;
	unsigned long beg = start;

	dir = pgd_offset(current->mm, start);
Linus Torvalds's avatar
Linus Torvalds committed
97
	flush_cache_range(vma, beg, end);
Linus Torvalds's avatar
Linus Torvalds committed
98 99 100 101 102 103 104 105
	if (start >= end)
		BUG();
	spin_lock(&current->mm->page_table_lock);
	do {
		change_pmd_range(dir, start, end - start, newprot);
		start = (start + PGDIR_SIZE) & PGDIR_MASK;
		dir++;
	} while (start && (start < end));
Linus Torvalds's avatar
Linus Torvalds committed
106
	flush_tlb_range(vma, beg, end);
107
	spin_unlock(&current->mm->page_table_lock);
Linus Torvalds's avatar
Linus Torvalds committed
108 109 110
	return;
}

111 112
static int
mprotect_fixup(struct vm_area_struct *vma, struct vm_area_struct **pprev,
Linus Torvalds's avatar
Linus Torvalds committed
113 114
	unsigned long start, unsigned long end, unsigned int newflags)
{
115 116
	struct mm_struct * mm = vma->vm_mm;
	unsigned long charged = 0;
Linus Torvalds's avatar
Linus Torvalds committed
117
	pgprot_t newprot;
118
	pgoff_t pgoff;
Linus Torvalds's avatar
Linus Torvalds committed
119 120
	int error;

Linus Torvalds's avatar
Linus Torvalds committed
121 122
	if (newflags == vma->vm_flags) {
		*pprev = vma;
Linus Torvalds's avatar
Linus Torvalds committed
123
		return 0;
Linus Torvalds's avatar
Linus Torvalds committed
124
	}
125 126 127 128 129 130 131 132 133

	/*
	 * If we make a private mapping writable we increase our commit;
	 * but (without finer accounting) cannot reduce our commit if we
	 * make it unwritable again.
	 *
	 * FIXME? We haven't defined a VM_NORESERVE flag, so mprotecting
	 * a MAP_NORESERVE private mapping to writable will now reserve.
	 */
134
	if (newflags & VM_WRITE) {
Andrew Morton's avatar
Andrew Morton committed
135
		if (!(vma->vm_flags & (VM_ACCOUNT|VM_WRITE|VM_SHARED|VM_HUGETLB))) {
136
			charged = (end - start) >> PAGE_SHIFT;
137
			if (security_vm_enough_memory(charged))
138 139 140
				return -ENOMEM;
			newflags |= VM_ACCOUNT;
		}
141
	}
142

Linus Torvalds's avatar
Linus Torvalds committed
143
	newprot = protection_map[newflags & 0xf];
144

145 146 147 148 149
	/*
	 * First try to merge with previous and/or next vma.
	 */
	pgoff = vma->vm_pgoff + ((start - vma->vm_start) >> PAGE_SHIFT);
	*pprev = vma_merge(mm, *pprev, start, end, newflags,
150
			vma->anon_vma, vma->vm_file, pgoff, vma_policy(vma));
151 152 153 154 155 156
	if (*pprev) {
		vma = *pprev;
		goto success;
	}

	if (start != vma->vm_start) {
157 158 159 160
		error = split_vma(mm, vma, start, 1);
		if (error)
			goto fail;
	}
161 162 163 164 165
	/*
	 * Unless it returns an error, this function always sets *pprev to
	 * the first vma for which vma->vm_end >= end.
	 */
	*pprev = vma;
166 167 168 169 170

	if (end != vma->vm_end) {
		error = split_vma(mm, vma, end, 0);
		if (error)
			goto fail;
Andrew Morton's avatar
Andrew Morton committed
171
	}
172

173
success:
174 175 176 177
	/*
	 * vm_flags and vm_page_prot are protected by the mmap_sem
	 * held in write mode.
	 */
178 179
	vma->vm_flags = newflags;
	vma->vm_page_prot = newprot;
Linus Torvalds's avatar
Linus Torvalds committed
180
	change_protection(vma, start, end, newprot);
Linus Torvalds's avatar
Linus Torvalds committed
181
	return 0;
182 183 184 185

fail:
	vm_unacct_memory(charged);
	return error;
Linus Torvalds's avatar
Linus Torvalds committed
186 187
}

188 189
asmlinkage long
sys_mprotect(unsigned long start, size_t len, unsigned long prot)
Linus Torvalds's avatar
Linus Torvalds committed
190
{
191
	unsigned long vm_flags, nstart, end, tmp;
192
	struct vm_area_struct *vma, *prev;
Linus Torvalds's avatar
Linus Torvalds committed
193
	int error = -EINVAL;
194 195 196 197
	const int grows = prot & (PROT_GROWSDOWN|PROT_GROWSUP);
	prot &= ~(PROT_GROWSDOWN|PROT_GROWSUP);
	if (grows == (PROT_GROWSDOWN|PROT_GROWSUP)) /* can't be both */
		return -EINVAL;
Linus Torvalds's avatar
Linus Torvalds committed
198 199 200 201 202 203

	if (start & ~PAGE_MASK)
		return -EINVAL;
	len = PAGE_ALIGN(len);
	end = start + len;
	if (end < start)
204
		return -ENOMEM;
205
	if (prot & ~(PROT_READ | PROT_WRITE | PROT_EXEC | PROT_SEM))
Linus Torvalds's avatar
Linus Torvalds committed
206 207 208
		return -EINVAL;
	if (end == start)
		return 0;
209 210 211 212 213 214
	/*
	 * Does the application expect PROT_READ to imply PROT_EXEC:
	 */
	if (unlikely((prot & PROT_READ) &&
			(current->personality & READ_IMPLIES_EXEC)))
		prot |= PROT_EXEC;
Linus Torvalds's avatar
Linus Torvalds committed
215

216 217
	vm_flags = calc_vm_prot_bits(prot);

Linus Torvalds's avatar
Linus Torvalds committed
218
	down_write(&current->mm->mmap_sem);
Linus Torvalds's avatar
Linus Torvalds committed
219

Linus Torvalds's avatar
Linus Torvalds committed
220
	vma = find_vma_prev(current->mm, start, &prev);
221
	error = -ENOMEM;
222
	if (!vma)
Linus Torvalds's avatar
Linus Torvalds committed
223
		goto out;
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
	if (unlikely(grows & PROT_GROWSDOWN)) {
		if (vma->vm_start >= end)
			goto out;
		start = vma->vm_start;
		error = -EINVAL;
		if (!(vma->vm_flags & VM_GROWSDOWN))
			goto out;
	}
	else {
		if (vma->vm_start > start)
			goto out;
		if (unlikely(grows & PROT_GROWSUP)) {
			end = vma->vm_end;
			error = -EINVAL;
			if (!(vma->vm_flags & VM_GROWSUP))
				goto out;
		}
	}
242 243
	if (start > vma->vm_start)
		prev = vma;
Linus Torvalds's avatar
Linus Torvalds committed
244 245 246 247 248 249

	for (nstart = start ; ; ) {
		unsigned int newflags;

		/* Here we know that  vma->vm_start <= nstart < vma->vm_end. */

Andrew Morton's avatar
Andrew Morton committed
250 251 252 253 254
		if (is_vm_hugetlb_page(vma)) {
			error = -EACCES;
			goto out;
		}

255 256
		newflags = vm_flags | (vma->vm_flags & ~(VM_READ | VM_WRITE | VM_EXEC));

Linus Torvalds's avatar
Linus Torvalds committed
257 258
		if ((newflags & ~(newflags >> 4)) & 0xf) {
			error = -EACCES;
Linus Torvalds's avatar
Linus Torvalds committed
259
			goto out;
Linus Torvalds's avatar
Linus Torvalds committed
260 261
		}

262 263
		error = security_file_mprotect(vma, prot);
		if (error)
264 265
			goto out;

Linus Torvalds's avatar
Linus Torvalds committed
266
		tmp = vma->vm_end;
267 268
		if (tmp > end)
			tmp = end;
Linus Torvalds's avatar
Linus Torvalds committed
269
		error = mprotect_fixup(vma, &prev, nstart, tmp, newflags);
Linus Torvalds's avatar
Linus Torvalds committed
270
		if (error)
Linus Torvalds's avatar
Linus Torvalds committed
271
			goto out;
Linus Torvalds's avatar
Linus Torvalds committed
272
		nstart = tmp;
273 274 275 276 277 278 279

		if (nstart < prev->vm_end)
			nstart = prev->vm_end;
		if (nstart >= end)
			goto out;

		vma = prev->vm_next;
Linus Torvalds's avatar
Linus Torvalds committed
280
		if (!vma || vma->vm_start != nstart) {
281
			error = -ENOMEM;
Linus Torvalds's avatar
Linus Torvalds committed
282
			goto out;
Linus Torvalds's avatar
Linus Torvalds committed
283 284 285
		}
	}
out:
Linus Torvalds's avatar
Linus Torvalds committed
286
	up_write(&current->mm->mmap_sem);
Linus Torvalds's avatar
Linus Torvalds committed
287 288
	return error;
}