forked from mirrors/linux
		
	net/sched: tcindex: update imperfect hash filters respecting rcu
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: 9b0d4446b5 ("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>
			
			
This commit is contained in:
		
							parent
							
								
									a1221703a0
								
							
						
					
					
						commit
						ee059170b1
					
				
					 1 changed files with 30 additions and 4 deletions
				
			
		| 
						 | 
					@ -12,6 +12,7 @@
 | 
				
			||||||
#include <linux/errno.h>
 | 
					#include <linux/errno.h>
 | 
				
			||||||
#include <linux/slab.h>
 | 
					#include <linux/slab.h>
 | 
				
			||||||
#include <linux/refcount.h>
 | 
					#include <linux/refcount.h>
 | 
				
			||||||
 | 
					#include <linux/rcupdate.h>
 | 
				
			||||||
#include <net/act_api.h>
 | 
					#include <net/act_api.h>
 | 
				
			||||||
#include <net/netlink.h>
 | 
					#include <net/netlink.h>
 | 
				
			||||||
#include <net/pkt_cls.h>
 | 
					#include <net/pkt_cls.h>
 | 
				
			||||||
| 
						 | 
					@ -339,6 +340,7 @@ tcindex_set_parms(struct net *net, struct tcf_proto *tp, unsigned long base,
 | 
				
			||||||
	struct tcf_result cr = {};
 | 
						struct tcf_result cr = {};
 | 
				
			||||||
	int err, balloc = 0;
 | 
						int err, balloc = 0;
 | 
				
			||||||
	struct tcf_exts e;
 | 
						struct tcf_exts e;
 | 
				
			||||||
 | 
						bool update_h = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = tcf_exts_init(&e, net, TCA_TCINDEX_ACT, TCA_TCINDEX_POLICE);
 | 
						err = tcf_exts_init(&e, net, TCA_TCINDEX_ACT, TCA_TCINDEX_POLICE);
 | 
				
			||||||
	if (err < 0)
 | 
						if (err < 0)
 | 
				
			||||||
| 
						 | 
					@ -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;
 | 
							r = cp->perfect + handle;
 | 
				
			||||||
	else
 | 
						} else {
 | 
				
			||||||
		r = tcindex_lookup(cp, handle) ? : &new_filter_result;
 | 
							/* imperfect area is updated in-place using rcu */
 | 
				
			||||||
 | 
							update_h = !!tcindex_lookup(cp, handle);
 | 
				
			||||||
 | 
							r = &new_filter_result;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (r == &new_filter_result) {
 | 
						if (r == &new_filter_result) {
 | 
				
			||||||
		f = kzalloc(sizeof(*f), GFP_KERNEL);
 | 
							f = kzalloc(sizeof(*f), GFP_KERNEL);
 | 
				
			||||||
| 
						 | 
					@ -485,7 +490,28 @@ tcindex_set_parms(struct net *net, struct tcf_proto *tp, unsigned long base,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	rcu_assign_pointer(tp->root, cp);
 | 
						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 *nfp;
 | 
				
			||||||
		struct tcindex_filter __rcu **fp;
 | 
							struct tcindex_filter __rcu **fp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue