Blame view

mm/mprotect.c 15.2 KB
b24413180   Greg Kroah-Hartman   License cleanup: ...
1
  // SPDX-License-Identifier: GPL-2.0
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
2
3
4
5
6
7
  /*
   *  mm/mprotect.c
   *
   *  (C) Copyright 1994 Linus Torvalds
   *  (C) Copyright 2002 Christoph Hellwig
   *
046c68842   Alan Cox   mm: update my add...
8
   *  Address space accounting code	<alan@lxorguk.ukuu.org.uk>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
9
10
11
12
13
   *  (C) Copyright 2002 Red Hat Inc, All Rights Reserved
   */
  
  #include <linux/mm.h>
  #include <linux/hugetlb.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
14
15
16
17
18
19
20
21
  #include <linux/shm.h>
  #include <linux/mman.h>
  #include <linux/fs.h>
  #include <linux/highmem.h>
  #include <linux/security.h>
  #include <linux/mempolicy.h>
  #include <linux/personality.h>
  #include <linux/syscalls.h>
0697212a4   Christoph Lameter   [PATCH] Swapless ...
22
23
  #include <linux/swap.h>
  #include <linux/swapops.h>
cddb8a5c1   Andrea Arcangeli   mmu-notifiers: core
24
  #include <linux/mmu_notifier.h>
64cdd548f   KOSAKI Motohiro   mm: cleanup: remo...
25
  #include <linux/migrate.h>
cdd6c482c   Ingo Molnar   perf: Do the big ...
26
  #include <linux/perf_event.h>
e8c24d3a2   Dave Hansen   x86/pkeys: Alloca...
27
  #include <linux/pkeys.h>
64a9a34e2   Mel Gorman   mm: numa: do not ...
28
  #include <linux/ksm.h>
7c0f6ba68   Linus Torvalds   Replace <asm/uacc...
29
  #include <linux/uaccess.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
30
31
  #include <asm/pgtable.h>
  #include <asm/cacheflush.h>
e8c24d3a2   Dave Hansen   x86/pkeys: Alloca...
32
  #include <asm/mmu_context.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
33
  #include <asm/tlbflush.h>
