All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH nf V2] netfilter: ctnetlink: make it safer when updating ct->status
@ 2017-04-13 12:53 Liping Zhang
  2017-04-13 23:25 ` Pablo Neira Ayuso
  0 siblings, 1 reply; 3+ messages in thread
From: Liping Zhang @ 2017-04-13 12:53 UTC (permalink / raw)
  To: pablo; +Cc: netfilter-devel, cernekee, fgao, Liping Zhang

From: Liping Zhang <zlpnobody@gmail.com>

User can update the ct->status via nfnetlink, but using a non-atomic
operation "ct->status |= status;". This is unsafe, and may clear
IPS_DYING_BIT bit set by another CPU unexpectedly. For example:
         CPU0                            CPU1
  ctnetlink_change_status        __nf_conntrack_find_get
      old = ct->status              nf_ct_gc_expired
          -                         nf_ct_kill
          -                      test_and_set_bit(IPS_DYING_BIT
      new = old | status;                 -
  ct->status = new; <-- oops, _DYING_ is cleared!

So using a series of atomic bit operation to solve the above issue.

Also note, user shouldn't set IPS_TEMPLATE, IPS_SEQ_ADJUST directly.

If we set the IPS_TEMPLATE_BIT, ct will be freed by nf_ct_tmpl_free,
but actually it is alloced by nf_conntrack_alloc.

If we set the IPS_SEQ_ADJUST_BIT, this may cause the NULL pointer
deference, as the nfct_seqadj(ct) maybe NULL.

So make these two bits be unchangable too.

Last, add some comments to describe the logic change due to the
commit a963d710f367 ("netfilter: ctnetlink: Fix regression in CTA_STATUS
processing"), which makes me feel a little confusing.

Signed-off-by: Liping Zhang <zlpnobody@gmail.com>
---
 V2: make __ctnetlink_change_status more clean, per Gao Feng

 include/uapi/linux/netfilter/nf_conntrack_common.h | 13 ++++++---
 net/netfilter/nf_conntrack_netlink.c               | 33 ++++++++++++++++------
 2 files changed, 33 insertions(+), 13 deletions(-)

diff --git a/include/uapi/linux/netfilter/nf_conntrack_common.h b/include/uapi/linux/netfilter/nf_conntrack_common.h
index 6a8e33d..38fc383 100644
--- a/include/uapi/linux/netfilter/nf_conntrack_common.h
+++ b/include/uapi/linux/netfilter/nf_conntrack_common.h
@@ -82,10 +82,6 @@ enum ip_conntrack_status {
 	IPS_DYING_BIT = 9,
 	IPS_DYING = (1 << IPS_DYING_BIT),
 
-	/* Bits that cannot be altered from userland. */
-	IPS_UNCHANGEABLE_MASK = (IPS_NAT_DONE_MASK | IPS_NAT_MASK |
-				 IPS_EXPECTED | IPS_CONFIRMED | IPS_DYING),
-
 	/* Connection has fixed timeout. */
 	IPS_FIXED_TIMEOUT_BIT = 10,
 	IPS_FIXED_TIMEOUT = (1 << IPS_FIXED_TIMEOUT_BIT),
@@ -101,6 +97,15 @@ enum ip_conntrack_status {
 	/* Conntrack got a helper explicitly attached via CT target. */
 	IPS_HELPER_BIT = 13,
 	IPS_HELPER = (1 << IPS_HELPER_BIT),
+
+	/* Be careful here, modifying these bits can make things messy,
+	 * so don't let users modify them directly.
+	 */
+	IPS_UNCHANGEABLE_MASK = (IPS_NAT_DONE_MASK | IPS_NAT_MASK |
+				 IPS_EXPECTED | IPS_CONFIRMED | IPS_DYING |
+				 IPS_SEQ_ADJUST | IPS_TEMPLATE),
+
+	__IPS_MAX_BIT = 14,
 };
 
 /* Connection tracking event types */
diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c
index 908d858..c16641e 100644
--- a/net/netfilter/nf_conntrack_netlink.c
+++ b/net/netfilter/nf_conntrack_netlink.c
@@ -1419,6 +1419,24 @@ ctnetlink_parse_nat_setup(struct nf_conn *ct,
 }
 #endif
 
+static void
+__ctnetlink_change_status(struct nf_conn *ct, unsigned long on,
+			  unsigned long off)
+{
+	unsigned int bit;
+
+	/* Ignore these unchangable bits */
+	on &= ~IPS_UNCHANGEABLE_MASK;
+	off &= ~IPS_UNCHANGEABLE_MASK;
+
+	for (bit = 0; bit < __IPS_MAX_BIT; bit++) {
+		if (on & (1 << bit))
+			set_bit(bit, &ct->status);
+		else if (off & (1 << bit))
+			clear_bit(bit, &ct->status);
+	}
+}
+
 static int
 ctnetlink_change_status(struct nf_conn *ct, const struct nlattr * const cda[])
 {
@@ -1438,10 +1456,7 @@ ctnetlink_change_status(struct nf_conn *ct, const struct nlattr * const cda[])
 		/* ASSURED bit can only be set */
 		return -EBUSY;
 
-	/* Be careful here, modifying NAT bits can screw up things,
-	 * so don't let users modify them directly if they don't pass
-	 * nf_nat_range. */
-	ct->status |= status & ~(IPS_NAT_DONE_MASK | IPS_NAT_MASK);
+	__ctnetlink_change_status(ct, status, 0);
 	return 0;
 }
 
@@ -1632,7 +1647,7 @@ ctnetlink_change_seq_adj(struct nf_conn *ct,
 		if (ret < 0)
 			return ret;
 
-		ct->status |= IPS_SEQ_ADJUST;
+		set_bit(IPS_SEQ_ADJUST_BIT, &ct->status);
 	}
 
 	if (cda[CTA_SEQ_ADJ_REPLY]) {
@@ -1641,7 +1656,7 @@ ctnetlink_change_seq_adj(struct nf_conn *ct,
 		if (ret < 0)
 			return ret;
 
-		ct->status |= IPS_SEQ_ADJUST;
+		set_bit(IPS_SEQ_ADJUST_BIT, &ct->status);
 	}
 
 	return 0;
@@ -2295,10 +2310,10 @@ ctnetlink_update_status(struct nf_conn *ct, const struct nlattr * const cda[])
 	/* This check is less strict than ctnetlink_change_status()
 	 * because callers often flip IPS_EXPECTED bits when sending
 	 * an NFQA_CT attribute to the kernel.  So ignore the
-	 * unchangeable bits but do not error out.
+	 * unchangeable bits but do not error out. Also user programs
+	 * are allowed to clear the bits that they are allowed to change.
 	 */
-	ct->status = (status & ~IPS_UNCHANGEABLE_MASK) |
-		     (ct->status & IPS_UNCHANGEABLE_MASK);
+	__ctnetlink_change_status(ct, status, ~status);
 	return 0;
 }
 
-- 
2.5.5



^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH nf V2] netfilter: ctnetlink: make it safer when updating ct->status
  2017-04-13 12:53 [PATCH nf V2] netfilter: ctnetlink: make it safer when updating ct->status Liping Zhang
@ 2017-04-13 23:25 ` Pablo Neira Ayuso
  2017-04-14  2:43   ` Liping Zhang
  0 siblings, 1 reply; 3+ messages in thread
From: Pablo Neira Ayuso @ 2017-04-13 23:25 UTC (permalink / raw)
  To: Liping Zhang; +Cc: netfilter-devel, cernekee, fgao, Liping Zhang

On Thu, Apr 13, 2017 at 08:53:47PM +0800, Liping Zhang wrote:
> From: Liping Zhang <zlpnobody@gmail.com>
> 
> User can update the ct->status via nfnetlink, but using a non-atomic
> operation "ct->status |= status;". This is unsafe, and may clear
> IPS_DYING_BIT bit set by another CPU unexpectedly. For example:
>          CPU0                            CPU1
>   ctnetlink_change_status        __nf_conntrack_find_get
>       old = ct->status              nf_ct_gc_expired
>           -                         nf_ct_kill
>           -                      test_and_set_bit(IPS_DYING_BIT
>       new = old | status;                 -
>   ct->status = new; <-- oops, _DYING_ is cleared!

This is fixing an issue that was introduced in ca7433df3a67.

So I would like this comes in a patch batch including the several
patches that we need to fix the conntrack update path from ctnetlink.

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH nf V2] netfilter: ctnetlink: make it safer when updating ct->status
  2017-04-13 23:25 ` Pablo Neira Ayuso
