diff mbox series

net: tap: validate metadata and length for XDP buff before building up skb

Message ID 1717026141-25716-1-git-send-email-si-wei.liu@oracle.com (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series net: tap: validate metadata and length for XDP buff before building up skb | expand

Checks

Context Check Description
netdev/series_format warning Single patches do not need cover letters; Target tree name not specified in the subject
netdev/tree_selection success Guessed tree name to be net-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 902 this patch: 902
netdev/build_tools success No tools touched, skip
netdev/cc_maintainers warning 4 maintainers not CCed: john.fastabend@gmail.com hawk@kernel.org ast@kernel.org daniel@iogearbox.net
netdev/build_clang success Errors and warnings before: 906 this patch: 906
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success Fixes tag looks correct
netdev/build_allmodconfig_warn success Errors and warnings before: 906 this patch: 906
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 13 lines checked
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
netdev/contest success net-next-2024-05-30--09-00 (tests: 1041)

Commit Message

Si-Wei Liu May 29, 2024, 11:42 p.m. UTC
The cited commit missed to check against the validity of the length
and various pointers on the XDP buff metadata in the tap_get_user_xdp()
path, which could cause a corrupted skb to be sent downstack. For
instance, tap_get_user() prohibits short frame which has the length
less than Ethernet header size from being transmitted, while the
skb_set_network_header() in tap_get_user_xdp() would set skb's
network_header regardless of the actual XDP buff data size. This
could either cause out-of-bound access beyond the actual length, or
confuse the underlayer with incorrect or inconsistent header length
in the skb metadata.

Propose to drop any frame shorter than the Ethernet header size just
like how tap_get_user() does. While at it, validate the pointers in
XDP buff to avoid potential size overrun.

Fixes: 0efac27791ee ("tap: accept an array of XDP buffs through sendmsg()")
Cc: jasowang@redhat.com
Signed-off-by: Si-Wei Liu <si-wei.liu@oracle.com>
---
 drivers/net/tap.c | 7 +++++++
 1 file changed, 7 insertions(+)

Comments

Jason Wang May 30, 2024, 2:26 a.m. UTC | #1
On Thu, May 30, 2024 at 8:54 AM Si-Wei Liu <si-wei.liu@oracle.com> wrote:
>
> The cited commit missed to check against the validity of the length
> and various pointers on the XDP buff metadata in the tap_get_user_xdp()
> path, which could cause a corrupted skb to be sent downstack. For
> instance, tap_get_user() prohibits short frame which has the length
> less than Ethernet header size from being transmitted, while the
> skb_set_network_header() in tap_get_user_xdp() would set skb's
> network_header regardless of the actual XDP buff data size. This
> could either cause out-of-bound access beyond the actual length, or
> confuse the underlayer with incorrect or inconsistent header length
> in the skb metadata.
>
> Propose to drop any frame shorter than the Ethernet header size just
> like how tap_get_user() does. While at it, validate the pointers in
> XDP buff to avoid potential size overrun.
>
> Fixes: 0efac27791ee ("tap: accept an array of XDP buffs through sendmsg()")
> Cc: jasowang@redhat.com
> Signed-off-by: Si-Wei Liu <si-wei.liu@oracle.com>
> ---
>  drivers/net/tap.c | 7 +++++++
>  1 file changed, 7 insertions(+)
>
> diff --git a/drivers/net/tap.c b/drivers/net/tap.c
> index bfdd3875fe86..69596479536f 100644
> --- a/drivers/net/tap.c
> +++ b/drivers/net/tap.c
> @@ -1177,6 +1177,13 @@ static int tap_get_user_xdp(struct tap_queue *q, struct xdp_buff *xdp)
>         struct sk_buff *skb;
>         int err, depth;
>
> +       if (unlikely(xdp->data < xdp->data_hard_start ||
> +                    xdp->data_end < xdp->data ||
> +                    xdp->data_end - xdp->data < ETH_HLEN)) {
> +               err = -EINVAL;
> +               goto err;
> +       }

For ETH_HLEN check, is it better to do it in vhost-net? It seems
tuntap suffers from this as well.

And for the check for other xdp fields, it deserves a BUG_ON() or at
least WARN_ON() as they are set by vhost-net.

Thanks

> +
>         if (q->flags & IFF_VNET_HDR)
>                 vnet_hdr_len = READ_ONCE(q->vnet_hdr_sz);
>
> --
> 2.39.3
>
Si-Wei Liu May 30, 2024, 9:04 p.m. UTC | #2
On 5/29/2024 7:26 PM, Jason Wang wrote:
> On Thu, May 30, 2024 at 8:54 AM Si-Wei Liu <si-wei.liu@oracle.com> wrote:
>> The cited commit missed to check against the validity of the length
>> and various pointers on the XDP buff metadata in the tap_get_user_xdp()
>> path, which could cause a corrupted skb to be sent downstack. For
>> instance, tap_get_user() prohibits short frame which has the length
>> less than Ethernet header size from being transmitted, while the
>> skb_set_network_header() in tap_get_user_xdp() would set skb's
>> network_header regardless of the actual XDP buff data size. This
>> could either cause out-of-bound access beyond the actual length, or
>> confuse the underlayer with incorrect or inconsistent header length
>> in the skb metadata.
>>
>> Propose to drop any frame shorter than the Ethernet header size just
>> like how tap_get_user() does. While at it, validate the pointers in
>> XDP buff to avoid potential size overrun.
>>
>> Fixes: 0efac27791ee ("tap: accept an array of XDP buffs through sendmsg()")
>> Cc: jasowang@redhat.com
>> Signed-off-by: Si-Wei Liu <si-wei.liu@oracle.com>
>> ---
>>   drivers/net/tap.c | 7 +++++++
>>   1 file changed, 7 insertions(+)
>>
>> diff --git a/drivers/net/tap.c b/drivers/net/tap.c
>> index bfdd3875fe86..69596479536f 100644
>> --- a/drivers/net/tap.c
>> +++ b/drivers/net/tap.c
>> @@ -1177,6 +1177,13 @@ static int tap_get_user_xdp(struct tap_queue *q, struct xdp_buff *xdp)
>>          struct sk_buff *skb;
>>          int err, depth;
>>
>> +       if (unlikely(xdp->data < xdp->data_hard_start ||
>> +                    xdp->data_end < xdp->data ||
>> +                    xdp->data_end - xdp->data < ETH_HLEN)) {
>> +               err = -EINVAL;
>> +               goto err;
>> +       }
> For ETH_HLEN check, is it better to do it in vhost-net?
Not sure. Initially I thought about this as well, but changed mind. 
Although the TUN_MSG_PTR interface was specifically customized for 
vhost-net in the kernel, could there be any userspace app do sendmsg() 
also with customized TUN_MSG_PTR control message over tap's fd? If 
that's possible in reality, I guess limiting the fix to only vhost-net 
in the kernel is narrow scoped.

Additionally, it seems just the skb delivery path in the tap driver (or 
tuntap) that populates the relevant skb field needs the ETH_HLEN check, 
the XDP fast path can just transmit or forward xdp buff as-is without 
having to check the (header) length of payload data. That said, it may 
break some guest applications that intentionally send out short frames 
(for test purpose?) if unconditionally drop all of them from the vhost-net.

> It seems tuntap suffers from this as well.
True, theoretically I can fix tuntap as well, but I don't have a setup 
to test out the code change thoroughly. Any volunteer here to do so 
(test it or fix it)?

>
> And for the check for other xdp fields, it deserves a BUG_ON() or at
> least WARN_ON() as they are set by vhost-net.
Hmmm, WARN_ON may be fine (I don't see userspace is prevented from 
fabricating such invalid addresses through the TUN_MSG_PTR uAPI).

-Siwei
>
> Thanks
>
>> +
>>          if (q->flags & IFF_VNET_HDR)
>>                  vnet_hdr_len = READ_ONCE(q->vnet_hdr_sz);
>>
>> --
>> 2.39.3
>>
Jason Wang May 31, 2024, 12:26 a.m. UTC | #3
On Fri, May 31, 2024 at 5:05 AM Si-Wei Liu <si-wei.liu@oracle.com> wrote:
>
>
>
> On 5/29/2024 7:26 PM, Jason Wang wrote:
> > On Thu, May 30, 2024 at 8:54 AM Si-Wei Liu <si-wei.liu@oracle.com> wrote:
> >> The cited commit missed to check against the validity of the length
> >> and various pointers on the XDP buff metadata in the tap_get_user_xdp()
> >> path, which could cause a corrupted skb to be sent downstack. For
> >> instance, tap_get_user() prohibits short frame which has the length
> >> less than Ethernet header size from being transmitted, while the
> >> skb_set_network_header() in tap_get_user_xdp() would set skb's
> >> network_header regardless of the actual XDP buff data size. This
> >> could either cause out-of-bound access beyond the actual length, or
> >> confuse the underlayer with incorrect or inconsistent header length
> >> in the skb metadata.
> >>
> >> Propose to drop any frame shorter than the Ethernet header size just
> >> like how tap_get_user() does. While at it, validate the pointers in
> >> XDP buff to avoid potential size overrun.
> >>
> >> Fixes: 0efac27791ee ("tap: accept an array of XDP buffs through sendmsg()")
> >> Cc: jasowang@redhat.com
> >> Signed-off-by: Si-Wei Liu <si-wei.liu@oracle.com>
> >> ---
> >>   drivers/net/tap.c | 7 +++++++
> >>   1 file changed, 7 insertions(+)
> >>
> >> diff --git a/drivers/net/tap.c b/drivers/net/tap.c
> >> index bfdd3875fe86..69596479536f 100644
> >> --- a/drivers/net/tap.c
> >> +++ b/drivers/net/tap.c
> >> @@ -1177,6 +1177,13 @@ static int tap_get_user_xdp(struct tap_queue *q, struct xdp_buff *xdp)
> >>          struct sk_buff *skb;
> >>          int err, depth;
> >>
> >> +       if (unlikely(xdp->data < xdp->data_hard_start ||
> >> +                    xdp->data_end < xdp->data ||
> >> +                    xdp->data_end - xdp->data < ETH_HLEN)) {
> >> +               err = -EINVAL;
> >> +               goto err;
> >> +       }
> > For ETH_HLEN check, is it better to do it in vhost-net?
> Not sure. Initially I thought about this as well, but changed mind.
> Although the TUN_MSG_PTR interface was specifically customized for
> vhost-net in the kernel, could there be any userspace app do sendmsg()
> also with customized TUN_MSG_PTR control message over tap's fd? If
> that's possible in reality, I guess limiting the fix to only vhost-net
> in the kernel is narrow scoped.

I think not, sendmsg() can't be used for tuntap.

>
> Additionally, it seems just the skb delivery path in the tap driver (or
> tuntap) that populates the relevant skb field needs the ETH_HLEN check,
> the XDP fast path can just transmit or forward xdp buff as-is without
> having to check the (header) length of payload data. That said, it may
> break some guest applications that intentionally send out short frames
> (for test purpose?)

I don't think so, various hypervisors will just drop short ethernet frames.

> if unconditionally drop all of them from the vhost-net.
>
> > It seems tuntap suffers from this as well.
> True, theoretically I can fix tuntap as well, but I don't have a setup
> to test out the code change thoroughly. Any volunteer here to do so
> (test it or fix it)?

It should be easier than the tap. If you can't find one, I can test.

>
> >
> > And for the check for other xdp fields, it deserves a BUG_ON() or at
> > least WARN_ON() as they are set by vhost-net.
> Hmmm, WARN_ON may be fine (I don't see userspace is prevented from
> fabricating such invalid addresses through the TUN_MSG_PTR uAPI).

Tap doesn't export socket objects to userspace, so it's not an uAPI.

Thanks

>
> -Siwei
> >
> > Thanks
> >
> >> +
> >>          if (q->flags & IFF_VNET_HDR)
> >>                  vnet_hdr_len = READ_ONCE(q->vnet_hdr_sz);
> >>
> >> --
> >> 2.39.3
> >>
>
diff mbox series

Patch

diff --git a/drivers/net/tap.c b/drivers/net/tap.c
index bfdd3875fe86..69596479536f 100644
--- a/drivers/net/tap.c
+++ b/drivers/net/tap.c
@@ -1177,6 +1177,13 @@  static int tap_get_user_xdp(struct tap_queue *q, struct xdp_buff *xdp)
 	struct sk_buff *skb;
 	int err, depth;
 
+	if (unlikely(xdp->data < xdp->data_hard_start ||
+		     xdp->data_end < xdp->data ||
+		     xdp->data_end - xdp->data < ETH_HLEN)) {
+		err = -EINVAL;
+		goto err;
+	}
+
 	if (q->flags & IFF_VNET_HDR)
 		vnet_hdr_len = READ_ONCE(q->vnet_hdr_sz);