mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-11 21:14:07 +08:00
net/sched: matchall: Fix configuration race
In the current version, the matchall internal state is split into two
structs: cls_matchall_head and cls_matchall_filter. This makes little
sense, as matchall instance supports only one filter, and there is no
situation where one exists and the other does not. In addition, that led
to some races when filter was deleted while packet was processed.
Unify that two structs into one, thus simplifying the process of matchall
creation and deletion. As a result, the new, delete and get callbacks have
a dummy implementation where all the work is done in destroy and change
callbacks, as was done in cls_cgroup.
Fixes: bf3994d2ed
("net/sched: introduce Match-all classifier")
Reported-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Yotam Gigi <yotamg@mellanox.com>
Acked-by: Jiri Pirko <jiri@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
4993b39ab0
commit
fd62d9f5c5
@ -16,16 +16,11 @@
|
|||||||
#include <net/sch_generic.h>
|
#include <net/sch_generic.h>
|
||||||
#include <net/pkt_cls.h>
|
#include <net/pkt_cls.h>
|
||||||
|
|
||||||
struct cls_mall_filter {
|
struct cls_mall_head {
|
||||||
struct tcf_exts exts;
|
struct tcf_exts exts;
|
||||||
struct tcf_result res;
|
struct tcf_result res;
|
||||||
u32 handle;
|
u32 handle;
|
||||||
struct rcu_head rcu;
|
|
||||||
u32 flags;
|
u32 flags;
|
||||||
};
|
|
||||||
|
|
||||||
struct cls_mall_head {
|
|
||||||
struct cls_mall_filter *filter;
|
|
||||||
struct rcu_head rcu;
|
struct rcu_head rcu;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -33,38 +28,29 @@ static int mall_classify(struct sk_buff *skb, const struct tcf_proto *tp,
|
|||||||
struct tcf_result *res)
|
struct tcf_result *res)
|
||||||
{
|
{
|
||||||
struct cls_mall_head *head = rcu_dereference_bh(tp->root);
|
struct cls_mall_head *head = rcu_dereference_bh(tp->root);
|
||||||
struct cls_mall_filter *f = head->filter;
|
|
||||||
|
|
||||||
if (tc_skip_sw(f->flags))
|
if (tc_skip_sw(head->flags))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
return tcf_exts_exec(skb, &f->exts, res);
|
return tcf_exts_exec(skb, &head->exts, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int mall_init(struct tcf_proto *tp)
|
static int mall_init(struct tcf_proto *tp)
|
||||||
{
|
{
|
||||||
struct cls_mall_head *head;
|
|
||||||
|
|
||||||
head = kzalloc(sizeof(*head), GFP_KERNEL);
|
|
||||||
if (!head)
|
|
||||||
return -ENOBUFS;
|
|
||||||
|
|
||||||
rcu_assign_pointer(tp->root, head);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void mall_destroy_filter(struct rcu_head *head)
|
static void mall_destroy_rcu(struct rcu_head *rcu)
|
||||||
{
|
{
|
||||||
struct cls_mall_filter *f = container_of(head, struct cls_mall_filter, rcu);
|
struct cls_mall_head *head = container_of(rcu, struct cls_mall_head,
|
||||||
|
rcu);
|
||||||
|
|
||||||
tcf_exts_destroy(&f->exts);
|
tcf_exts_destroy(&head->exts);
|
||||||
|
kfree(head);
|
||||||
kfree(f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int mall_replace_hw_filter(struct tcf_proto *tp,
|
static int mall_replace_hw_filter(struct tcf_proto *tp,
|
||||||
struct cls_mall_filter *f,
|
struct cls_mall_head *head,
|
||||||
unsigned long cookie)
|
unsigned long cookie)
|
||||||
{
|
{
|
||||||
struct net_device *dev = tp->q->dev_queue->dev;
|
struct net_device *dev = tp->q->dev_queue->dev;
|
||||||
@ -74,7 +60,7 @@ static int mall_replace_hw_filter(struct tcf_proto *tp,
|
|||||||
offload.type = TC_SETUP_MATCHALL;
|
offload.type = TC_SETUP_MATCHALL;
|
||||||
offload.cls_mall = &mall_offload;
|
offload.cls_mall = &mall_offload;
|
||||||
offload.cls_mall->command = TC_CLSMATCHALL_REPLACE;
|
offload.cls_mall->command = TC_CLSMATCHALL_REPLACE;
|
||||||
offload.cls_mall->exts = &f->exts;
|
offload.cls_mall->exts = &head->exts;
|
||||||
offload.cls_mall->cookie = cookie;
|
offload.cls_mall->cookie = cookie;
|
||||||
|
|
||||||
return dev->netdev_ops->ndo_setup_tc(dev, tp->q->handle, tp->protocol,
|
return dev->netdev_ops->ndo_setup_tc(dev, tp->q->handle, tp->protocol,
|
||||||
@ -82,7 +68,7 @@ static int mall_replace_hw_filter(struct tcf_proto *tp,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void mall_destroy_hw_filter(struct tcf_proto *tp,
|
static void mall_destroy_hw_filter(struct tcf_proto *tp,
|
||||||
struct cls_mall_filter *f,
|
struct cls_mall_head *head,
|
||||||
unsigned long cookie)
|
unsigned long cookie)
|
||||||
{
|
{
|
||||||
struct net_device *dev = tp->q->dev_queue->dev;
|
struct net_device *dev = tp->q->dev_queue->dev;
|
||||||
@ -103,29 +89,20 @@ static bool mall_destroy(struct tcf_proto *tp, bool force)
|
|||||||
{
|
{
|
||||||
struct cls_mall_head *head = rtnl_dereference(tp->root);
|
struct cls_mall_head *head = rtnl_dereference(tp->root);
|
||||||
struct net_device *dev = tp->q->dev_queue->dev;
|
struct net_device *dev = tp->q->dev_queue->dev;
|
||||||
struct cls_mall_filter *f = head->filter;
|
|
||||||
|
|
||||||
if (!force && f)
|
if (!head)
|
||||||
return false;
|
return true;
|
||||||
|
|
||||||
if (f) {
|
if (tc_should_offload(dev, tp, head->flags))
|
||||||
if (tc_should_offload(dev, tp, f->flags))
|
mall_destroy_hw_filter(tp, head, (unsigned long) head);
|
||||||
mall_destroy_hw_filter(tp, f, (unsigned long) f);
|
|
||||||
|
|
||||||
call_rcu(&f->rcu, mall_destroy_filter);
|
call_rcu(&head->rcu, mall_destroy_rcu);
|
||||||
}
|
|
||||||
kfree_rcu(head, rcu);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static unsigned long mall_get(struct tcf_proto *tp, u32 handle)
|
static unsigned long mall_get(struct tcf_proto *tp, u32 handle)
|
||||||
{
|
{
|
||||||
struct cls_mall_head *head = rtnl_dereference(tp->root);
|
return 0UL;
|
||||||
struct cls_mall_filter *f = head->filter;
|
|
||||||
|
|
||||||
if (f && f->handle == handle)
|
|
||||||
return (unsigned long) f;
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct nla_policy mall_policy[TCA_MATCHALL_MAX + 1] = {
|
static const struct nla_policy mall_policy[TCA_MATCHALL_MAX + 1] = {
|
||||||
@ -134,7 +111,7 @@ static const struct nla_policy mall_policy[TCA_MATCHALL_MAX + 1] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static int mall_set_parms(struct net *net, struct tcf_proto *tp,
|
static int mall_set_parms(struct net *net, struct tcf_proto *tp,
|
||||||
struct cls_mall_filter *f,
|
struct cls_mall_head *head,
|
||||||
unsigned long base, struct nlattr **tb,
|
unsigned long base, struct nlattr **tb,
|
||||||
struct nlattr *est, bool ovr)
|
struct nlattr *est, bool ovr)
|
||||||
{
|
{
|
||||||
@ -147,11 +124,11 @@ static int mall_set_parms(struct net *net, struct tcf_proto *tp,
|
|||||||
return err;
|
return err;
|
||||||
|
|
||||||
if (tb[TCA_MATCHALL_CLASSID]) {
|
if (tb[TCA_MATCHALL_CLASSID]) {
|
||||||
f->res.classid = nla_get_u32(tb[TCA_MATCHALL_CLASSID]);
|
head->res.classid = nla_get_u32(tb[TCA_MATCHALL_CLASSID]);
|
||||||
tcf_bind_filter(tp, &f->res, base);
|
tcf_bind_filter(tp, &head->res, base);
|
||||||
}
|
}
|
||||||
|
|
||||||
tcf_exts_change(tp, &f->exts, &e);
|
tcf_exts_change(tp, &head->exts, &e);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -162,21 +139,17 @@ static int mall_change(struct net *net, struct sk_buff *in_skb,
|
|||||||
unsigned long *arg, bool ovr)
|
unsigned long *arg, bool ovr)
|
||||||
{
|
{
|
||||||
struct cls_mall_head *head = rtnl_dereference(tp->root);
|
struct cls_mall_head *head = rtnl_dereference(tp->root);
|
||||||
struct cls_mall_filter *fold = (struct cls_mall_filter *) *arg;
|
|
||||||
struct net_device *dev = tp->q->dev_queue->dev;
|
struct net_device *dev = tp->q->dev_queue->dev;
|
||||||
struct cls_mall_filter *f;
|
|
||||||
struct nlattr *tb[TCA_MATCHALL_MAX + 1];
|
struct nlattr *tb[TCA_MATCHALL_MAX + 1];
|
||||||
|
struct cls_mall_head *new;
|
||||||
u32 flags = 0;
|
u32 flags = 0;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (!tca[TCA_OPTIONS])
|
if (!tca[TCA_OPTIONS])
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
if (head->filter)
|
if (head)
|
||||||
return -EBUSY;
|
return -EEXIST;
|
||||||
|
|
||||||
if (fold)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
err = nla_parse_nested(tb, TCA_MATCHALL_MAX,
|
err = nla_parse_nested(tb, TCA_MATCHALL_MAX,
|
||||||
tca[TCA_OPTIONS], mall_policy);
|
tca[TCA_OPTIONS], mall_policy);
|
||||||
@ -189,23 +162,23 @@ static int mall_change(struct net *net, struct sk_buff *in_skb,
|
|||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
f = kzalloc(sizeof(*f), GFP_KERNEL);
|
new = kzalloc(sizeof(*new), GFP_KERNEL);
|
||||||
if (!f)
|
if (!new)
|
||||||
return -ENOBUFS;
|
return -ENOBUFS;
|
||||||
|
|
||||||
tcf_exts_init(&f->exts, TCA_MATCHALL_ACT, 0);
|
tcf_exts_init(&new->exts, TCA_MATCHALL_ACT, 0);
|
||||||
|
|
||||||
if (!handle)
|
if (!handle)
|
||||||
handle = 1;
|
handle = 1;
|
||||||
f->handle = handle;
|
new->handle = handle;
|
||||||
f->flags = flags;
|
new->flags = flags;
|
||||||
|
|
||||||
err = mall_set_parms(net, tp, f, base, tb, tca[TCA_RATE], ovr);
|
err = mall_set_parms(net, tp, new, base, tb, tca[TCA_RATE], ovr);
|
||||||
if (err)
|
if (err)
|
||||||
goto errout;
|
goto errout;
|
||||||
|
|
||||||
if (tc_should_offload(dev, tp, flags)) {
|
if (tc_should_offload(dev, tp, flags)) {
|
||||||
err = mall_replace_hw_filter(tp, f, (unsigned long) f);
|
err = mall_replace_hw_filter(tp, new, (unsigned long) new);
|
||||||
if (err) {
|
if (err) {
|
||||||
if (tc_skip_sw(flags))
|
if (tc_skip_sw(flags))
|
||||||
goto errout;
|
goto errout;
|
||||||
@ -214,39 +187,29 @@ static int mall_change(struct net *net, struct sk_buff *in_skb,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*arg = (unsigned long) f;
|
*arg = (unsigned long) head;
|
||||||
rcu_assign_pointer(head->filter, f);
|
rcu_assign_pointer(tp->root, new);
|
||||||
|
if (head)
|
||||||
|
call_rcu(&head->rcu, mall_destroy_rcu);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
errout:
|
errout:
|
||||||
kfree(f);
|
kfree(new);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int mall_delete(struct tcf_proto *tp, unsigned long arg)
|
static int mall_delete(struct tcf_proto *tp, unsigned long arg)
|
||||||
{
|
{
|
||||||
struct cls_mall_head *head = rtnl_dereference(tp->root);
|
return -EOPNOTSUPP;
|
||||||
struct cls_mall_filter *f = (struct cls_mall_filter *) arg;
|
|
||||||
struct net_device *dev = tp->q->dev_queue->dev;
|
|
||||||
|
|
||||||
if (tc_should_offload(dev, tp, f->flags))
|
|
||||||
mall_destroy_hw_filter(tp, f, (unsigned long) f);
|
|
||||||
|
|
||||||
RCU_INIT_POINTER(head->filter, NULL);
|
|
||||||
tcf_unbind_filter(tp, &f->res);
|
|
||||||
call_rcu(&f->rcu, mall_destroy_filter);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void mall_walk(struct tcf_proto *tp, struct tcf_walker *arg)
|
static void mall_walk(struct tcf_proto *tp, struct tcf_walker *arg)
|
||||||
{
|
{
|
||||||
struct cls_mall_head *head = rtnl_dereference(tp->root);
|
struct cls_mall_head *head = rtnl_dereference(tp->root);
|
||||||
struct cls_mall_filter *f = head->filter;
|
|
||||||
|
|
||||||
if (arg->count < arg->skip)
|
if (arg->count < arg->skip)
|
||||||
goto skip;
|
goto skip;
|
||||||
if (arg->fn(tp, (unsigned long) f, arg) < 0)
|
if (arg->fn(tp, (unsigned long) head, arg) < 0)
|
||||||
arg->stop = 1;
|
arg->stop = 1;
|
||||||
skip:
|
skip:
|
||||||
arg->count++;
|
arg->count++;
|
||||||
@ -255,28 +218,28 @@ skip:
|
|||||||
static int mall_dump(struct net *net, struct tcf_proto *tp, unsigned long fh,
|
static int mall_dump(struct net *net, struct tcf_proto *tp, unsigned long fh,
|
||||||
struct sk_buff *skb, struct tcmsg *t)
|
struct sk_buff *skb, struct tcmsg *t)
|
||||||
{
|
{
|
||||||
struct cls_mall_filter *f = (struct cls_mall_filter *) fh;
|
struct cls_mall_head *head = (struct cls_mall_head *) fh;
|
||||||
struct nlattr *nest;
|
struct nlattr *nest;
|
||||||
|
|
||||||
if (!f)
|
if (!head)
|
||||||
return skb->len;
|
return skb->len;
|
||||||
|
|
||||||
t->tcm_handle = f->handle;
|
t->tcm_handle = head->handle;
|
||||||
|
|
||||||
nest = nla_nest_start(skb, TCA_OPTIONS);
|
nest = nla_nest_start(skb, TCA_OPTIONS);
|
||||||
if (!nest)
|
if (!nest)
|
||||||
goto nla_put_failure;
|
goto nla_put_failure;
|
||||||
|
|
||||||
if (f->res.classid &&
|
if (head->res.classid &&
|
||||||
nla_put_u32(skb, TCA_MATCHALL_CLASSID, f->res.classid))
|
nla_put_u32(skb, TCA_MATCHALL_CLASSID, head->res.classid))
|
||||||
goto nla_put_failure;
|
goto nla_put_failure;
|
||||||
|
|
||||||
if (tcf_exts_dump(skb, &f->exts))
|
if (tcf_exts_dump(skb, &head->exts))
|
||||||
goto nla_put_failure;
|
goto nla_put_failure;
|
||||||
|
|
||||||
nla_nest_end(skb, nest);
|
nla_nest_end(skb, nest);
|
||||||
|
|
||||||
if (tcf_exts_dump_stats(skb, &f->exts) < 0)
|
if (tcf_exts_dump_stats(skb, &head->exts) < 0)
|
||||||
goto nla_put_failure;
|
goto nla_put_failure;
|
||||||
|
|
||||||
return skb->len;
|
return skb->len;
|
||||||
|
Loading…
Reference in New Issue
Block a user