diff mbox series

net/mlx4_en: Introduce flexible array to silence overflow warning

Message ID 20230218183842.never.954-kees@kernel.org (mailing list archive)
State Mainlined
Commit f8f185e39b4de91bc5235e5be0d829bea69d9b06
Headers show
Series net/mlx4_en: Introduce flexible array to silence overflow warning | expand

Commit Message

Kees Cook Feb. 18, 2023, 6:38 p.m. UTC
The call "skb_copy_from_linear_data(skb, inl + 1, spc)" triggers a FORTIFY
memcpy() warning on ppc64 platform:

In function ‘fortify_memcpy_chk’,
    inlined from ‘skb_copy_from_linear_data’ at ./include/linux/skbuff.h:4029:2,
    inlined from ‘build_inline_wqe’ at drivers/net/ethernet/mellanox/mlx4/en_tx.c:722:4,
    inlined from ‘mlx4_en_xmit’ at drivers/net/ethernet/mellanox/mlx4/en_tx.c:1066:3:
./include/linux/fortify-string.h:513:25: error: call to ‘__write_overflow_field’ declared with
attribute warning: detected write beyond size of field (1st parameter); maybe use struct_group()?
[-Werror=attribute-warning]
  513 |                         __write_overflow_field(p_size_field, size);
      |                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Same behaviour on x86 you can get if you use "__always_inline" instead of
"inline" for skb_copy_from_linear_data() in skbuff.h

The call here copies data into inlined tx destricptor, which has 104
bytes (MAX_INLINE) space for data payload. In this case "spc" is known
in compile-time but the destination is used with hidden knowledge
(real structure of destination is different from that the compiler
can see). That cause the fortify warning because compiler can check
bounds, but the real bounds are different.  "spc" can't be bigger than
64 bytes (MLX4_INLINE_ALIGN), so the data can always fit into inlined
tx descriptor. The fact that "inl" points into inlined tx descriptor is
determined earlier in mlx4_en_xmit().

Avoid confusing the compiler with "inl + 1" constructions to get to past
the inl header by introducing a flexible array "data" to the struct so
that the compiler can see that we are not dealing with an array of inl
structs, but rather, arbitrary data following the structure. There are
no changes to the structure layout reported by pahole, and the resulting
machine code is actually smaller.

Reported-by: Josef Oskera <joskera@redhat.com>
Link: https://lore.kernel.org/lkml/20230217094541.2362873-1-joskera@redhat.com
Fixes: f68f2ff91512 ("fortify: Detect struct member overflows in memcpy() at compile-time")
Cc: Tariq Toukan <tariqt@nvidia.com>
Cc: "David S. Miller" <davem@davemloft.net>
Cc: Eric Dumazet <edumazet@google.com>
Cc: Jakub Kicinski <kuba@kernel.org>
Cc: Paolo Abeni <pabeni@redhat.com>
Cc: Yishai Hadas <yishaih@nvidia.com>
Cc: netdev@vger.kernel.org
Cc: linux-rdma@vger.kernel.org
Signed-off-by: Kees Cook <keescook@chromium.org>
---
 drivers/net/ethernet/mellanox/mlx4/en_tx.c | 22 +++++++++++-----------
 include/linux/mlx4/qp.h                    |  1 +
 2 files changed, 12 insertions(+), 11 deletions(-)

Comments

