Skip to content

Commit e88f2be

Browse files
Jon Maloydavem330
authored andcommitted
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 <[email protected]> Signed-off-by: Jon Maloy <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 10a435a commit e88f2be

File tree

3 files changed

+51
-46
lines changed

3 files changed

+51
-46
lines changed

net/tipc/server.c

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,11 @@ static struct tipc_conn *tipc_conn_lookup(struct tipc_server *s, int conid)
132132

133133
spin_lock_bh(&s->idr_lock);
134134
con = idr_find(&s->conn_idr, conid);
135-
if (con && test_bit(CF_CONNECTED, &con->flags))
136-
conn_get(con);
137-
else
138-
con = NULL;
135+
if (con) {
136+
if (!test_bit(CF_CONNECTED, &con->flags) ||
137+
!kref_get_unless_zero(&con->kref))
138+
con = NULL;
139+
}
139140
spin_unlock_bh(&s->idr_lock);
140141
return con;
141142
}
@@ -183,35 +184,28 @@ static void tipc_register_callbacks(struct socket *sock, struct tipc_conn *con)
183184
write_unlock_bh(&sk->sk_callback_lock);
184185
}
185186

186-
static 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-
195187
static void tipc_close_conn(struct tipc_conn *con)
196188
{
197189
struct tipc_server *s = con->server;
190+
struct sock *sk = con->sock->sk;
191+
bool disconnect = false;
198192

199-
if (test_and_clear_bit(CF_CONNECTED, &con->flags)) {
200-
if (con->sock)
201-
tipc_unregister_callbacks(con);
202-
193+
write_lock_bh(&sk->sk_callback_lock);
194+
disconnect = test_and_clear_bit(CF_CONNECTED, &con->flags);
195+
if (disconnect) {
196+
sk->sk_user_data = NULL;
203197
if (con->conid)
204198
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);
214199
}
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);
215209
}
216210

217211
static 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)
248242

249243
static int tipc_receive_from_sock(struct tipc_conn *con)
250244
{
251-
struct msghdr msg = {};
252245
struct tipc_server *s = con->server;
246+
struct sock *sk = con->sock->sk;
253247
struct sockaddr_tipc addr;
248+
struct msghdr msg = {};
254249
struct kvec iov;
255250
void *buf;
256251
int ret;
@@ -271,12 +266,15 @@ static int tipc_receive_from_sock(struct tipc_conn *con)
271266
goto out_close;
272267
}
273268

274-
s->tipc_conn_recvmsg(sock_net(con->sock->sk), con->conid, &addr,
275-
con->usr_data, buf, ret);
276-
269+
read_lock_bh(&sk->sk_callback_lock);
270+
if (test_bit(CF_CONNECTED, &con->flags))
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);
277274
kmem_cache_free(s->rcvbuf_cache, buf);
278-
279-
return 0;
275+
if (ret < 0)
276+
tipc_conn_terminate(s, con->conid);
277+
return ret;
280278

281279
out_close:
282280
if (ret != -EWOULDBLOCK)
@@ -525,11 +523,17 @@ bool tipc_topsrv_kern_subscr(struct net *net, u32 port, u32 type, u32 lower,
525523
void tipc_topsrv_kern_unsubscr(struct net *net, int conid)
526524
{
527525
struct tipc_conn *con;
526+
struct tipc_server *srv;
528527

529528
con = tipc_conn_lookup(tipc_topsrv(net), conid);
530529
if (!con)
531530
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);
533537
conn_put(con);
534538
}
535539

net/tipc/server.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,9 @@ struct tipc_server {
7474
int max_rcvbuf_size;
7575
void *(*tipc_conn_new)(int conid);
7676
void (*tipc_conn_release)(int conid, void *usr_data);
77-
void (*tipc_conn_recvmsg)(struct net *net, int conid,
78-
struct sockaddr_tipc *addr, void *usr_data,
79-
void *buf, size_t len);
77+
int (*tipc_conn_recvmsg)(struct net *net, int conid,
78+
struct sockaddr_tipc *addr, void *usr_data,
79+
void *buf, size_t len);
8080
struct sockaddr_tipc *saddr;
8181
char name[TIPC_SERVER_NAME_LEN];
8282
int imp;

net/tipc/subscr.c

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -289,17 +289,16 @@ static struct tipc_subscription *tipc_subscrp_create(struct net *net,
289289
return sub;
290290
}
291291

292-
static void tipc_subscrp_subscribe(struct net *net, struct tipc_subscr *s,
293-
struct tipc_subscriber *subscriber, int swap,
294-
bool status)
292+
static int tipc_subscrp_subscribe(struct net *net, struct tipc_subscr *s,
293+
struct tipc_subscriber *subscriber, int swap,
294+
bool status)
295295
{
296-
struct tipc_net *tn = net_generic(net, tipc_net_id);
297296
struct tipc_subscription *sub = NULL;
298297
u32 timeout;
299298

300299
sub = tipc_subscrp_create(net, s, swap);
301300
if (!sub)
302-
return tipc_conn_terminate(tn->topsrv, subscriber->conid);
301+
return -1;
303302

304303
spin_lock_bh(&subscriber->lock);
305304
list_add(&sub->subscrp_list, &subscriber->subscrp_list);
@@ -313,6 +312,7 @@ static void tipc_subscrp_subscribe(struct net *net, struct tipc_subscr *s,
313312

314313
if (timeout != TIPC_WAIT_FOREVER)
315314
mod_timer(&sub->timer, jiffies + msecs_to_jiffies(timeout));
315+
return 0;
316316
}
317317

318318
/* Handle one termination request for the subscriber */
@@ -322,9 +322,9 @@ static void tipc_subscrb_release_cb(int conid, void *usr_data)
322322
}
323323

324324
/* Handle one request to create a new subscription for the subscriber */
325-
static void tipc_subscrb_rcv_cb(struct net *net, int conid,
326-
struct sockaddr_tipc *addr, void *usr_data,
327-
void *buf, size_t len)
325+
static int tipc_subscrb_rcv_cb(struct net *net, int conid,
326+
struct sockaddr_tipc *addr, void *usr_data,
327+
void *buf, size_t len)
328328
{
329329
struct tipc_subscriber *subscriber = usr_data;
330330
struct tipc_subscr *s = (struct tipc_subscr *)buf;
@@ -338,10 +338,11 @@ static void tipc_subscrb_rcv_cb(struct net *net, int conid,
338338
/* Detect & process a subscription cancellation request */
339339
if (s->filter & htohl(TIPC_SUB_CANCEL, swap)) {
340340
s->filter &= ~htohl(TIPC_SUB_CANCEL, swap);
341-
return tipc_subscrp_cancel(s, subscriber);
341+
tipc_subscrp_cancel(s, subscriber);
342+
return 0;
342343
}
343344
status = !(s->filter & htohl(TIPC_SUB_NO_STATUS, swap));
344-
tipc_subscrp_subscribe(net, s, subscriber, swap, status);
345+
return tipc_subscrp_subscribe(net, s, subscriber, swap, status);
345346
}
346347

347348
/* Handle one request to establish a new subscriber */

0 commit comments

Comments
 (0)