| From foo@baz Wed Sep 30 05:25:07 CEST 2015 |
| From: Herbert Xu <herbert@gondor.apana.org.au> |
| Date: Fri, 18 Sep 2015 19:16:50 +0800 |
| Subject: netlink: Fix autobind race condition that leads to zero port ID |
| |
| From: Herbert Xu <herbert@gondor.apana.org.au> |
| |
| [ Upstream commit 1f770c0a09da855a2b51af6d19de97fb955eca85 ] |
| |
| The commit c0bb07df7d981e4091432754e30c9c720e2c0c78 ("netlink: |
| Reset portid after netlink_insert failure") introduced a race |
| condition where if two threads try to autobind the same socket |
| one of them may end up with a zero port ID. This led to kernel |
| deadlocks that were observed by multiple people. |
| |
| This patch reverts that commit and instead fixes it by introducing |
| a separte rhash_portid variable so that the real portid is only set |
| after the socket has been successfully hashed. |
| |
| Fixes: c0bb07df7d98 ("netlink: Reset portid after netlink_insert failure") |
| Reported-by: Tejun Heo <tj@kernel.org> |
| Reported-by: Linus Torvalds <torvalds@linux-foundation.org> |
| Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> |
| Signed-off-by: David S. Miller <davem@davemloft.net> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| net/netlink/af_netlink.c | 12 +++++++----- |
| net/netlink/af_netlink.h | 1 + |
| 2 files changed, 8 insertions(+), 5 deletions(-) |
| |
| --- a/net/netlink/af_netlink.c |
| +++ b/net/netlink/af_netlink.c |
| @@ -1019,7 +1019,7 @@ static inline int netlink_compare(struct |
| const struct netlink_compare_arg *x = arg->key; |
| const struct netlink_sock *nlk = ptr; |
| |
| - return nlk->portid != x->portid || |
| + return nlk->rhash_portid != x->portid || |
| !net_eq(sock_net(&nlk->sk), read_pnet(&x->pnet)); |
| } |
| |
| @@ -1045,7 +1045,7 @@ static int __netlink_insert(struct netli |
| { |
| struct netlink_compare_arg arg; |
| |
| - netlink_compare_arg_init(&arg, sock_net(sk), nlk_sk(sk)->portid); |
| + netlink_compare_arg_init(&arg, sock_net(sk), nlk_sk(sk)->rhash_portid); |
| return rhashtable_lookup_insert_key(&table->hash, &arg, |
| &nlk_sk(sk)->node, |
| netlink_rhashtable_params); |
| @@ -1107,7 +1107,7 @@ static int netlink_insert(struct sock *s |
| unlikely(atomic_read(&table->hash.nelems) >= UINT_MAX)) |
| goto err; |
| |
| - nlk_sk(sk)->portid = portid; |
| + nlk_sk(sk)->rhash_portid = portid; |
| sock_hold(sk); |
| |
| err = __netlink_insert(table, sk); |
| @@ -1119,10 +1119,12 @@ static int netlink_insert(struct sock *s |
| err = -EOVERFLOW; |
| if (err == -EEXIST) |
| err = -EADDRINUSE; |
| - nlk_sk(sk)->portid = 0; |
| sock_put(sk); |
| + goto err; |
| } |
| |
| + nlk_sk(sk)->portid = portid; |
| + |
| err: |
| release_sock(sk); |
| return err; |
| @@ -3233,7 +3235,7 @@ static inline u32 netlink_hash(const voi |
| const struct netlink_sock *nlk = data; |
| struct netlink_compare_arg arg; |
| |
| - netlink_compare_arg_init(&arg, sock_net(&nlk->sk), nlk->portid); |
| + netlink_compare_arg_init(&arg, sock_net(&nlk->sk), nlk->rhash_portid); |
| return jhash2((u32 *)&arg, netlink_compare_arg_len / sizeof(u32), seed); |
| } |
| |
| --- a/net/netlink/af_netlink.h |
| +++ b/net/netlink/af_netlink.h |
| @@ -25,6 +25,7 @@ struct netlink_ring { |
| struct netlink_sock { |
| /* struct sock has to be the first member of netlink_sock */ |
| struct sock sk; |
| + u32 rhash_portid; |
| u32 portid; |
| u32 dst_portid; |
| u32 dst_group; |