diff options
author | Rafael Aquini <aquini@redhat.com> | 2014-01-27 20:07:01 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2014-01-28 00:02:39 -0500 |
commit | 0f3d2b0135f4bdbfe47a99753923a64efd373d11 (patch) | |
tree | 4b211b7502e2412aedd94400bdcb012a2b5814b3 /ipc | |
parent | 78f5009cc35eb5e52d276a046d90ee2f41b60f8c (diff) |
ipc: introduce ipc_valid_object() helper to sort out IPC_RMID races
After the locking semantics for the SysV IPC API got improved, a couple
of IPC_RMID race windows were opened because we ended up dropping the
'kern_ipc_perm.deleted' check performed way down in ipc_lock(). The
spotted races got sorted out by re-introducing the old test within the
racy critical sections.
This patch introduces ipc_valid_object() to consolidate the way we cope
with IPC_RMID races by using the same abstraction across the API
implementation.
Signed-off-by: Rafael Aquini <aquini@redhat.com>
Acked-by: Rik van Riel <riel@redhat.com>
Acked-by: Greg Thelen <gthelen@google.com>
Reviewed-by: Davidlohr Bueso <davidlohr@hp.com>
Cc: Manfred Spraul <manfred@colorfullife.com>
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/msg.c | 7 | ||||
-rw-r--r-- | ipc/sem.c | 24 | ||||
-rw-r--r-- | ipc/shm.c | 16 | ||||
-rw-r--r-- | ipc/util.h | 13 |
4 files changed, 41 insertions, 19 deletions
@@ -696,7 +696,7 @@ long do_msgsnd(int msqid, long mtype, void __user *mtext, | |||
696 | goto out_unlock0; | 696 | goto out_unlock0; |
697 | 697 | ||
698 | /* raced with RMID? */ | 698 | /* raced with RMID? */ |
699 | if (msq->q_perm.deleted) { | 699 | if (!ipc_valid_object(&msq->q_perm)) { |
700 | err = -EIDRM; | 700 | err = -EIDRM; |
701 | goto out_unlock0; | 701 | goto out_unlock0; |
702 | } | 702 | } |
@@ -731,7 +731,8 @@ long do_msgsnd(int msqid, long mtype, void __user *mtext, | |||
731 | ipc_lock_object(&msq->q_perm); | 731 | ipc_lock_object(&msq->q_perm); |
732 | 732 | ||
733 | ipc_rcu_putref(msq, ipc_rcu_free); | 733 | ipc_rcu_putref(msq, ipc_rcu_free); |
734 | if (msq->q_perm.deleted) { | 734 | /* raced with RMID? */ |
735 | if (!ipc_valid_object(&msq->q_perm)) { | ||
735 | err = -EIDRM; | 736 | err = -EIDRM; |
736 | goto out_unlock0; | 737 | goto out_unlock0; |
737 | } | 738 | } |
@@ -909,7 +910,7 @@ long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, int msgfl | |||
909 | ipc_lock_object(&msq->q_perm); | 910 | ipc_lock_object(&msq->q_perm); |
910 | 911 | ||
911 | /* raced with RMID? */ | 912 | /* raced with RMID? */ |
912 | if (msq->q_perm.deleted) { | 913 | if (!ipc_valid_object(&msq->q_perm)) { |
913 | msg = ERR_PTR(-EIDRM); | 914 | msg = ERR_PTR(-EIDRM); |
914 | goto out_unlock0; | 915 | goto out_unlock0; |
915 | } | 916 | } |
@@ -1284,7 +1284,7 @@ static int semctl_setval(struct ipc_namespace *ns, int semid, int semnum, | |||
1284 | 1284 | ||
1285 | sem_lock(sma, NULL, -1); | 1285 | sem_lock(sma, NULL, -1); |
1286 | 1286 | ||
1287 | if (sma->sem_perm.deleted) { | 1287 | if (!ipc_valid_object(&sma->sem_perm)) { |
1288 | sem_unlock(sma, -1); | 1288 | sem_unlock(sma, -1); |
1289 | rcu_read_unlock(); | 1289 | rcu_read_unlock(); |
1290 | return -EIDRM; | 1290 | return -EIDRM; |
@@ -1344,7 +1344,7 @@ static int semctl_main(struct ipc_namespace *ns, int semid, int semnum, | |||
1344 | int i; | 1344 | int i; |
1345 | 1345 | ||
1346 | sem_lock(sma, NULL, -1); | 1346 | sem_lock(sma, NULL, -1); |
1347 | if (sma->sem_perm.deleted) { | 1347 | if (!ipc_valid_object(&sma->sem_perm)) { |
1348 | err = -EIDRM; | 1348 | err = -EIDRM; |
1349 | goto out_unlock; | 1349 | goto out_unlock; |
1350 | } | 1350 | } |
@@ -1363,7 +1363,7 @@ static int semctl_main(struct ipc_namespace *ns, int semid, int semnum, | |||
1363 | 1363 | ||
1364 | rcu_read_lock(); | 1364 | rcu_read_lock(); |
1365 | sem_lock_and_putref(sma); | 1365 | sem_lock_and_putref(sma); |
1366 | if (sma->sem_perm.deleted) { | 1366 | if (!ipc_valid_object(&sma->sem_perm)) { |
1367 | err = -EIDRM; | 1367 | err = -EIDRM; |
1368 | goto out_unlock; | 1368 | goto out_unlock; |
1369 | } | 1369 | } |
@@ -1411,7 +1411,7 @@ static int semctl_main(struct ipc_namespace *ns, int semid, int semnum, | |||
1411 | } | 1411 | } |
1412 | rcu_read_lock(); | 1412 | rcu_read_lock(); |
1413 | sem_lock_and_putref(sma); | 1413 | sem_lock_and_putref(sma); |
1414 | if (sma->sem_perm.deleted) { | 1414 | if (!ipc_valid_object(&sma->sem_perm)) { |
1415 | err = -EIDRM; | 1415 | err = -EIDRM; |
1416 | goto out_unlock; | 1416 | goto out_unlock; |
1417 | } | 1417 | } |
@@ -1437,7 +1437,7 @@ static int semctl_main(struct ipc_namespace *ns, int semid, int semnum, | |||
1437 | goto out_rcu_wakeup; | 1437 | goto out_rcu_wakeup; |
1438 | 1438 | ||
1439 | sem_lock(sma, NULL, -1); | 1439 | sem_lock(sma, NULL, -1); |
1440 | if (sma->sem_perm.deleted) { | 1440 | if (!ipc_valid_object(&sma->sem_perm)) { |
1441 | err = -EIDRM; | 1441 | err = -EIDRM; |
1442 | goto out_unlock; | 1442 | goto out_unlock; |
1443 | } | 1443 | } |
@@ -1701,7 +1701,7 @@ static struct sem_undo *find_alloc_undo(struct ipc_namespace *ns, int semid) | |||
1701 | /* step 3: Acquire the lock on semaphore array */ | 1701 | /* step 3: Acquire the lock on semaphore array */ |
1702 | rcu_read_lock(); | 1702 | rcu_read_lock(); |
1703 | sem_lock_and_putref(sma); | 1703 | sem_lock_and_putref(sma); |
1704 | if (sma->sem_perm.deleted) { | 1704 | if (!ipc_valid_object(&sma->sem_perm)) { |
1705 | sem_unlock(sma, -1); | 1705 | sem_unlock(sma, -1); |
1706 | rcu_read_unlock(); | 1706 | rcu_read_unlock(); |
1707 | kfree(new); | 1707 | kfree(new); |
@@ -1848,7 +1848,15 @@ SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsops, | |||
1848 | 1848 | ||
1849 | error = -EIDRM; | 1849 | error = -EIDRM; |
1850 | locknum = sem_lock(sma, sops, nsops); | 1850 | locknum = sem_lock(sma, sops, nsops); |
1851 | if (sma->sem_perm.deleted) | 1851 | /* |
1852 | * We eventually might perform the following check in a lockless | ||
1853 | * fashion, considering ipc_valid_object() locking constraints. | ||
1854 | * If nsops == 1 and there is no contention for sem_perm.lock, then | ||
1855 | * only a per-semaphore lock is held and it's OK to proceed with the | ||
1856 | * check below. More details on the fine grained locking scheme | ||
1857 | * entangled here and why it's RMID race safe on comments at sem_lock() | ||
1858 | */ | ||
1859 | if (!ipc_valid_object(&sma->sem_perm)) | ||
1852 | goto out_unlock_free; | 1860 | goto out_unlock_free; |
1853 | /* | 1861 | /* |
1854 | * semid identifiers are not unique - find_alloc_undo may have | 1862 | * semid identifiers are not unique - find_alloc_undo may have |
@@ -2070,7 +2078,7 @@ void exit_sem(struct task_struct *tsk) | |||
2070 | 2078 | ||
2071 | sem_lock(sma, NULL, -1); | 2079 | sem_lock(sma, NULL, -1); |
2072 | /* exit_sem raced with IPC_RMID, nothing to do */ | 2080 | /* exit_sem raced with IPC_RMID, nothing to do */ |
2073 | if (sma->sem_perm.deleted) { | 2081 | if (!ipc_valid_object(&sma->sem_perm)) { |
2074 | sem_unlock(sma, -1); | 2082 | sem_unlock(sma, -1); |
2075 | rcu_read_unlock(); | 2083 | rcu_read_unlock(); |
2076 | continue; | 2084 | continue; |
@@ -975,6 +975,13 @@ SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf) | |||
975 | goto out_unlock1; | 975 | goto out_unlock1; |
976 | 976 | ||
977 | ipc_lock_object(&shp->shm_perm); | 977 | ipc_lock_object(&shp->shm_perm); |
978 | |||
979 | /* check if shm_destroy() is tearing down shp */ | ||
980 | if (!ipc_valid_object(&shp->shm_perm)) { | ||
981 | err = -EIDRM; | ||
982 | goto out_unlock0; | ||
983 | } | ||
984 | |||
978 | if (!ns_capable(ns->user_ns, CAP_IPC_LOCK)) { | 985 | if (!ns_capable(ns->user_ns, CAP_IPC_LOCK)) { |
979 | kuid_t euid = current_euid(); | 986 | kuid_t euid = current_euid(); |
980 | if (!uid_eq(euid, shp->shm_perm.uid) && | 987 | if (!uid_eq(euid, shp->shm_perm.uid) && |
@@ -989,13 +996,6 @@ SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf) | |||
989 | } | 996 | } |
990 | 997 | ||
991 | shm_file = shp->shm_file; | 998 | shm_file = shp->shm_file; |
992 | |||
993 | /* check if shm_destroy() is tearing down shp */ | ||
994 | if (shm_file == NULL) { | ||
995 | err = -EIDRM; | ||
996 | goto out_unlock0; | ||
997 | } | ||
998 | |||
999 | if (is_file_hugepages(shm_file)) | 999 | if (is_file_hugepages(shm_file)) |
1000 | goto out_unlock0; | 1000 | goto out_unlock0; |
1001 | 1001 | ||
@@ -1116,7 +1116,7 @@ long do_shmat(int shmid, char __user *shmaddr, int shmflg, ulong *raddr, | |||
1116 | ipc_lock_object(&shp->shm_perm); | 1116 | ipc_lock_object(&shp->shm_perm); |
1117 | 1117 | ||
1118 | /* check if shm_destroy() is tearing down shp */ | 1118 | /* check if shm_destroy() is tearing down shp */ |
1119 | if (shp->shm_file == NULL) { | 1119 | if (!ipc_valid_object(&shp->shm_perm)) { |
1120 | ipc_unlock_object(&shp->shm_perm); | 1120 | ipc_unlock_object(&shp->shm_perm); |
1121 | err = -EIDRM; | 1121 | err = -EIDRM; |
1122 | goto out_unlock; | 1122 | goto out_unlock; |
diff --git a/ipc/util.h b/ipc/util.h index 59d78aa94987..d05b7085a887 100644 --- a/ipc/util.h +++ b/ipc/util.h | |||
@@ -185,6 +185,19 @@ static inline void ipc_unlock(struct kern_ipc_perm *perm) | |||
185 | rcu_read_unlock(); | 185 | rcu_read_unlock(); |
186 | } | 186 | } |
187 | 187 | ||
188 | /* | ||
189 | * ipc_valid_object() - helper to sort out IPC_RMID races for codepaths | ||
190 | * where the respective ipc_ids.rwsem is not being held down. | ||
191 | * Checks whether the ipc object is still around or if it's gone already, as | ||
192 | * ipc_rmid() may have already freed the ID while the ipc lock was spinning. | ||
193 | * Needs to be called with kern_ipc_perm.lock held -- exception made for one | ||
194 | * checkpoint case at sys_semtimedop() as noted in code commentary. | ||
195 | */ | ||
196 | static inline bool ipc_valid_object(struct kern_ipc_perm *perm) | ||
197 | { | ||
198 | return perm->deleted == 0; | ||
199 | } | ||
200 | |||
188 | struct kern_ipc_perm *ipc_obtain_object_check(struct ipc_ids *ids, int id); | 201 | struct kern_ipc_perm *ipc_obtain_object_check(struct ipc_ids *ids, int id); |
189 | int ipcget(struct ipc_namespace *ns, struct ipc_ids *ids, | 202 | int ipcget(struct ipc_namespace *ns, struct ipc_ids *ids, |
190 | struct ipc_ops *ops, struct ipc_params *params); | 203 | struct ipc_ops *ops, struct ipc_params *params); |