Commit 2e24cd75 authored by Cong Wang's avatar Cong Wang Committed by David S. Miller

net_sched: fix ops->bind_class() implementations

The current implementations of ops->bind_class() are merely
searching for classid and updating class in the struct tcf_result,
without invoking either of cl_ops->bind_tcf() or
cl_ops->unbind_tcf(). This breaks the design of them as qdisc's
like cbq use them to count filters too. This is why syzbot triggered
the warning in cbq_destroy_class().

In order to fix this, we have to call cl_ops->bind_tcf() and
cl_ops->unbind_tcf() like the filter binding path. This patch does
so by refactoring out two helper functions __tcf_bind_filter()
and __tcf_unbind_filter(), which are lockless and accept a Qdisc
pointer, then teaching each implementation to call them correctly.

Note, we merely pass the Qdisc pointer as an opaque pointer to
each filter, they only need to pass it down to the helper
functions without understanding it at all.

Fixes: 07d79fc7 ("net_sched: add reverse binding for tc class")
Reported-and-tested-by: syzbot+0a0596220218fcb603a8@syzkaller.appspotmail.com
Reported-and-tested-by: syzbot+63bdb6006961d8c917c6@syzkaller.appspotmail.com
Cc: Jamal Hadi Salim <jhs@mojatatu.com>
Cc: Jiri Pirko <jiri@resnulli.us>
Signed-off-by: default avatarCong Wang <xiyou.wangcong@gmail.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 16b25d1a
......@@ -141,31 +141,38 @@ __cls_set_class(unsigned long *clp, unsigned long cl)
return xchg(clp, cl);
}
static inline unsigned long
cls_set_class(struct Qdisc *q, unsigned long *clp, unsigned long cl)
static inline void
__tcf_bind_filter(struct Qdisc *q, struct tcf_result *r, unsigned long base)
{
unsigned long old_cl;
unsigned long cl;
sch_tree_lock(q);
old_cl = __cls_set_class(clp, cl);
sch_tree_unlock(q);
return old_cl;
cl = q->ops->cl_ops->bind_tcf(q, base, r->classid);
cl = __cls_set_class(&r->class, cl);
if (cl)
q->ops->cl_ops->unbind_tcf(q, cl);
}
static inline void
tcf_bind_filter(struct tcf_proto *tp, struct tcf_result *r, unsigned long base)
{
struct Qdisc *q = tp->chain->block->q;
unsigned long cl;
/* Check q as it is not set for shared blocks. In that case,
* setting class is not supported.
*/
if (!q)
return;
cl = q->ops->cl_ops->bind_tcf(q, base, r->classid);
cl = cls_set_class(q, &r->class, cl);
if (cl)
sch_tree_lock(q);
__tcf_bind_filter(q, r, base);
sch_tree_unlock(q);
}
static inline void
__tcf_unbind_filter(struct Qdisc *q, struct tcf_result *r)
{
unsigned long cl;
if ((cl = __cls_set_class(&r->class, 0)) != 0)
q->ops->cl_ops->unbind_tcf(q, cl);
}
......@@ -173,12 +180,10 @@ static inline void
tcf_unbind_filter(struct tcf_proto *tp, struct tcf_result *r)
{
struct Qdisc *q = tp->chain->block->q;
unsigned long cl;
if (!q)
return;
if ((cl = __cls_set_class(&r->class, 0)) != 0)
q->ops->cl_ops->unbind_tcf(q, cl);
__tcf_unbind_filter(q, r);
}
struct tcf_exts {
......
......@@ -318,7 +318,8 @@ struct tcf_proto_ops {
void *type_data);
void (*hw_del)(struct tcf_proto *tp,
void *type_data);
void (*bind_class)(void *, u32, unsigned long);
void (*bind_class)(void *, u32, unsigned long,
void *, unsigned long);
void * (*tmplt_create)(struct net *net,
struct tcf_chain *chain,
struct nlattr **tca,
......
......@@ -263,12 +263,17 @@ static void basic_walk(struct tcf_proto *tp, struct tcf_walker *arg,
}
}
static void basic_bind_class(void *fh, u32 classid, unsigned long cl)
static void basic_bind_class(void *fh, u32 classid, unsigned long cl, void *q,
unsigned long base)
{
struct basic_filter *f = fh;
if (f && f->res.classid == classid)
f->res.class = cl;
if (f && f->res.classid == classid) {
if (cl)
__tcf_bind_filter(q, &f->res, base);
else
__tcf_unbind_filter(q, &f->res);
}
}
static int basic_dump(struct net *net, struct tcf_proto *tp, void *fh,
......
......@@ -631,12 +631,17 @@ static int cls_bpf_dump(struct net *net, struct tcf_proto *tp, void *fh,
return -1;
}
static void cls_bpf_bind_class(void *fh, u32 classid, unsigned long cl)
static void cls_bpf_bind_class(void *fh, u32 classid, unsigned long cl,
void *q, unsigned long base)
{
struct cls_bpf_prog *prog = fh;
if (prog && prog->res.classid == classid)
prog->res.class = cl;
if (prog && prog->res.classid == classid) {
if (cl)
__tcf_bind_filter(q, &prog->res, base);
else
__tcf_unbind_filter(q, &prog->res);
}
}
static void cls_bpf_walk(struct tcf_proto *tp, struct tcf_walker *arg,
......
......@@ -2765,12 +2765,17 @@ static int fl_tmplt_dump(struct sk_buff *skb, struct net *net, void *tmplt_priv)
return -EMSGSIZE;
}
static void fl_bind_class(void *fh, u32 classid, unsigned long cl)
static void fl_bind_class(void *fh, u32 classid, unsigned long cl, void *q,
unsigned long base)
{
struct cls_fl_filter *f = fh;
if (f && f->res.classid == classid)
f->res.class = cl;
if (f && f->res.classid == classid) {
if (cl)
__tcf_bind_filter(q, &f->res, base);
else
__tcf_unbind_filter(q, &f->res);
}
}
static bool fl_delete_empty(struct tcf_proto *tp)
......
......@@ -419,12 +419,17 @@ static int fw_dump(struct net *net, struct tcf_proto *tp, void *fh,
return -1;
}
static void fw_bind_class(void *fh, u32 classid, unsigned long cl)
static void fw_bind_class(void *fh, u32 classid, unsigned long cl, void *q,
unsigned long base)
{
struct fw_filter *f = fh;
if (f && f->res.classid == classid)
f->res.class = cl;
if (f && f->res.classid == classid) {
if (cl)
__tcf_bind_filter(q, &f->res, base);
else
__tcf_unbind_filter(q, &f->res);
}
}
static struct tcf_proto_ops cls_fw_ops __read_mostly = {
......
......@@ -393,12 +393,17 @@ static int mall_dump(struct net *net, struct tcf_proto *tp, void *fh,
return -1;
}
static void mall_bind_class(void *fh, u32 classid, unsigned long cl)
static void mall_bind_class(void *fh, u32 classid, unsigned long cl, void *q,
unsigned long base)
{
struct cls_mall_head *head = fh;
if (head && head->res.classid == classid)
head->res.class = cl;
if (head && head->res.classid == classid) {
if (cl)
__tcf_bind_filter(q, &head->res, base);
else
__tcf_unbind_filter(q, &head->res);
}
}
static struct tcf_proto_ops cls_mall_ops __read_mostly = {
......
......@@ -641,12 +641,17 @@ static int route4_dump(struct net *net, struct tcf_proto *tp, void *fh,
return -1;
}
static void route4_bind_class(void *fh, u32 classid, unsigned long cl)
static void route4_bind_class(void *fh, u32 classid, unsigned long cl, void *q,
unsigned long base)
{
struct route4_filter *f = fh;
if (f && f->res.classid == classid)
f->res.class = cl;
if (f && f->res.classid == classid) {
if (cl)
__tcf_bind_filter(q, &f->res, base);
else
__tcf_unbind_filter(q, &f->res);
}
}
static struct tcf_proto_ops cls_route4_ops __read_mostly = {
......
......@@ -738,12 +738,17 @@ static int rsvp_dump(struct net *net, struct tcf_proto *tp, void *fh,
return -1;
}
static void rsvp_bind_class(void *fh, u32 classid, unsigned long cl)
static void rsvp_bind_class(void *fh, u32 classid, unsigned long cl, void *q,
unsigned long base)
{
struct rsvp_filter *f = fh;
if (f && f->res.classid == classid)
f->res.class = cl;
if (f && f->res.classid == classid) {
if (cl)
__tcf_bind_filter(q, &f->res, base);
else
__tcf_unbind_filter(q, &f->res);
}
}
static struct tcf_proto_ops RSVP_OPS __read_mostly = {
......
......@@ -654,12 +654,17 @@ static int tcindex_dump(struct net *net, struct tcf_proto *tp, void *fh,
return -1;
}
static void tcindex_bind_class(void *fh, u32 classid, unsigned long cl)
static void tcindex_bind_class(void *fh, u32 classid, unsigned long cl,
void *q, unsigned long base)
{
struct tcindex_filter_result *r = fh;
if (r && r->res.classid == classid)
r->res.class = cl;
if (r && r->res.classid == classid) {
if (cl)
__tcf_bind_filter(q, &r->res, base);
else
__tcf_unbind_filter(q, &r->res);
}
}
static struct tcf_proto_ops cls_tcindex_ops __read_mostly = {
......
......@@ -1255,12 +1255,17 @@ static int u32_reoffload(struct tcf_proto *tp, bool add, flow_setup_cb_t *cb,
return 0;
}
static void u32_bind_class(void *fh, u32 classid, unsigned long cl)
static void u32_bind_class(void *fh, u32 classid, unsigned long cl, void *q,
unsigned long base)
{
struct tc_u_knode *n = fh;
if (n && n->res.classid == classid)
n->res.class = cl;
if (n && n->res.classid == classid) {
if (cl)
__tcf_bind_filter(q, &n->res, base);
else
__tcf_unbind_filter(q, &n->res);
}
}
static int u32_dump(struct net *net, struct tcf_proto *tp, void *fh,
......
......@@ -1891,8 +1891,9 @@ static int tclass_del_notify(struct net *net,
struct tcf_bind_args {
struct tcf_walker w;
u32 classid;
unsigned long base;
unsigned long cl;
u32 classid;
};
static int tcf_node_bind(struct tcf_proto *tp, void *n, struct tcf_walker *arg)
......@@ -1903,7 +1904,7 @@ static int tcf_node_bind(struct tcf_proto *tp, void *n, struct tcf_walker *arg)
struct Qdisc *q = tcf_block_q(tp->chain->block);
sch_tree_lock(q);
tp->ops->bind_class(n, a->classid, a->cl);
tp->ops->bind_class(n, a->classid, a->cl, q, a->base);
sch_tree_unlock(q);
}
return 0;
......@@ -1936,6 +1937,7 @@ static void tc_bind_tclass(struct Qdisc *q, u32 portid, u32 clid,
arg.w.fn = tcf_node_bind;
arg.classid = clid;
arg.base = cl;
arg.cl = new_cl;
tp->ops->walk(tp, &arg.w, true);
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment