mm: fix overly aggressive shmdt() when calls span multiple segments
PATCH 1/1 mm: fix overly aggressive shmdt() when calls span multiple segments
From: Dave Hansen <[email protected]>
This is a highly-contrived scenario. But, a single shmdt() call can be induced in to unmapping memory from mulitple shm segments. Example code is here:
http://www.sr71.net/~dave/intel/shmfun.c
The fix is pretty simple: Record the 'struct file' for the first VMA we encounter and then stick to it. Decline to unmap anything not from the same file and thus the same segment.
I found this by inspection and the odds of anyone hitting this in practice are pretty darn small.
Lightly tested, but it's a pretty small patch.
---
b/ipc/shm.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-)
ipc/shm.c renamed from ipc/shm.c~mm-shmdt-fix-over-aggressive-unmap +13 -5
@@ -1229,6 +1229,7 @@ SYSCALL_DEFINE1(shmdt, char __user *, shint retval = -EINVAL ;#ifdef CONFIG_MMU loff_t size =0 ; +struct file *file;struct vm_area_struct *next;#endif
@@ -1245,7 +1246,8 @@ SYSCALL_DEFINE1(shmdt, char __user *, sh* started at address shmaddr. It records it's size and then unmaps * it. * - Then it unmaps all shm vmas that started at shmaddr and that -* are within the initially determined size. +* are within the initially determined size and that are from the +* same shm segment from which we determined the size. * Errors from do_munmap are ignored: the function only fails if * it's called with invalid parameters or if it's called to unmap * a part of a vma. Both calls in this function are for full vmas,
@@ -1271,8 +1273,14 @@ SYSCALL_DEFINE1(shmdt, char __user *, shif ((vma->vm_ops == &shm_vm_ops) && (vma->vm_start - addr)/PAGE_SIZE == vma->vm_pgoff) { - - size =file_inode (vma->vm_file)->i_size; +/* +* Record the file of the shm segment being +* unmapped. With mremap(), someone could place +* page from another segment but with equal offsets +* in the range we are unmapping. +*/ + file = vma->vm_file; + size =file_inode (file)->i_size;do_munmap (mm, vma->vm_start, vma->vm_end - vma->vm_start);/* * We discovered the size of the shm segment, so
@@ -1298,8 +1306,8 @@ SYSCALL_DEFINE1(shmdt, char __user *, sh/* finding a matching vma now does not alter retval */ if ((vma->vm_ops == &shm_vm_ops) && - (vma->vm_start - addr)/PAGE_SIZE == vma->vm_pgoff) - + ((vma->vm_start - addr)/PAGE_SIZE == vma->vm_pgoff) && + (vma->vm_file == file))do_munmap (mm, vma->vm_start, vma->vm_end - vma->vm_start); vma = next; }
_
Andrew Morton
maintainerQuoted 8 lines · On Mon, 03 Nov 2014 16:06:33 -0800 Dave Hansen <[email protected]> wrote:
From: Dave Hansen <[email protected]>
This is a highly-contrived scenario. But, a single
shmdt()call can be induced in to unmapping memory from mulitple shm segments. Example code is here:http://www.sr71.net/~dave/intel/shmfun.c
Could be preserved in tools/testing/selftests/ipc/
Quoted 35 lines
The fix is pretty simple: Record the 'struct file' for the first VMA we encounter and then stick to it. Decline to unmap anything not from the same file and thus the same segment.
I found this by inspection and the odds of anyone hitting this in practice are pretty darn small.
Lightly tested, but it's a pretty small patch.
...
ipc/shm.c renamed from ipc/shm.c~mm-shmdt-fix-over-aggressive-unmap +3
-1@@ -1229,6 +1229,7 @@ SYSCALL_DEFINE1(shmdt, char __user *, shint retval = -EINVAL ;#ifdef CONFIG_MMU loff_t size =0 ; +struct file *file;struct vm_area_struct *next;#endif @@ -1245,7 +1246,8 @@ SYSCALL_DEFINE1(shmdt, char __user *, sh* started at address shmaddr. It records it's size and then unmaps * it. * - Then it unmaps all shm vmas that started at shmaddr and that -* are within the initially determined size. +* are within the initially determined size and that are from the +* same shm segment from which we determined the size. * Errors from do_munmap are ignored: the function only fails if * it's called with invalid parameters or if it's called to unmap * a part of a vma. Both calls in this function are for full vmas, @@ -1271,8 +1273,14 @@ SYSCALL_DEFINE1(shmdt, char __user *, shif ((vma->vm_ops == &shm_vm_ops) && (vma->vm_start - addr)/PAGE_SIZE == vma->vm_pgoff) {
ipc/shm.c SYSCALL_DEFINE1(shmdt, char __user *, sh · line 1283
Quoted 10 lines
ipc/shm.c renamed from ipc/shm.c~mm-shmdt-fix-over-aggressive-unmap +8
-2- - size =file_inode (vma->vm_file)->i_size; +/* +* Record the file of the shm segment being +* unmapped. With mremap(), someone could place +* page from another segment but with equal offsets +* in the range we are unmapping. +*/ + file = vma->vm_file; + size =file_inode (file)->i_size;
Maybe we should have used i_size_read() here. I don't think i_mutex is held?
ipc/shm.c SYSCALL_DEFINE1(shmdt, char __user *, sh · line 1286
Quoted 3 lines
ipc/shm.c renamed from ipc/shm.c~mm-shmdt-fix-over-aggressive-unmap +0
-0do_munmap (mm, vma->vm_start, vma->vm_end - vma->vm_start);/* * We discovered the size of the shm segment, so
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 8 lines · Dave Hansen
From: Dave Hansen <[email protected]>
This is a highly-contrived scenario. But, a single
shmdt()call can be induced in to unmapping memory from mulitple shm segments. Example code is here:http://www.sr71.net/~dave/intel/shmfun.c
Quoted 8 lines
The fix is pretty simple: Record the 'struct file' for the first VMA we encounter and then stick to it. Decline to unmap anything not from the same file and thus the same segment.
I found this by inspection and the odds of anyone hitting this in practice are pretty darn small.
Lightly tested, but it's a pretty small patch.
Passed shmdt ltp tests, fwiw.
Quoted 1 line
Signed-off-by Dave Hansen <[email protected]>
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>