@ 2017-04-14  2:43   ` Liping Zhang
  0 siblings, 0 replies; 3+ messages in thread
From: Liping Zhang @ 2017-04-14  2:43 UTC (permalink / raw)
  To: Pablo Neira Ayuso
  Cc: Liping Zhang, Netfilter Developer Mailing List, cernekee, Gao Feng

Hi Pablo,

2017-04-14 7:25 GMT+08:00 Pablo Neira Ayuso <pablo@netfilter.org>:
> On Thu, Apr 13, 2017 at 08:53:47PM +0800, Liping Zhang wrote:
>> From: Liping Zhang <zlpnobody@gmail.com>
>>
>> User can update the ct->status via nfnetlink, but using a non-atomic
>> operation "ct->status |= status;". This is unsafe, and may clear
>> IPS_DYING_BIT bit set by another CPU unexpectedly. For example:
>>          CPU0                            CPU1
>>   ctnetlink_change_status        __nf_conntrack_find_get
>>       old = ct->status              nf_ct_gc_expired
>>           -                         nf_ct_kill
>>           -                      test_and_set_bit(IPS_DYING_BIT
>>       new = old | status;                 -
>>   ct->status = new; <-- oops, _DYING_ is cleared!
>
> This is fixing an issue that was introduced in ca7433df3a67.

Actually, even holding the global nf_conntrack_lock, this won't help anything.
Because data processing path is protected by rcu, so they can manipulate
the same _ct_ at the same time.

After having a close investigation, I found the above issue was
introduced by commit 76507f69c44e ("[NETFILTER]: nf_conntrack: use RCU
for conntrack hash"):
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=76507f69c44ed199a1a68086145398459e55835d

The commit ca7433df3a67 ("netfilter: conntrack: seperate expect
locking from nf_conntrack_lock")
only introduced the dead lock indeed.

> So I would like this comes in a patch batch including the several
> patches that we need to fix the conntrack update path from ctnetlink.

OK, I will try to send a patch set.

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2017-04-14  2:43 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-04-13 12:53 [PATCH nf V2] netfilter: ctnetlink: make it safer when updating ct->status Liping Zhang
2017-04-13 23:25 ` Pablo Neira Ayuso
2017-04-14  2:43   ` Liping Zhang

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.