diff options
author | Greg Thelen <gthelen@google.com> | 2013-11-21 17:32:00 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2013-11-21 19:42:27 -0500 |
commit | a399b29dfbaaaf91162b2dc5a5875dd51bbfa2a1 (patch) | |
tree | dfff401b36b593b284cd21e4051ad7a23a944845 /ipc | |
parent | 30b0a105d9f7141e4cbf72ae5511832457d89788 (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.c | 28 |
1 files changed, 23 insertions, 5 deletions
@@ -208,15 +208,18 @@ static void shm_open(struct vm_area_struct *vma) | |||
208 | */ | 208 | */ |
209 | static void shm_destroy(struct ipc_namespace *ns, struct shmid_kernel *shp) | 209 | static 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++; |