Tariq Toukan Feb. 19, 2023, 9:43 a.m. UTC | #1
On 18/02/2023 20:38, Kees Cook wrote:
> The call "skb_copy_from_linear_data(skb, inl + 1, spc)" triggers a FORTIFY
> memcpy() warning on ppc64 platform:
> 
> In function ‘fortify_memcpy_chk’,
>      inlined from ‘skb_copy_from_linear_data’ at ./include/linux/skbuff.h:4029:2,
>      inlined from ‘build_inline_wqe’ at drivers/net/ethernet/mellanox/mlx4/en_tx.c:722:4,
>      inlined from ‘mlx4_en_xmit’ at drivers/net/ethernet/mellanox/mlx4/en_tx.c:1066:3:
> ./include/linux/fortify-string.h:513:25: error: call to ‘__write_overflow_field’ declared with
> attribute warning: detected write beyond size of field (1st parameter); maybe use struct_group()?
> [-Werror=attribute-warning]
>    513 |                         __write_overflow_field(p_size_field, size);
>        |                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> 
> Same behaviour on x86 you can get if you use "__always_inline" instead of
> "inline" for skb_copy_from_linear_data() in skbuff.h
> 
> The call here copies data into inlined tx destricptor, which has 104
> bytes (MAX_INLINE) space for data payload. In this case "spc" is known
> in compile-time but the destination is used with hidden knowledge
> (real structure of destination is different from that the compiler
> can see). That cause the fortify warning because compiler can check
> bounds, but the real bounds are different.  "spc" can't be bigger than
> 64 bytes (MLX4_INLINE_ALIGN), so the data can always fit into inlined
> tx descriptor. The fact that "inl" points into inlined tx descriptor is
> determined earlier in mlx4_en_xmit().
> 
> Avoid confusing the compiler with "inl + 1" constructions to get to past
> the inl header by introducing a flexible array "data" to the struct so
> that the compiler can see that we are not dealing with an array of inl
> structs, but rather, arbitrary data following the structure. There are
> no changes to the structure layout reported by pahole, and the resulting
> machine code is actually smaller.
> 
> Reported-by: Josef Oskera <joskera@redhat.com>
> Link: https://lore.kernel.org/lkml/20230217094541.2362873-1-joskera@redhat.com
> Fixes: f68f2ff91512 ("fortify: Detect struct member overflows in memcpy() at compile-time")
> Cc: Tariq Toukan <tariqt@nvidia.com>
> Cc: "David S. Miller" <davem@davemloft.net>
> Cc: Eric Dumazet <edumazet@google.com>
> Cc: Jakub Kicinski <kuba@kernel.org>
> Cc: Paolo Abeni <pabeni@redhat.com>
> Cc: Yishai Hadas <yishaih@nvidia.com>
> Cc: netdev@vger.kernel.org
> Cc: linux-rdma@vger.kernel.org
> Signed-off-by: Kees Cook <keescook@chromium.org>
> ---
>   drivers/net/ethernet/mellanox/mlx4/en_tx.c | 22 +++++++++++-----------
>   include/linux/mlx4/qp.h                    |  1 +
>   2 files changed, 12 insertions(+), 11 deletions(-)
> 

Just saw your patch now, after commenting on the other thread. :)

So you choose not to fix similar usages in RDMA driver 
drivers/infiniband/hw/mlx4/qp.c, like:

3204         spc = MLX4_INLINE_ALIGN -
3205                 ((unsigned long) (inl + 1) & (MLX4_INLINE_ALIGN - 1));
3206         if (header_size <= spc) {
3207                 inl->byte_count = cpu_to_be32(1 << 31 | header_size);
3208                 memcpy(inl + 1, sqp->header_buf, header_size);
3209                 i = 1;
3210         } else {
3211                 inl->byte_count = cpu_to_be32(1 << 31 | spc);
3212                 memcpy(inl + 1, sqp->header_buf, spc);
3213
3214                 inl = (void *) (inl + 1) + spc;
3215                 memcpy(inl + 1, sqp->header_buf + spc, header_size 
- spc);

This keeps the patch minimal indeed.

Did you repro the issue and test this solution?
Maybe Josef can also verify it works for him?

> diff --git a/drivers/net/ethernet/mellanox/mlx4/en_tx.c b/drivers/net/ethernet/mellanox/mlx4/en_tx.c
> index c5758637b7be..2f79378fbf6e 100644
> --- a/drivers/net/ethernet/mellanox/mlx4/en_tx.c
> +++ b/drivers/net/ethernet/mellanox/mlx4/en_tx.c
> @@ -699,32 +699,32 @@ static void build_inline_wqe(struct mlx4_en_tx_desc *tx_desc,
>   			inl->byte_count = cpu_to_be32(1 << 31 | skb->len);
>   		} else {
>   			inl->byte_count = cpu_to_be32(1 << 31 | MIN_PKT_LEN);
> -			memset(((void *)(inl + 1)) + skb->len, 0,
> +			memset(inl->data + skb->len, 0,
>   			       MIN_PKT_LEN - skb->len);
>   		}
> -		skb_copy_from_linear_data(skb, inl + 1, hlen);
> +		skb_copy_from_linear_data(skb, inl->data, hlen);
>   		if (shinfo->nr_frags)
> -			memcpy(((void *)(inl + 1)) + hlen, fragptr,
> +			memcpy(inl->data + hlen, fragptr,
>   			       skb_frag_size(&shinfo->frags[0]));
>   
>   	} else {
>   		inl->byte_count = cpu_to_be32(1 << 31 | spc);
>   		if (hlen <= spc) {
> -			skb_copy_from_linear_data(skb, inl + 1, hlen);
> +			skb_copy_from_linear_data(skb, inl->data, hlen);
>   			if (hlen < spc) {
> -				memcpy(((void *)(inl + 1)) + hlen,
> +				memcpy(inl->data + hlen,
>   				       fragptr, spc - hlen);
>   				fragptr +=  spc - hlen;
>   			}
> -			inl = (void *) (inl + 1) + spc;
> -			memcpy(((void *)(inl + 1)), fragptr, skb->len - spc);
> +			inl = (void *)inl->data + spc;
> +			memcpy(inl->data, fragptr, skb->len - spc);
>   		} else {
> -			skb_copy_from_linear_data(skb, inl + 1, spc);
> -			inl = (void *) (inl + 1) + spc;
> -			skb_copy_from_linear_data_offset(skb, spc, inl + 1,
> +			skb_copy_from_linear_data(skb, inl->data, spc);
> +			inl = (void *)inl->data + spc;

No need now for all these (void *) castings.

> +			skb_copy_from_linear_data_offset(skb, spc, inl->data,
>   							 hlen - spc);
>   			if (shinfo->nr_frags)
> -				memcpy(((void *)(inl + 1)) + hlen - spc,
> +				memcpy(inl->data + hlen - spc,
>   				       fragptr,
>   				       skb_frag_size(&shinfo->frags[0]));
>   		}
> diff --git a/include/linux/mlx4/qp.h b/include/linux/mlx4/qp.h
> index c78b90f2e9a1..b9a7b1319f5d 100644
> --- a/include/linux/mlx4/qp.h
> +++ b/include/linux/mlx4/qp.h
> @@ -446,6 +446,7 @@ enum {
>   
>   struct mlx4_wqe_inline_seg {
>   	__be32			byte_count;
> +	__u8			data[];
>   };
>   
>   enum mlx4_update_qp_attr {
Josef Oskera Feb. 20, 2023, 2:09 a.m. UTC | #2
I've tried Kees's patch and it works for me. I am able to compile mlx4
without the fortify warning.

ne 19. 2. 2023 v 10:43 odesílatel Tariq Toukan <ttoukan.linux@gmail.com> napsal:
>
>
>
> On 18/02/2023 20:38, Kees Cook wrote:
> > The call "skb_copy_from_linear_data(skb, inl + 1, spc)" triggers a FORTIFY
> > memcpy() warning on ppc64 platform:
> >
> > In function ‘fortify_memcpy_chk’,
> >      inlined from ‘skb_copy_from_linear_data’ at ./include/linux/skbuff.h:4029:2,
> >      inlined from ‘build_inline_wqe’ at drivers/net/ethernet/mellanox/mlx4/en_tx.c:722:4,
> >      inlined from ‘mlx4_en_xmit’ at drivers/net/ethernet/mellanox/mlx4/en_tx.c:1066:3:
> > ./include/linux/fortify-string.h:513:25: error: call to ‘__write_overflow_field’ declared with
> > attribute warning: detected write beyond size of field (1st parameter); maybe use struct_group()?
> > [-Werror=attribute-warning]
> >    513 |                         __write_overflow_field(p_size_field, size);
> >        |                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> >
> > Same behaviour on x86 you can get if you use "__always_inline" instead of
> > "inline" for skb_copy_from_linear_data() in skbuff.h
> >
> > The call here copies data into inlined tx destricptor, which has 104
> > bytes (MAX_INLINE) space for data payload. In this case "spc" is known
> > in compile-time but the destination is used with hidden knowledge
> > (real structure of destination is different from that the compiler
> > can see). That cause the fortify warning because compiler can check
> > bounds, but the real bounds are different.  "spc" can't be bigger than
> > 64 bytes (MLX4_INLINE_ALIGN), so the data can always fit into inlined
> > tx descriptor. The fact that "inl" points into inlined tx descriptor is
> > determined earlier in mlx4_en_xmit().
> >
> > Avoid confusing the compiler with "inl + 1" constructions to get to past
> > the inl header by introducing a flexible array "data" to the struct so
> > that the compiler can see that we are not dealing with an array of inl
> > structs, but rather, arbitrary data following the structure. There are
> > no changes to the structure layout reported by pahole, and the resulting
> > machine code is actually smaller.
> >
> > Reported-by: Josef Oskera <joskera@redhat.com>
> > Link: https://lore.kernel.org/lkml/20230217094541.2362873-1-joskera@redhat.com
> > Fixes: f68f2ff91512 ("fortify: Detect struct member overflows in memcpy() at compile-time")
> > Cc: Tariq Toukan <tariqt@nvidia.com>
> > Cc: "David S. Miller" <davem@davemloft.net>
> > Cc: Eric Dumazet <edumazet@google.com>
> > Cc: Jakub Kicinski <kuba@kernel.org>
> > Cc: Paolo Abeni <pabeni@redhat.com>
> > Cc: Yishai Hadas <yishaih@nvidia.com>
> > Cc: netdev@vger.kernel.org
> > Cc: linux-rdma@vger.kernel.org
> > Signed-off-by: Kees Cook <keescook@chromium.org>
> > ---
> >   drivers/net/ethernet/mellanox/mlx4/en_tx.c | 22 +++++++++++-----------
> >   include/linux/mlx4/qp.h                    |  1 +
> >   2 files changed, 12 insertions(+), 11 deletions(-)
> >
>
> Just saw your patch now, after commenting on the other thread. :)
>
> So you choose not to fix similar usages in RDMA driver
> drivers/infiniband/hw/mlx4/qp.c, like:
>
> 3204         spc = MLX4_INLINE_ALIGN -
> 3205                 ((unsigned long) (inl + 1) & (MLX4_INLINE_ALIGN - 1));
> 3206         if (header_size <= spc) {
> 3207                 inl->byte_count = cpu_to_be32(1 << 31 | header_size);
> 3208                 memcpy(inl + 1, sqp->header_buf, header_size);
> 3209                 i = 1;
> 3210         } else {
> 3211                 inl->byte_count = cpu_to_be32(1 << 31 | spc);
> 3212                 memcpy(inl + 1, sqp->header_buf, spc);
> 3213
> 3214                 inl = (void *) (inl + 1) + spc;
> 3215                 memcpy(inl + 1, sqp->header_buf + spc, header_size
> - spc);
>
> This keeps the patch minimal indeed.
>
> Did you repro the issue and test this solution?
> Maybe Josef can also verify it works for him?
>
> > diff --git a/drivers/net/ethernet/mellanox/mlx4/en_tx.c b/drivers/net/ethernet/mellanox/mlx4/en_tx.c
> > index c5758637b7be..2f79378fbf6e 100644
> > --- a/drivers/net/ethernet/mellanox/mlx4/en_tx.c
> > +++ b/drivers/net/ethernet/mellanox/mlx4/en_tx.c
> > @@ -699,32 +699,32 @@ static void build_inline_wqe(struct mlx4_en_tx_desc *tx_desc,
> >                       inl->byte_count = cpu_to_be32(1 << 31 | skb->len);
> >               } else {
> >                       inl->byte_count = cpu_to_be32(1 << 31 | MIN_PKT_LEN);
> > -                     memset(((void *)(inl + 1)) + skb->len, 0,
> > +                     memset(inl->data + skb->len, 0,
> >                              MIN_PKT_LEN - skb->len);
> >               }
> > -             skb_copy_from_linear_data(skb, inl + 1, hlen);
> > +             skb_copy_from_linear_data(skb, inl->data, hlen);
> >               if (shinfo->nr_frags)
> > -                     memcpy(((void *)(inl + 1)) + hlen, fragptr,
> > +                     memcpy(inl->data + hlen, fragptr,
> >                              skb_frag_size(&shinfo->frags[0]));
> >
> >       } else {
> >               inl->byte_count = cpu_to_be32(1 << 31 | spc);
> >               if (hlen <= spc) {
> > -                     skb_copy_from_linear_data(skb, inl + 1, hlen);
> > +                     skb_copy_from_linear_data(skb, inl->data, hlen);
> >                       if (hlen < spc) {
> > -                             memcpy(((void *)(inl + 1)) + hlen,
> > +                             memcpy(inl->data + hlen,
> >                                      fragptr, spc - hlen);
> >                               fragptr +=  spc - hlen;
> >                       }
> > -                     inl = (void *) (inl + 1) + spc;
> > -                     memcpy(((void *)(inl + 1)), fragptr, skb->len - spc);
> > +                     inl = (void *)inl->data + spc;
> > +                     memcpy(inl->data, fragptr, skb->len - spc);
> >               } else {
> > -                     skb_copy_from_linear_data(skb, inl + 1, spc);
> > -                     inl = (void *) (inl + 1) + spc;
> > -                     skb_copy_from_linear_data_offset(skb, spc, inl + 1,
> > +                     skb_copy_from_linear_data(skb, inl->data, spc);
> > +                     inl = (void *)inl->data + spc;
>
> No need now for all these (void *) castings.
>
> > +                     skb_copy_from_linear_data_offset(skb, spc, inl->data,
> >                                                        hlen - spc);
> >                       if (shinfo->nr_frags)
> > -                             memcpy(((void *)(inl + 1)) + hlen - spc,
> > +                             memcpy(inl->data + hlen - spc,
> >                                      fragptr,
> >                                      skb_frag_size(&shinfo->frags[0]));
> >               }
> > diff --git a/include/linux/mlx4/qp.h b/include/linux/mlx4/qp.h
> > index c78b90f2e9a1..b9a7b1319f5d 100644
> > --- a/include/linux/mlx4/qp.h
> > +++ b/include/linux/mlx4/qp.h
> > @@ -446,6 +446,7 @@ enum {
> >
> >   struct mlx4_wqe_inline_seg {
> >       __be32                  byte_count;
> > +     __u8                    data[];
> >   };
> >
> >   enum mlx4_update_qp_attr {
>
Tariq Toukan Feb. 20, 2023, 8:30 a.m. UTC | #3
On 20/02/2023 4:09, Josef Oskera wrote:
> I've tried Kees's patch and it works for me. I am able to compile mlx4
> without the fortify warning.
> 
> ne 19. 2. 2023 v 10:43 odesílatel Tariq Toukan <ttoukan.linux@gmail.com> napsal:

Thanks Josef.
Then let's go with this one.

Reviewed-by: Tariq Toukan <tariqt@nvidia.com>
patchwork-bot+netdevbpf@kernel.org Feb. 21, 2023, 12:40 a.m. UTC | #4
Hello:

This patch was applied to netdev/net-next.git (master)
by Jakub Kicinski <kuba@kernel.org>:

On Sat, 18 Feb 2023 10:38:50 -0800 you wrote:
> The call "skb_copy_from_linear_data(skb, inl + 1, spc)" triggers a FORTIFY
> memcpy() warning on ppc64 platform:
> 
> In function ‘fortify_memcpy_chk’,
>     inlined from ‘skb_copy_from_linear_data’ at ./include/linux/skbuff.h:4029:2,
>     inlined from ‘build_inline_wqe’ at drivers/net/ethernet/mellanox/mlx4/en_tx.c:722:4,
>     inlined from ‘mlx4_en_xmit’ at drivers/net/ethernet/mellanox/mlx4/en_tx.c:1066:3:
> ./include/linux/fortify-string.h:513:25: error: call to ‘__write_overflow_field’ declared with
> attribute warning: detected write beyond size of field (1st parameter); maybe use struct_group()?
> [-Werror=attribute-warning]
>   513 |                         __write_overflow_field(p_size_field, size);
>       |                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> 
> [...]

Here is the summary with links:
  - net/mlx4_en: Introduce flexible array to silence overflow warning
    https://git.kernel.org/netdev/net-next/c/f8f185e39b4d

You are awesome, thank you!
diff mbox series

Patch

diff --git a/drivers/net/ethernet/mellanox/mlx4/en_tx.c b/drivers/net/ethernet/mellanox/mlx4/en_tx.c
index c5758637b7be..2f79378fbf6e 100644
--- a/drivers/net/ethernet/mellanox/mlx4/en_tx.c
+++ b/drivers/net/ethernet/mellanox/mlx4/en_tx.c
@@ -699,32 +699,32 @@  static void build_inline_wqe(struct mlx4_en_tx_desc *tx_desc,
 			inl->byte_count = cpu_to_be32(1 << 31 | skb->len);
 		} else {
 			inl->byte_count = cpu_to_be32(1 << 31 | MIN_PKT_LEN);
-			memset(((void *)(inl + 1)) + skb->len, 0,
+			memset(inl->data + skb->len, 0,
 			       MIN_PKT_LEN - skb->len);
 		}
-		skb_copy_from_linear_data(skb, inl + 1, hlen);
+		skb_copy_from_linear_data(skb, inl->data, hlen);
 		if (shinfo->nr_frags)
-			memcpy(((void *)(inl + 1)) + hlen, fragptr,
+			memcpy(inl->data + hlen, fragptr,
 			       skb_frag_size(&shinfo->frags[0]));
 
 	} else {
 		inl->byte_count = cpu_to_be32(1 << 31 | spc);
 		if (hlen <= spc) {
-			skb_copy_from_linear_data(skb, inl + 1, hlen);
+			skb_copy_from_linear_data(skb, inl->data, hlen);
 			if (hlen < spc) {
-				memcpy(((void *)(inl + 1)) + hlen,
+				memcpy(inl->data + hlen,
 				       fragptr, spc - hlen);
 				fragptr +=  spc - hlen;
 			}
-			inl = (void *) (inl + 1) + spc;
-			memcpy(((void *)(inl + 1)), fragptr, skb->len - spc);
+			inl = (void *)inl->data + spc;
+			memcpy(inl->data, fragptr, skb->len - spc);
 		} else {
-			skb_copy_from_linear_data(skb, inl + 1, spc);
-			inl = (void *) (inl + 1) + spc;
-			skb_copy_from_linear_data_offset(skb, spc, inl + 1,
+			skb_copy_from_linear_data(skb, inl->data, spc);
+			inl = (void *)inl->data + spc;
+			skb_copy_from_linear_data_offset(skb, spc, inl->data,
 							 hlen - spc);
 			if (shinfo->nr_frags)
-				memcpy(((void *)(inl + 1)) + hlen - spc,
+				memcpy(inl->data + hlen - spc,
 				       fragptr,
 				       skb_frag_size(&shinfo->frags[0]));
 		}
diff --git a/include/linux/mlx4/qp.h b/include/linux/mlx4/qp.h
index c78b90f2e9a1..b9a7b1319f5d 100644
--- a/include/linux/mlx4/qp.h
+++ b/include/linux/mlx4/qp.h
@@ -446,6 +446,7 @@  enum {
 
 struct mlx4_wqe_inline_seg {
 	__be32			byte_count;
+	__u8			data[];
 };
 
 enum mlx4_update_qp_attr {