aboutsummaryrefslogtreecommitdiffstats
path: root/ipc
diff options
context:
space:
mode:
authorGreg Thelen <gthelen@google.com>2013-11-21 17:32:00 -0500
committerLinus Torvalds <torvalds@linux-foundation.org>2013-11-21 19:42:27 -0500
commita399b29dfbaaaf91162b2dc5a5875dd51bbfa2a1 (patch)
treedfff401b36b593b284cd21e4051ad7a23a944845 /ipc
parent30b0a105d9f7141e4cbf72ae5511832457d89788 (diff)
ipc,shm: fix shm_file deletion races
When IPC_RMID races with other shm operations there's potential for use-after-free of the shm object's associated file (shm_file). Here's the race before this patch: TASK 1 TASK 2 ------ ------ shm_rmid() ipc_lock_object() shmctl() shp = shm_obtain_object_check() shm_destroy() shum_unlock() fput(shp->shm_file) ipc_lock_object() shmem_lock(shp->shm_file) <OOPS> The oops is caused because shm_destroy() calls fput() after dropping the ipc_lock. fput() clears the file's f_inode, f_path.dentry, and f_path.mnt, which causes various NULL pointer references in task 2. I reliably see the oops in task 2 if with shmlock, shmu This patch fixes the races by: 1) set shm_file=NULL in shm_destroy() while holding ipc_object_lock(). 2) modify at risk operations to check shm_file while holding ipc_object_lock(). Example workloads, which each trigger oops... Workload 1: while true; do id=$(shmget 1 4096) shm_rmid $id & shmlock $id & wait done The oops stack shows accessing NULL f_inode due to racing fput: _raw_spin_lock shmem_lock SyS_shmctl Workload 2: while true; do id=$(shmget 1 4096) shmat $id 4096 & shm_rmid $id & wait done The oops stack is similar to workload 1 due to NULL f_inode: touch_atime shmem_mmap shm_mmap mmap_region do_mmap_pgoff do_shmat SyS_shmat Workload 3: while true; do id=$(shmget 1 4096) shmlock $id shm_rmid $id & shmunlock $id & wait done The oops stack shows second fput tripping on an NULL f_inode. The first fput() completed via from shm_destroy(), but a racing thread did a get_file() and queued this fput(): locks_remove_flock __fput ____fput task_work_run do_notify_resume int_signal Fixes: c2c737a0461e ("ipc,shm: shorten critical region for shmat") Fixes: 2caacaa82a51 ("ipc,shm: shorten critical region for shmctl") Signed-off-by: Greg Thelen <gthelen@google.com> Cc: Davidlohr Bueso <davidlohr@hp.com> Cc: Rik van Riel <riel@redhat.com> Cc: Manfred Spraul <manfred@colorfullife.com> Cc: <stable@vger.kernel.org> # 3.10.17+ 3.11.6+ Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'ipc')
-rw-r--r--ipc/shm.c28
1 files changed, 23 insertions, 5 deletions
diff --git a/ipc/shm.c b/ipc/shm.c
index d69739610fd4..0bdf21c6814e 100644
--- a/ipc/shm.c
+++ b/ipc/shm.c
@@ -208,15 +208,18 @@ static void shm_open(struct vm_area_struct *vma)
208 */ 208 */
209static void shm_destroy(struct ipc_namespace *ns, struct shmid_kernel *shp) 209static void shm_destroy(struct ipc_namespace *ns, struct shmid_kernel *shp)
210{ 210{
211 struct file *shm_file;
212
213 shm_file = shp->shm_file;
214 shp->shm_file = NULL;
211 ns->shm_tot -= (shp->shm_segsz + PAGE_SIZE - 1) >> PAGE_SHIFT; 215 ns->shm_tot -= (shp->shm_segsz + PAGE_SIZE - 1) >> PAGE_SHIFT;
212 shm_rmid(ns, shp); 216 shm_rmid(ns, shp);
213 shm_unlock(shp); 217 shm_unlock(shp);
214 if (!is_file_hugepages(shp->shm_file)) 218 if (!is_file_hugepages(shm_file))
215 shmem_lock(shp->shm_file, 0, shp->mlock_user); 219 shmem_lock(shm_file, 0, shp->mlock_user);
216 else if (shp->mlock_user) 220 else if (shp->mlock_user)
217 user_shm_unlock(file_inode(shp->shm_file)->i_size, 221 user_shm_unlock(file_inode(shm_file)->i_size, shp->mlock_user);
218 shp->mlock_user); 222 fput(shm_file);
219 fput (shp->shm_file);
220 ipc_rcu_putref(shp, shm_rcu_free); 223 ipc_rcu_putref(shp, shm_rcu_free);
221} 224}
222 225
@@ -983,6 +986,13 @@ SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf)
983 } 986 }
984 987
985 shm_file = shp->shm_file; 988 shm_file = shp->shm_file;
989
990 /* check if shm_destroy() is tearing down shp */
991 if (shm_file == NULL) {
992 err = -EIDRM;
993 goto out_unlock0;
994 }
995
986 if (is_file_hugepages(shm_file)) 996 if (is_file_hugepages(shm_file))
987 goto out_unlock0; 997 goto out_unlock0;
988 998
@@ -1101,6 +1111,14 @@ long do_shmat(int shmid, char __user *shmaddr, int shmflg, ulong *raddr,
1101 goto out_unlock; 1111 goto out_unlock;
1102 1112
1103 ipc_lock_object(&shp->shm_perm); 1113 ipc_lock_object(&shp->shm_perm);
1114
1115 /* check if shm_destroy() is tearing down shp */
1116 if (shp->shm_file == NULL) {
1117 ipc_unlock_object(&shp->shm_perm);
1118 err = -EIDRM;
1119 goto out_unlock;
1120 }
1121
1104 path = shp->shm_file->f_path; 1122 path = shp->shm_file->f_path;
1105 path_get(&path); 1123 path_get(&path);
1106 shp->shm_nattch++; 1124 shp->shm_nattch++;