diff mbox series

[libnetfilter_queue] src: fix IPv6 header handling

Message ID 28947A39-55C4-4C68-8421-DEC950CF7963@apple.com
State Superseded
Delegated to: Pablo Neira
Headers show
Series [libnetfilter_queue] src: fix IPv6 header handling | expand

Commit Message

Etan Kissling Jan. 13, 2021, 9:58 a.m. UTC
This corrects issues in IPv6 header handling that sometimes resulted
in an endless loop.

Signed-off-by: Etan Kissling <etan_kissling@apple.com>
---
 src/extra/ipv6.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

Comments

Pablo Neira Ayuso Feb. 9, 2021, 4:30 p.m. UTC | #1
Hi Etan,

On Wed, Jan 13, 2021 at 10:58:52AM +0100, Etan Kissling wrote:
> diff --git a/src/extra/ipv6.c b/src/extra/ipv6.c
> index 42c5e25..1eb822f 100644
> --- a/src/extra/ipv6.c
> +++ b/src/extra/ipv6.c
> @@ -72,7 +72,8 @@ int nfq_ip6_set_transport_header(struct pkt_buff *pktb, struct ip6_hdr *ip6h,

Note: nfq_ip6_set_transport_header() is very much similar to
ipv6_skip_exthdr() in the Linux kernel, see net/ipv6/exthdrs_core.c

>  		uint32_t hdrlen;
>  
>  		/* No more extensions, we're done. */
> -		if (nexthdr == IPPROTO_NONE) {
> +		if (nexthdr == IPPROTO_TCP || nexthdr == IPPROTO_UDP || nexthdr == IPPROTO_ESP ||
> +		        nexthdr == IPPROTO_ICMPV6 || nexthdr == IPPROTO_NONE) {
>  			cur = NULL;
>  			break;
>  		}
> @@ -107,7 +108,7 @@ int nfq_ip6_set_transport_header(struct pkt_buff *pktb, struct ip6_hdr *ip6h,
>  		} else if (nexthdr == IPPROTO_AH)
>  			hdrlen = (ip6_ext->ip6e_len + 2) << 2;
>  		else
> -			hdrlen = ip6_ext->ip6e_len;
> +			hdrlen = (ip6_ext->ip6e_len + 1) << 3;

This looks correct, IPv6 optlen is miscalculated.

The chunk above to stop the iteration, so I think the chunk that fixes
optlen is sufficient to fix the bug.

Thanks.
Etan Kissling Feb. 17, 2021, 3:09 p.m. UTC | #2
On 09.02.21, 17:33, "Pablo Neira Ayuso" <pablo@netfilter.org> wrote:

Thanks for looking into this.

> Note: nfq_ip6_set_transport_header() is very much similar to
> ipv6_skip_exthdr() in the Linux kernel, see net/ipv6/exthdrs_core.c

I submitted a revised version that uses similar logic as in the kernel
to exit the loop once extension headers are fully processed.

> >  		uint32_t hdrlen;
> >  
> >  		/* No more extensions, we're done. */
> > -		if (nexthdr == IPPROTO_NONE) {
> > +		if (nexthdr == IPPROTO_TCP || nexthdr == IPPROTO_UDP || nexthdr == IPPROTO_ESP ||
> > +		        nexthdr == IPPROTO_ICMPV6 || nexthdr == IPPROTO_NONE) {
> >  			cur = NULL;
> >  			break;
> >  		}
> > @@ -107,7 +108,7 @@ int nfq_ip6_set_transport_header(struct pkt_buff *pktb, struct ip6_hdr *ip6h,
> >  		} else if (nexthdr == IPPROTO_AH)
> >  			hdrlen = (ip6_ext->ip6e_len + 2) << 2;
> >  		else
> > -			hdrlen = ip6_ext->ip6e_len;
> > +			hdrlen = (ip6_ext->ip6e_len + 1) << 3;
>
> This looks correct, IPv6 optlen is miscalculated.
>
> The chunk above to stop the iteration, so I think the chunk that fixes
> optlen is sufficient to fix the bug.

The optlen fix is not sufficient when a non-existent 'target' is given.
For example, if a UDP packet is passed with 'target' IPPROTO_TCP,
the loop will go on and attempt to interpret the UDP packet body
as another IP extension header. Instead, by exiting the loop also
when all extension headers are processed, the function will now return
0 in that case, as documented in the header file.

Thanks

Etan
diff mbox series

Patch

diff --git a/src/extra/ipv6.c b/src/extra/ipv6.c
index 42c5e25..1eb822f 100644
--- a/src/extra/ipv6.c
+++ b/src/extra/ipv6.c
@@ -72,7 +72,8 @@  int nfq_ip6_set_transport_header(struct pkt_buff *pktb, struct ip6_hdr *ip6h,
 		uint32_t hdrlen;
 
 		/* No more extensions, we're done. */
-		if (nexthdr == IPPROTO_NONE) {
+		if (nexthdr == IPPROTO_TCP || nexthdr == IPPROTO_UDP || nexthdr == IPPROTO_ESP ||
+		        nexthdr == IPPROTO_ICMPV6 || nexthdr == IPPROTO_NONE) {
 			cur = NULL;
 			break;
 		}
@@ -107,7 +108,7 @@  int nfq_ip6_set_transport_header(struct pkt_buff *pktb, struct ip6_hdr *ip6h,
 		} else if (nexthdr == IPPROTO_AH)
 			hdrlen = (ip6_ext->ip6e_len + 2) << 2;
 		else
-			hdrlen = ip6_ext->ip6e_len;
+			hdrlen = (ip6_ext->ip6e_len + 1) << 3;
 
 		nexthdr = ip6_ext->ip6e_nxt;
 		cur += hdrlen;