diff options
author | Daniel Borkmann <daniel@iogearbox.net> | 2015-07-29 17:35:25 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2015-07-30 17:20:39 -0400 |
commit | 28e6b67f0b292f557468c139085303b15f1a678f (patch) | |
tree | a50641ae4b95168a1b64a423d612bc027b982817 | |
parent | 990c9b347245ef0d5df1c70659df68e7e9ba6a72 (diff) |
net: sched: fix refcount imbalance in actions
Since commit 55334a5db5cd ("net_sched: act: refuse to remove bound action
outside"), we end up with a wrong reference count for a tc action.
Test case 1:
FOO="1,6 0 0 4294967295,"
BAR="1,6 0 0 4294967294,"
tc filter add dev foo parent 1: bpf bytecode "$FOO" flowid 1:1 \
action bpf bytecode "$FOO"
tc actions show action bpf
action order 0: bpf bytecode '1,6 0 0 4294967295' default-action pipe
index 1 ref 1 bind 1
tc actions replace action bpf bytecode "$BAR" index 1
tc actions show action bpf
action order 0: bpf bytecode '1,6 0 0 4294967294' default-action pipe
index 1 ref 2 bind 1
tc actions replace action bpf bytecode "$FOO" index 1
tc actions show action bpf
action order 0: bpf bytecode '1,6 0 0 4294967295' default-action pipe
index 1 ref 3 bind 1
Test case 2:
FOO="1,6 0 0 4294967295,"
tc filter add dev foo parent 1: bpf bytecode "$FOO" flowid 1:1 action ok
tc actions show action gact
action order 0: gact action pass
random type none pass val 0
index 1 ref 1 bind 1
tc actions add action drop index 1
RTNETLINK answers: File exists [...]
tc actions show action gact
action order 0: gact action pass
random type none pass val 0
index 1 ref 2 bind 1
tc actions add action drop index 1
RTNETLINK answers: File exists [...]
tc actions show action gact
action order 0: gact action pass
random type none pass val 0
index 1 ref 3 bind 1
What happens is that in tcf_hash_check(), we check tcf_common for a given
index and increase tcfc_refcnt and conditionally tcfc_bindcnt when we've
found an existing action. Now there are the following cases:
1) We do a late binding of an action. In that case, we leave the
tcfc_refcnt/tcfc_bindcnt increased and are done with the ->init()
handler. This is correctly handeled.
2) We replace the given action, or we try to add one without replacing
and find out that the action at a specific index already exists
(thus, we go out with error in that case).
In case of 2), we have to undo the reference count increase from
tcf_hash_check() in the tcf_hash_check() function. Currently, we fail to
do so because of the 'tcfc_bindcnt > 0' check which bails out early with
an -EPERM error.
Now, while commit 55334a5db5cd prevents 'tc actions del action ...' on an
already classifier-bound action to drop the reference count (which could
then become negative, wrap around etc), this restriction only accounts for
invocations outside a specific action's ->init() handler.
One possible solution would be to add a flag thus we possibly trigger
the -EPERM ony in situations where it is indeed relevant.
After the patch, above test cases have correct reference count again.
Fixes: 55334a5db5cd ("net_sched: act: refuse to remove bound action outside")
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Reviewed-by: Cong Wang <cwang@twopensource.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | include/net/act_api.h | 8 | ||||
-rw-r--r-- | net/sched/act_api.c | 11 |
2 files changed, 13 insertions, 6 deletions
diff --git a/include/net/act_api.h b/include/net/act_api.h index 3ee4c92afd1b..931738bc5bba 100644 --- a/include/net/act_api.h +++ b/include/net/act_api.h | |||
@@ -99,7 +99,6 @@ struct tc_action_ops { | |||
99 | 99 | ||
100 | int tcf_hash_search(struct tc_action *a, u32 index); | 100 | int tcf_hash_search(struct tc_action *a, u32 index); |
101 | void tcf_hash_destroy(struct tc_action *a); | 101 | void tcf_hash_destroy(struct tc_action *a); |
102 | int tcf_hash_release(struct tc_action *a, int bind); | ||
103 | u32 tcf_hash_new_index(struct tcf_hashinfo *hinfo); | 102 | u32 tcf_hash_new_index(struct tcf_hashinfo *hinfo); |
104 | int tcf_hash_check(u32 index, struct tc_action *a, int bind); | 103 | int tcf_hash_check(u32 index, struct tc_action *a, int bind); |
105 | int tcf_hash_create(u32 index, struct nlattr *est, struct tc_action *a, | 104 | int tcf_hash_create(u32 index, struct nlattr *est, struct tc_action *a, |
@@ -107,6 +106,13 @@ int tcf_hash_create(u32 index, struct nlattr *est, struct tc_action *a, | |||
107 | void tcf_hash_cleanup(struct tc_action *a, struct nlattr *est); | 106 | void tcf_hash_cleanup(struct tc_action *a, struct nlattr *est); |
108 | void tcf_hash_insert(struct tc_action *a); | 107 | void tcf_hash_insert(struct tc_action *a); |
109 | 108 | ||
109 | int __tcf_hash_release(struct tc_action *a, bool bind, bool strict); | ||
110 | |||
111 | static inline int tcf_hash_release(struct tc_action *a, bool bind) | ||
112 | { | ||
113 | return __tcf_hash_release(a, bind, false); | ||
114 | } | ||
115 | |||
110 | int tcf_register_action(struct tc_action_ops *a, unsigned int mask); | 116 | int tcf_register_action(struct tc_action_ops *a, unsigned int mask); |
111 | int tcf_unregister_action(struct tc_action_ops *a); | 117 | int tcf_unregister_action(struct tc_action_ops *a); |
112 | int tcf_action_destroy(struct list_head *actions, int bind); | 118 | int tcf_action_destroy(struct list_head *actions, int bind); |
diff --git a/net/sched/act_api.c b/net/sched/act_api.c index af427a3dbcba..43ec92680ae8 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c | |||
@@ -45,7 +45,7 @@ void tcf_hash_destroy(struct tc_action *a) | |||
45 | } | 45 | } |
46 | EXPORT_SYMBOL(tcf_hash_destroy); | 46 | EXPORT_SYMBOL(tcf_hash_destroy); |
47 | 47 | ||
48 | int tcf_hash_release(struct tc_action *a, int bind) | 48 | int __tcf_hash_release(struct tc_action *a, bool bind, bool strict) |
49 | { | 49 | { |
50 | struct tcf_common *p = a->priv; | 50 | struct tcf_common *p = a->priv; |
51 | int ret = 0; | 51 | int ret = 0; |
@@ -53,7 +53,7 @@ int tcf_hash_release(struct tc_action *a, int bind) | |||
53 | if (p) { | 53 | if (p) { |
54 | if (bind) | 54 | if (bind) |
55 | p->tcfc_bindcnt--; | 55 | p->tcfc_bindcnt--; |
56 | else if (p->tcfc_bindcnt > 0) | 56 | else if (strict && p->tcfc_bindcnt > 0) |
57 | return -EPERM; | 57 | return -EPERM; |
58 | 58 | ||
59 | p->tcfc_refcnt--; | 59 | p->tcfc_refcnt--; |
@@ -64,9 +64,10 @@ int tcf_hash_release(struct tc_action *a, int bind) | |||
64 | ret = 1; | 64 | ret = 1; |
65 | } | 65 | } |
66 | } | 66 | } |
67 | |||
67 | return ret; | 68 | return ret; |
68 | } | 69 | } |
69 | EXPORT_SYMBOL(tcf_hash_release); | 70 | EXPORT_SYMBOL(__tcf_hash_release); |
70 | 71 | ||
71 | static int tcf_dump_walker(struct sk_buff *skb, struct netlink_callback *cb, | 72 | static int tcf_dump_walker(struct sk_buff *skb, struct netlink_callback *cb, |
72 | struct tc_action *a) | 73 | struct tc_action *a) |
@@ -136,7 +137,7 @@ static int tcf_del_walker(struct sk_buff *skb, struct tc_action *a) | |||
136 | head = &hinfo->htab[tcf_hash(i, hinfo->hmask)]; | 137 | head = &hinfo->htab[tcf_hash(i, hinfo->hmask)]; |
137 | hlist_for_each_entry_safe(p, n, head, tcfc_head) { | 138 | hlist_for_each_entry_safe(p, n, head, tcfc_head) { |
138 | a->priv = p; | 139 | a->priv = p; |
139 | ret = tcf_hash_release(a, 0); | 140 | ret = __tcf_hash_release(a, false, true); |
140 | if (ret == ACT_P_DELETED) { | 141 | if (ret == ACT_P_DELETED) { |
141 | module_put(a->ops->owner); | 142 | module_put(a->ops->owner); |
142 | n_i++; | 143 | n_i++; |
@@ -408,7 +409,7 @@ int tcf_action_destroy(struct list_head *actions, int bind) | |||
408 | int ret = 0; | 409 | int ret = 0; |
409 | 410 | ||
410 | list_for_each_entry_safe(a, tmp, actions, list) { | 411 | list_for_each_entry_safe(a, tmp, actions, list) { |
411 | ret = tcf_hash_release(a, bind); | 412 | ret = __tcf_hash_release(a, bind, true); |
412 | if (ret == ACT_P_DELETED) | 413 | if (ret == ACT_P_DELETED) |
413 | module_put(a->ops->owner); | 414 | module_put(a->ops->owner); |
414 | else if (ret < 0) | 415 | else if (ret < 0) |