diff options
Diffstat (limited to 'mm/mremap.c')
-rw-r--r-- | mm/mremap.c | 249 |
1 files changed, 161 insertions, 88 deletions
diff --git a/mm/mremap.c b/mm/mremap.c index 97bff2547719..cde56ee51ef7 100644 --- a/mm/mremap.c +++ b/mm/mremap.c | |||
@@ -9,7 +9,6 @@ | |||
9 | 9 | ||
10 | #include <linux/mm.h> | 10 | #include <linux/mm.h> |
11 | #include <linux/hugetlb.h> | 11 | #include <linux/hugetlb.h> |
12 | #include <linux/slab.h> | ||
13 | #include <linux/shm.h> | 12 | #include <linux/shm.h> |
14 | #include <linux/ksm.h> | 13 | #include <linux/ksm.h> |
15 | #include <linux/mman.h> | 14 | #include <linux/mman.h> |
@@ -261,6 +260,137 @@ static unsigned long move_vma(struct vm_area_struct *vma, | |||
261 | return new_addr; | 260 | return new_addr; |
262 | } | 261 | } |
263 | 262 | ||
263 | static struct vm_area_struct *vma_to_resize(unsigned long addr, | ||
264 | unsigned long old_len, unsigned long new_len, unsigned long *p) | ||
265 | { | ||
266 | struct mm_struct *mm = current->mm; | ||
267 | struct vm_area_struct *vma = find_vma(mm, addr); | ||
268 | |||
269 | if (!vma || vma->vm_start > addr) | ||
270 | goto Efault; | ||
271 | |||
272 | if (is_vm_hugetlb_page(vma)) | ||
273 | goto Einval; | ||
274 | |||
275 | /* We can't remap across vm area boundaries */ | ||
276 | if (old_len > vma->vm_end - addr) | ||
277 | goto Efault; | ||
278 | |||
279 | if (vma->vm_flags & (VM_DONTEXPAND | VM_PFNMAP)) { | ||
280 | if (new_len > old_len) | ||
281 | goto Efault; | ||
282 | } | ||
283 | |||
284 | if (vma->vm_flags & VM_LOCKED) { | ||
285 | unsigned long locked, lock_limit; | ||
286 | locked = mm->locked_vm << PAGE_SHIFT; | ||
287 | lock_limit = rlimit(RLIMIT_MEMLOCK); | ||
288 | locked += new_len - old_len; | ||
289 | if (locked > lock_limit && !capable(CAP_IPC_LOCK)) | ||
290 | goto Eagain; | ||
291 | } | ||
292 | |||
293 | if (!may_expand_vm(mm, (new_len - old_len) >> PAGE_SHIFT)) | ||
294 | goto Enomem; | ||
295 | |||
296 | if (vma->vm_flags & VM_ACCOUNT) { | ||
297 | unsigned long charged = (new_len - old_len) >> PAGE_SHIFT; | ||
298 | if (security_vm_enough_memory(charged)) | ||
299 | goto Efault; | ||
300 | *p = charged; | ||
301 | } | ||
302 | |||
303 | return vma; | ||
304 | |||
305 | Efault: /* very odd choice for most of the cases, but... */ | ||
306 | return ERR_PTR(-EFAULT); | ||
307 | Einval: | ||
308 | return ERR_PTR(-EINVAL); | ||
309 | Enomem: | ||
310 | return ERR_PTR(-ENOMEM); | ||
311 | Eagain: | ||
312 | return ERR_PTR(-EAGAIN); | ||
313 | } | ||
314 | |||
315 | static unsigned long mremap_to(unsigned long addr, | ||
316 | unsigned long old_len, unsigned long new_addr, | ||
317 | unsigned long new_len) | ||
318 | { | ||
319 | struct mm_struct *mm = current->mm; | ||
320 | struct vm_area_struct *vma; | ||
321 | unsigned long ret = -EINVAL; | ||
322 | unsigned long charged = 0; | ||
323 | unsigned long map_flags; | ||
324 | |||
325 | if (new_addr & ~PAGE_MASK) | ||
326 | goto out; | ||
327 | |||
328 | if (new_len > TASK_SIZE || new_addr > TASK_SIZE - new_len) | ||
329 | goto out; | ||
330 | |||
331 | /* Check if the location we're moving into overlaps the | ||
332 | * old location at all, and fail if it does. | ||
333 | */ | ||
334 | if ((new_addr <= addr) && (new_addr+new_len) > addr) | ||
335 | goto out; | ||
336 | |||
337 | if ((addr <= new_addr) && (addr+old_len) > new_addr) | ||
338 | goto out; | ||
339 | |||
340 | ret = security_file_mmap(NULL, 0, 0, 0, new_addr, 1); | ||
341 | if (ret) | ||
342 | goto out; | ||
343 | |||
344 | ret = do_munmap(mm, new_addr, new_len); | ||
345 | if (ret) | ||
346 | goto out; | ||
347 | |||
348 | if (old_len >= new_len) { | ||
349 | ret = do_munmap(mm, addr+new_len, old_len - new_len); | ||
350 | if (ret && old_len != new_len) | ||
351 | goto out; | ||
352 | old_len = new_len; | ||
353 | } | ||
354 | |||
355 | vma = vma_to_resize(addr, old_len, new_len, &charged); | ||
356 | if (IS_ERR(vma)) { | ||
357 | ret = PTR_ERR(vma); | ||
358 | goto out; | ||
359 | } | ||
360 | |||
361 | map_flags = MAP_FIXED; | ||
362 | if (vma->vm_flags & VM_MAYSHARE) | ||
363 | map_flags |= MAP_SHARED; | ||
364 | |||
365 | ret = get_unmapped_area(vma->vm_file, new_addr, new_len, vma->vm_pgoff + | ||
366 | ((addr - vma->vm_start) >> PAGE_SHIFT), | ||
367 | map_flags); | ||
368 | if (ret & ~PAGE_MASK) | ||
369 | goto out1; | ||
370 | |||
371 | ret = move_vma(vma, addr, old_len, new_len, new_addr); | ||
372 | if (!(ret & ~PAGE_MASK)) | ||
373 | goto out; | ||
374 | out1: | ||
375 | vm_unacct_memory(charged); | ||
376 | |||
377 | out: | ||
378 | return ret; | ||
379 | } | ||
380 | |||
381 | static int vma_expandable(struct vm_area_struct *vma, unsigned long delta) | ||
382 | { | ||
383 | unsigned long end = vma->vm_end + delta; | ||
384 | if (end < vma->vm_end) /* overflow */ | ||
385 | return 0; | ||
386 | if (vma->vm_next && vma->vm_next->vm_start < end) /* intersection */ | ||
387 | return 0; | ||
388 | if (get_unmapped_area(NULL, vma->vm_start, end - vma->vm_start, | ||
389 | 0, MAP_FIXED) & ~PAGE_MASK) | ||
390 | return 0; | ||
391 | return 1; | ||
392 | } | ||
393 | |||
264 | /* | 394 | /* |
265 | * Expand (or shrink) an existing mapping, potentially moving it at the | 395 | * Expand (or shrink) an existing mapping, potentially moving it at the |
266 | * same time (controlled by the MREMAP_MAYMOVE flag and available VM space) | 396 | * same time (controlled by the MREMAP_MAYMOVE flag and available VM space) |
@@ -294,32 +424,10 @@ unsigned long do_mremap(unsigned long addr, | |||
294 | if (!new_len) | 424 | if (!new_len) |
295 | goto out; | 425 | goto out; |
296 | 426 | ||
297 | /* new_addr is only valid if MREMAP_FIXED is specified */ | ||
298 | if (flags & MREMAP_FIXED) { | 427 | if (flags & MREMAP_FIXED) { |
299 | if (new_addr & ~PAGE_MASK) | 428 | if (flags & MREMAP_MAYMOVE) |
300 | goto out; | 429 | ret = mremap_to(addr, old_len, new_addr, new_len); |
301 | if (!(flags & MREMAP_MAYMOVE)) | 430 | goto out; |
302 | goto out; | ||
303 | |||
304 | if (new_len > TASK_SIZE || new_addr > TASK_SIZE - new_len) | ||
305 | goto out; | ||
306 | |||
307 | /* Check if the location we're moving into overlaps the | ||
308 | * old location at all, and fail if it does. | ||
309 | */ | ||
310 | if ((new_addr <= addr) && (new_addr+new_len) > addr) | ||
311 | goto out; | ||
312 | |||
313 | if ((addr <= new_addr) && (addr+old_len) > new_addr) | ||
314 | goto out; | ||
315 | |||
316 | ret = security_file_mmap(NULL, 0, 0, 0, new_addr, 1); | ||
317 | if (ret) | ||
318 | goto out; | ||
319 | |||
320 | ret = do_munmap(mm, new_addr, new_len); | ||
321 | if (ret) | ||
322 | goto out; | ||
323 | } | 431 | } |
324 | 432 | ||
325 | /* | 433 | /* |
@@ -332,64 +440,30 @@ unsigned long do_mremap(unsigned long addr, | |||
332 | if (ret && old_len != new_len) | 440 | if (ret && old_len != new_len) |
333 | goto out; | 441 | goto out; |
334 | ret = addr; | 442 | ret = addr; |
335 | if (!(flags & MREMAP_FIXED) || (new_addr == addr)) | 443 | goto out; |
336 | goto out; | ||
337 | old_len = new_len; | ||
338 | } | 444 | } |
339 | 445 | ||
340 | /* | 446 | /* |
341 | * Ok, we need to grow.. or relocate. | 447 | * Ok, we need to grow.. |
342 | */ | 448 | */ |
343 | ret = -EFAULT; | 449 | vma = vma_to_resize(addr, old_len, new_len, &charged); |
344 | vma = find_vma(mm, addr); | 450 | if (IS_ERR(vma)) { |
345 | if (!vma || vma->vm_start > addr) | 451 | ret = PTR_ERR(vma); |
346 | goto out; | ||
347 | if (is_vm_hugetlb_page(vma)) { | ||
348 | ret = -EINVAL; | ||
349 | goto out; | ||
350 | } | ||
351 | /* We can't remap across vm area boundaries */ | ||
352 | if (old_len > vma->vm_end - addr) | ||
353 | goto out; | ||
354 | if (vma->vm_flags & (VM_DONTEXPAND | VM_PFNMAP)) { | ||
355 | if (new_len > old_len) | ||
356 | goto out; | ||
357 | } | ||
358 | if (vma->vm_flags & VM_LOCKED) { | ||
359 | unsigned long locked, lock_limit; | ||
360 | locked = mm->locked_vm << PAGE_SHIFT; | ||
361 | lock_limit = current->signal->rlim[RLIMIT_MEMLOCK].rlim_cur; | ||
362 | locked += new_len - old_len; | ||
363 | ret = -EAGAIN; | ||
364 | if (locked > lock_limit && !capable(CAP_IPC_LOCK)) | ||
365 | goto out; | ||
366 | } | ||
367 | if (!may_expand_vm(mm, (new_len - old_len) >> PAGE_SHIFT)) { | ||
368 | ret = -ENOMEM; | ||
369 | goto out; | 452 | goto out; |
370 | } | 453 | } |
371 | 454 | ||
372 | if (vma->vm_flags & VM_ACCOUNT) { | ||
373 | charged = (new_len - old_len) >> PAGE_SHIFT; | ||
374 | if (security_vm_enough_memory(charged)) | ||
375 | goto out_nc; | ||
376 | } | ||
377 | |||
378 | /* old_len exactly to the end of the area.. | 455 | /* old_len exactly to the end of the area.. |
379 | * And we're not relocating the area. | ||
380 | */ | 456 | */ |
381 | if (old_len == vma->vm_end - addr && | 457 | if (old_len == vma->vm_end - addr) { |
382 | !((flags & MREMAP_FIXED) && (addr != new_addr)) && | ||
383 | (old_len != new_len || !(flags & MREMAP_MAYMOVE))) { | ||
384 | unsigned long max_addr = TASK_SIZE; | ||
385 | if (vma->vm_next) | ||
386 | max_addr = vma->vm_next->vm_start; | ||
387 | /* can we just expand the current mapping? */ | 458 | /* can we just expand the current mapping? */ |
388 | if (max_addr - addr >= new_len) { | 459 | if (vma_expandable(vma, new_len - old_len)) { |
389 | int pages = (new_len - old_len) >> PAGE_SHIFT; | 460 | int pages = (new_len - old_len) >> PAGE_SHIFT; |
390 | 461 | ||
391 | vma_adjust(vma, vma->vm_start, | 462 | if (vma_adjust(vma, vma->vm_start, addr + new_len, |
392 | addr + new_len, vma->vm_pgoff, NULL); | 463 | vma->vm_pgoff, NULL)) { |
464 | ret = -ENOMEM; | ||
465 | goto out; | ||
466 | } | ||
393 | 467 | ||
394 | mm->total_vm += pages; | 468 | mm->total_vm += pages; |
395 | vm_stat_account(mm, vma->vm_flags, vma->vm_file, pages); | 469 | vm_stat_account(mm, vma->vm_flags, vma->vm_file, pages); |
@@ -409,28 +483,27 @@ unsigned long do_mremap(unsigned long addr, | |||
409 | */ | 483 | */ |
410 | ret = -ENOMEM; | 484 | ret = -ENOMEM; |
411 | if (flags & MREMAP_MAYMOVE) { | 485 | if (flags & MREMAP_MAYMOVE) { |
412 | if (!(flags & MREMAP_FIXED)) { | 486 | unsigned long map_flags = 0; |
413 | unsigned long map_flags = 0; | 487 | if (vma->vm_flags & VM_MAYSHARE) |
414 | if (vma->vm_flags & VM_MAYSHARE) | 488 | map_flags |= MAP_SHARED; |
415 | map_flags |= MAP_SHARED; | 489 | |
416 | 490 | new_addr = get_unmapped_area(vma->vm_file, 0, new_len, | |
417 | new_addr = get_unmapped_area(vma->vm_file, 0, new_len, | 491 | vma->vm_pgoff + |
418 | vma->vm_pgoff, map_flags); | 492 | ((addr - vma->vm_start) >> PAGE_SHIFT), |
419 | if (new_addr & ~PAGE_MASK) { | 493 | map_flags); |
420 | ret = new_addr; | 494 | if (new_addr & ~PAGE_MASK) { |
421 | goto out; | 495 | ret = new_addr; |
422 | } | 496 | goto out; |
423 | |||
424 | ret = security_file_mmap(NULL, 0, 0, 0, new_addr, 1); | ||
425 | if (ret) | ||
426 | goto out; | ||
427 | } | 497 | } |
498 | |||
499 | ret = security_file_mmap(NULL, 0, 0, 0, new_addr, 1); | ||
500 | if (ret) | ||
501 | goto out; | ||
428 | ret = move_vma(vma, addr, old_len, new_len, new_addr); | 502 | ret = move_vma(vma, addr, old_len, new_len, new_addr); |
429 | } | 503 | } |
430 | out: | 504 | out: |
431 | if (ret & ~PAGE_MASK) | 505 | if (ret & ~PAGE_MASK) |
432 | vm_unacct_memory(charged); | 506 | vm_unacct_memory(charged); |
433 | out_nc: | ||
434 | return ret; | 507 | return ret; |
435 | } | 508 | } |
436 | 509 | ||