diff options
author | Christoph Paasch <christoph.paasch@uclouvain.be> | 2014-01-21 07:30:26 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2014-01-23 00:26:16 -0500 |
commit | 00ca9c5b2b11d44eaf20a4b647efc999734323ec (patch) | |
tree | fbcafcdcc52950691a0430ad377448216fbcfd84 | |
parent | 7705b10463622006dce368a47fa9d4dd7b6489ec (diff) |
tcp: metrics: Fix rcu-race when deleting multiple entries
In bbf852b96ebdc6d1 I introduced the tmlist, which allows to delete
multiple entries from the cache that match a specified destination if no
source-IP is specified.
However, as the cache is an RCU-list, we should not create this tmlist, as
it will change the tcpm_next pointer of the element that will be deleted
and so a thread iterating over the cache's entries while holding the
RCU-lock might get "redirected" to this tmlist.
This patch fixes this, by reverting back to the old behavior prior to
bbf852b96ebdc6d1, which means that we simply change the tcpm_next
pointer of the previous element (pp) to jump over the one we are
deleting.
The difference is that we call kfree_rcu() directly on the cache entry,
which allows us to delete multiple entries from the list.
Fixes: bbf852b96ebdc6d1 (tcp: metrics: Delete all entries matching a certain destination)
Signed-off-by: Christoph Paasch <christoph.paasch@uclouvain.be>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | net/ipv4/tcp_metrics.c | 14 |
1 files changed, 5 insertions, 9 deletions
diff --git a/net/ipv4/tcp_metrics.c b/net/ipv4/tcp_metrics.c index fa950941de65..9ae48b4a37d1 100644 --- a/net/ipv4/tcp_metrics.c +++ b/net/ipv4/tcp_metrics.c | |||
@@ -1019,13 +1019,13 @@ static int tcp_metrics_flush_all(struct net *net) | |||
1019 | static int tcp_metrics_nl_cmd_del(struct sk_buff *skb, struct genl_info *info) | 1019 | static int tcp_metrics_nl_cmd_del(struct sk_buff *skb, struct genl_info *info) |
1020 | { | 1020 | { |
1021 | struct tcpm_hash_bucket *hb; | 1021 | struct tcpm_hash_bucket *hb; |
1022 | struct tcp_metrics_block *tm, *tmlist = NULL; | 1022 | struct tcp_metrics_block *tm; |
1023 | struct tcp_metrics_block __rcu **pp; | 1023 | struct tcp_metrics_block __rcu **pp; |
1024 | struct inetpeer_addr saddr, daddr; | 1024 | struct inetpeer_addr saddr, daddr; |
1025 | unsigned int hash; | 1025 | unsigned int hash; |
1026 | struct net *net = genl_info_net(info); | 1026 | struct net *net = genl_info_net(info); |
1027 | int ret; | 1027 | int ret; |
1028 | bool src = true; | 1028 | bool src = true, found = false; |
1029 | 1029 | ||
1030 | ret = parse_nl_addr(info, &daddr, &hash, 1); | 1030 | ret = parse_nl_addr(info, &daddr, &hash, 1); |
1031 | if (ret < 0) | 1031 | if (ret < 0) |
@@ -1044,19 +1044,15 @@ static int tcp_metrics_nl_cmd_del(struct sk_buff *skb, struct genl_info *info) | |||
1044 | if (addr_same(&tm->tcpm_daddr, &daddr) && | 1044 | if (addr_same(&tm->tcpm_daddr, &daddr) && |
1045 | (!src || addr_same(&tm->tcpm_saddr, &saddr))) { | 1045 | (!src || addr_same(&tm->tcpm_saddr, &saddr))) { |
1046 | *pp = tm->tcpm_next; | 1046 | *pp = tm->tcpm_next; |
1047 | tm->tcpm_next = tmlist; | 1047 | kfree_rcu(tm, rcu_head); |
1048 | tmlist = tm; | 1048 | found = true; |
1049 | } else { | 1049 | } else { |
1050 | pp = &tm->tcpm_next; | 1050 | pp = &tm->tcpm_next; |
1051 | } | 1051 | } |
1052 | } | 1052 | } |
1053 | spin_unlock_bh(&tcp_metrics_lock); | 1053 | spin_unlock_bh(&tcp_metrics_lock); |
1054 | if (!tmlist) | 1054 | if (!found) |
1055 | return -ESRCH; | 1055 | return -ESRCH; |
1056 | for (tm = tmlist; tm; tm = tmlist) { | ||
1057 | tmlist = tm->tcpm_next; | ||
1058 | kfree_rcu(tm, rcu_head); | ||
1059 | } | ||
1060 | return 0; | 1056 | return 0; |
1061 | } | 1057 | } |
1062 | 1058 | ||