diff mbox

[ovs-dev] conntrack: Fix checks for TCP, UDP, and IPv6 header sizes.

Message ID 20170303220829.22050-1-blp@ovn.org
State Superseded
Headers show

Commit Message

Ben Pfaff March 3, 2017, 10:08 p.m. UTC
Otherwise a malformed packet could cause a read up to about 40 bytes past
the end of the packet.  The packet would still likely be dropped because
of checksum verification.

Reported-by: Bhargava Shastry <bshastry@sec.t-labs.tu-berlin.de>
Signed-off-by: Ben Pfaff <blp@ovn.org>
---
 lib/conntrack.c | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

Comments

Daniele Di Proietto March 4, 2017, 1 a.m. UTC | #1
2017-03-03 14:08 GMT-08:00 Ben Pfaff <blp@ovn.org>:
> Otherwise a malformed packet could cause a read up to about 40 bytes past
> the end of the packet.  The packet would still likely be dropped because
> of checksum verification.
>
> Reported-by: Bhargava Shastry <bshastry@sec.t-labs.tu-berlin.de>
> Signed-off-by: Ben Pfaff <blp@ovn.org>

Oops, thanks for the fix, Ben

Fixes: a489b16854b5("conntrack: New userspace connection tracker.")

One minor comment below,

Acked-by: Daniele Di Proietto <diproiettod@vmware.com>


