aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorXin Long <lucien.xin@gmail.com>2017-09-14 23:02:21 -0400
committerDavid S. Miller <davem@davemloft.net>2017-09-15 17:47:49 -0400
commitd25adbeb0cdb860fb39e09cdd025e9cfc954c5ab (patch)
treef837b2ada6d0f4c23319a8950fa8dff821e07b72
parent5023a6db73196695f4cc2db1a0eb37957ca27772 (diff)
sctp: fix an use-after-free issue in sctp_sock_dump
Commit 86fdb3448cc1 ("sctp: ensure ep is not destroyed before doing the dump") tried to fix an use-after-free issue by checking !sctp_sk(sk)->ep with holding sock and sock lock. But Paolo noticed that endpoint could be destroyed in sctp_rcv without sock lock protection. It means the use-after-free issue still could be triggered when sctp_rcv put and destroy ep after sctp_sock_dump checks !ep, although it's pretty hard to reproduce. I could reproduce it by mdelay in sctp_rcv while msleep in sctp_close and sctp_sock_dump long time. This patch is to add another param cb_done to sctp_for_each_transport and dump ep->assocs with holding tsp after jumping out of transport's traversal in it to avoid this issue. It can also improve sctp diag dump to make it run faster, as no need to save sk into cb->args[5] and keep calling sctp_for_each_transport any more. This patch is also to use int * instead of int for the pos argument in sctp_for_each_transport, which could make postion increment only in sctp_for_each_transport and no need to keep changing cb->args[2] in sctp_sock_filter and sctp_sock_dump any more. Fixes: 86fdb3448cc1 ("sctp: ensure ep is not destroyed before doing the dump") Reported-by: Paolo Abeni <pabeni@redhat.com> Signed-off-by: Xin Long <lucien.xin@gmail.com> Acked-by: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com> Acked-by: Neil Horman <nhorman@tuxdriver.com> Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r--include/net/sctp/sctp.h3
-rw-r--r--net/sctp/sctp_diag.c32
-rw-r--r--net/sctp/socket.c40
3 files changed, 36 insertions, 39 deletions
diff --git a/include/net/sctp/sctp.h b/include/net/sctp/sctp.h
index 06b4f515e157..d7d8cba01469 100644
--- a/include/net/sctp/sctp.h
+++ b/include/net/sctp/sctp.h
@@ -127,7 +127,8 @@ int sctp_transport_lookup_process(int (*cb)(struct sctp_transport *, void *),
127 const union sctp_addr *laddr, 127 const union sctp_addr *laddr,
128 const union sctp_addr *paddr, void *p); 128 const union sctp_addr *paddr, void *p);
129int sctp_for_each_transport(int (*cb)(struct sctp_transport *, void *), 129int sctp_for_each_transport(int (*cb)(struct sctp_transport *, void *),
130 struct net *net, int pos, void *p); 130 int (*cb_done)(struct sctp_transport *, void *),
131 struct net *net, int *pos, void *p);
131int sctp_for_each_endpoint(int (*cb)(struct sctp_endpoint *, void *), void *p); 132int sctp_for_each_endpoint(int (*cb)(struct sctp_endpoint *, void *), void *p);
132int sctp_get_sctp_info(struct sock *sk, struct sctp_association *asoc, 133int sctp_get_sctp_info(struct sock *sk, struct sctp_association *asoc,
133 struct sctp_info *info); 134 struct sctp_info *info);
diff --git a/net/sctp/sctp_diag.c b/net/sctp/sctp_diag.c
index e99518e79b52..7008a992749b 100644
--- a/net/sctp/sctp_diag.c
+++ b/net/sctp/sctp_diag.c
@@ -279,9 +279,11 @@ out:
279 return err; 279 return err;
280} 280}
281 281
282static int sctp_sock_dump(struct sock *sk, void *p) 282static int sctp_sock_dump(struct sctp_transport *tsp, void *p)
283{ 283{
284 struct sctp_endpoint *ep = tsp->asoc->ep;
284 struct sctp_comm_param *commp = p; 285 struct sctp_comm_param *commp = p;
286 struct sock *sk = ep->base.sk;
285 struct sk_buff *skb = commp->skb; 287 struct sk_buff *skb = commp->skb;
286 struct netlink_callback *cb = commp->cb; 288 struct netlink_callback *cb = commp->cb;
287 const struct inet_diag_req_v2 *r = commp->r; 289 const struct inet_diag_req_v2 *r = commp->r;
@@ -289,9 +291,7 @@ static int sctp_sock_dump(struct sock *sk, void *p)
289 int err = 0; 291 int err = 0;
290 292
291 lock_sock(sk); 293 lock_sock(sk);
292 if (!sctp_sk(sk)->ep) 294 list_for_each_entry(assoc, &ep->asocs, asocs) {
293 goto release;
294 list_for_each_entry(assoc, &sctp_sk(sk)->ep->asocs, asocs) {
295 if (cb->args[4] < cb->args[1]) 295 if (cb->args[4] < cb->args[1])
296 goto next; 296 goto next;
297 297
@@ -327,40 +327,30 @@ next:
327 cb->args[4]++; 327 cb->args[4]++;
328 } 328 }
329 cb->args[1] = 0; 329 cb->args[1] = 0;
330 cb->args[2]++;
331 cb->args[3] = 0; 330 cb->args[3] = 0;
332 cb->args[4] = 0; 331 cb->args[4] = 0;
333release: 332release:
334 release_sock(sk); 333 release_sock(sk);
335 sock_put(sk);
336 return err; 334 return err;
337} 335}
338 336
339static int sctp_get_sock(struct sctp_transport *tsp, void *p) 337static int sctp_sock_filter(struct sctp_transport *tsp, void *p)
340{ 338{
341 struct sctp_endpoint *ep = tsp->asoc->ep; 339 struct sctp_endpoint *ep = tsp->asoc->ep;
342 struct sctp_comm_param *commp = p; 340 struct sctp_comm_param *commp = p;
343 struct sock *sk = ep->base.sk; 341 struct sock *sk = ep->base.sk;
344 struct netlink_callback *cb = commp->cb;
345 const struct inet_diag_req_v2 *r = commp->r; 342 const struct inet_diag_req_v2 *r = commp->r;
346 struct sctp_association *assoc = 343 struct sctp_association *assoc =
347 list_entry(ep->asocs.next, struct sctp_association, asocs); 344 list_entry(ep->asocs.next, struct sctp_association, asocs);
348 345
349 /* find the ep only once through the transports by this condition */ 346 /* find the ep only once through the transports by this condition */
350 if (tsp->asoc != assoc) 347 if (tsp->asoc != assoc)
351 goto out; 348 return 0;
352 349
353 if (r->sdiag_family != AF_UNSPEC && sk->sk_family != r->sdiag_family) 350 if (r->sdiag_family != AF_UNSPEC && sk->sk_family != r->sdiag_family)
354 goto out; 351 return 0;
355
356 sock_hold(sk);
357 cb->args[5] = (long)sk;
358 352
359 return 1; 353 return 1;
360
361out:
362 cb->args[2]++;
363 return 0;
364} 354}
365 355
366static int sctp_ep_dump(struct sctp_endpoint *ep, void *p) 356static int sctp_ep_dump(struct sctp_endpoint *ep, void *p)
@@ -503,12 +493,8 @@ skip:
503 if (!(idiag_states & ~(TCPF_LISTEN | TCPF_CLOSE))) 493 if (!(idiag_states & ~(TCPF_LISTEN | TCPF_CLOSE)))
504 goto done; 494 goto done;
505 495
506next: 496 sctp_for_each_transport(sctp_sock_filter, sctp_sock_dump,
507 cb->args[5] = 0; 497 net, (int *)&cb->args[2], &commp);
508 sctp_for_each_transport(sctp_get_sock, net, cb->args[2], &commp);
509
510 if (cb->args[5] && !sctp_sock_dump((struct sock *)cb->args[5], &commp))
511 goto next;
512 498
513done: 499done:
514 cb->args[1] = cb->args[4]; 500 cb->args[1] = cb->args[4];
diff --git a/net/sctp/socket.c b/net/sctp/socket.c
index 1b00a1e09b93..d4730ada7f32 100644
--- a/net/sctp/socket.c
+++ b/net/sctp/socket.c
@@ -4658,29 +4658,39 @@ int sctp_transport_lookup_process(int (*cb)(struct sctp_transport *, void *),
4658EXPORT_SYMBOL_GPL(sctp_transport_lookup_process); 4658EXPORT_SYMBOL_GPL(sctp_transport_lookup_process);
4659 4659
4660int sctp_for_each_transport(int (*cb)(struct sctp_transport *, void *), 4660int sctp_for_each_transport(int (*cb)(struct sctp_transport *, void *),
4661 struct net *net, int pos, void *p) { 4661 int (*cb_done)(struct sctp_transport *, void *),
4662 struct net *net, int *pos, void *p) {
4662 struct rhashtable_iter hti; 4663 struct rhashtable_iter hti;
4663 void *obj; 4664 struct sctp_transport *tsp;
4664 int err; 4665 int ret;
4665
4666 err = sctp_transport_walk_start(&hti);
4667 if (err)
4668 return err;
4669 4666
4670 obj = sctp_transport_get_idx(net, &hti, pos + 1); 4667again:
4671 for (; !IS_ERR_OR_NULL(obj); obj = sctp_transport_get_next(net, &hti)) { 4668 ret = sctp_transport_walk_start(&hti);
4672 struct sctp_transport *transport = obj; 4669 if (ret)
4670 return ret;
4673 4671
4674 if (!sctp_transport_hold(transport)) 4672 tsp = sctp_transport_get_idx(net, &hti, *pos + 1);
4673 for (; !IS_ERR_OR_NULL(tsp); tsp = sctp_transport_get_next(net, &hti)) {
4674 if (!sctp_transport_hold(tsp))
4675 continue; 4675 continue;
4676 err = cb(transport, p); 4676 ret = cb(tsp, p);
4677 sctp_transport_put(transport); 4677 if (ret)
4678 if (err)
4679 break; 4678 break;
4679 (*pos)++;
4680 sctp_transport_put(tsp);
4680 } 4681 }
4681 sctp_transport_walk_stop(&hti); 4682 sctp_transport_walk_stop(&hti);
4682 4683
4683 return err; 4684 if (ret) {
4685 if (cb_done && !cb_done(tsp, p)) {
4686 (*pos)++;
4687 sctp_transport_put(tsp);
4688 goto again;
4689 }
4690 sctp_transport_put(tsp);
4691 }
4692
4693 return ret;
4684} 4694}
4685EXPORT_SYMBOL_GPL(sctp_for_each_transport); 4695EXPORT_SYMBOL_GPL(sctp_for_each_transport);
4686 4696