Sophie

Sophie

distrib > Scientific%20Linux > 5x > x86_64 > by-pkgid > 89877e42827f16fa5f86b1df0c2860b1 > files > 1507

kernel-2.6.18-128.1.10.el5.src.rpm

Date: Mon, 09 Oct 2006 13:56:56 -0400
From: Eric Paris <eparis@redhat.com>
Subject: [RHEL5 PATCH 1/2] BZ 209171 Fix xfrm to not deny encryption of data

this patch has been tested upstream by both the LSPP group and the
original reporter.  the full patch description is below.  Without this
patch it is possible that a user can believe traffic is flowing
encrypted through an ipsec tunnel but it will not!

*******

Currently when an IPSec policy rule doesn't specify a security context,
it is assumed to be "unlabeled" by SELinux, and so the IPSec policy rule
fails to match to a flow that it would otherwise match to, unless one
has explicitly added an SELinux policy rule allowing the flow to
"polmatch" to the "unlabeled" IPSec policy rules. In the absence of such
an explicitly added SELinux policy rule, the IPSec policy rule fails to
match and so the packet(s) flow in clear text without the otherwise
applicable xfrm(s) applied.

The above SELinux behavior violates the SELinux security notion of "deny
by default" which should actually translate to "encrypt by default" in
the above case.

This was first reported by Evgeniy Polyakov and the way James Morris was
seeing the problem was when connecting via IPsec to a confined service
on an SELinux box (vsftpd), which did not have the appropriate SELinux
policy permissions to send packets via IPsec.

With this patch applied, SELinux "polmatching" of flows Vs. IPSec policy
rules will only come into play when there's a explicit context specified
for the IPSec policy rule (which also means there's corresponding
SELinux policy allowing appropriate domains/flows to polmatch to this
context).

Secondly, when a security module is loaded (in this case, SELinux), the
security_xfrm_policy_lookup() hook can return errors other than access
denied, such as -EINVAL.  We were not handling that correctly, and in
fact inverting the return logic and propagating a false "ok" back up to 
xfrm_lookup(), which then allowed packets to pass as if they were not
associated with an xfrm policy.

The solution for this is to first ensure that errno values are correctly
propagated all the way back up through the various call chains from
security_xfrm_policy_lookup(), and handled correctly.

Then, flow_cache_lookup() is modified, so that if the policy resolver
fails (typically a permission denied via the security module), the flow
cache entry is killed rather than having a null policy assigned (which
indicates that the packet can pass freely).  This also forces any future
lookups for the same flow to consult the security module (e.g. SELinux)
for current security policy (rather than, say, caching the error on the
flow cache entry).

This patch: Fix the selinux side of things.

This makes sure SELinux polmatching of flow contexts to IPSec policy
rules comes into play only when an explicit context is associated with
the IPSec policy rule.

Also, this no longer defaults the context of a socket policy to the
context of the socket since the "no explicit context" case is now
handled properly.

--- linux-2.6.18.i686/security/selinux/xfrm.c.leak.xfrm	2006-10-09 12:59:52.000000000 -0400
+++ linux-2.6.18.i686/security/selinux/xfrm.c	2006-10-09 13:00:07.000000000 -0400
@@ -77,8 +77,8 @@ static inline int selinux_authorizable_x
  */
 int selinux_xfrm_policy_lookup(struct xfrm_policy *xp, u32 fl_secid, u8 dir)
 {
-	int rc = 0;
-	u32 sel_sid = SECINITSID_UNLABELED;
+	int rc;
+	u32 sel_sid;
 	struct xfrm_sec_ctx *ctx;
 
 	/* Context sid is either set to label or ANY_ASSOC */
@@ -88,11 +88,21 @@ int selinux_xfrm_policy_lookup(struct xf
 
 		sel_sid = ctx->ctx_sid;
 	}
+	else
+		/*
+		 * All flows should be treated as polmatch'ing an
+		 * otherwise applicable "non-labeled" policy. This
+		 * would prevent inadvertent "leaks".
+		 */
+		return 0;
 
 	rc = avc_has_perm(fl_secid, sel_sid, SECCLASS_ASSOCIATION,
 			  ASSOCIATION__POLMATCH,
 			  NULL);
 
+	if (rc == -EACCES)
+		rc = -ESRCH;
+
 	return rc;
 }
 
