mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-26 02:39:48 +00:00
[PPPOE]: Fix device tear-down notification.
pppoe_flush_dev() kicks all sockets bound to a device that is going down. In doing so, locks must be taken in the right order consistently (sock lock, followed by the pppoe_hash_lock). However, the scan process is based on us holding the sock lock. So, when something is found in the scan we must release the lock we're holding and grab the sock lock. This patch fixes race conditions between this code and pppoe_release(), both of which perform similar functions but would naturally prefer to grab locks in opposing orders. Both code paths are now going after these locks in a consistent manner. pppoe_hash_lock protects the contents of the "pppox_sock" objects that reside inside the hash. Thus, NULL'ing out the pppoe_dev field should be done under the protection of this lock. Signed-off-by: Michal Ostrowski <mostrows@earthlink.net> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
202a03acf9
commit
42dc9cd54b
1 changed files with 55 additions and 42 deletions
|
@ -241,54 +241,53 @@ static inline struct pppox_sock *delete_item(unsigned long sid, char *addr, int
|
|||
static void pppoe_flush_dev(struct net_device *dev)
|
||||
{
|
||||
int hash;
|
||||
|
||||
BUG_ON(dev == NULL);
|
||||
|
||||
read_lock_bh(&pppoe_hash_lock);
|
||||
write_lock_bh(&pppoe_hash_lock);
|
||||
for (hash = 0; hash < PPPOE_HASH_SIZE; hash++) {
|
||||
struct pppox_sock *po = item_hash_table[hash];
|
||||
|
||||
while (po != NULL) {
|
||||
if (po->pppoe_dev == dev) {
|
||||
struct sock *sk = sk_pppox(po);
|
||||
|
||||
sock_hold(sk);
|
||||
po->pppoe_dev = NULL;
|
||||
|
||||
/* We hold a reference to SK, now drop the
|
||||
* hash table lock so that we may attempt
|
||||
* to lock the socket (which can sleep).
|
||||
*/
|
||||
read_unlock_bh(&pppoe_hash_lock);
|
||||
|
||||
lock_sock(sk);
|
||||
|
||||
if (sk->sk_state &
|
||||
(PPPOX_CONNECTED | PPPOX_BOUND)) {
|
||||
pppox_unbind_sock(sk);
|
||||
dev_put(dev);
|
||||
sk->sk_state = PPPOX_ZOMBIE;
|
||||
sk->sk_state_change(sk);
|
||||
}
|
||||
|
||||
release_sock(sk);
|
||||
|
||||
sock_put(sk);
|
||||
|
||||
read_lock_bh(&pppoe_hash_lock);
|
||||
|
||||
/* Now restart from the beginning of this
|
||||
* hash chain. We always NULL out pppoe_dev
|
||||
* so we are guaranteed to make forward
|
||||
* progress.
|
||||
*/
|
||||
po = item_hash_table[hash];
|
||||
struct sock *sk = sk_pppox(po);
|
||||
if (po->pppoe_dev != dev) {
|
||||
po = po->next;
|
||||
continue;
|
||||
}
|
||||
po = po->next;
|
||||
po->pppoe_dev = NULL;
|
||||
dev_put(dev);
|
||||
|
||||
|
||||
/* We always grab the socket lock, followed by the
|
||||
* pppoe_hash_lock, in that order. Since we should
|
||||
* hold the sock lock while doing any unbinding,
|
||||
* we need to release the lock we're holding.
|
||||
* Hold a reference to the sock so it doesn't disappear
|
||||
* as we're jumping between locks.
|
||||
*/
|
||||
|
||||
sock_hold(sk);
|
||||
|
||||
write_unlock_bh(&pppoe_hash_lock);
|
||||
lock_sock(sk);
|
||||
|
||||
if (sk->sk_state & (PPPOX_CONNECTED | PPPOX_BOUND)) {
|
||||
pppox_unbind_sock(sk);
|
||||
sk->sk_state = PPPOX_ZOMBIE;
|
||||
sk->sk_state_change(sk);
|
||||
}
|
||||
|
||||
release_sock(sk);
|
||||
sock_put(sk);
|
||||
|
||||
/* Restart scan at the beginning of this hash chain.
|
||||
* While the lock was dropped the chain contents may
|
||||
* have changed.
|
||||
*/
|
||||
write_lock_bh(&pppoe_hash_lock);
|
||||
po = item_hash_table[hash];
|
||||
}
|
||||
}
|
||||
read_unlock_bh(&pppoe_hash_lock);
|
||||
write_unlock_bh(&pppoe_hash_lock);
|
||||
}
|
||||
|
||||
static int pppoe_device_event(struct notifier_block *this,
|
||||
|
@ -504,28 +503,42 @@ static int pppoe_release(struct socket *sock)
|
|||
if (!sk)
|
||||
return 0;
|
||||
|
||||
if (sock_flag(sk, SOCK_DEAD))
|
||||
lock_sock(sk);
|
||||
if (sock_flag(sk, SOCK_DEAD)){
|
||||
release_sock(sk);
|
||||
return -EBADF;
|
||||
}
|
||||
|
||||
pppox_unbind_sock(sk);
|
||||
|
||||
/* Signal the death of the socket. */
|
||||
sk->sk_state = PPPOX_DEAD;
|
||||
|
||||
|
||||
/* Write lock on hash lock protects the entire "po" struct from
|
||||
* concurrent updates via pppoe_flush_dev. The "po" struct should
|
||||
* be considered part of the hash table contents, thus protected
|
||||
* by the hash table lock */
|
||||
write_lock_bh(&pppoe_hash_lock);
|
||||
|
||||
po = pppox_sk(sk);
|
||||
if (po->pppoe_pa.sid) {
|
||||
delete_item(po->pppoe_pa.sid, po->pppoe_pa.remote, po->pppoe_ifindex);
|
||||
__delete_item(po->pppoe_pa.sid,
|
||||
po->pppoe_pa.remote, po->pppoe_ifindex);
|
||||
}
|
||||
|
||||
if (po->pppoe_dev)
|
||||
if (po->pppoe_dev) {
|
||||
dev_put(po->pppoe_dev);
|
||||
po->pppoe_dev = NULL;
|
||||
}
|
||||
|
||||
po->pppoe_dev = NULL;
|
||||
write_unlock_bh(&pppoe_hash_lock);
|
||||
|
||||
sock_orphan(sk);
|
||||
sock->sk = NULL;
|
||||
|
||||
skb_queue_purge(&sk->sk_receive_queue);
|
||||
release_sock(sk);
|
||||
sock_put(sk);
|
||||
|
||||
return 0;
|
||||
|
|
Loading…
Reference in a new issue