> ---
>  lib/conntrack.c | 14 ++++++++++++--
>  1 file changed, 12 insertions(+), 2 deletions(-)
>
> diff --git a/lib/conntrack.c b/lib/conntrack.c
> index 9bea3d93e4ad..9c1dd63648b8 100644
> --- a/lib/conntrack.c
> +++ b/lib/conntrack.c
> @@ -568,6 +568,10 @@ extract_l3_ipv6(struct conn_key *key, const void *data, size_t size,
>                  const char **new_data)
>  {
>      const struct ovs_16aligned_ip6_hdr *ip6 = data;
> +    if (size < sizeof *ip6) {
> +        return false;
> +    }
> +

We can read 'ip6->ip6_nxt' even though there's not enough data.  It
cannot happen
for regular TCP and UDP packets (those are covered my
miniflow_extract), but only
when parsing the nested l3 header in an ICMP error message.

The code has the same check two lines below, maybe we can reuse that.
Technically
the check is necessary only if new_data != NULL, as explained by the comment
above, but perhaps it's more clear to always perform it.


>      uint8_t nw_proto = ip6->ip6_nxt;
>      uint8_t nw_frag = 0;
>
> @@ -623,8 +627,11 @@ check_l4_tcp(const struct conn_key *key, const void *data, size_t size,
>               const void *l3)
>  {
>      const struct tcp_header *tcp = data;
> -    size_t tcp_len = TCP_OFFSET(tcp->tcp_ctl) * 4;
> +    if (size < sizeof *tcp) {
> +        return false;
> +    }
>
> +    size_t tcp_len = TCP_OFFSET(tcp->tcp_ctl) * 4;
>      if (OVS_UNLIKELY(tcp_len < TCP_HEADER_LEN || tcp_len > size)) {
>          return false;
>      }
> @@ -637,8 +644,11 @@ check_l4_udp(const struct conn_key *key, const void *data, size_t size,
>               const void *l3)
>  {
>      const struct udp_header *udp = data;
> -    size_t udp_len = ntohs(udp->udp_len);
> +    if (size < sizeof *udp) {
> +        return false;
> +    }
>
> +    size_t udp_len = ntohs(udp->udp_len);
>      if (OVS_UNLIKELY(udp_len < UDP_HEADER_LEN || udp_len > size)) {
>          return false;
>      }
> --
> 2.10.2
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
Ben Pfaff March 4, 2017, 5:18 a.m. UTC | #2
On Fri, Mar 03, 2017 at 05:00:38PM -0800, Daniele Di Proietto wrote:
> 2017-03-03 14:08 GMT-08:00 Ben Pfaff <blp@ovn.org>:
> > Otherwise a malformed packet could cause a read up to about 40 bytes past
> > the end of the packet.  The packet would still likely be dropped because
> > of checksum verification.
> >
> > Reported-by: Bhargava Shastry <bshastry@sec.t-labs.tu-berlin.de>
> > Signed-off-by: Ben Pfaff <blp@ovn.org>
> 
> Oops, thanks for the fix, Ben
> 
> Fixes: a489b16854b5("conntrack: New userspace connection tracker.")
> 
> One minor comment below,
> 
> Acked-by: Daniele Di Proietto <diproiettod@vmware.com>
> 
> 
> > ---
> >  lib/conntrack.c | 14 ++++++++++++--
> >  1 file changed, 12 insertions(+), 2 deletions(-)
> >
> > diff --git a/lib/conntrack.c b/lib/conntrack.c
> > index 9bea3d93e4ad..9c1dd63648b8 100644
> > --- a/lib/conntrack.c
> > +++ b/lib/conntrack.c
> > @@ -568,6 +568,10 @@ extract_l3_ipv6(struct conn_key *key, const void *data, size_t size,
> >                  const char **new_data)
> >  {
> >      const struct ovs_16aligned_ip6_hdr *ip6 = data;
> > +    if (size < sizeof *ip6) {
> > +        return false;
> > +    }
> > +
> 
> We can read 'ip6->ip6_nxt' even though there's not enough data.  It
> cannot happen
> for regular TCP and UDP packets (those are covered my
> miniflow_extract), but only
> when parsing the nested l3 header in an ICMP error message.
> 
> The code has the same check two lines below, maybe we can reuse that.
> Technically
> the check is necessary only if new_data != NULL, as explained by the comment
> above, but perhaps it's more clear to always perform it.

Thanks for pointing out the duplicate check.  I guess that I did not
read the code carefully enough.

Usually, I would argue to always do the check, but I prefer to make this
a minimal change.

I will post a v2.
Bhargava Shastry March 4, 2017, 9:09 a.m. UTC | #3
Hi Ben,

Question regarding patch: Shouldn't the fix be applied in flow extract code itself? I think the malformedness is evident during flow extraction. Might save you a few cycles/more secure.


On March 4, 2017 6:18:54 AM GMT+01:00, Ben Pfaff <blp@ovn.org> wrote:
>On Fri, Mar 03, 2017 at 05:00:38PM -0800, Daniele Di Proietto wrote:
>> 2017-03-03 14:08 GMT-08:00 Ben Pfaff <blp@ovn.org>:
>> > Otherwise a malformed packet could cause a read up to about 40
>bytes past
>> > the end of the packet.  The packet would still likely be dropped
>because
>> > of checksum verification.
>> >
>> > Reported-by: Bhargava Shastry <bshastry@sec.t-labs.tu-berlin.de>
>> > Signed-off-by: Ben Pfaff <blp@ovn.org>
>> 
>> Oops, thanks for the fix, Ben
>> 
>> Fixes: a489b16854b5("conntrack: New userspace connection tracker.")
>> 
>> One minor comment below,
>> 
>> Acked-by: Daniele Di Proietto <diproiettod@vmware.com>
>> 
>> 
>> > ---
>> >  lib/conntrack.c | 14 ++++++++++++--
>> >  1 file changed, 12 insertions(+), 2 deletions(-)
>> >
>> > diff --git a/lib/conntrack.c b/lib/conntrack.c
>> > index 9bea3d93e4ad..9c1dd63648b8 100644
>> > --- a/lib/conntrack.c
>> > +++ b/lib/conntrack.c
>> > @@ -568,6 +568,10 @@ extract_l3_ipv6(struct conn_key *key, const
>void *data, size_t size,
>> >                  const char **new_data)
>> >  {
>> >      const struct ovs_16aligned_ip6_hdr *ip6 = data;
>> > +    if (size < sizeof *ip6) {
>> > +        return false;
>> > +    }
>> > +
>> 
>> We can read 'ip6->ip6_nxt' even though there's not enough data.  It
>> cannot happen
>> for regular TCP and UDP packets (those are covered my
>> miniflow_extract), but only
>> when parsing the nested l3 header in an ICMP error message.
>> 
>> The code has the same check two lines below, maybe we can reuse that.
>> Technically
>> the check is necessary only if new_data != NULL, as explained by the
>comment
>> above, but perhaps it's more clear to always perform it.
>
>Thanks for pointing out the duplicate check.  I guess that I did not
>read the code carefully enough.
>
>Usually, I would argue to always do the check, but I prefer to make
>this
>a minimal change.
>
>I will post a v2.
Ben Pfaff March 4, 2017, 4:03 p.m. UTC | #4
What bug do you see in the flow extract code?

On Sat, Mar 04, 2017 at 10:09:26AM +0100, Bhargava Shastry wrote:
> Hi Ben,
> 
> Question regarding patch: Shouldn't the fix be applied in flow extract code itself? I think the malformedness is evident during flow extraction. Might save you a few cycles/more secure.
> 
> 
> On March 4, 2017 6:18:54 AM GMT+01:00, Ben Pfaff <blp@ovn.org> wrote:
> >On Fri, Mar 03, 2017 at 05:00:38PM -0800, Daniele Di Proietto wrote:
> >> 2017-03-03 14:08 GMT-08:00 Ben Pfaff <blp@ovn.org>:
> >> > Otherwise a malformed packet could cause a read up to about 40
> >bytes past
> >> > the end of the packet.  The packet would still likely be dropped
> >because
> >> > of checksum verification.
> >> >
> >> > Reported-by: Bhargava Shastry <bshastry@sec.t-labs.tu-berlin.de>
> >> > Signed-off-by: Ben Pfaff <blp@ovn.org>
> >> 
> >> Oops, thanks for the fix, Ben
> >> 
> >> Fixes: a489b16854b5("conntrack: New userspace connection tracker.")
> >> 
> >> One minor comment below,
> >> 
> >> Acked-by: Daniele Di Proietto <diproiettod@vmware.com>
> >> 
> >> 
> >> > ---
> >> >  lib/conntrack.c | 14 ++++++++++++--
> >> >  1 file changed, 12 insertions(+), 2 deletions(-)
> >> >
> >> > diff --git a/lib/conntrack.c b/lib/conntrack.c
> >> > index 9bea3d93e4ad..9c1dd63648b8 100644
> >> > --- a/lib/conntrack.c
> >> > +++ b/lib/conntrack.c
> >> > @@ -568,6 +568,10 @@ extract_l3_ipv6(struct conn_key *key, const
> >void *data, size_t size,
> >> >                  const char **new_data)
> >> >  {
> >> >      const struct ovs_16aligned_ip6_hdr *ip6 = data;
> >> > +    if (size < sizeof *ip6) {
> >> > +        return false;
> >> > +    }
> >> > +
> >> 
> >> We can read 'ip6->ip6_nxt' even though there's not enough data.  It
> >> cannot happen
> >> for regular TCP and UDP packets (those are covered my
> >> miniflow_extract), but only
> >> when parsing the nested l3 header in an ICMP error message.
> >> 
> >> The code has the same check two lines below, maybe we can reuse that.
> >> Technically
> >> the check is necessary only if new_data != NULL, as explained by the
> >comment
> >> above, but perhaps it's more clear to always perform it.
> >
> >Thanks for pointing out the duplicate check.  I guess that I did not
> >read the code carefully enough.
> >
> >Usually, I would argue to always do the check, but I prefer to make
> >this
> >a minimal change.
> >
> >I will post a v2.
> 
> -- 
> Sent from my Android device with K-9 Mail. Please excuse my brevity.
Bhargava Shastry March 4, 2017, 7:32 p.m. UTC | #5
My point is "miniflow_extract" has these checks that indicate a failed
parsing attempt for the packets in question. For example,

```C
else if (OVS_LIKELY(nw_proto == IPPROTO_ICMP)) {
   if (OVS_LIKELY(size >= ICMP_HEADER_LEN)) {
	do_something_with_valid_icmp_packet();
   }
   // Signaling of failed parsing attempt does not take place  //
   // i.e., no else corresponding to above predicate	       //
}

...

out:
   dst->map = mf.map
```

So when you know that a packet is malformed during flow extraction
itself, why would you let the packet float around in your downstream
packet processing pipeline? Similar argument for malformed TCP/UDP packets.

I'm afraid I don't know the code well, but I feel that a failed parsing
attempt must be signaled by flow_extract _somehow_ and no subsequent
processing of the invalid packet should take place. This is because, in
the absence of a fail signal, downstream processing code (e.g.
conntrack) implicitly trusts all packets to be valid. Of course, fixing
the bug at conntrack will close the specific holes in point, but there
may be bugs elsewhere that share the same root cause. Am I making sense?

Regards,
Bhargava

On 03/04/2017 05:03 PM, Ben Pfaff wrote:
> What bug do you see in the flow extract code?
> 
> On Sat, Mar 04, 2017 at 10:09:26AM +0100, Bhargava Shastry wrote:
>> Hi Ben,
>>
>> Question regarding patch: Shouldn't the fix be applied in flow extract code itself? I think the malformedness is evident during flow extraction. Might save you a few cycles/more secure.
>>
>>
>> On March 4, 2017 6:18:54 AM GMT+01:00, Ben Pfaff <blp@ovn.org> wrote:
>>> On Fri, Mar 03, 2017 at 05:00:38PM -0800, Daniele Di Proietto wrote:
>>>> 2017-03-03 14:08 GMT-08:00 Ben Pfaff <blp@ovn.org>:
>>>>> Otherwise a malformed packet could cause a read up to about 40
>>> bytes past
>>>>> the end of the packet.  The packet would still likely be dropped
>>> because
>>>>> of checksum verification.
>>>>>
>>>>> Reported-by: Bhargava Shastry <bshastry@sec.t-labs.tu-berlin.de>
>>>>> Signed-off-by: Ben Pfaff <blp@ovn.org>
>>>>
>>>> Oops, thanks for the fix, Ben
>>>>
>>>> Fixes: a489b16854b5("conntrack: New userspace connection tracker.")
>>>>
>>>> One minor comment below,
>>>>
>>>> Acked-by: Daniele Di Proietto <diproiettod@vmware.com>
>>>>
>>>>
>>>>> ---
>>>>>  lib/conntrack.c | 14 ++++++++++++--
>>>>>  1 file changed, 12 insertions(+), 2 deletions(-)
>>>>>
>>>>> diff --git a/lib/conntrack.c b/lib/conntrack.c
>>>>> index 9bea3d93e4ad..9c1dd63648b8 100644
>>>>> --- a/lib/conntrack.c
>>>>> +++ b/lib/conntrack.c
>>>>> @@ -568,6 +568,10 @@ extract_l3_ipv6(struct conn_key *key, const
>>> void *data, size_t size,
>>>>>                  const char **new_data)
>>>>>  {
>>>>>      const struct ovs_16aligned_ip6_hdr *ip6 = data;
>>>>> +    if (size < sizeof *ip6) {
>>>>> +        return false;
>>>>> +    }
>>>>> +
>>>>
>>>> We can read 'ip6->ip6_nxt' even though there's not enough data.  It
>>>> cannot happen
>>>> for regular TCP and UDP packets (those are covered my
>>>> miniflow_extract), but only
>>>> when parsing the nested l3 header in an ICMP error message.
>>>>
>>>> The code has the same check two lines below, maybe we can reuse that.
>>>> Technically
>>>> the check is necessary only if new_data != NULL, as explained by the
>>> comment
>>>> above, but perhaps it's more clear to always perform it.
>>>
>>> Thanks for pointing out the duplicate check.  I guess that I did not
>>> read the code carefully enough.
>>>
>>> Usually, I would argue to always do the check, but I prefer to make
>>> this
>>> a minimal change.
>>>
>>> I will post a v2.
>>
>> -- 
>> Sent from my Android device with K-9 Mail. Please excuse my brevity.
Ben Pfaff March 4, 2017, 8:20 p.m. UTC | #6
On Sat, Mar 04, 2017 at 08:32:39PM +0100, Bhargava Shastry wrote:
> My point is "miniflow_extract" has these checks that indicate a failed
> parsing attempt for the packets in question. For example,
> 
> ```C
> else if (OVS_LIKELY(nw_proto == IPPROTO_ICMP)) {
>    if (OVS_LIKELY(size >= ICMP_HEADER_LEN)) {
> 	do_something_with_valid_icmp_packet();
>    }
>    // Signaling of failed parsing attempt does not take place  //
>    // i.e., no else corresponding to above predicate	       //
> }
> 
> ...
> 
> out:
>    dst->map = mf.map
> ```
> 
> So when you know that a packet is malformed during flow extraction
> itself, why would you let the packet float around in your downstream
> packet processing pipeline? Similar argument for malformed TCP/UDP packets.

OVS isn't just a firewall.  It's also a switch that should be able to
handle any packet, not just the ones that pass some kind of firewall
check.
diff mbox

Patch

diff --git a/lib/conntrack.c b/lib/conntrack.c
index 9bea3d93e4ad..9c1dd63648b8 100644
--- a/lib/conntrack.c
+++ b/lib/conntrack.c
@@ -568,6 +568,10 @@  extract_l3_ipv6(struct conn_key *key, const void *data, size_t size,
                 const char **new_data)
 {
     const struct ovs_16aligned_ip6_hdr *ip6 = data;
+    if (size < sizeof *ip6) {
+        return false;
+    }
+
     uint8_t nw_proto = ip6->ip6_nxt;
     uint8_t nw_frag = 0;
 
@@ -623,8 +627,11 @@  check_l4_tcp(const struct conn_key *key, const void *data, size_t size,
              const void *l3)
 {
     const struct tcp_header *tcp = data;
-    size_t tcp_len = TCP_OFFSET(tcp->tcp_ctl) * 4;
+    if (size < sizeof *tcp) {
+        return false;
+    }
 
+    size_t tcp_len = TCP_OFFSET(tcp->tcp_ctl) * 4;
     if (OVS_UNLIKELY(tcp_len < TCP_HEADER_LEN || tcp_len > size)) {
         return false;
     }
@@ -637,8 +644,11 @@  check_l4_udp(const struct conn_key *key, const void *data, size_t size,
              const void *l3)
 {
     const struct udp_header *udp = data;
-    size_t udp_len = ntohs(udp->udp_len);
+    if (size < sizeof *udp) {
+        return false;
+    }
 
+    size_t udp_len = ntohs(udp->udp_len);
     if (OVS_UNLIKELY(udp_len < UDP_HEADER_LEN || udp_len > size)) {
         return false;
     }