@@ -108,15 +118,20 @@ int selinux_xfrm_state_pol_flow_match(st
 	u32 pol_sid;
 	int err;
 
-	if (x->security)
-		state_sid = x->security->ctx_sid;
-	else
-		state_sid = SECINITSID_UNLABELED;
-
-	if (xp->security)
+	if (xp->security) {
+		if (!x->security)
+			/* unlabeled SA and labeled policy can't match */
+			return 0;
+		else
+			state_sid = x->security->ctx_sid;
 		pol_sid = xp->security->ctx_sid;
-	else
-		pol_sid = SECINITSID_UNLABELED;
+	} else
+		if (x->security)
+			/* unlabeled policy and labeled SA can't match */
+			return 0;
+		else
+			/* unlabeled policy and unlabeled SA match all flows */
+			return 1;
 
 	err = avc_has_perm(state_sid, pol_sid, SECCLASS_ASSOCIATION,
 			  ASSOCIATION__POLMATCH,
@@ -125,7 +140,11 @@ int selinux_xfrm_state_pol_flow_match(st
 	if (err)
 		return 0;
 
-	return selinux_xfrm_flow_state_match(fl, x);
+	err = avc_has_perm(fl->secid, state_sid, SECCLASS_ASSOCIATION,
+			  ASSOCIATION__SENDTO,
+			  NULL)? 0:1;
+
+	return err;
 }
 
 /*
@@ -133,12 +152,22 @@ int selinux_xfrm_state_pol_flow_match(st
  * can use a given security association.
  */
 
-int selinux_xfrm_flow_state_match(struct flowi *fl, struct xfrm_state *xfrm)
+int selinux_xfrm_flow_state_match(struct flowi *fl, struct xfrm_state *xfrm,
+				  struct xfrm_policy *xp)
 {
 	int rc = 0;
 	u32 sel_sid = SECINITSID_UNLABELED;
 	struct xfrm_sec_ctx *ctx;
 
+	if (!xp->security)
+		if (!xfrm->security)
+			return 1;
+		else
+			return 0;
+	else
+		if (!xfrm->security)
+			return 0;
+
 	/* Context sid is either set to label or ANY_ASSOC */
 	if ((ctx = xfrm->security)) {
 		if (!selinux_authorizable_ctx(ctx))
--- linux-2.6.18.i686/security/selinux/include/xfrm.h.leak.xfrm	2006-10-09 12:59:52.000000000 -0400
+++ linux-2.6.18.i686/security/selinux/include/xfrm.h	2006-10-09 13:00:07.000000000 -0400
@@ -19,7 +19,8 @@ int selinux_xfrm_state_delete(struct xfr
 int selinux_xfrm_policy_lookup(struct xfrm_policy *xp, u32 fl_secid, u8 dir);
 int selinux_xfrm_state_pol_flow_match(struct xfrm_state *x,
 			struct xfrm_policy *xp, struct flowi *fl);
-int selinux_xfrm_flow_state_match(struct flowi *fl, struct xfrm_state *xfrm);
+int selinux_xfrm_flow_state_match(struct flowi *fl, struct xfrm_state *xfrm,
+			struct xfrm_policy *xp);
 
 
 /*
--- linux-2.6.18.i686/security/dummy.c.leak.xfrm	2006-10-09 12:59:52.000000000 -0400
+++ linux-2.6.18.i686/security/dummy.c	2006-10-09 13:00:07.000000000 -0400
@@ -881,7 +881,8 @@ static int dummy_xfrm_state_pol_flow_mat
 	return 1;
 }
 
-static int dummy_xfrm_flow_state_match(struct flowi *fl, struct xfrm_state *xfrm)
+static int dummy_xfrm_flow_state_match(struct flowi *fl, struct xfrm_state *xfrm,
+				struct xfrm_policy *xp)
 {
 	return 1;
 }
--- linux-2.6.18.i686/net/key/af_key.c.leak.xfrm	2006-10-09 12:59:52.000000000 -0400
+++ linux-2.6.18.i686/net/key/af_key.c	2006-10-09 13:00:07.000000000 -0400
@@ -2920,11 +2920,6 @@ static struct xfrm_policy *pfkey_compile
 		if (*dir)
 			goto out;
 	}
-	else {
-		*dir = security_xfrm_sock_policy_alloc(xp, sk);
-		if (*dir)
-			goto out;
-	}
 
 	*dir = pol->sadb_x_policy_dir-1;
 	return xp;
--- linux-2.6.18.i686/net/ipv6/xfrm6_policy.c.leak.xfrm	2006-10-09 12:59:52.000000000 -0400
+++ linux-2.6.18.i686/net/ipv6/xfrm6_policy.c	2006-10-09 13:00:07.000000000 -0400
@@ -50,7 +50,7 @@ __xfrm6_find_bundle(struct flowi *fl, st
 				 xdst->u.rt6.rt6i_src.plen);
 		if (ipv6_addr_equal(&xdst->u.rt6.rt6i_dst.addr, &fl_dst_prefix) &&
 		    ipv6_addr_equal(&xdst->u.rt6.rt6i_src.addr, &fl_src_prefix) &&
-		    xfrm_bundle_ok(xdst, fl, AF_INET6)) {
+		    xfrm_bundle_ok(policy, xdst, fl, AF_INET6)) {
 			dst_clone(dst);
 			break;
 		}
--- linux-2.6.18.i686/net/xfrm/xfrm_user.c.leak.xfrm	2006-10-09 12:59:52.000000000 -0400
+++ linux-2.6.18.i686/net/xfrm/xfrm_user.c	2006-10-09 13:00:07.000000000 -0400
@@ -1805,15 +1805,6 @@ static struct xfrm_policy *xfrm_compile_
 	copy_from_user_policy(xp, p);
 	copy_templates(xp, ut, nr);
 
-	if (!xp->security) {
-		int err = security_xfrm_sock_policy_alloc(xp, sk);
-		if (err) {
-			kfree(xp);
-			*dir = err;
-			return NULL;
-		}
-	}
-
 	*dir = p->dir;
 
 	return xp;
--- linux-2.6.18.i686/net/xfrm/xfrm_policy.c.leak.xfrm	2006-10-09 12:59:52.000000000 -0400
+++ linux-2.6.18.i686/net/xfrm/xfrm_policy.c	2006-10-09 13:00:07.000000000 -0400
@@ -1165,7 +1165,7 @@ static struct dst_entry *xfrm_dst_check(
 
 static int stale_bundle(struct dst_entry *dst)
 {
-	return !xfrm_bundle_ok((struct xfrm_dst *)dst, NULL, AF_UNSPEC);
+	return !xfrm_bundle_ok(NULL, (struct xfrm_dst *)dst, NULL, AF_UNSPEC);
 }
 
 void xfrm_dst_ifdown(struct dst_entry *dst, struct net_device *dev)
@@ -1280,7 +1280,8 @@ EXPORT_SYMBOL(xfrm_init_pmtu);
  * still valid.
  */
 
-int xfrm_bundle_ok(struct xfrm_dst *first, struct flowi *fl, int family)
+int xfrm_bundle_ok(struct xfrm_policy *pol, struct xfrm_dst *first, 
+		struct flowi *fl, int family)
 {
 	struct dst_entry *dst = &first->u.dst;
 	struct xfrm_dst *last;
@@ -1297,7 +1298,7 @@ int xfrm_bundle_ok(struct xfrm_dst *firs
 
 		if (fl && !xfrm_selector_match(&dst->xfrm->sel, fl, family))
 			return 0;
-		if (fl && !security_xfrm_flow_state_match(fl, dst->xfrm))
+		if (fl && !security_xfrm_flow_state_match(fl, dst->xfrm, pol))
 			return 0;
 		if (dst->xfrm->km.state != XFRM_STATE_VALID)
 			return 0;
--- linux-2.6.18.i686/net/ipv4/xfrm4_policy.c.leak.xfrm	2006-10-09 12:59:52.000000000 -0400
+++ linux-2.6.18.i686/net/ipv4/xfrm4_policy.c	2006-10-09 13:00:07.000000000 -0400
@@ -33,7 +33,7 @@ __xfrm4_find_bundle(struct flowi *fl, st
 		    xdst->u.rt.fl.fl4_dst == fl->fl4_dst &&
 	    	    xdst->u.rt.fl.fl4_src == fl->fl4_src &&
 	    	    xdst->u.rt.fl.fl4_tos == fl->fl4_tos &&
-		    xfrm_bundle_ok(xdst, fl, AF_INET)) {
+		    xfrm_bundle_ok(policy, xdst, fl, AF_INET)) {
 			dst_clone(dst);
 			break;
 		}
--- linux-2.6.18.i686/include/linux/security.h.leak.xfrm	2006-10-09 12:59:52.000000000 -0400
+++ linux-2.6.18.i686/include/linux/security.h	2006-10-09 13:00:07.000000000 -0400
@@ -882,7 +882,8 @@ struct request_sock;
  *	Check permission when a flow selects a xfrm_policy for processing
  *	XFRMs on a packet.  The hook is called when selecting either a
  *	per-socket policy or a generic xfrm policy.
- *	Return 0 if permission is granted.
+ *	Return 0 if permission is granted, -ESRCH otherwise, or -errno
+ *	on other errors.
  * @xfrm_state_pol_flow_match:
  *	@x contains the state to match.
  *	@xp contains the policy to check for a match.
@@ -891,6 +892,7 @@ struct request_sock;
  * @xfrm_flow_state_match:
  *	@fl contains the flow key to match.
  *	@xfrm points to the xfrm_state to match.
+ *	@xp points to the xfrm_policy to match.
  *	Return 1 if there is a match.
  * @xfrm_decode_session:
  *	@skb points to skb to decode.
@@ -1388,7 +1390,8 @@ struct security_operations {
 	int (*xfrm_policy_lookup)(struct xfrm_policy *xp, u32 fl_secid, u8 dir);
 	int (*xfrm_state_pol_flow_match)(struct xfrm_state *x,
 			struct xfrm_policy *xp, struct flowi *fl);
-	int (*xfrm_flow_state_match)(struct flowi *fl, struct xfrm_state *xfrm);
+	int (*xfrm_flow_state_match)(struct flowi *fl, struct xfrm_state *xfrm,
+			struct xfrm_policy *xp);
 	int (*xfrm_decode_session)(struct sk_buff *skb, u32 *secid, int ckall);
 #endif	/* CONFIG_SECURITY_NETWORK_XFRM */
 
@@ -3119,11 +3122,6 @@ static inline int security_xfrm_policy_a
 	return security_ops->xfrm_policy_alloc_security(xp, sec_ctx, NULL);
 }
 
-static inline int security_xfrm_sock_policy_alloc(struct xfrm_policy *xp, struct sock *sk)
-{
-	return security_ops->xfrm_policy_alloc_security(xp, NULL, sk);
-}
-
 static inline int security_xfrm_policy_clone(struct xfrm_policy *old, struct xfrm_policy *new)
 {
 	return security_ops->xfrm_policy_clone_security(old, new);
@@ -3174,9 +3172,10 @@ static inline int security_xfrm_state_po
 	return security_ops->xfrm_state_pol_flow_match(x, xp, fl);
 }
 
-static inline int security_xfrm_flow_state_match(struct flowi *fl, struct xfrm_state *xfrm)
+static inline int security_xfrm_flow_state_match(struct flowi *fl,
+			struct xfrm_state *xfrm, struct xfrm_policy *xp)
 {
-	return security_ops->xfrm_flow_state_match(fl, xfrm);
+	return security_ops->xfrm_flow_state_match(fl, xfrm, xp);
 }
 
 static inline int security_xfrm_decode_session(struct sk_buff *skb, u32 *secid)
@@ -3196,11 +3195,6 @@ static inline int security_xfrm_policy_a
 	return 0;
 }
 
-static inline int security_xfrm_sock_policy_alloc(struct xfrm_policy *xp, struct sock *sk)
-{
-	return 0;
-}
-
 static inline int security_xfrm_policy_clone(struct xfrm_policy *old, struct xfrm_policy *new)
 {
 	return 0;
@@ -3248,7 +3242,7 @@ static inline int security_xfrm_state_po
 }
 
 static inline int security_xfrm_flow_state_match(struct flowi *fl,
-                                struct xfrm_state *xfrm)
+			struct xfrm_state *xfrm, struct xfrm_policy *xp)
 {
 	return 1;
 }
--- linux-2.6.18.i686/include/net/xfrm.h.leak.xfrm	2006-10-09 12:59:52.000000000 -0400
+++ linux-2.6.18.i686/include/net/xfrm.h	2006-10-09 13:00:07.000000000 -0400
@@ -962,7 +962,8 @@ extern void xfrm_policy_flush(void);
 extern int xfrm_sk_policy_insert(struct sock *sk, int dir, struct xfrm_policy *pol);
 extern int xfrm_flush_bundles(void);
 extern void xfrm_flush_all_bundles(void);
-extern int xfrm_bundle_ok(struct xfrm_dst *xdst, struct flowi *fl, int family);
+extern int xfrm_bundle_ok(struct xfrm_policy *pol, struct xfrm_dst *xdst, 
+			struct flowi *fl, int family);
 extern void xfrm_init_pmtu(struct dst_entry *dst);
 
 extern wait_queue_head_t km_waitq;

Date: Mon, 09 Oct 2006 13:56:59 -0400
From: Eric Paris <eparis@redhat.com>
Subject: [RHEL5 PATCH 2/2] BZ 209191 fix ipsec information leak with error
	propagation

This is the complete fix to the ipsec leak issue.  This allows up to
propagate error messages back up the stack from SELinux denials.  It has
also been tested upstream and found to solve the problem.  It should
appear in 2.6.19.

*********

When a security module is loaded (in this case, SELinux), the
security_xfrm_policy_lookup() hook can return an access denied
permission (or other error).  We were not handling that correctly, and
in fact inverting the return logic and propagating a false "ok" back up
to xfrm_lookup(), which then allowed packets to pass as if they were not
associated with an xfrm policy.

The way I was seeing the problem was when connecting via IPsec to a
confined service on an SELinux box (vsftpd), which did not have the
appropriate SELinux policy permissions to send packets via IPsec.

The first SYNACK would be blocked, because of an uncached lookup via
flow_cache_lookup(), which would fail to resolve an xfrm policy because
the SELinux policy is checked at that point via the resolver.

However, retransmitted SYNACKs would then find a cached flow entry when
calling into flow_cache_lookup() with a null xfrm policy, which is
interpreted by xfrm_lookup() as the packet not having any associated
policy and similarly to the first case, allowing it to pass without
transformation.

The solution presented here is to first ensure that errno values are
correctly propagated all the way back up through the various call chains
from security_xfrm_policy_lookup(), and handled correctly.

Then, flow_cache_lookup() is modified, so that if the policy resolver
fails (typically a permission denied via the security module), the flow
cache entry is killed rather than having a null policy assigned (which
indicates that the packet can pass freely).  This also forces any future
lookups for the same flow to consult the security module (e.g. SELinux)
for current security policy (rather than, say, caching the error on the
flow cache entry).

--- linux-2.6.18.i686/net/xfrm/xfrm_policy.c.error.prop	2006-10-06 16:47:08.000000000 -0400
+++ linux-2.6.18.i686/net/xfrm/xfrm_policy.c	2006-10-06 16:55:25.000000000 -0400
@@ -595,12 +595,17 @@ out:
 }
 EXPORT_SYMBOL(xfrm_policy_walk);
 
-/* Find policy to apply to this flow. */
+/*
+ * Find policy to apply to this flow.
+ *
+ * Returns 0 if policy found, else an -errno.
+ */
 
-static void xfrm_policy_lookup(struct flowi *fl, u16 family, u8 dir,
+static int xfrm_policy_lookup(struct flowi *fl, u16 family, u8 dir,
 			       void **objp, atomic_t **obj_refp)
 {
 	struct xfrm_policy *pol;
+	int ret = -ESRCH;
 
 	read_lock_bh(&xfrm_policy_lock);
 	for (pol = xfrm_policy_list[dir]; pol; pol = pol->next) {
@@ -613,7 +618,8 @@ static void xfrm_policy_lookup(struct fl
 		match = xfrm_selector_match(sel, fl, family);
 
 		if (match) {
- 			if (!security_xfrm_policy_lookup(pol, fl->secid, dir)) {
+ 			ret = security_xfrm_policy_lookup(pol, fl->secid, dir);
+ 			if (!ret) {
 				xfrm_pol_hold(pol);
 				break;
 			}
@@ -622,6 +628,7 @@ static void xfrm_policy_lookup(struct fl
 	read_unlock_bh(&xfrm_policy_lock);
 	if ((*objp = (void *) pol) != NULL)
 		*obj_refp = &pol->refcnt;
+	return ret;
 }
 
 static inline int policy_to_flow_dir(int dir)
@@ -651,12 +658,16 @@ static struct xfrm_policy *xfrm_sk_polic
 						sk->sk_family);
  		int err = 0;
 
-		if (match)
-		  err = security_xfrm_policy_lookup(pol, fl->secid, policy_to_flow_dir(dir));
-
- 		if (match && !err)
-			xfrm_pol_hold(pol);
-		else
+		if (match) {
+			err = security_xfrm_policy_lookup(pol, fl->secid,
+					policy_to_flow_dir(dir));
+			if (!err)
+				xfrm_pol_hold(pol);
+			else if (err == -ESRCH)
+				pol = NULL;
+			else
+				pol = ERR_PTR(err);
+		} else
 			pol = NULL;
 	}
 	read_unlock_bh(&xfrm_policy_lock);
@@ -866,8 +877,11 @@ int xfrm_lookup(struct dst_entry **dst_p
 restart:
 	genid = atomic_read(&flow_cache_genid);
 	policy = NULL;
-	if (sk && sk->sk_policy[1])
+	if (sk && sk->sk_policy[1]) {
 		policy = xfrm_sk_policy_lookup(sk, XFRM_POLICY_OUT, fl);
+		if (IS_ERR(policy))
+			return PTR_ERR(policy);
+	}
 
 	if (!policy) {
 		/* To accelerate a bit...  */
@@ -876,6 +890,8 @@ restart:
 
 		policy = flow_cache_lookup(fl, dst_orig->ops->family,
 					   dir, xfrm_policy_lookup);
+		if (IS_ERR(policy))
+			return PTR_ERR(policy);
 	}
 
 	if (!policy)
@@ -1077,12 +1093,18 @@ int __xfrm_policy_check(struct sock *sk,
 	}
 
 	pol = NULL;
-	if (sk && sk->sk_policy[dir])
+
+	if (sk && sk->sk_policy[dir]) {
 		pol = xfrm_sk_policy_lookup(sk, dir, &fl);
+		if (IS_ERR(pol))
+			return 0;
+	}
 
 	if (!pol)
 		pol = flow_cache_lookup(&fl, family, fl_dir,
 					xfrm_policy_lookup);
+	if (IS_ERR(pol))
+		return 0;
 
 	if (!pol)
 		return !skb->sp || !secpath_has_tunnel(skb->sp, 0);
--- linux-2.6.18.i686/net/core/flow.c.error.prop	2006-10-06 16:47:03.000000000 -0400
+++ linux-2.6.18.i686/net/core/flow.c	2006-10-06 16:51:51.000000000 -0400
@@ -85,6 +85,14 @@ static void flow_cache_new_hashrnd(unsig
 	add_timer(&flow_hash_rnd_timer);
 }
 
+static void flow_entry_kill(int cpu, struct flow_cache_entry *fle)
+{
+	if (fle->object)
+		atomic_dec(fle->object_ref);
+	kmem_cache_free(flow_cachep, fle);
+	flow_count(cpu)--;
+}
+
 static void __flow_cache_shrink(int cpu, int shrink_to)
 {
 	struct flow_cache_entry *fle, **flp;
@@ -100,10 +108,7 @@ static void __flow_cache_shrink(int cpu,
 		}
 		while ((fle = *flp) != NULL) {
 			*flp = fle->next;
-			if (fle->object)
-				atomic_dec(fle->object_ref);
-			kmem_cache_free(flow_cachep, fle);
-			flow_count(cpu)--;
+			flow_entry_kill(cpu, fle);
 		}
 	}
 }
@@ -220,24 +225,33 @@ void *flow_cache_lookup(struct flowi *ke
 
 nocache:
 	{
+		int err;
 		void *obj;
 		atomic_t *obj_ref;
 
-		resolver(key, family, dir, &obj, &obj_ref);
+		err = resolver(key, family, dir, &obj, &obj_ref);
 
 		if (fle) {
-			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);
+			if (err) {
+				/* Force security policy check on next lookup */
+				*head = fle->next;
+				flow_entry_kill(cpu, fle);
+			} else {
+				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;
 	}
 }
--- linux-2.6.18.i686/include/net/flow.h.error.prop	2006-10-06 16:47:06.000000000 -0400
+++ linux-2.6.18.i686/include/net/flow.h	2006-10-06 16:51:51.000000000 -0400
@@ -88,7 +88,7 @@ struct flowi {
 #define FLOW_DIR_FWD	2
 
 struct sock;
-typedef void (*flow_resolve_t)(struct flowi *key, u16 family, u8 dir,
+typedef int (*flow_resolve_t)(struct flowi *key, u16 family, u8 dir,
 			       void **objp, atomic_t **obj_refp);
 
 extern void *flow_cache_lookup(struct flowi *key, u16 family, u8 dir,