Skip to content

Commit

Permalink
net/sched: tcindex: update imperfect hash filters respecting rcu
Browse files Browse the repository at this point in the history
The imperfect hash area can be updated while packets are traversing,
which will cause a use-after-free when 'tcf_exts_exec()' is called
with the destroyed tcf_ext.

CPU 0:               CPU 1:
tcindex_set_parms    tcindex_classify
tcindex_lookup
                     tcindex_lookup
tcf_exts_change
                     tcf_exts_exec [UAF]

Stop operating on the shared area directly, by using a local copy,
and update the filter with 'rcu_replace_pointer()'. Delete the old
filter version only after a rcu grace period elapsed.

Fixes: 9b0d444 ("net: sched: avoid atomic swap in tcf_exts_change")
Reported-by: valis <sec@valis.email>
Suggested-by: valis <sec@valis.email>
Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: Pedro Tammela <pctammela@mojatatu.com>
Link: https://lore.kernel.org/r/20230209143739.279867-1-pctammela@mojatatu.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
  • Loading branch information
Pedro Tammela authored and Jakub Kicinski committed Feb 11, 2023
1 parent a122170 commit ee05917
Showing 1 changed file with 30 additions and 4 deletions.
34 changes: 30 additions & 4 deletions net/sched/cls_tcindex.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/refcount.h>
#include <linux/rcupdate.h>
#include <net/act_api.h>
#include <net/netlink.h>
#include <net/pkt_cls.h>
Expand Down Expand Up @@ -339,6 +340,7 @@ tcindex_set_parms(struct net *net, struct tcf_proto *tp, unsigned long base,
struct tcf_result cr = {};
int err, balloc = 0;
struct tcf_exts e;
bool update_h = false;

err = tcf_exts_init(&e, net, TCA_TCINDEX_ACT, TCA_TCINDEX_POLICE);
if (err < 0)
Expand Down Expand Up @@ -456,10 +458,13 @@ tcindex_set_parms(struct net *net, struct tcf_proto *tp, unsigned long base,
}
}

if (cp->perfect)
if (cp->perfect) {
r = cp->perfect + handle;
else
r = tcindex_lookup(cp, handle) ? : &new_filter_result;
} else {
/* imperfect area is updated in-place using rcu */
update_h = !!tcindex_lookup(cp, handle);
r = &new_filter_result;
}

if (r == &new_filter_result) {
f = kzalloc(sizeof(*f), GFP_KERNEL);
Expand All @@ -485,7 +490,28 @@ tcindex_set_parms(struct net *net, struct tcf_proto *tp, unsigned long base,

rcu_assign_pointer(tp->root, cp);

if (r == &new_filter_result) {
if (update_h) {
struct tcindex_filter __rcu **fp;
struct tcindex_filter *cf;

f->result.res = r->res;
tcf_exts_change(&f->result.exts, &r->exts);

/* imperfect area bucket */
fp = cp->h + (handle % cp->hash);

/* lookup the filter, guaranteed to exist */
for (cf = rcu_dereference_bh_rtnl(*fp); cf;
fp = &cf->next, cf = rcu_dereference_bh_rtnl(*fp))
if (cf->key == handle)
break;

f->next = cf->next;

cf = rcu_replace_pointer(*fp, f, 1);
tcf_exts_get_net(&cf->result.exts);
tcf_queue_work(&cf->rwork, tcindex_destroy_fexts_work);
} else if (r == &new_filter_result) {
struct tcindex_filter *nfp;
struct tcindex_filter __rcu **fp;

Expand Down

0 comments on commit ee05917

Please sign in to comment.