36f881883   Kirill A. Shutemov   mm: fix mprotect(...
34
  #include "internal.h"
4b10e7d56   Mel Gorman   mm: mempolicy: Im...
35
  static unsigned long change_pte_range(struct vm_area_struct *vma, pmd_t *pmd,
c1e6098b2   Peter Zijlstra   [PATCH] mm: optim...
36
  		unsigned long addr, unsigned long end, pgprot_t newprot,
0f19c1792   Mel Gorman   mm: numa: Do not ...
37
  		int dirty_accountable, int prot_numa)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
38
  {
4b10e7d56   Mel Gorman   mm: mempolicy: Im...
39
  	struct mm_struct *mm = vma->vm_mm;
0697212a4   Christoph Lameter   [PATCH] Swapless ...
40
  	pte_t *pte, oldpte;
705e87c0c   Hugh Dickins   [PATCH] mm: pte_o...
41
  	spinlock_t *ptl;
7da4d641c   Peter Zijlstra   mm: Count the num...
42
  	unsigned long pages = 0;
3e3215876   Andi Kleen   mm/mprotect.c: do...
43
  	int target_node = NUMA_NO_NODE;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
44

175ad4f1e   Andrea Arcangeli   mm: mprotect: use...
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
  	/*
  	 * Can be called with only the mmap_sem for reading by
  	 * prot_numa so we must check the pmd isn't constantly
  	 * changing from under us from pmd_none to pmd_trans_huge
  	 * and/or the other way around.
  	 */
  	if (pmd_trans_unstable(pmd))
  		return 0;
  
  	/*
  	 * The pmd points to a regular pte so the pmd can't change
  	 * from under us even if the mmap_sem is only hold for
  	 * reading.
  	 */
  	pte = pte_offset_map_lock(vma->vm_mm, pmd, addr, &ptl);
1ad9f620c   Mel Gorman   mm: numa: recheck...
60

3e3215876   Andi Kleen   mm/mprotect.c: do...
61
62
63
64
  	/* Get target node for single threaded private VMAs */
  	if (prot_numa && !(vma->vm_flags & VM_SHARED) &&
  	    atomic_read(&vma->vm_mm->mm_users) == 1)
  		target_node = numa_node_id();
3ea277194   Mel Gorman   mm, mprotect: flu...
65
  	flush_tlb_batched_pending(vma->vm_mm);
6606c3e0d   Zachary Amsden   [PATCH] paravirt:...
66
  	arch_enter_lazy_mmu_mode();
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
67
  	do {
0697212a4   Christoph Lameter   [PATCH] Swapless ...
68
69
  		oldpte = *pte;
  		if (pte_present(oldpte)) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
70
  			pte_t ptent;
b191f9b10   Mel Gorman   mm: numa: preserv...
71
  			bool preserve_write = prot_numa && pte_write(oldpte);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
72

e944fd67b   Mel Gorman   mm: numa: do not ...
73
74
75
76
77
78
79
80
81
82
  			/*
  			 * Avoid trapping faults against the zero or KSM
  			 * pages. See similar comment in change_huge_pmd.
  			 */
  			if (prot_numa) {
  				struct page *page;
  
  				page = vm_normal_page(vma, addr, oldpte);
  				if (!page || PageKsm(page))
  					continue;
10c1045f2   Mel Gorman   mm: numa: avoid u...
83
84
85
86
  
  				/* Avoid TLB flush if possible */
  				if (pte_protnone(oldpte))
  					continue;
3e3215876   Andi Kleen   mm/mprotect.c: do...
87
88
89
90
91
92
93
  
  				/*
  				 * Don't mess with PTEs if page is already on the node
  				 * a single-threaded process is running on.
  				 */
  				if (target_node == page_to_nid(page))
  					continue;
e944fd67b   Mel Gorman   mm: numa: do not ...
94
  			}
8a0516ed8   Mel Gorman   mm: convert p[te|...
95
96
  			ptent = ptep_modify_prot_start(mm, addr, pte);
  			ptent = pte_modify(ptent, newprot);
b191f9b10   Mel Gorman   mm: numa: preserv...
97
  			if (preserve_write)
288bc5494   Aneesh Kumar K.V   mm/autonuma: let ...
98
  				ptent = pte_mk_savedwrite(ptent);
4b10e7d56   Mel Gorman   mm: mempolicy: Im...
99

8a0516ed8   Mel Gorman   mm: convert p[te|...
100
101
102
103
104
  			/* Avoid taking write faults for known dirty pages */
  			if (dirty_accountable && pte_dirty(ptent) &&
  					(pte_soft_dirty(ptent) ||
  					 !(vma->vm_flags & VM_SOFTDIRTY))) {
  				ptent = pte_mkwrite(ptent);
4b10e7d56   Mel Gorman   mm: mempolicy: Im...
105
  			}
8a0516ed8   Mel Gorman   mm: convert p[te|...
106
107
  			ptep_modify_prot_commit(mm, addr, pte, ptent);
  			pages++;
0661a3361   Kirill A. Shutemov   mm: remove rest u...
108
  		} else if (IS_ENABLED(CONFIG_MIGRATION)) {
0697212a4   Christoph Lameter   [PATCH] Swapless ...
109
110
111
  			swp_entry_t entry = pte_to_swp_entry(oldpte);
  
  			if (is_write_migration_entry(entry)) {
c3d16e165   Cyrill Gorcunov   mm: migration: do...
112
  				pte_t newpte;
0697212a4   Christoph Lameter   [PATCH] Swapless ...
113
114
115
116
117
  				/*
  				 * A protection check is difficult so
  				 * just be safe and disable write
  				 */
  				make_migration_entry_read(&entry);
c3d16e165   Cyrill Gorcunov   mm: migration: do...
118
119
120
121
  				newpte = swp_entry_to_pte(entry);
  				if (pte_swp_soft_dirty(oldpte))
  					newpte = pte_swp_mksoft_dirty(newpte);
  				set_pte_at(mm, addr, pte, newpte);
e920e14ca   Mel Gorman   mm: Do not flush ...
122
123
  
  				pages++;
0697212a4   Christoph Lameter   [PATCH] Swapless ...
124
  			}
5042db43c   Jérôme Glisse   mm/ZONE_DEVICE: n...
125
126
127
128
129
130
131
132
133
134
135
136
137
138
  
  			if (is_write_device_private_entry(entry)) {
  				pte_t newpte;
  
  				/*
  				 * We do not preserve soft-dirtiness. See
  				 * copy_one_pte() for explanation.
  				 */
  				make_device_private_entry_read(&entry);
  				newpte = swp_entry_to_pte(entry);
  				set_pte_at(mm, addr, pte, newpte);
  
  				pages++;
  			}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
139
140
  		}
  	} while (pte++, addr += PAGE_SIZE, addr != end);
6606c3e0d   Zachary Amsden   [PATCH] paravirt:...
141
  	arch_leave_lazy_mmu_mode();
705e87c0c   Hugh Dickins   [PATCH] mm: pte_o...
142
  	pte_unmap_unlock(pte - 1, ptl);
7da4d641c   Peter Zijlstra   mm: Count the num...
143
144
  
  	return pages;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
145
  }
7d12efaea   Andrew Morton   mm/mprotect.c: co...
146
147
148
  static inline unsigned long change_pmd_range(struct vm_area_struct *vma,
  		pud_t *pud, unsigned long addr, unsigned long end,
  		pgprot_t newprot, int dirty_accountable, int prot_numa)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
149
150
  {
  	pmd_t *pmd;
a5338093b   Rik van Riel   mm: move mmu noti...
151
  	struct mm_struct *mm = vma->vm_mm;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
152
  	unsigned long next;
7da4d641c   Peter Zijlstra   mm: Count the num...
153
  	unsigned long pages = 0;
72403b4a0   Mel Gorman   mm: numa: return ...
154
  	unsigned long nr_huge_updates = 0;
a5338093b   Rik van Riel   mm: move mmu noti...
155
  	unsigned long mni_start = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
156
157
158
  
  	pmd = pmd_offset(pud, addr);
  	do {
25cbbef19   Mel Gorman   mm: numa: Trap pm...
159
  		unsigned long this_pages;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
160
  		next = pmd_addr_end(addr, end);
84c3fc4e9   Zi Yan   mm: thp: check pm...
161
  		if (!is_swap_pmd(*pmd) && !pmd_trans_huge(*pmd) && !pmd_devmap(*pmd)
5c7fb56e5   Dan Williams   mm, dax: dax-pmd ...
162
  				&& pmd_none_or_clear_bad(pmd))
abcc78627   Anshuman Khandual   mm/mprotect: add ...
163
  			goto next;
a5338093b   Rik van Riel   mm: move mmu noti...
164
165
166
167
168
169
  
  		/* invoke the mmu notifier if the pmd is populated */
  		if (!mni_start) {
  			mni_start = addr;
  			mmu_notifier_invalidate_range_start(mm, mni_start, end);
  		}
84c3fc4e9   Zi Yan   mm: thp: check pm...
170
  		if (is_swap_pmd(*pmd) || pmd_trans_huge(*pmd) || pmd_devmap(*pmd)) {
6b9116a65   Kirill A. Shutemov   mm, dax: check fo...
171
  			if (next - addr != HPAGE_PMD_SIZE) {
fd60775ae   David Rientjes   mm, thp: avoid un...
172
  				__split_huge_pmd(vma, pmd, addr, false, NULL);
6b9116a65   Kirill A. Shutemov   mm, dax: check fo...
173
  			} else {
f123d74ab   Mel Gorman   mm: Only flush TL...
174
  				int nr_ptes = change_huge_pmd(vma, pmd, addr,
e944fd67b   Mel Gorman   mm: numa: do not ...
175
  						newprot, prot_numa);
f123d74ab   Mel Gorman   mm: Only flush TL...
176
177
  
  				if (nr_ptes) {
72403b4a0   Mel Gorman   mm: numa: return ...
178
179
180
181
  					if (nr_ptes == HPAGE_PMD_NR) {
  						pages += HPAGE_PMD_NR;
  						nr_huge_updates++;
  					}
1ad9f620c   Mel Gorman   mm: numa: recheck...
182
183
  
  					/* huge pmd was handled */
abcc78627   Anshuman Khandual   mm/mprotect: add ...
184
  					goto next;
f123d74ab   Mel Gorman   mm: Only flush TL...
185
  				}
7da4d641c   Peter Zijlstra   mm: Count the num...
186
  			}
88a9ab6e3   Rik van Riel   mm,numa: reorgani...
187
  			/* fall through, the trans huge pmd just split */
cd7548ab3   Johannes Weiner   thp: mprotect: tr...
188
  		}
25cbbef19   Mel Gorman   mm: numa: Trap pm...
189
  		this_pages = change_pte_range(vma, pmd, addr, next, newprot,
0f19c1792   Mel Gorman   mm: numa: Do not ...
190
  				 dirty_accountable, prot_numa);
25cbbef19   Mel Gorman   mm: numa: Trap pm...
191
  		pages += this_pages;
abcc78627   Anshuman Khandual   mm/mprotect: add ...
192
193
  next:
  		cond_resched();
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
194
  	} while (pmd++, addr = next, addr != end);
7da4d641c   Peter Zijlstra   mm: Count the num...
195

a5338093b   Rik van Riel   mm: move mmu noti...
196
197
  	if (mni_start)
  		mmu_notifier_invalidate_range_end(mm, mni_start, end);
72403b4a0   Mel Gorman   mm: numa: return ...
198
199
  	if (nr_huge_updates)
  		count_vm_numa_events(NUMA_HUGE_PTE_UPDATES, nr_huge_updates);
7da4d641c   Peter Zijlstra   mm: Count the num...
200
  	return pages;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
201
  }
7d12efaea   Andrew Morton   mm/mprotect.c: co...
202
  static inline unsigned long change_pud_range(struct vm_area_struct *vma,
c2febafc6   Kirill A. Shutemov   mm: convert gener...
203
  		p4d_t *p4d, unsigned long addr, unsigned long end,
7d12efaea   Andrew Morton   mm/mprotect.c: co...
204
  		pgprot_t newprot, int dirty_accountable, int prot_numa)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
205
206
207
  {
  	pud_t *pud;
  	unsigned long next;
7da4d641c   Peter Zijlstra   mm: Count the num...
208
  	unsigned long pages = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
209

c2febafc6   Kirill A. Shutemov   mm: convert gener...
210
  	pud = pud_offset(p4d, addr);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
211
212
213
214
  	do {
  		next = pud_addr_end(addr, end);
  		if (pud_none_or_clear_bad(pud))
  			continue;
7da4d641c   Peter Zijlstra   mm: Count the num...
215
  		pages += change_pmd_range(vma, pud, addr, next, newprot,
4b10e7d56   Mel Gorman   mm: mempolicy: Im...
216
  				 dirty_accountable, prot_numa);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
217
  	} while (pud++, addr = next, addr != end);
7da4d641c   Peter Zijlstra   mm: Count the num...
218
219
  
  	return pages;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
220
  }
c2febafc6   Kirill A. Shutemov   mm: convert gener...
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
  static inline unsigned long change_p4d_range(struct vm_area_struct *vma,
  		pgd_t *pgd, unsigned long addr, unsigned long end,
  		pgprot_t newprot, int dirty_accountable, int prot_numa)
  {
  	p4d_t *p4d;
  	unsigned long next;
  	unsigned long pages = 0;
  
  	p4d = p4d_offset(pgd, addr);
  	do {
  		next = p4d_addr_end(addr, end);
  		if (p4d_none_or_clear_bad(p4d))
  			continue;
  		pages += change_pud_range(vma, p4d, addr, next, newprot,
  				 dirty_accountable, prot_numa);
  	} while (p4d++, addr = next, addr != end);
  
  	return pages;
  }
7da4d641c   Peter Zijlstra   mm: Count the num...
240
  static unsigned long change_protection_range(struct vm_area_struct *vma,
c1e6098b2   Peter Zijlstra   [PATCH] mm: optim...
241
  		unsigned long addr, unsigned long end, pgprot_t newprot,
4b10e7d56   Mel Gorman   mm: mempolicy: Im...
242
  		int dirty_accountable, int prot_numa)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
243
244
245
246
247
  {
  	struct mm_struct *mm = vma->vm_mm;
  	pgd_t *pgd;
  	unsigned long next;
  	unsigned long start = addr;
7da4d641c   Peter Zijlstra   mm: Count the num...
248
  	unsigned long pages = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
249
250
251
252
  
  	BUG_ON(addr >= end);
  	pgd = pgd_offset(mm, addr);
  	flush_cache_range(vma, addr, end);
16af97dc5   Nadav Amit   mm: migrate: prev...
253
  	inc_tlb_flush_pending(mm);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
254
255
256
257
  	do {
  		next = pgd_addr_end(addr, end);
  		if (pgd_none_or_clear_bad(pgd))
  			continue;
c2febafc6   Kirill A. Shutemov   mm: convert gener...
258
  		pages += change_p4d_range(vma, pgd, addr, next, newprot,
4b10e7d56   Mel Gorman   mm: mempolicy: Im...
259
  				 dirty_accountable, prot_numa);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
260
  	} while (pgd++, addr = next, addr != end);
7da4d641c   Peter Zijlstra   mm: Count the num...
261

1233d5882   Ingo Molnar   mm: Optimize the ...
262
263
264
  	/* Only flush the TLB if we actually modified any entries: */
  	if (pages)
  		flush_tlb_range(vma, start, end);
16af97dc5   Nadav Amit   mm: migrate: prev...
265
  	dec_tlb_flush_pending(mm);
7da4d641c   Peter Zijlstra   mm: Count the num...
266
267
268
269
270
271
  
  	return pages;
  }
  
  unsigned long change_protection(struct vm_area_struct *vma, unsigned long start,
  		       unsigned long end, pgprot_t newprot,
4b10e7d56   Mel Gorman   mm: mempolicy: Im...
272
  		       int dirty_accountable, int prot_numa)
7da4d641c   Peter Zijlstra   mm: Count the num...
273
  {
7da4d641c   Peter Zijlstra   mm: Count the num...
274
  	unsigned long pages;
7da4d641c   Peter Zijlstra   mm: Count the num...
275
276
277
  	if (is_vm_hugetlb_page(vma))
  		pages = hugetlb_change_protection(vma, start, end, newprot);
  	else
4b10e7d56   Mel Gorman   mm: mempolicy: Im...
278
  		pages = change_protection_range(vma, start, end, newprot, dirty_accountable, prot_numa);
7da4d641c   Peter Zijlstra   mm: Count the num...
279
280
  
  	return pages;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
281
  }
a4116334b   Andi Kleen   x86/speculation/l...
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
  static int prot_none_pte_entry(pte_t *pte, unsigned long addr,
  			       unsigned long next, struct mm_walk *walk)
  {
  	return pfn_modify_allowed(pte_pfn(*pte), *(pgprot_t *)(walk->private)) ?
  		0 : -EACCES;
  }
  
  static int prot_none_hugetlb_entry(pte_t *pte, unsigned long hmask,
  				   unsigned long addr, unsigned long next,
  				   struct mm_walk *walk)
  {
  	return pfn_modify_allowed(pte_pfn(*pte), *(pgprot_t *)(walk->private)) ?
  		0 : -EACCES;
  }
  
  static int prot_none_test(unsigned long addr, unsigned long next,
  			  struct mm_walk *walk)
  {
  	return 0;
  }
  
  static int prot_none_walk(struct vm_area_struct *vma, unsigned long start,
  			   unsigned long end, unsigned long newflags)
  {
  	pgprot_t new_pgprot = vm_get_page_prot(newflags);
  	struct mm_walk prot_none_walk = {
  		.pte_entry = prot_none_pte_entry,
  		.hugetlb_entry = prot_none_hugetlb_entry,
  		.test_walk = prot_none_test,
  		.mm = current->mm,
  		.private = &new_pgprot,
  	};
  
  	return walk_page_range(start, end, &prot_none_walk);
  }
b6a2fea39   Ollie Wild   mm: variable leng...
317
  int
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
318
319
320
321
322
323
324
  mprotect_fixup(struct vm_area_struct *vma, struct vm_area_struct **pprev,
  	unsigned long start, unsigned long end, unsigned long newflags)
  {
  	struct mm_struct *mm = vma->vm_mm;
  	unsigned long oldflags = vma->vm_flags;
  	long nrpages = (end - start) >> PAGE_SHIFT;
  	unsigned long charged = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
325
326
  	pgoff_t pgoff;
  	int error;
c1e6098b2   Peter Zijlstra   [PATCH] mm: optim...
327
  	int dirty_accountable = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
328
329
330
331
332
333
334
  
  	if (newflags == oldflags) {
  		*pprev = vma;
  		return 0;
  	}
  
  	/*
a4116334b   Andi Kleen   x86/speculation/l...
335
336
337
338
339
340
341
342
343
344
345
346
347
  	 * Do PROT_NONE PFN permission checks here when we can still
  	 * bail out without undoing a lot of state. This is a rather
  	 * uncommon case, so doesn't need to be very optimized.
  	 */
  	if (arch_has_pfn_modify_check() &&
  	    (vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)) &&
  	    (newflags & (VM_READ|VM_WRITE|VM_EXEC)) == 0) {
  		error = prot_none_walk(vma, start, end, newflags);
  		if (error)
  			return error;
  	}
  
  	/*
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
348
349
  	 * If we make a private mapping writable we increase our commit;
  	 * but (without finer accounting) cannot reduce our commit if we
5a6fe1259   Mel Gorman   Do not account fo...
350
351
  	 * make it unwritable again. hugetlb mapping were accounted for
  	 * even if read-only so there is no need to account for them here
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
352
353
  	 */
  	if (newflags & VM_WRITE) {
846383359   Konstantin Khlebnikov   mm: rework virtua...
354
355
356
357
  		/* Check space limits when area turns into data. */
  		if (!may_expand_vm(mm, newflags, nrpages) &&
  				may_expand_vm(mm, oldflags, nrpages))
  			return -ENOMEM;
5a6fe1259   Mel Gorman   Do not account fo...
358
  		if (!(oldflags & (VM_ACCOUNT|VM_WRITE|VM_HUGETLB|
cdfd4325c   Andy Whitcroft   mm: record MAP_NO...
359
  						VM_SHARED|VM_NORESERVE))) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
360
  			charged = nrpages;
191c54244   Al Viro   mm: collapse secu...
361
  			if (security_vm_enough_memory_mm(mm, charged))
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
362
363
364
365
  				return -ENOMEM;
  			newflags |= VM_ACCOUNT;
  		}
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
366
367
368
369
370
  	/*
  	 * 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,
19a809afe   Andrea Arcangeli   userfaultfd: teac...
371
372
  			   vma->anon_vma, vma->vm_file, pgoff, vma_policy(vma),
  			   vma->vm_userfaultfd_ctx);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
373
374
  	if (*pprev) {
  		vma = *pprev;
e86f15ee6   Andrea Arcangeli   mm: vma_merge: fi...
375
  		VM_WARN_ON((vma->vm_flags ^ newflags) & ~VM_SOFTDIRTY);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
  		goto success;
  	}
  
  	*pprev = vma;
  
  	if (start != vma->vm_start) {
  		error = split_vma(mm, vma, start, 1);
  		if (error)
  			goto fail;
  	}
  
  	if (end != vma->vm_end) {
  		error = split_vma(mm, vma, end, 0);
  		if (error)
  			goto fail;
  	}
  
  success:
  	/*
  	 * vm_flags and vm_page_prot are protected by the mmap_sem
  	 * held in write mode.
  	 */
  	vma->vm_flags = newflags;
6d2329f88   Andrea Arcangeli   mm: vm_page_prot:...
399
  	dirty_accountable = vma_wants_writenotify(vma, vma->vm_page_prot);
64e455079   Peter Feiner   mm: softdirty: en...
400
  	vma_set_page_prot(vma);
d08b3851d   Peter Zijlstra   [PATCH] mm: track...
401

7d12efaea   Andrew Morton   mm/mprotect.c: co...
402
403
  	change_protection(vma, start, end, vma->vm_page_prot,
  			  dirty_accountable, 0);
7da4d641c   Peter Zijlstra   mm: Count the num...
404

36f881883   Kirill A. Shutemov   mm: fix mprotect(...
405
406
407
408
409
410
411
412
  	/*
  	 * Private VM_LOCKED VMA becoming writable: trigger COW to avoid major
  	 * fault on access.
  	 */
  	if ((oldflags & (VM_WRITE | VM_SHARED | VM_LOCKED)) == VM_LOCKED &&
  			(newflags & VM_WRITE)) {
  		populate_vma_page_range(vma, start, end, NULL);
  	}
846383359   Konstantin Khlebnikov   mm: rework virtua...
413
414
  	vm_stat_account(mm, oldflags, -nrpages);
  	vm_stat_account(mm, newflags, nrpages);
63bfd7384   Pekka Enberg   perf_events: Fix ...
415
  	perf_event_mmap(vma);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
416
417
418
419
420
421
  	return 0;
  
  fail:
  	vm_unacct_memory(charged);
  	return error;
  }
7d06d9c9b   Dave Hansen   mm: Implement new...
422
423
424
425
426
  /*
   * pkey==-1 when doing a legacy mprotect()
   */
  static int do_mprotect_pkey(unsigned long start, size_t len,
  		unsigned long prot, int pkey)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
427
  {
62b5f7d01   Dave Hansen   mm/core, x86/mm/p...
428
  	unsigned long nstart, end, tmp, reqprot;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
429
430
431
  	struct vm_area_struct *vma, *prev;
  	int error = -EINVAL;
  	const int grows = prot & (PROT_GROWSDOWN|PROT_GROWSUP);
f138556da   Piotr Kwapulinski   mm/mprotect.c: do...
432
433
  	const bool rier = (current->personality & READ_IMPLIES_EXEC) &&
  				(prot & PROT_READ);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
434
435
436
437
438
439
440
441
442
443
444
445
  	prot &= ~(PROT_GROWSDOWN|PROT_GROWSUP);
  	if (grows == (PROT_GROWSDOWN|PROT_GROWSUP)) /* can't be both */
  		return -EINVAL;
  
  	if (start & ~PAGE_MASK)
  		return -EINVAL;
  	if (!len)
  		return 0;
  	len = PAGE_ALIGN(len);
  	end = start + len;
  	if (end <= start)
  		return -ENOMEM;
b845f313d   Dave Kleikamp   mm: Allow archite...
446
  	if (!arch_validate_prot(prot))
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
447
448
449
  		return -EINVAL;
  
  	reqprot = prot;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
450

dc0ef0df7   Michal Hocko   mm: make mmap_sem...
451
452
  	if (down_write_killable(&current->mm->mmap_sem))
  		return -EINTR;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
453

e8c24d3a2   Dave Hansen   x86/pkeys: Alloca...
454
455
456
457
458
459
460
  	/*
  	 * If userspace did not allocate the pkey, do not let
  	 * them use it here.
  	 */
  	error = -EINVAL;
  	if ((pkey != -1) && !mm_pkey_is_allocated(current->mm, pkey))
  		goto out;
097d59106   Linus Torvalds   vm: avoid using f...
461
  	vma = find_vma(current->mm, start);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
462
463
464
  	error = -ENOMEM;
  	if (!vma)
  		goto out;
097d59106   Linus Torvalds   vm: avoid using f...
465
  	prev = vma->vm_prev;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
466
467
468
469
470
471
472
  	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;
7d12efaea   Andrew Morton   mm/mprotect.c: co...
473
  	} else {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
474
475
476
477
478
479
480
481
482
483
484
485
486
  		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;
  		}
  	}
  	if (start > vma->vm_start)
  		prev = vma;
  
  	for (nstart = start ; ; ) {
a8502b67d   Dave Hansen   x86/pkeys: Make m...
487
  		unsigned long mask_off_old_flags;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
488
  		unsigned long newflags;
7d06d9c9b   Dave Hansen   mm: Implement new...
489
  		int new_vma_pkey;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
490

7d12efaea   Andrew Morton   mm/mprotect.c: co...
491
  		/* Here we know that vma->vm_start <= nstart < vma->vm_end. */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
492

f138556da   Piotr Kwapulinski   mm/mprotect.c: do...
493
494
495
  		/* Does the application expect PROT_READ to imply PROT_EXEC */
  		if (rier && (vma->vm_flags & VM_MAYEXEC))
  			prot |= PROT_EXEC;
a8502b67d   Dave Hansen   x86/pkeys: Make m...
496
497
498
499
500
501
502
  		/*
  		 * Each mprotect() call explicitly passes r/w/x permissions.
  		 * If a permission is not passed to mprotect(), it must be
  		 * cleared from the VMA.
  		 */
  		mask_off_old_flags = VM_READ | VM_WRITE | VM_EXEC |
  					ARCH_VM_PKEY_FLAGS;
7d06d9c9b   Dave Hansen   mm: Implement new...
503
504
  		new_vma_pkey = arch_override_mprotect_pkey(vma, prot, pkey);
  		newflags = calc_vm_prot_bits(prot, new_vma_pkey);
a8502b67d   Dave Hansen   x86/pkeys: Make m...
505
  		newflags |= (vma->vm_flags & ~mask_off_old_flags);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
506

7e2cff42c   Paolo 'Blaisorblade' Giarrusso   [PATCH] mm: add a...
507
508
  		/* newflags >> 4 shift VM_MAY% in place of VM_% */
  		if ((newflags & ~(newflags >> 4)) & (VM_READ | VM_WRITE | VM_EXEC)) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
  			error = -EACCES;
  			goto out;
  		}
  
  		error = security_file_mprotect(vma, reqprot, prot);
  		if (error)
  			goto out;
  
  		tmp = vma->vm_end;
  		if (tmp > end)
  			tmp = end;
  		error = mprotect_fixup(vma, &prev, nstart, tmp, newflags);
  		if (error)
  			goto out;
  		nstart = tmp;
  
  		if (nstart < prev->vm_end)
  			nstart = prev->vm_end;
  		if (nstart >= end)
  			goto out;
  
  		vma = prev->vm_next;
  		if (!vma || vma->vm_start != nstart) {
  			error = -ENOMEM;
  			goto out;
  		}
f138556da   Piotr Kwapulinski   mm/mprotect.c: do...
535
  		prot = reqprot;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
536
537
538
539
540
  	}
  out:
  	up_write(&current->mm->mmap_sem);
  	return error;
  }
