1
2
3
4
5
6#include <linux/slab.h>
7#include <linux/smp_lock.h>
8#include <linux/shm.h>
9#include <linux/mman.h>
10
11#include <asm/uaccess.h>
12#include <asm/pgtable.h>
13
14static inline void change_pte_range(pmd_t * pmd, unsigned long address,
15 unsigned long size, pgprot_t newprot)
16{
17 pte_t * pte;
18 unsigned long end;
19
20 if (pmd_none(*pmd))
21 return;
22 if (pmd_bad(*pmd)) {
23 printk("change_pte_range: bad pmd (%08lx)\n", pmd_val(*pmd));
24 pmd_clear(pmd);
25 return;
26 }
27 pte = pte_offset(pmd, address);
28 address &= ~PMD_MASK;
29 end = address + size;
30 if (end > PMD_SIZE)
31 end = PMD_SIZE;
32 do {
33 pte_t entry = *pte;
34 if (pte_present(entry))
35 set_pte(pte, pte_modify(entry, newprot));
36 address += PAGE_SIZE;
37 pte++;
38 } while (address < end);
39}
40
41static inline void change_pmd_range(pgd_t * pgd, unsigned long address,
42 unsigned long size, pgprot_t newprot)
43{
44 pmd_t * pmd;
45 unsigned long end;
46
47 if (pgd_none(*pgd))
48 return;
49 if (pgd_bad(*pgd)) {
50 printk("change_pmd_range: bad pgd (%08lx)\n", pgd_val(*pgd));
51 pgd_clear(pgd);
52 return;
53 }
54 pmd = pmd_offset(pgd, address);
55 address &= ~PGDIR_MASK;
56 end = address + size;
57 if (end > PGDIR_SIZE)
58 end = PGDIR_SIZE;
59 do {
60 change_pte_range(pmd, address, end - address, newprot);
61 address = (address + PMD_SIZE) & PMD_MASK;
62 pmd++;
63 } while (address < end);
64}
65
66static void change_protection(unsigned long start, unsigned long end, pgprot_t newprot)
67{
68 pgd_t *dir;
69 unsigned long beg = start;
70
71 dir = pgd_offset(current->mm, start);
72 flush_cache_range(current->mm, beg, end);
73 while (start < end) {
74 change_pmd_range(dir, start, end - start, newprot);
75 start = (start + PGDIR_SIZE) & PGDIR_MASK;
76 dir++;
77 }
78 flush_tlb_range(current->mm, beg, end);
79 return;
80}
81
82static inline int mprotect_fixup_all(struct vm_area_struct * vma,
83 int newflags, pgprot_t prot)
84{
85 vma->vm_flags = newflags;
86 vma->vm_page_prot = prot;
87 return 0;
88}
89
90static inline int mprotect_fixup_start(struct vm_area_struct * vma,
91 unsigned long end,
92 int newflags, pgprot_t prot)
93{
94 struct vm_area_struct * n;
95
96 n = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
97 if (!n)
98 return -ENOMEM;
99 *n = *vma;
100 vma->vm_start = end;
101 n->vm_end = end;
102 vma->vm_offset += vma->vm_start - n->vm_start;
103 n->vm_flags = newflags;
104 n->vm_page_prot = prot;
105 if (n->vm_file)
106 n->vm_file->f_count++;
107 if (n->vm_ops && n->vm_ops->open)
108 n->vm_ops->open(n);
109 insert_vm_struct(current->mm, n);
110 return 0;
111}
112
113static inline int mprotect_fixup_end(struct vm_area_struct * vma,
114 unsigned long start,
115 int newflags, pgprot_t prot)
116{
117 struct vm_area_struct * n;
118
119 n = kmem_cache_alloc(vm_area_cachep, GFP_KERNEL);
120 if (!n)
121 return -ENOMEM;
122 *n = *vma;
123 vma->vm_end = start;
124 n->vm_start = start;
125 n->vm_offset += n->vm_start - vma->vm_start;
126 n->vm_flags = newflags;
127 n->vm_page_prot = prot;
128 if (n->vm_file)
129 n->vm_file->f_count++;
130 if (n->vm_ops && n->vm_ops->open)
131 n->vm_ops->open(n);
132 insert_vm_struct(current->mm, n);
133 return 0;
134}
135
136static inline int mprotect_fixup_middle(struct vm_area_struct * vma,
137 unsigned long start, unsigned long end,
138 int newflags, pgprot_t prot)
139{
140 struct vm_area_struct * left, * right;
141
142 left = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
143 if (!left)
144 return -ENOMEM;
145 right = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
146 if (!right) {
147 kmem_cache_free(vm_area_cachep, left);
148 return -ENOMEM;
149 }
150 *left = *vma;
151 *right = *vma;
152 left->vm_end = start;
153 vma->vm_start = start;
154 vma->vm_end = end;
155 right->vm_start = end;
156 vma->vm_offset += vma->vm_start - left->vm_start;
157 right->vm_offset += right->vm_start - left->vm_start;
158 vma->vm_flags = newflags;
159 vma->vm_page_prot = prot;
160 if (vma->vm_file)
161 vma->vm_file->f_count += 2;
162 if (vma->vm_ops && vma->vm_ops->open) {
163 vma->vm_ops->open(left);
164 vma->vm_ops->open(right);
165 }
166 insert_vm_struct(current->mm, left);
167 insert_vm_struct(current->mm, right);
168 return 0;
169}
170
171static int mprotect_fixup(struct vm_area_struct * vma,
172 unsigned long start, unsigned long end, unsigned int newflags)
173{
174 pgprot_t newprot;
175 int error;
176
177 if (newflags == vma->vm_flags)
178 return 0;
179 newprot = protection_map[newflags & 0xf];
180 if (start == vma->vm_start) {
181 if (end == vma->vm_end)
182 error = mprotect_fixup_all(vma, newflags, newprot);
183 else
184 error = mprotect_fixup_start(vma, end, newflags, newprot);
185 } else if (end == vma->vm_end)
186 error = mprotect_fixup_end(vma, start, newflags, newprot);
187 else
188 error = mprotect_fixup_middle(vma, start, end, newflags, newprot);
189
190 if (error)
191 return error;
192
193 change_protection(start, end, newprot);
194 return 0;
195}
196
197asmlinkage int sys_mprotect(unsigned long start, size_t len, unsigned long prot)
198{
199 unsigned long nstart, end, tmp;
200 struct vm_area_struct * vma, * next;
201 int error = -EINVAL;
202
203 if (start & ~PAGE_MASK)
204 return -EINVAL;
205 len = (len + ~PAGE_MASK) & PAGE_MASK;
206 end = start + len;
207 if (end < start)
208 return -EINVAL;
209 if (prot & ~(PROT_READ | PROT_WRITE | PROT_EXEC))
210 return -EINVAL;
211 if (end == start)
212 return 0;
213
214 down(¤t->mm->mmap_sem);
215 lock_kernel();
216
217 vma = find_vma(current->mm, start);
218 error = -EFAULT;
219 if (!vma || vma->vm_start > start)
220 goto out;
221
222 for (nstart = start ; ; ) {
223 unsigned int newflags;
224
225
226
227 newflags = prot | (vma->vm_flags & ~(PROT_READ | PROT_WRITE | PROT_EXEC));
228 if ((newflags & ~(newflags >> 4)) & 0xf) {
229 error = -EACCES;
230 break;
231 }
232
233 if (vma->vm_end >= end) {
234 error = mprotect_fixup(vma, nstart, end, newflags);
235 break;
236 }
237
238 tmp = vma->vm_end;
239 next = vma->vm_next;
240 error = mprotect_fixup(vma, nstart, tmp, newflags);
241 if (error)
242 break;
243 nstart = tmp;
244 vma = next;
245 if (!vma || vma->vm_start != nstart) {
246 error = -EFAULT;
247 break;
248 }
249 }
250 merge_segments(current->mm, start, end);
251out:
252 unlock_kernel();
253 up(¤t->mm->mmap_sem);
254 return error;
255}
256