diff options
| author | Manfred Spraul <manfred@colorfullife.com> | 2013-10-16 16:46:45 -0400 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2013-10-17 00:35:52 -0400 |
| commit | 6e224f94597842c5eb17f1fc2208d20b6f7f7d49 (patch) | |
| tree | f2a2ed86aa847b16b0d952411aed975f3a978908 /ipc | |
| parent | 18ccee263c7e250a57f01c9434658f11f4118a64 (diff) | |
ipc/sem.c: synchronize semop and semctl with IPC_RMID
After acquiring the semlock spinlock, operations must test that the
array is still valid.
- semctl() and exit_sem() would walk stale linked lists (ugly, but
should be ok: all lists are empty)
- semtimedop() would sleep forever - and if woken up due to a signal -
access memory after free.
The patch also:
- standardizes the tests for .deleted, so that all tests in one
function leave the function with the same approach.
- unconditionally tests for .deleted immediately after every call to
sem_lock - even it it means that for semctl(GETALL), .deleted will be
tested twice.
Both changes make the review simpler: After every sem_lock, there must
be a test of .deleted, followed by a goto to the cleanup code (if the
function uses "goto cleanup").
The only exception is semctl_down(): If sem_ids().rwsem is locked, then
the presence in ids->ipcs_idr is equivalent to !.deleted, thus no
additional test is required.
Signed-off-by: Manfred Spraul <manfred@colorfullife.com>
Cc: Mike Galbraith <efault@gmx.de>
Acked-by: Davidlohr Bueso <davidlohr@hp.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/sem.c | 42 |
1 files changed, 29 insertions, 13 deletions
| @@ -1282,6 +1282,12 @@ static int semctl_setval(struct ipc_namespace *ns, int semid, int semnum, | |||
| 1282 | 1282 | ||
| 1283 | sem_lock(sma, NULL, -1); | 1283 | sem_lock(sma, NULL, -1); |
| 1284 | 1284 | ||
| 1285 | if (sma->sem_perm.deleted) { | ||
| 1286 | sem_unlock(sma, -1); | ||
| 1287 | rcu_read_unlock(); | ||
| 1288 | return -EIDRM; | ||
| 1289 | } | ||
| 1290 | |||
| 1285 | curr = &sma->sem_base[semnum]; | 1291 | curr = &sma->sem_base[semnum]; |
| 1286 | 1292 | ||
| 1287 | ipc_assert_locked_object(&sma->sem_perm); | 1293 | ipc_assert_locked_object(&sma->sem_perm); |
| @@ -1336,12 +1342,14 @@ static int semctl_main(struct ipc_namespace *ns, int semid, int semnum, | |||
| 1336 | int i; | 1342 | int i; |
| 1337 | 1343 | ||
| 1338 | sem_lock(sma, NULL, -1); | 1344 | sem_lock(sma, NULL, -1); |
| 1345 | if (sma->sem_perm.deleted) { | ||
| 1346 | err = -EIDRM; | ||
| 1347 | goto out_unlock; | ||
| 1348 | } | ||
| 1339 | if(nsems > SEMMSL_FAST) { | 1349 | if(nsems > SEMMSL_FAST) { |
| 1340 | if (!ipc_rcu_getref(sma)) { | 1350 | if (!ipc_rcu_getref(sma)) { |
| 1341 | sem_unlock(sma, -1); | ||
| 1342 | rcu_read_unlock(); | ||
| 1343 | err = -EIDRM; | 1351 | err = -EIDRM; |
| 1344 | goto out_free; | 1352 | goto out_unlock; |
| 1345 | } | 1353 | } |
| 1346 | sem_unlock(sma, -1); | 1354 | sem_unlock(sma, -1); |
| 1347 | rcu_read_unlock(); | 1355 | rcu_read_unlock(); |
| @@ -1354,10 +1362,8 @@ static int semctl_main(struct ipc_namespace *ns, int semid, int semnum, | |||
| 1354 | rcu_read_lock(); | 1362 | rcu_read_lock(); |
| 1355 | sem_lock_and_putref(sma); | 1363 | sem_lock_and_putref(sma); |
| 1356 | if (sma->sem_perm.deleted) { | 1364 | if (sma->sem_perm.deleted) { |
| 1357 | sem_unlock(sma, -1); | ||
| 1358 | rcu_read_unlock(); | ||
| 1359 | err = -EIDRM; | 1365 | err = -EIDRM; |
| 1360 | goto out_free; | 1366 | goto out_unlock; |
| 1361 | } | 1367 | } |
| 1362 | } | 1368 | } |
| 1363 | for (i = 0; i < sma->sem_nsems; i++) | 1369 | for (i = 0; i < sma->sem_nsems; i++) |
| @@ -1375,8 +1381,8 @@ static int semctl_main(struct ipc_namespace *ns, int semid, int semnum, | |||
| 1375 | struct sem_undo *un; | 1381 | struct sem_undo *un; |
| 1376 | 1382 | ||
| 1377 | if (!ipc_rcu_getref(sma)) { | 1383 | if (!ipc_rcu_getref(sma)) { |
| 1378 | rcu_read_unlock(); | 1384 | err = -EIDRM; |
| 1379 | return -EIDRM; | 1385 | goto out_rcu_wakeup; |
| 1380 | } | 1386 | } |
| 1381 | rcu_read_unlock(); | 1387 | rcu_read_unlock(); |
| 1382 | 1388 | ||
| @@ -1404,10 +1410,8 @@ static int semctl_main(struct ipc_namespace *ns, int semid, int semnum, | |||
| 1404 | rcu_read_lock(); | 1410 | rcu_read_lock(); |
| 1405 | sem_lock_and_putref(sma); | 1411 | sem_lock_and_putref(sma); |
| 1406 | if (sma->sem_perm.deleted) { | 1412 | if (sma->sem_perm.deleted) { |
| 1407 | sem_unlock(sma, -1); | ||
| 1408 | rcu_read_unlock(); | ||
| 1409 | err = -EIDRM; | 1413 | err = -EIDRM; |
| 1410 | goto out_free; | 1414 | goto out_unlock; |
| 1411 | } | 1415 | } |
| 1412 | 1416 | ||
| 1413 | for (i = 0; i < nsems; i++) | 1417 | for (i = 0; i < nsems; i++) |
| @@ -1431,6 +1435,10 @@ static int semctl_main(struct ipc_namespace *ns, int semid, int semnum, | |||
| 1431 | goto out_rcu_wakeup; | 1435 | goto out_rcu_wakeup; |
| 1432 | 1436 | ||
| 1433 | sem_lock(sma, NULL, -1); | 1437 | sem_lock(sma, NULL, -1); |
| 1438 | if (sma->sem_perm.deleted) { | ||
| 1439 | err = -EIDRM; | ||
| 1440 | goto out_unlock; | ||
| 1441 | } | ||
| 1434 | curr = &sma->sem_base[semnum]; | 1442 | curr = &sma->sem_base[semnum]; |
| 1435 | 1443 | ||
| 1436 | switch (cmd) { | 1444 | switch (cmd) { |
| @@ -1836,6 +1844,10 @@ SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsops, | |||
| 1836 | if (error) | 1844 | if (error) |
| 1837 | goto out_rcu_wakeup; | 1845 | goto out_rcu_wakeup; |
| 1838 | 1846 | ||
| 1847 | error = -EIDRM; | ||
| 1848 | locknum = sem_lock(sma, sops, nsops); | ||
| 1849 | if (sma->sem_perm.deleted) | ||
| 1850 | goto out_unlock_free; | ||
| 1839 | /* | 1851 | /* |
| 1840 | * semid identifiers are not unique - find_alloc_undo may have | 1852 | * semid identifiers are not unique - find_alloc_undo may have |
| 1841 | * allocated an undo structure, it was invalidated by an RMID | 1853 | * allocated an undo structure, it was invalidated by an RMID |
| @@ -1843,8 +1855,6 @@ SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsops, | |||
| 1843 | * This case can be detected checking un->semid. The existence of | 1855 | * This case can be detected checking un->semid. The existence of |
| 1844 | * "un" itself is guaranteed by rcu. | 1856 | * "un" itself is guaranteed by rcu. |
| 1845 | */ | 1857 | */ |
| 1846 | error = -EIDRM; | ||
| 1847 | locknum = sem_lock(sma, sops, nsops); | ||
| 1848 | if (un && un->semid == -1) | 1858 | if (un && un->semid == -1) |
| 1849 | goto out_unlock_free; | 1859 | goto out_unlock_free; |
| 1850 | 1860 | ||
| @@ -2057,6 +2067,12 @@ void exit_sem(struct task_struct *tsk) | |||
| 2057 | } | 2067 | } |
| 2058 | 2068 | ||
| 2059 | sem_lock(sma, NULL, -1); | 2069 | sem_lock(sma, NULL, -1); |
| 2070 | /* exit_sem raced with IPC_RMID, nothing to do */ | ||
| 2071 | if (sma->sem_perm.deleted) { | ||
| 2072 | sem_unlock(sma, -1); | ||
| 2073 | rcu_read_unlock(); | ||
| 2074 | continue; | ||
| 2075 | } | ||
| 2060 | un = __lookup_undo(ulp, semid); | 2076 | un = __lookup_undo(ulp, semid); |
| 2061 | if (un == NULL) { | 2077 | if (un == NULL) { |
| 2062 | /* exit_sem raced with IPC_RMID+semget() that created | 2078 | /* exit_sem raced with IPC_RMID+semget() that created |
