diff mbox series

[nft] evaluate: Detect address family in inet context

Message ID 20180618095710.13306-1-ecklm94@gmail.com
State Changes Requested
Delegated to: Pablo Neira
Headers show
Series [nft] evaluate: Detect address family in inet context | expand

Commit Message

Máté Eckl June 18, 2018, 9:57 a.m. UTC
Signed-off-by: Máté Eckl <ecklm94@gmail.com>
---
 src/evaluate.c | 20 ++++++++++++++++++--
 1 file changed, 18 insertions(+), 2 deletions(-)

Comments

Pablo Neira Ayuso June 18, 2018, 12:25 p.m. UTC | #1
Hi Máté,

Please, provide a meaning full patch description on why this is need
and resubmit.

Thanks!
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Pablo Neira Ayuso June 20, 2018, 11:40 a.m. UTC | #2
On Mon, Jun 18, 2018 at 11:57:10AM +0200, Máté Eckl wrote:
> Signed-off-by: Máté Eckl <ecklm94@gmail.com>
> ---
>  src/evaluate.c | 20 ++++++++++++++++++--
>  1 file changed, 18 insertions(+), 2 deletions(-)
> 
> diff --git a/src/evaluate.c b/src/evaluate.c
> index d6aff61..0564b44 100644
> --- a/src/evaluate.c
> +++ b/src/evaluate.c
> @@ -2431,12 +2431,28 @@ static int evaluate_addr(struct eval_ctx *ctx, struct stmt *stmt,
>  	const struct datatype *dtype;
>  	unsigned int len;
>  
> -	if (pctx->family == NFPROTO_IPV4) {
> +	switch (pctx->family) {
> +	case NFPROTO_IPV4:
>  		dtype = &ipaddr_type;
>  		len   = 4 * BITS_PER_BYTE;
> -	} else {
> +		break;
> +	case NFPROTO_IPV6:
>  		dtype = &ip6addr_type;
>  		len   = 16 * BITS_PER_BYTE;
> +		break;
> +	case NFPROTO_INET:
> +		if (strchr((*expr)->identifier, ':')) {

I'd suggest you specify this in this syntax:

        tproxy ip to 1.1.1.1

for the bridge/netdev/inet families.

From the kernel, this will also skip non-IP packets, so we don't need
to build an IP dependency for this statement.
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Máté Eckl June 20, 2018, 12:21 p.m. UTC | #3
On Wed, Jun 20, 2018 at 01:40:45PM +0200, Pablo Neira Ayuso wrote:
> On Mon, Jun 18, 2018 at 11:57:10AM +0200, Máté Eckl wrote:
> > Signed-off-by: Máté Eckl <ecklm94@gmail.com>
> > ---
> >  src/evaluate.c | 20 ++++++++++++++++++--
> >  1 file changed, 18 insertions(+), 2 deletions(-)
> > 
> > diff --git a/src/evaluate.c b/src/evaluate.c
> > index d6aff61..0564b44 100644
> > --- a/src/evaluate.c
> > +++ b/src/evaluate.c
> > @@ -2431,12 +2431,28 @@ static int evaluate_addr(struct eval_ctx *ctx, struct stmt *stmt,
> >  	const struct datatype *dtype;
> >  	unsigned int len;
> >  
> > -	if (pctx->family == NFPROTO_IPV4) {
> > +	switch (pctx->family) {
> > +	case NFPROTO_IPV4:
> >  		dtype = &ipaddr_type;
> >  		len   = 4 * BITS_PER_BYTE;
> > -	} else {
> > +		break;
> > +	case NFPROTO_IPV6:
> >  		dtype = &ip6addr_type;
> >  		len   = 16 * BITS_PER_BYTE;
> > +		break;
> > +	case NFPROTO_INET:
> > +		if (strchr((*expr)->identifier, ':')) {
> 
> I'd suggest you specify this in this syntax:
> 
>         tproxy ip to 1.1.1.1
> 
> for the bridge/netdev/inet families.
> 
> From the kernel, this will also skip non-IP packets, so we don't need
> to build an IP dependency for this statement.

This patch solves a problem regardless of the tproxy functionality, as it was
impossible to specify an address other than ipv6 in non-ip tables. Tproxy was
only an example to demonstrate the error.

If this patch is applied, there is no need for the 'ip' here (and I'd like to
avoid it). Bridge and netdev tables are not supported to use tproxy in.
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Pablo Neira Ayuso June 20, 2018, 12:26 p.m. UTC | #4
On Wed, Jun 20, 2018 at 02:21:18PM +0200, Máté Eckl wrote:
> On Wed, Jun 20, 2018 at 01:40:45PM +0200, Pablo Neira Ayuso wrote:
> > On Mon, Jun 18, 2018 at 11:57:10AM +0200, Máté Eckl wrote:
> > > Signed-off-by: Máté Eckl <ecklm94@gmail.com>
> > > ---
> > >  src/evaluate.c | 20 ++++++++++++++++++--
> > >  1 file changed, 18 insertions(+), 2 deletions(-)
> > > 
> > > diff --git a/src/evaluate.c b/src/evaluate.c
> > > index d6aff61..0564b44 100644
> > > --- a/src/evaluate.c
> > > +++ b/src/evaluate.c
> > > @@ -2431,12 +2431,28 @@ static int evaluate_addr(struct eval_ctx *ctx, struct stmt *stmt,
> > >  	const struct datatype *dtype;
> > >  	unsigned int len;
> > >  
> > > -	if (pctx->family == NFPROTO_IPV4) {
> > > +	switch (pctx->family) {
> > > +	case NFPROTO_IPV4:
> > >  		dtype = &ipaddr_type;
> > >  		len   = 4 * BITS_PER_BYTE;
> > > -	} else {
> > > +		break;
> > > +	case NFPROTO_IPV6:
> > >  		dtype = &ip6addr_type;
> > >  		len   = 16 * BITS_PER_BYTE;
> > > +		break;
> > > +	case NFPROTO_INET:
> > > +		if (strchr((*expr)->identifier, ':')) {
> > 
> > I'd suggest you specify this in this syntax:
> > 
> >         tproxy ip to 1.1.1.1
> > 
> > for the bridge/netdev/inet families.
> > 
> > From the kernel, this will also skip non-IP packets, so we don't need
> > to build an IP dependency for this statement.
> 
> This patch solves a problem regardless of the tproxy functionality, as it was
> impossible to specify an address other than ipv6 in non-ip tables. Tproxy was
> only an example to demonstrate the error.
> 
> If this patch is applied, there is no need for the 'ip' here (and I'd like to
> avoid it). Bridge and netdev tables are not supported to use tproxy in.

For ip/ip6, tproxy to 1.1.1.1 is fine.

But for bridge/netdev/inet, I think it makes explicit the dependency
with either ip and ip6. So this is visible from the ruleset that this
rule will only apply to either ip or ip6.

And we'll skip having to generate a dependency for this, which is
always more work, given that from the kernel we can just add:

        if (skb->protocol != htons(ETH_P_IP))
                ... break verdict ...

which is actually needed for safety reasons.

I think your kernel patch is also lacking this, and a custom userspace
program may add a tproxy expression to deal with IPV6 traffic, which
may result in crashing the kernel.
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Máté Eckl June 22, 2018, 9:45 a.m. UTC | #5
On Wed, Jun 20, 2018 at 02:26:36PM +0200, Pablo Neira Ayuso wrote:
> On Wed, Jun 20, 2018 at 02:21:18PM +0200, Máté Eckl wrote:
> > On Wed, Jun 20, 2018 at 01:40:45PM +0200, Pablo Neira Ayuso wrote:
> > > On Mon, Jun 18, 2018 at 11:57:10AM +0200, Máté Eckl wrote:
> > > > Signed-off-by: Máté Eckl <ecklm94@gmail.com>
> > > > ---
> > > >  src/evaluate.c | 20 ++++++++++++++++++--
> > > >  1 file changed, 18 insertions(+), 2 deletions(-)
> > > > 
> > > > diff --git a/src/evaluate.c b/src/evaluate.c
> > > > index d6aff61..0564b44 100644
> > > > --- a/src/evaluate.c
> > > > +++ b/src/evaluate.c
> > > > @@ -2431,12 +2431,28 @@ static int evaluate_addr(struct eval_ctx *ctx, struct stmt *stmt,
> > > >  	const struct datatype *dtype;
> > > >  	unsigned int len;
> > > >  
> > > > -	if (pctx->family == NFPROTO_IPV4) {
> > > > +	switch (pctx->family) {
> > > > +	case NFPROTO_IPV4:
> > > >  		dtype = &ipaddr_type;
> > > >  		len   = 4 * BITS_PER_BYTE;
> > > > -	} else {
> > > > +		break;
> > > > +	case NFPROTO_IPV6:
> > > >  		dtype = &ip6addr_type;
> > > >  		len   = 16 * BITS_PER_BYTE;
> > > > +		break;
> > > > +	case NFPROTO_INET:
> > > > +		if (strchr((*expr)->identifier, ':')) {
> > > 
> > > I'd suggest you specify this in this syntax:
> > > 
> > >         tproxy ip to 1.1.1.1
> > > 
> > > for the bridge/netdev/inet families.
> > > 
> > > From the kernel, this will also skip non-IP packets, so we don't need
> > > to build an IP dependency for this statement.
> > 
> > This patch solves a problem regardless of the tproxy functionality, as it was
> > impossible to specify an address other than ipv6 in non-ip tables. Tproxy was
> > only an example to demonstrate the error.
> > 
> > If this patch is applied, there is no need for the 'ip' here (and I'd like to
> > avoid it). Bridge and netdev tables are not supported to use tproxy in.
> 
> For ip/ip6, tproxy to 1.1.1.1 is fine.
> 
> But for bridge/netdev/inet, I think it makes explicit the dependency
> with either ip and ip6. So this is visible from the ruleset that this
> rule will only apply to either ip or ip6.

I thought that the address itself will be explicit enough to decide which of the
families the rule deals with. I (as a user) would never think that any ipv4
address will be used for ipv6 socket lookup.
If you disagree, I can specify it in case of address is specified, I just liked
the idea of not having to specify this.

> And we'll skip having to generate a dependency for this, which is
> always more work, given that from the kernel we can just add:

I might not understand clearly what you mean by dependency.  I thought that ip,
ip6 and inet tables only receive packets that have ipv4/ipv6 network layer
header, so this dependency is satisfied by definition, doesn't it? I don't
think I generated any dependency explicitly.

>         if (skb->protocol != htons(ETH_P_IP))
>                 ... break verdict ...
> 
> which is actually needed for safety reasons.

This is something that should appear in the eval function right?
Isn't it the same as what I added there?

		switch (nft_pf(pkt)) {
		case NFPROTO_IPV4:
			switch (priv->family) {
			case NFPROTO_IPV4:
			case NFPROTO_INET:
				nft_tproxy_eval_v4(expr, regs, pkt);
				break;
			default:
				regs->verdict.code = NFT_BREAK;
				break;
			}
			break;
	#if IS_ENABLED(CONFIG_NF_TPROXY_IPV6)
		case NFPROTO_IPV6:
			switch (priv->family) {
			case NFPROTO_IPV6:
			case NFPROTO_INET:
				nft_tproxy_eval_v6(expr, regs, pkt);
				break;
			default:
				regs->verdict.code = NFT_BREAK;
				break;
			}
			break;
	#endif
		}

> I think your kernel patch is also lacking this, and a custom userspace
> program may add a tproxy expression to deal with IPV6 traffic, which
> may result in crashing the kernel.

Given the switch-case I don't think an ipv6 packet can pass to ipv4 evaluation.
Am I missing something?
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Pablo Neira Ayuso June 22, 2018, 9:53 a.m. UTC | #6
On Fri, Jun 22, 2018 at 11:45:12AM +0200, Máté Eckl wrote:
[...]
> >         if (skb->protocol != htons(ETH_P_IP))
> >                 ... break verdict ...
>
> > which is actually needed for safety reasons.
> 
> This is something that should appear in the eval function right?

Also in the kernel for safety reasons.

> Isn't it the same as what I added there?

This is needed because someone may use the raw kernel netlink
interface (not libnftnl / nftables) to generate an incorrect
combination such as allow IPv6 to be passed to tproxy in IPV4 mode
which may crash the kernel.

Well, it may be just result in a packet drop, but better be safe than
sorry.
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Máté Eckl June 24, 2018, 1:10 p.m. UTC | #7
On Fri, Jun 22, 2018 at 11:53:44AM +0200, Pablo Neira Ayuso wrote:
> On Fri, Jun 22, 2018 at 11:45:12AM +0200, Máté Eckl wrote:
> [...]
> > >         if (skb->protocol != htons(ETH_P_IP))
> > >                 ... break verdict ...
> >
> > > which is actually needed for safety reasons.
> > 
> > This is something that should appear in the eval function right?
> 
> Also in the kernel for safety reasons.

I meant the kernel eval function (which evaluates packets against rules).

> > Isn't it the same as what I added there?
> 
> This is needed because someone may use the raw kernel netlink
> interface (not libnftnl / nftables) to generate an incorrect
> combination such as allow IPv6 to be passed to tproxy in IPV4 mode
> which may crash the kernel.
> 
> Well, it may be just result in a packet drop, but better be safe than
> sorry.

The code snippet included in my previous email was also from the kernel code
and it seems to provide the same check as the 'if' you suggested.
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Máté Eckl June 26, 2018, 1:54 p.m. UTC | #8
Hi Pablo,

Could we make things clear about this discussion? I think we have some
misunderstandings, and I am certainly unaware of some concerns you are trying to
enforce.

How about discussing them one by one?


= Detect or specify address family in tproxy command =

I submitted a patch to detect the address family in evaluate_addr in case the
table is INET and print an error if table is not IPV4, IPV6 or INET.

Do you disagree with this solution in general or only in case of tproxy?

In general, I think it is better to have explicit checks here as the error
message generated by the current solution may be misleading in a situation like
mine (basically every time trying ot evaluate an ipv4 address to a rule in inet
table).

Regarding family detection, you say family should be specified in the rule in
case the table is inet. Do you say this because of UX or technical reasons?

I think, the technical part can easily be covered (as I do in
stmt_evaluate_tproxy) with setting a family attribute after a successfull
evaluation and sending it to the kernel. As a consequence the family of the rule
match is not ambiguous technically.

The UX part is more subjective by nature. In my opinion, if a user specifies an
IPv4 address he/she expects that it will match IPv4 for packets and the same
with IPv6. Therefor my intention is to leave explicit specification out.
Do you think it is not this obvious?

In tproxy case, the kernel will receive a family attribute with the value of
IPV4 or IPV6 in case an address is specified so it will not be necessary to
generate ip dependency to an ipv6 rule (although I don't think I generate any
dependency explicitly) and this information is used for matching, so kernel has
sufficient information of the family of the role.


= Dependency =

You mentioned some kind of dependency in some of your emails, but I don't know
what you think of. Could you describe what a dependency is in this context? Is
it in user- or kernelspace?

For ip/ip6/inet tables isn't that default to process *only* packets with IPv4 or
IPv6 header? If it is default, I don't understand why any other dependency
should be generated, I can just check the family of the packet against the
family of the, can't I?

You bring bridge/netdev as examples, but tproxy is not supported in these tables
(refused from nft_tproxy_init in the kernel), so I don't see why we should
consider them. Regarding the address family detection, the evaluate_addr
function only cares about L3 addresses so bridge/netdev seems to be irrelevant
here, too.


= Kernel, address family check =

You had a comment in an email which I still don't really understand.

	I think your kernel patch is also lacking this, and a custom userspace
	program may add a tproxy expression to deal with IPV6 traffic, which
	may result in crashing the kernel.

Do you mean that a program can add an expression with an IPv6 address with a
family of NFPROTO_IPV4? Then I suppose the nft_validate_register_load should
return error in the init function, shouldn't it?

Do you mean that address may be specified whereas tproxy family is left on
default? Yes, this was possible, I added a check for this in my v2 patch.

Do you mean, that an IPv6 packet can be passed to the IPv4 evaluation function?
Your code snippet seems to be related to this. It examined if the L3 header is IP
header, but it really seems to be the same as what I do (at least in ip/ip6/inet
tables). I compare the packet's family (from nft_pf()) and the tproxy family,
and I pass the packet to the v4 or v6 eval function respectively. The L3 header
should be present so this should work securely with such a strict init function.

If none of these, could you please define the scenario with more details? In
which point do you think my code is voulnerable and what is a scenario?


I hope I grabbed the important points to make this situation clear.
It would be nice if you could reply to all these notes and questions as I am
confused about what we disagree on right now.

Regards,
Máté
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Pablo Neira Ayuso June 26, 2018, 2:28 p.m. UTC | #9
On Tue, Jun 26, 2018 at 03:54:51PM +0200, Máté Eckl wrote:
[...]
> Do you disagree with this solution in general or only in case of tproxy?
> 
> In general, I think it is better to have explicit checks here as the error
> message generated by the current solution may be misleading in a situation like
> mine (basically every time trying ot evaluate an ipv4 address to a rule in inet
> table).

Please, don't do this character probing from the evaluation step. It's
a hack. I would like not to see patches that address parser
limitations from the evaluation phase.

[...]
> = Dependency =
> 
> You mentioned some kind of dependency in some of your emails, but I don't know
> what you think of. Could you describe what a dependency is in this context? Is
> it in user- or kernelspace?
> 
> For ip/ip6/inet tables isn't that default to process *only* packets with IPv4 or
> IPv6 header? If it is default, I don't understand why any other dependency
> should be generated, I can just check the family of the packet against the
> family of the, can't I?

I was refering to the inet family, specifically.

> You bring bridge/netdev as examples, but tproxy is not supported in these tables
> (refused from nft_tproxy_init in the kernel), so I don't see why we should
> consider them. Regarding the address family detection, the evaluate_addr
> function only cares about L3 addresses so bridge/netdev seems to be irrelevant
> here, too.

Right, there's no bridge/netdev in tproxy .I was arguing about the
syntax for this command and commands that look similar, like dup and fwd.

Never mind about the dependency, actually "Kernel, address family
check" and "Dependency" are the same thing.

> = Kernel, address family check =
> 
> You had a comment in an email which I still don't really understand.
> 
> 	I think your kernel patch is also lacking this, and a custom userspace
> 	program may add a tproxy expression to deal with IPV6 traffic, which
> 	may result in crashing the kernel.

My point is: Just make sure that your code doesn't allow IPv6 packets
to run through the IPv4 path, and the other way around.

Please, just send a v2 of your patches that we can review, we can
address your concerns in a follow up step.

If there's any concern, we can revisit, don't worry.
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox series

Patch

diff --git a/src/evaluate.c b/src/evaluate.c
index d6aff61..0564b44 100644
--- a/src/evaluate.c
+++ b/src/evaluate.c
@@ -2431,12 +2431,28 @@  static int evaluate_addr(struct eval_ctx *ctx, struct stmt *stmt,
 	const struct datatype *dtype;
 	unsigned int len;
 
-	if (pctx->family == NFPROTO_IPV4) {
+	switch (pctx->family) {
+	case NFPROTO_IPV4:
 		dtype = &ipaddr_type;
 		len   = 4 * BITS_PER_BYTE;
-	} else {
+		break;
+	case NFPROTO_IPV6:
 		dtype = &ip6addr_type;
 		len   = 16 * BITS_PER_BYTE;
+		break;
+	case NFPROTO_INET:
+		if (strchr((*expr)->identifier, ':')) {
+			dtype = &ip6addr_type;
+			len   = 16 * BITS_PER_BYTE;
+		}
+		else {
+			dtype = &ipaddr_type;
+			len   = 4 * BITS_PER_BYTE;
+		}
+		break;
+	default:
+		return stmt_binary_error(ctx, *expr, stmt,
+					 "Invalid context family for address evaluation");
 	}
 
 	return stmt_evaluate_arg(ctx, stmt, dtype, len, BYTEORDER_BIG_ENDIAN,