diff mbox series

[v3,1/2] net: nf_tables: Make nft_meta expression more robust

Message ID 20190723132753.13781-1-phil@nwl.cc
State Accepted
Delegated to: Pablo Neira
Headers show
Series [v3,1/2] net: nf_tables: Make nft_meta expression more robust | expand

Commit Message

Phil Sutter July 23, 2019, 1:27 p.m. UTC
nft_meta_get_eval()'s tendency to bail out setting NFT_BREAK verdict in
situations where required data is missing leads to unexpected behaviour
with inverted checks like so:

| meta iifname != eth0 accept

This rule will never match if there is no input interface (or it is not
known) which is not intuitive and, what's worse, breaks consistency of
iptables-nft with iptables-legacy.

Fix this by falling back to placing a value in dreg which never matches
(avoiding accidental matches), i.e. zero for interface index and an
empty string for interface name.
---
Changes since v1:
- Apply same fix to net/bridge/netfilter/nft_meta_bridge.c as well.

Changes since v2:
- Limit this fix to address only expressions returning an interface
  index or name. As Florian correctly criticized, these changes may be
  problematic as they tend to change nftables' behaviour. Hence fix only
  the cases needed to establish iptables-nft compatibility.

Signed-off-by: Phil Sutter <phil@nwl.cc>
---
 net/bridge/netfilter/nft_meta_bridge.c |  6 +-----
 net/netfilter/nft_meta.c               | 16 ++++------------
 2 files changed, 5 insertions(+), 17 deletions(-)

Comments

Pablo Neira Ayuso July 23, 2019, 6:43 p.m. UTC | #1
On Tue, Jul 23, 2019 at 03:27:52PM +0200, Phil Sutter wrote:
> nft_meta_get_eval()'s tendency to bail out setting NFT_BREAK verdict in
> situations where required data is missing leads to unexpected behaviour
> with inverted checks like so:
> 
> | meta iifname != eth0 accept
> 
> This rule will never match if there is no input interface (or it is not
> known) which is not intuitive and, what's worse, breaks consistency of
> iptables-nft with iptables-legacy.
> 
> Fix this by falling back to placing a value in dreg which never matches
> (avoiding accidental matches), i.e. zero for interface index and an
> empty string for interface name.
> ---
> Changes since v1:
> - Apply same fix to net/bridge/netfilter/nft_meta_bridge.c as well.
> 
> Changes since v2:
> - Limit this fix to address only expressions returning an interface
>   index or name. As Florian correctly criticized, these changes may be
>   problematic as they tend to change nftables' behaviour. Hence fix only
>   the cases needed to establish iptables-nft compatibility.

This leaves things in an inconsistent situation.

What's the concern here? Matching iifgroup/oifgroup from the wrong
packet path?
Florian Westphal July 23, 2019, 10:33 p.m. UTC | #2
Pablo Neira Ayuso <pablo@netfilter.org> wrote:
> > Changes since v1:
> > - Apply same fix to net/bridge/netfilter/nft_meta_bridge.c as well.
> > 
> > Changes since v2:
> > - Limit this fix to address only expressions returning an interface
> >   index or name. As Florian correctly criticized, these changes may be
> >   problematic as they tend to change nftables' behaviour. Hence fix only
> >   the cases needed to establish iptables-nft compatibility.
> 
> This leaves things in an inconsistent situation.
> 
> What's the concern here? Matching iifgroup/oifgroup from the wrong
> packet path?

There is no solution for this problem -- thats the core issue.

Purely from a technical p.o.v., we're asking to match oif, but its not
there, so to me, the current behaviour is the right one: break, because
we can't provide the requested information.

On the other hand, this makes things rather un-intuitive when asking for
not eq "foo" -- and not getting a match.

Plus, there is the "compat" angle.

For iifname and ifindex we're safe because "" is not a valid interface
name and 0 is not a valid ifindex.  But for everything else, I'd be
extra careful.

One alternative is to add the "compat" netlink attribute for
iptables-nft and only change behaviour for iptables-nft sake.

But I feel that noone has real evidence in this matter, just hunches.

If in doubt, I would even prefer to keep things as-is and add the
compat attr for meta so we get iptables-nft to behave like
iptables-legacy and keep nft as-is.

This will become relevant once we get a saner behaviour for non-matching
sets (such as a default action):

meta iifname vmap { "foo" : jump foochain, "bar" : accept, default : jump "rest" }