7d06d9c9b   Dave Hansen   mm: Implement new...
541
542
543
544
545
546
  
  SYSCALL_DEFINE3(mprotect, unsigned long, start, size_t, len,
  		unsigned long, prot)
  {
  	return do_mprotect_pkey(start, len, prot, -1);
  }
c7142aead   Heiko Carstens   mm/pkeys: generat...
547
  #ifdef CONFIG_ARCH_HAS_PKEYS
7d06d9c9b   Dave Hansen   mm: Implement new...
548
549
550
551
552
  SYSCALL_DEFINE4(pkey_mprotect, unsigned long, start, size_t, len,
  		unsigned long, prot, int, pkey)
  {
  	return do_mprotect_pkey(start, len, prot, pkey);
  }
e8c24d3a2   Dave Hansen   x86/pkeys: Alloca...
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
  
  SYSCALL_DEFINE2(pkey_alloc, unsigned long, flags, unsigned long, init_val)
  {
  	int pkey;
  	int ret;
  
  	/* No flags supported yet. */
  	if (flags)
  		return -EINVAL;
  	/* check for unsupported init values */
  	if (init_val & ~PKEY_ACCESS_MASK)
  		return -EINVAL;
  
  	down_write(&current->mm->mmap_sem);
  	pkey = mm_pkey_alloc(current->mm);
  
  	ret = -ENOSPC;
  	if (pkey == -1)
  		goto out;
  
  	ret = arch_set_user_pkey_access(current, pkey, init_val);
  	if (ret) {
  		mm_pkey_free(current->mm, pkey);
  		goto out;
  	}
  	ret = pkey;
  out:
  	up_write(&current->mm->mmap_sem);
  	return ret;
  }
  
  SYSCALL_DEFINE1(pkey_free, int, pkey)
  {
  	int ret;
  
  	down_write(&current->mm->mmap_sem);
  	ret = mm_pkey_free(current->mm, pkey);
  	up_write(&current->mm->mmap_sem);
  
  	/*
  	 * We could provie warnings or errors if any VMA still
  	 * has the pkey set here.
  	 */
  	return ret;
  }
c7142aead   Heiko Carstens   mm/pkeys: generat...
598
599
  
  #endif /* CONFIG_ARCH_HAS_PKEYS */