diff options
Diffstat (limited to 'main/linux-grsec/0014-flow-virtualize-flow-cache-entry-methods.patch')
| -rw-r--r-- | main/linux-grsec/0014-flow-virtualize-flow-cache-entry-methods.patch | 513 | 
1 files changed, 513 insertions, 0 deletions
diff --git a/main/linux-grsec/0014-flow-virtualize-flow-cache-entry-methods.patch b/main/linux-grsec/0014-flow-virtualize-flow-cache-entry-methods.patch new file mode 100644 index 0000000000..5c4a9ea594 --- /dev/null +++ b/main/linux-grsec/0014-flow-virtualize-flow-cache-entry-methods.patch @@ -0,0 +1,513 @@ +From d56cd1c538e5448fe43acc69991aa842f382a622 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Timo=20Ter=C3=A4s?= <timo.teras@iki.fi> +Date: Wed, 7 Apr 2010 00:30:04 +0000 +Subject: [PATCH 14/18] flow: virtualize flow cache entry methods + +This allows to validate the cached object before returning it. +It also allows to destruct object properly, if the last reference +was held in flow cache. This is also a prepartion for caching +bundles in the flow cache. + +In return for virtualizing the methods, we save on: +- not having to regenerate the whole flow cache on policy removal: +  each flow matching a killed policy gets refreshed as the getter +  function notices it smartly. +- we do not have to call flow_cache_flush from policy gc, since the +  flow cache now properly deletes the object if it had any references + +Signed-off-by: Timo Teras <timo.teras@iki.fi> +Acked-by: Herbert Xu <herbert@gondor.apana.org.au> +Signed-off-by: David S. Miller <davem@davemloft.net> +(backported from commit fe1a5f031e76bd8761a7803d75b95ee96e84a574) +--- + include/net/flow.h     |   23 +++++++-- + include/net/xfrm.h     |    3 + + net/core/flow.c        |  128 +++++++++++++++++++++++++---------------------- + net/xfrm/xfrm_policy.c |  111 ++++++++++++++++++++++++++++-------------- + 4 files changed, 164 insertions(+), 101 deletions(-) + +diff --git a/include/net/flow.h b/include/net/flow.h +index 809970b..bb08692 100644 +--- a/include/net/flow.h ++++ b/include/net/flow.h +@@ -86,11 +86,26 @@ struct flowi { +  + struct net; + struct sock; +-typedef int (*flow_resolve_t)(struct net *net, struct flowi *key, u16 family, +-			      u8 dir, void **objp, atomic_t **obj_refp); ++struct flow_cache_ops; ++ ++struct flow_cache_object { ++	const struct flow_cache_ops *ops; ++}; ++ ++struct flow_cache_ops { ++	struct flow_cache_object *(*get)(struct flow_cache_object *); ++	int (*check)(struct flow_cache_object *); ++	void (*delete)(struct flow_cache_object *); ++}; ++ ++typedef struct flow_cache_object *(*flow_resolve_t)( ++		struct net *net, struct flowi *key, u16 family, ++		u8 dir, struct flow_cache_object *oldobj, void *ctx); ++ ++extern struct flow_cache_object *flow_cache_lookup( ++		struct net *net, struct flowi *key, u16 family, ++		u8 dir, flow_resolve_t resolver, void *ctx); +  +-extern void *flow_cache_lookup(struct net *net, struct flowi *key, u16 family, +-			       u8 dir, flow_resolve_t resolver); + extern void flow_cache_flush(void); + extern atomic_t flow_cache_genid; +  +diff --git a/include/net/xfrm.h b/include/net/xfrm.h +index 6960be2..6023a48 100644 +--- a/include/net/xfrm.h ++++ b/include/net/xfrm.h +@@ -19,6 +19,8 @@ + #include <net/route.h> + #include <net/ipv6.h> + #include <net/ip6_fib.h> ++#include <net/flow.h> ++ + #ifdef CONFIG_XFRM_STATISTICS + #include <net/snmp.h> + #endif +@@ -482,6 +484,7 @@ struct xfrm_policy + 	atomic_t		refcnt; + 	struct timer_list	timer; +  ++	struct flow_cache_object flo; + 	u32			priority; + 	u32			index; + 	struct xfrm_selector	selector; +diff --git a/net/core/flow.c b/net/core/flow.c +index 1d27ca6..521df52 100644 +--- a/net/core/flow.c ++++ b/net/core/flow.c +@@ -26,17 +26,16 @@ + #include <linux/security.h> +  + struct flow_cache_entry { +-	struct flow_cache_entry	*next; +-	u16			family; +-	u8			dir; +-	u32			genid; +-	struct flowi		key; +-	void			*object; +-	atomic_t		*object_ref; ++	struct flow_cache_entry		*next; ++	u16				family; ++	u8				dir; ++	u32				genid; ++	struct flowi			key; ++	struct flow_cache_object	*object; + }; +  + struct flow_cache_percpu { +-	struct flow_cache_entry **	hash_table; ++	struct flow_cache_entry		**hash_table; + 	int				hash_count; + 	u32				hash_rnd; + 	int				hash_rnd_recalc; +@@ -44,7 +43,7 @@ struct flow_cache_percpu { + }; +  + struct flow_flush_info { +-	struct flow_cache *		cache; ++	struct flow_cache		*cache; + 	atomic_t			cpuleft; + 	struct completion		completion; + }; +@@ -52,7 +51,7 @@ struct flow_flush_info { + struct flow_cache { + 	u32				hash_shift; + 	unsigned long			order; +-	struct flow_cache_percpu *	percpu; ++	struct flow_cache_percpu	*percpu; + 	struct notifier_block		hotcpu_notifier; + 	int				low_watermark; + 	int				high_watermark; +@@ -78,12 +77,21 @@ static void flow_cache_new_hashrnd(unsigned long arg) + 	add_timer(&fc->rnd_timer); + } +  ++static int flow_entry_valid(struct flow_cache_entry *fle) ++{ ++	if (atomic_read(&flow_cache_genid) != fle->genid) ++		return 0; ++	if (fle->object && !fle->object->ops->check(fle->object)) ++		return 0; ++	return 1; ++} ++ + static void flow_entry_kill(struct flow_cache *fc, + 			    struct flow_cache_percpu *fcp, + 			    struct flow_cache_entry *fle) + { + 	if (fle->object) +-		atomic_dec(fle->object_ref); ++		fle->object->ops->delete(fle->object); + 	kmem_cache_free(flow_cachep, fle); + 	fcp->hash_count--; + } +@@ -96,16 +104,18 @@ static void __flow_cache_shrink(struct flow_cache *fc, + 	int i; +  + 	for (i = 0; i < flow_cache_hash_size(fc); i++) { +-		int k = 0; ++		int saved = 0; +  + 		flp = &fcp->hash_table[i]; +-		while ((fle = *flp) != NULL && k < shrink_to) { +-			k++; +-			flp = &fle->next; +-		} + 		while ((fle = *flp) != NULL) { +-			*flp = fle->next; +-			flow_entry_kill(fc, fcp, fle); ++			if (saved < shrink_to && ++			    flow_entry_valid(fle)) { ++				saved++; ++				flp = &fle->next; ++			} else { ++				*flp = fle->next; ++				flow_entry_kill(fc, fcp, fle); ++			} + 		} + 	} + } +@@ -166,18 +176,21 @@ static int flow_key_compare(struct flowi *key1, struct flowi *key2) + 	return 0; + } +  +-void *flow_cache_lookup(struct net *net, struct flowi *key, u16 family, u8 dir, +-			flow_resolve_t resolver) ++struct flow_cache_object * ++flow_cache_lookup(struct net *net, struct flowi *key, u16 family, u8 dir, ++		  flow_resolve_t resolver, void *ctx) + { + 	struct flow_cache *fc = &flow_cache_global; + 	struct flow_cache_percpu *fcp; + 	struct flow_cache_entry *fle, **head; ++	struct flow_cache_object *flo; + 	unsigned int hash; +  + 	local_bh_disable(); + 	fcp = per_cpu_ptr(fc->percpu, smp_processor_id()); +  + 	fle = NULL; ++	flo = NULL; + 	/* Packet really early in init?  Making flow_cache_init a + 	 * pre-smp initcall would solve this.  --RR */ + 	if (!fcp->hash_table) +@@ -185,27 +198,17 @@ void *flow_cache_lookup(struct net *net, struct flowi *key, u16 family, u8 dir, +  + 	if (fcp->hash_rnd_recalc) + 		flow_new_hash_rnd(fc, fcp); +-	hash = flow_hash_code(fc, fcp, key); +  ++	hash = flow_hash_code(fc, fcp, key); + 	head = &fcp->hash_table[hash]; + 	for (fle = *head; fle; fle = fle->next) { + 		if (fle->family == family && + 		    fle->dir == dir && +-		    flow_key_compare(key, &fle->key) == 0) { +-			if (fle->genid == atomic_read(&flow_cache_genid)) { +-				void *ret = fle->object; +- +-				if (ret) +-					atomic_inc(fle->object_ref); +-				local_bh_enable(); +- +-				return ret; +-			} ++		    flow_key_compare(key, &fle->key) == 0) + 			break; +-		} + 	} +  +-	if (!fle) { ++	if (unlikely(!fle)) { + 		if (fcp->hash_count > fc->high_watermark) + 			flow_cache_shrink(fc, fcp); +  +@@ -219,33 +222,39 @@ void *flow_cache_lookup(struct net *net, struct flowi *key, u16 family, u8 dir, + 			fle->object = NULL; + 			fcp->hash_count++; + 		} ++	} else if (likely(fle->genid == atomic_read(&flow_cache_genid))) { ++		flo = fle->object; ++		if (!flo) ++			goto ret_object; ++		flo = flo->ops->get(flo); ++		if (flo) ++			goto ret_object; ++	} else if (fle->object) { ++	        flo = fle->object; ++	        flo->ops->delete(flo); ++	        fle->object = NULL; + 	} +  + nocache: +-	{ +-		int err; +-		void *obj; +-		atomic_t *obj_ref; +- +-		err = resolver(net, key, family, dir, &obj, &obj_ref); +- +-		if (fle && !err) { +-			fle->genid = atomic_read(&flow_cache_genid); +- +-			if (fle->object) +-				atomic_dec(fle->object_ref); +- +-			fle->object = obj; +-			fle->object_ref = obj_ref; +-			if (obj) +-				atomic_inc(fle->object_ref); +-		} +-		local_bh_enable(); +- +-		if (err) +-			obj = ERR_PTR(err); +-		return obj; ++	flo = NULL; ++	if (fle) { ++		flo = fle->object; ++		fle->object = NULL; ++	} ++	flo = resolver(net, key, family, dir, flo, ctx); ++	if (fle) { ++		fle->genid = atomic_read(&flow_cache_genid); ++		if (!IS_ERR(flo)) ++			fle->object = flo; ++		else ++			fle->genid--; ++	} else { ++		if (flo && !IS_ERR(flo)) ++			flo->ops->delete(flo); + 	} ++ret_object: ++	local_bh_enable(); ++	return flo; + } +  + static void flow_cache_flush_tasklet(unsigned long data) +@@ -261,13 +270,12 @@ static void flow_cache_flush_tasklet(unsigned long data) +  + 		fle = fcp->hash_table[i]; + 		for (; fle; fle = fle->next) { +-			unsigned genid = atomic_read(&flow_cache_genid); +- +-			if (!fle->object || fle->genid == genid) ++			if (flow_entry_valid(fle)) + 				continue; +  ++			if (fle->object) ++				fle->object->ops->delete(fle->object); + 			fle->object = NULL; +-			atomic_dec(fle->object_ref); + 		} + 	} +  +diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c +index 110184f..d1eb2b5 100644 +--- a/net/xfrm/xfrm_policy.c ++++ b/net/xfrm/xfrm_policy.c +@@ -216,6 +216,35 @@ expired: + 	xfrm_pol_put(xp); + } +  ++static struct flow_cache_object *xfrm_policy_flo_get(struct flow_cache_object *flo) ++{ ++	struct xfrm_policy *pol = container_of(flo, struct xfrm_policy, flo); ++ ++	if (unlikely(pol->walk.dead)) ++		flo = NULL; ++	else ++		xfrm_pol_hold(pol); ++ ++	return flo; ++} ++ ++static int xfrm_policy_flo_check(struct flow_cache_object *flo) ++{ ++	struct xfrm_policy *pol = container_of(flo, struct xfrm_policy, flo); ++ ++	return !pol->walk.dead; ++} ++ ++static void xfrm_policy_flo_delete(struct flow_cache_object *flo) ++{ ++	xfrm_pol_put(container_of(flo, struct xfrm_policy, flo)); ++} ++ ++static const struct flow_cache_ops xfrm_policy_fc_ops = { ++	.get = xfrm_policy_flo_get, ++	.check = xfrm_policy_flo_check, ++	.delete = xfrm_policy_flo_delete, ++}; +  + /* Allocate xfrm_policy. Not used here, it is supposed to be used by pfkeyv2 +  * SPD calls. +@@ -236,6 +265,7 @@ struct xfrm_policy *xfrm_policy_alloc(struct net *net, gfp_t gfp) + 		atomic_set(&policy->refcnt, 1); + 		setup_timer(&policy->timer, xfrm_policy_timer, + 				(unsigned long)policy); ++		policy->flo.ops = &xfrm_policy_fc_ops; + 	} + 	return policy; + } +@@ -269,9 +299,6 @@ static void xfrm_policy_gc_kill(struct xfrm_policy *policy) + 	if (del_timer(&policy->timer)) + 		atomic_dec(&policy->refcnt); +  +-	if (atomic_read(&policy->refcnt) > 1) +-		flow_cache_flush(); +- + 	xfrm_pol_put(policy); + } +  +@@ -658,10 +685,8 @@ struct xfrm_policy *xfrm_policy_bysel_ctx(struct net *net, u8 type, int dir, + 	} + 	write_unlock_bh(&xfrm_policy_lock); +  +-	if (ret && delete) { +-		atomic_inc(&flow_cache_genid); ++	if (ret && delete) + 		xfrm_policy_kill(ret); +-	} + 	return ret; + } + EXPORT_SYMBOL(xfrm_policy_bysel_ctx); +@@ -699,10 +724,8 @@ struct xfrm_policy *xfrm_policy_byid(struct net *net, u8 type, int dir, u32 id, + 	} + 	write_unlock_bh(&xfrm_policy_lock); +  +-	if (ret && delete) { +-		atomic_inc(&flow_cache_genid); ++	if (ret && delete) + 		xfrm_policy_kill(ret); +-	} + 	return ret; + } + EXPORT_SYMBOL(xfrm_policy_byid); +@@ -967,32 +990,35 @@ fail: + 	return ret; + } +  +-static int xfrm_policy_lookup(struct net *net, struct flowi *fl, u16 family, +-			      u8 dir, void **objp, atomic_t **obj_refp) ++static struct flow_cache_object * ++xfrm_policy_lookup(struct net *net, struct flowi *fl, u16 family, ++		   u8 dir, struct flow_cache_object *old_obj, void *ctx) + { + 	struct xfrm_policy *pol; +-	int err = 0; ++ ++	if (old_obj) ++		xfrm_pol_put(container_of(old_obj, struct xfrm_policy, flo)); +  + #ifdef CONFIG_XFRM_SUB_POLICY + 	pol = xfrm_policy_lookup_bytype(net, XFRM_POLICY_TYPE_SUB, fl, family, dir); +-	if (IS_ERR(pol)) { +-		err = PTR_ERR(pol); +-		pol = NULL; +-	} +-	if (pol || err) +-		goto end; ++	if (IS_ERR(pol)) ++		return ERR_CAST(pol); ++	if (pol) ++		goto found; + #endif + 	pol = xfrm_policy_lookup_bytype(net, XFRM_POLICY_TYPE_MAIN, fl, family, dir); +-	if (IS_ERR(pol)) { +-		err = PTR_ERR(pol); +-		pol = NULL; +-	} +-#ifdef CONFIG_XFRM_SUB_POLICY +-end: +-#endif +-	if ((*objp = (void *) pol) != NULL) +-		*obj_refp = &pol->refcnt; +-	return err; ++	if (IS_ERR(pol)) ++		return ERR_CAST(pol); ++	if (pol) ++		goto found; ++	return NULL; ++ ++found: ++	/* Resolver returns two references: ++	 * one for cache and one for caller of flow_cache_lookup() */ ++	xfrm_pol_hold(pol); ++ ++	return &pol->flo; + } +  + static inline int policy_to_flow_dir(int dir) +@@ -1077,8 +1103,6 @@ int xfrm_policy_delete(struct xfrm_policy *pol, int dir) + 	pol = __xfrm_policy_unlink(pol, dir); + 	write_unlock_bh(&xfrm_policy_lock); + 	if (pol) { +-		if (dir < XFRM_POLICY_MAX) +-			atomic_inc(&flow_cache_genid); + 		xfrm_policy_kill(pol); + 		return 0; + 	} +@@ -1549,18 +1573,24 @@ restart: + 	} +  + 	if (!policy) { ++		struct flow_cache_object *flo; ++ + 		/* To accelerate a bit...  */ + 		if ((dst_orig->flags & DST_NOXFRM) || + 		    !net->xfrm.policy_count[XFRM_POLICY_OUT]) + 			goto nopol; +  +-		policy = flow_cache_lookup(net, fl, dst_orig->ops->family, +-					   dir, xfrm_policy_lookup); +-		err = PTR_ERR(policy); +-		if (IS_ERR(policy)) { ++		flo = flow_cache_lookup(net, fl, dst_orig->ops->family, ++					dir, xfrm_policy_lookup, NULL); ++		err = PTR_ERR(flo); ++		if (IS_ERR(flo)) { + 			XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTPOLERROR); + 			goto dropdst; + 		} ++		if (flo) ++			policy = container_of(flo, struct xfrm_policy, flo); ++		else ++			policy = NULL; + 	} +  + 	if (!policy) +@@ -1910,9 +1940,16 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb, + 		} + 	} +  +-	if (!pol) +-		pol = flow_cache_lookup(net, &fl, family, fl_dir, +-					xfrm_policy_lookup); ++	if (!pol) { ++		struct flow_cache_object *flo; ++ ++		flo = flow_cache_lookup(net, &fl, family, fl_dir, ++					xfrm_policy_lookup, NULL); ++		if (flo == NULL || IS_ERR(flo)) ++			pol = ERR_CAST(flo); ++		else ++			pol = container_of(flo, struct xfrm_policy, flo); ++	} +  + 	if (IS_ERR(pol)) { + 		XFRM_INC_STATS(net, LINUX_MIB_XFRMINPOLERROR); +--  +1.7.0.2 +  | 