Would you expect the "packet has no incoming interface" to hit the
default action or not?

If we change things now (set ifindex 0 / "" name), I do not think
we can't revert it later.
Florian Westphal July 23, 2019, 10:47 p.m. UTC | #3
Florian Westphal <fw@strlen.de> wrote:
> If we change things now (set ifindex 0 / "" name), I do not think
> we can't revert it later.

Grrr, should not reply at this hour.  I meant
"I do not think we can revert it later" -- if we change it now
it will be set in stone.
Pablo Neira Ayuso July 24, 2019, 9:31 a.m. UTC | #4
On Wed, Jul 24, 2019 at 12:33:06AM +0200, Florian Westphal wrote:
[...]
> If we change things now (set ifindex 0 / "" name), I do not think
> we can't revert it later.

OK, let's start simple as you propose, with iif/oif/iifname/oifname
and we revisit this later on.

Thanks for explaining.
Pablo Neira Ayuso July 24, 2019, 9:37 a.m. UTC | #5
On Tue, Jul 23, 2019 at 03:27:52PM +0200, Phil Sutter wrote:
> nft_meta_get_eval()'s tendency to bail out setting NFT_BREAK verdict in
> situations where required data is missing leads to unexpected behaviour
> with inverted checks like so:
> 
> | meta iifname != eth0 accept
> 
> This rule will never match if there is no input interface (or it is not
> known) which is not intuitive and, what's worse, breaks consistency of
> iptables-nft with iptables-legacy.
> 
> Fix this by falling back to placing a value in dreg which never matches
> (avoiding accidental matches), i.e. zero for interface index and an
> empty string for interface name.

Applied, thanks.
diff mbox series

Patch

diff --git a/net/bridge/netfilter/nft_meta_bridge.c b/net/bridge/netfilter/nft_meta_bridge.c
index bed66f536b345..a98dec2cf0cfd 100644
--- a/net/bridge/netfilter/nft_meta_bridge.c
+++ b/net/bridge/netfilter/nft_meta_bridge.c
@@ -30,13 +30,9 @@  static void nft_meta_bridge_get_eval(const struct nft_expr *expr,
 	switch (priv->key) {
 	case NFT_META_BRI_IIFNAME:
 		br_dev = nft_meta_get_bridge(in);
-		if (!br_dev)
-			goto err;
 		break;
 	case NFT_META_BRI_OIFNAME:
 		br_dev = nft_meta_get_bridge(out);
-		if (!br_dev)
-			goto err;
 		break;
 	case NFT_META_BRI_IIFPVID: {
 		u16 p_pvid;
@@ -64,7 +60,7 @@  static void nft_meta_bridge_get_eval(const struct nft_expr *expr,
 		goto out;
 	}
 
-	strncpy((char *)dest, br_dev->name, IFNAMSIZ);
+	strncpy((char *)dest, br_dev ? br_dev->name : "", IFNAMSIZ);
 	return;
 out:
 	return nft_meta_get_eval(expr, regs, pkt);
diff --git a/net/netfilter/nft_meta.c b/net/netfilter/nft_meta.c
index f1b1d948c07b4..f69afb9ff3cbe 100644
--- a/net/netfilter/nft_meta.c
+++ b/net/netfilter/nft_meta.c
@@ -60,24 +60,16 @@  void nft_meta_get_eval(const struct nft_expr *expr,
 		*dest = skb->mark;
 		break;
 	case NFT_META_IIF:
-		if (in == NULL)
-			goto err;
-		*dest = in->ifindex;
+		*dest = in ? in->ifindex : 0;
 		break;
 	case NFT_META_OIF:
-		if (out == NULL)
-			goto err;
-		*dest = out->ifindex;
+		*dest = out ? out->ifindex : 0;
 		break;
 	case NFT_META_IIFNAME:
-		if (in == NULL)
-			goto err;
-		strncpy((char *)dest, in->name, IFNAMSIZ);
+		strncpy((char *)dest, in ? in->name : "", IFNAMSIZ);
 		break;
 	case NFT_META_OIFNAME:
-		if (out == NULL)
-			goto err;
-		strncpy((char *)dest, out->name, IFNAMSIZ);
+		strncpy((char *)dest, out ? out->name : "", IFNAMSIZ);
 		break;
 	case NFT_META_IIFTYPE:
 		if (in == NULL)