summaryrefslogtreecommitdiffstats
path: root/net/tipc/server.c
diff options
context:
space:
mode:
authorJon Maloy <jon.maloy@ericsson.com>2018-01-15 11:56:28 -0500
committerDavid S. Miller <davem@davemloft.net>2018-01-16 14:42:41 -0500
commite88f2be83282d5ffc8f5ffe4c22606bf62eb1ac7 (patch)
treebd00b435d2d9ad85eb2d009820ee8971bf799245 /net/tipc/server.c
parent10a435ab3097a916c6989d53d4f5637621a009b5 (diff)
tipc: fix race condition at topology server receive
We have identified a race condition during reception of socket events and messages in the topology server. - The function tipc_close_conn() is releasing the corresponding struct tipc_subscriber instance without considering that there may still be items in the receive work queue. When those are scheduled, in the function tipc_receive_from_work(), they are using the subscriber pointer stored in struct tipc_conn, without first checking if this is valid or not. This will sometimes lead to crashes, as the next call of tipc_conn_recvmsg() will access the now deleted item. We fix this by making the usage of this pointer conditional on whether the connection is active or not. I.e., we check the condition test_bit(CF_CONNECTED) before making the call tipc_conn_recvmsg(). - Since the two functions may be running on different cores, the condition test described above is not enough. tipc_close_conn() may come in between and delete the subscriber item after the condition test is done, but before tipc_conn_recv_msg() is finished. This happens less frequently than the problem described above, but leads to the same symptoms. We fix this by using the existing sk_callback_lock for mutual exclusion in the two functions. In addition, we have to move a call to tipc_conn_terminate() outside the mentioned lock to avoid deadlock. Acked-by: Ying Xue <ying.xue@windriver.com> Signed-off-by: Jon Maloy <jon.maloy@ericsson.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/tipc/server.c')
-rw-r--r--net/tipc/server.c70
1 files changed, 37 insertions, 33 deletions
diff --git a/net/tipc/server.c b/net/tipc/server.c
index 8ee5e86b7870..c0d331f13eee 100644
--- a/net/tipc/server.c
+++ b/net/tipc/server.c
@@ -132,10 +132,11 @@ static struct tipc_conn *tipc_conn_lookup(struct tipc_server *s, int conid)
132 132
133 spin_lock_bh(&s->idr_lock); 133 spin_lock_bh(&s->idr_lock);
134 con = idr_find(&s->conn_idr, conid); 134 con = idr_find(&s->conn_idr, conid);
135 if (con && test_bit(CF_CONNECTED, &con->flags)) 135 if (con) {
136 conn_get(con); 136 if (!test_bit(CF_CONNECTED, &con->flags) ||
137 else 137 !kref_get_unless_zero(&con->kref))
138 con = NULL; 138 con = NULL;
139 }
139 spin_unlock_bh(&s->idr_lock); 140 spin_unlock_bh(&s->idr_lock);
140 return con; 141 return con;
141} 142}
@@ -183,35 +184,28 @@ static void tipc_register_callbacks(struct socket *sock, struct tipc_conn *con)
183 write_unlock_bh(&sk->sk_callback_lock); 184 write_unlock_bh(&sk->sk_callback_lock);
184} 185}
185 186
186static void tipc_unregister_callbacks(struct tipc_conn *con)
187{
188 struct sock *sk = con->sock->sk;
189
190 write_lock_bh(&sk->sk_callback_lock);
191 sk->sk_user_data = NULL;
192 write_unlock_bh(&sk->sk_callback_lock);
193}
194
195static void tipc_close_conn(struct tipc_conn *con) 187static void tipc_close_conn(struct tipc_conn *con)
196{ 188{
197 struct tipc_server *s = con->server; 189 struct tipc_server *s = con->server;
190 struct sock *sk = con->sock->sk;
191 bool disconnect = false;
198 192
199 if (test_and_clear_bit(CF_CONNECTED, &con->flags)) { 193 write_lock_bh(&sk->sk_callback_lock);
200 if (con->sock) 194 disconnect = test_and_clear_bit(CF_CONNECTED, &con->flags);
201 tipc_unregister_callbacks(con); 195 if (disconnect) {
202 196 sk->sk_user_data = NULL;
203 if (con->conid) 197 if (con->conid)
204 s->tipc_conn_release(con->conid, con->usr_data); 198 s->tipc_conn_release(con->conid, con->usr_data);
205
206 /* We shouldn't flush pending works as we may be in the
207 * thread. In fact the races with pending rx/tx work structs
208 * are harmless for us here as we have already deleted this
209 * connection from server connection list.
210 */
211 if (con->sock)
212 kernel_sock_shutdown(con->sock, SHUT_RDWR);
213 conn_put(con);
214 } 199 }
200 write_unlock_bh(&sk->sk_callback_lock);
201
202 /* Handle concurrent calls from sending and receiving threads */
203 if (!disconnect)
204 return;
205
206 /* Don't flush pending works, -just let them expire */
207 kernel_sock_shutdown(con->sock, SHUT_RDWR);
208 conn_put(con);
215} 209}
216 210
217static struct tipc_conn *tipc_alloc_conn(struct tipc_server *s) 211static struct tipc_conn *tipc_alloc_conn(struct tipc_server *s)
@@ -248,9 +242,10 @@ static struct tipc_conn *tipc_alloc_conn(struct tipc_server *s)
248 242
249static int tipc_receive_from_sock(struct tipc_conn *con) 243static int tipc_receive_from_sock(struct tipc_conn *con)
250{ 244{
251 struct msghdr msg = {};
252 struct tipc_server *s = con->server; 245 struct tipc_server *s = con->server;
246 struct sock *sk = con->sock->sk;
253 struct sockaddr_tipc addr; 247 struct sockaddr_tipc addr;
248 struct msghdr msg = {};
254 struct kvec iov; 249 struct kvec iov;
255 void *buf; 250 void *buf;
256 int ret; 251 int ret;
@@ -271,12 +266,15 @@ static int tipc_receive_from_sock(struct tipc_conn *con)
271 goto out_close; 266 goto out_close;
272 } 267 }
273 268
274 s->tipc_conn_recvmsg(sock_net(con->sock->sk), con->conid, &addr, 269 read_lock_bh(&sk->sk_callback_lock);
275 con->usr_data, buf, ret); 270 if (test_bit(CF_CONNECTED, &con->flags))
276 271 ret = s->tipc_conn_recvmsg(sock_net(con->sock->sk), con->conid,
272 &addr, con->usr_data, buf, ret);
273 read_unlock_bh(&sk->sk_callback_lock);
277 kmem_cache_free(s->rcvbuf_cache, buf); 274 kmem_cache_free(s->rcvbuf_cache, buf);
278 275 if (ret < 0)
279 return 0; 276 tipc_conn_terminate(s, con->conid);
277 return ret;
280 278
281out_close: 279out_close:
282 if (ret != -EWOULDBLOCK) 280 if (ret != -EWOULDBLOCK)
@@ -525,11 +523,17 @@ bool tipc_topsrv_kern_subscr(struct net *net, u32 port, u32 type, u32 lower,
525void tipc_topsrv_kern_unsubscr(struct net *net, int conid) 523void tipc_topsrv_kern_unsubscr(struct net *net, int conid)
526{ 524{
527 struct tipc_conn *con; 525 struct tipc_conn *con;
526 struct tipc_server *srv;
528 527
529 con = tipc_conn_lookup(tipc_topsrv(net), conid); 528 con = tipc_conn_lookup(tipc_topsrv(net), conid);
530 if (!con) 529 if (!con)
531 return; 530 return;
532 tipc_close_conn(con); 531
532 test_and_clear_bit(CF_CONNECTED, &con->flags);
533 srv = con->server;
534 if (con->conid)
535 srv->tipc_conn_release(con->conid, con->usr_data);
536 conn_put(con);
533 conn_put(con); 537 conn_put(con);
534} 538}
535 539