Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Bluetooth: SCO: hold sk properly in sco_conn_ready

sk deref in sco_conn_ready must be done either under conn->lock, or
holding a refcount, to avoid concurrent close. conn->sk and parent sk is
currently accessed without either, and without checking parent->sk_state:

[Task 1] [Task 2]
sco_sock_release
sco_conn_ready
sk = conn->sk
lock_sock(sk)
conn->sk = NULL
lock_sock(sk)
release_sock(sk)
sco_sock_kill(sk)
UAF on sk deref

and similarly for access to sco_get_sock_listen() return value.

Fix possible UAF by holding sk refcount in sco_conn_ready() and making
sco_get_sock_listen() increase refcount. Also recheck after lock_sock
that the socket is still valid. Adjust conn->sk locking so it's
protected also by lock_sock() of the associated socket if any.

Fixes: 27c24fda62b60 ("Bluetooth: switch to lock_sock in SCO")
Signed-off-by: Pauli Virtanen <pav@iki.fi>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

authored by

Pauli Virtanen and committed by
Luiz Augusto von Dentz
4e37f645 0a120d96

+32 -12
+32 -12
net/bluetooth/sco.c
··· 472 472 sk1 = sk; 473 473 } 474 474 475 + sk = sk ? sk : sk1; 476 + if (sk) 477 + sock_hold(sk); 478 + 475 479 read_unlock(&sco_sk_list.lock); 476 480 477 - return sk ? sk : sk1; 481 + return sk; 478 482 } 479 483 480 484 static void sco_sock_destruct(struct sock *sk) ··· 519 515 BT_DBG("sk %p state %d", sk, sk->sk_state); 520 516 521 517 /* Sock is dead, so set conn->sk to NULL to avoid possible UAF */ 518 + lock_sock(sk); 522 519 if (sco_pi(sk)->conn) { 523 520 sco_conn_lock(sco_pi(sk)->conn); 524 521 sco_pi(sk)->conn->sk = NULL; 525 522 sco_conn_unlock(sco_pi(sk)->conn); 526 523 } 524 + release_sock(sk); 527 525 528 526 /* Kill poor orphan */ 529 527 bt_sock_unlink(&sco_sk_list, sk); ··· 1371 1365 1372 1366 static void sco_conn_ready(struct sco_conn *conn) 1373 1367 { 1374 - struct sock *parent; 1375 - struct sock *sk = conn->sk; 1368 + struct sock *parent, *sk; 1369 + 1370 + sco_conn_lock(conn); 1371 + sk = sco_sock_hold(conn); 1372 + sco_conn_unlock(conn); 1376 1373 1377 1374 BT_DBG("conn %p", conn); 1378 1375 1379 1376 if (sk) { 1380 1377 lock_sock(sk); 1381 - sco_sock_clear_timer(sk); 1382 - sk->sk_state = BT_CONNECTED; 1383 - sk->sk_state_change(sk); 1378 + 1379 + /* conn->sk may have become NULL if racing with sk close, but 1380 + * due to held hdev->lock, it can't become different sk. 1381 + */ 1382 + if (conn->sk) { 1383 + sco_sock_clear_timer(sk); 1384 + sk->sk_state = BT_CONNECTED; 1385 + sk->sk_state_change(sk); 1386 + } 1387 + 1384 1388 release_sock(sk); 1389 + sock_put(sk); 1385 1390 } else { 1386 1391 if (!conn->hcon) 1387 1392 return; ··· 1407 1390 1408 1391 sco_conn_lock(conn); 1409 1392 1393 + /* hdev->lock guarantees conn->sk == NULL still here */ 1394 + 1395 + if (parent->sk_state != BT_LISTEN) 1396 + goto release; 1397 + 1410 1398 sk = sco_sock_alloc(sock_net(parent), NULL, 1411 1399 BTPROTO_SCO, GFP_ATOMIC, 0); 1412 - if (!sk) { 1413 - sco_conn_unlock(conn); 1414 - release_sock(parent); 1415 - return; 1416 - } 1400 + if (!sk) 1401 + goto release; 1417 1402 1418 1403 sco_sock_init(sk, parent); 1419 1404 ··· 1434 1415 /* Wake up parent */ 1435 1416 parent->sk_data_ready(parent); 1436 1417 1418 + release: 1437 1419 sco_conn_unlock(conn); 1438 - 1439 1420 release_sock(parent); 1421 + sock_put(parent); 1440 1422 } 1441 1423 } 1442 1424