diff options
author | Sowmini Varadhan <sowmini.varadhan@oracle.com> | 2018-02-03 15:26:51 +0300 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2018-02-08 23:23:52 +0300 |
commit | ebeeb1ad9b8adcc37c2ec21a96f39e9d35199b46 (patch) | |
tree | 25c1f3a044ab1c8f8e15031f606b00ae35db2ed0 /net/rds/connection.c | |
parent | 79a8a642bf05cd0dced20621f6fef9d884124abd (diff) | |
download | linux-ebeeb1ad9b8adcc37c2ec21a96f39e9d35199b46.tar.xz |
rds: tcp: use rds_destroy_pending() to synchronize netns/module teardown and rds connection/workq management
An rds_connection can get added during netns deletion between lines 528
and 529 of
506 static void rds_tcp_kill_sock(struct net *net)
:
/* code to pull out all the rds_connections that should be destroyed */
:
528 spin_unlock_irq(&rds_tcp_conn_lock);
529 list_for_each_entry_safe(tc, _tc, &tmp_list, t_tcp_node)
530 rds_conn_destroy(tc->t_cpath->cp_conn);
Such an rds_connection would miss out the rds_conn_destroy()
loop (that cancels all pending work) and (if it was scheduled
after netns deletion) could trigger the use-after-free.
A similar race-window exists for the module unload path
in rds_tcp_exit -> rds_tcp_destroy_conns
Concurrency with netns deletion (rds_tcp_kill_sock()) must be handled
by checking check_net() before enqueuing new work or adding new
connections.
Concurrency with module-unload is handled by maintaining a module
specific flag that is set at the start of the module exit function,
and must be checked before enqueuing new work or adding new connections.
This commit refactors existing RDS_DESTROY_PENDING checks added by
commit 3db6e0d172c9 ("rds: use RCU to synchronize work-enqueue with
connection teardown") and consolidates all the concurrency checks
listed above into the function rds_destroy_pending().
Signed-off-by: Sowmini Varadhan <sowmini.varadhan@oracle.com>
Acked-by: Santosh Shilimkar <santosh.shilimkar@oracle.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/rds/connection.c')
-rw-r--r-- | net/rds/connection.c | 15 |
1 files changed, 9 insertions, 6 deletions
diff --git a/net/rds/connection.c b/net/rds/connection.c index b10c0ef36d8d..94e190febfdd 100644 --- a/net/rds/connection.c +++ b/net/rds/connection.c @@ -220,8 +220,13 @@ static struct rds_connection *__rds_conn_create(struct net *net, is_outgoing); conn->c_path[i].cp_index = i; } - ret = trans->conn_alloc(conn, gfp); + rcu_read_lock(); + if (rds_destroy_pending(conn)) + ret = -ENETDOWN; + else + ret = trans->conn_alloc(conn, gfp); if (ret) { + rcu_read_unlock(); kfree(conn->c_path); kmem_cache_free(rds_conn_slab, conn); conn = ERR_PTR(ret); @@ -283,6 +288,7 @@ static struct rds_connection *__rds_conn_create(struct net *net, } } spin_unlock_irqrestore(&rds_conn_lock, flags); + rcu_read_unlock(); out: return conn; @@ -382,13 +388,10 @@ static void rds_conn_path_destroy(struct rds_conn_path *cp) { struct rds_message *rm, *rtmp; - set_bit(RDS_DESTROY_PENDING, &cp->cp_flags); - if (!cp->cp_transport_data) return; /* make sure lingering queued work won't try to ref the conn */ - synchronize_rcu(); cancel_delayed_work_sync(&cp->cp_send_w); cancel_delayed_work_sync(&cp->cp_recv_w); @@ -691,7 +694,7 @@ void rds_conn_path_drop(struct rds_conn_path *cp, bool destroy) atomic_set(&cp->cp_state, RDS_CONN_ERROR); rcu_read_lock(); - if (!destroy && test_bit(RDS_DESTROY_PENDING, &cp->cp_flags)) { + if (!destroy && rds_destroy_pending(cp->cp_conn)) { rcu_read_unlock(); return; } @@ -714,7 +717,7 @@ EXPORT_SYMBOL_GPL(rds_conn_drop); void rds_conn_path_connect_if_down(struct rds_conn_path *cp) { rcu_read_lock(); - if (test_bit(RDS_DESTROY_PENDING, &cp->cp_flags)) { + if (rds_destroy_pending(cp->cp_conn)) { rcu_read_unlock(); return; } |