PATCH] Fortran: fix stack overflow passing assumed-rank type to, assumed-rank class [PR60576]

Jerry D <[email protected]>
Newsgroups gmane.comp.gcc.patches,gmane.comp.gcc.fortran
Message-ID <[email protected]>
Hi all,

I tried something a little different. This patch regression tested on x86_64. 
The original bug report dates from 2019.

The patch fixes the issue found with -fsanitizer=address.

OK for mainline? Looks like it ought to be backported after a brief wait.

Regards,

Jerry
---
When an assumed-rank type(T) dummy argument was passed to an assumed-rank
class(T) dummy argument, gfortran generated a GFC_MAX_DIMENSIONS static
struct copy of the array descriptor's dim[] array.  The caller only
allocates storage for dtype.rank dimensions, so the copy read past the
physical end of the descriptor causing a stack-buffer-overflow detected
by AddressSanitizer.

Fix by checking expr->rank == -1 (assumed-rank actual) at the two copy
sites and replacing the static copy with a runtime-sized __builtin_memcpy
of dtype.rank * sizeof(descriptor_dimension) bytes.

Assisted-By: Claude Sonnet 4.6

	PR fortran/60576

gcc/fortran/ChangeLog:

	PR fortran/60576
	* trans-array.cc (gfc_conv_array_parameter): For an assumed-rank
	actual argument passed to a CLASS assumed-rank formal, use a
	runtime-sized memcpy for the dim[] entries instead of a full
	GFC_MAX_DIMENSIONS static copy.
	* trans-expr.cc (gfc_conv_derived_to_class): Likewise for the
	derived_array descriptor copy.

gcc/testsuite/ChangeLog:

	PR fortran/60576
	* gfortran.dg/assumed_rank_26.f90: New test.
---
PR60576-Fortran-fix-stack-overflow-passing-assumed-rank-type.patch (text/x-patch, 6.8 KB)
From f4ce7c377b0438fbd9c64a70dad6595b5704690c Mon Sep 17 00:00:00 2001
From: Jerry DeLisle <[email protected]>
Date: Tue, 2 Jun 2026 18:17:13 -0700
Subject: [PATCH] Fortran: fix stack overflow passing assumed-rank type to
 assumed-rank class [PR60576]

When an assumed-rank type(T) dummy argument was passed to an assumed-rank
class(T) dummy argument, gfortran generated a GFC_MAX_DIMENSIONS static
struct copy of the array descriptor's dim[] array.  The caller only
allocates storage for dtype.rank dimensions, so the copy read past the
physical end of the descriptor causing a stack-buffer-overflow detected
by AddressSanitizer.

Fix by checking expr->rank == -1 (assumed-rank actual) at the two copy
sites and replacing the static copy with a runtime-sized __builtin_memcpy
of dtype.rank * sizeof(descriptor_dimension) bytes.

Assisted by: Claude Sonnet 4.6

	PR fortran/60576

gcc/fortran/ChangeLog:

	PR fortran/60576
	* trans-array.cc (gfc_conv_array_parameter): For an assumed-rank
	actual argument passed to a CLASS assumed-rank formal, use a
	runtime-sized memcpy for the dim[] entries instead of a full
	GFC_MAX_DIMENSIONS static copy.
	* trans-expr.cc (gfc_conv_derived_to_class): Likewise for the
	derived_array descriptor copy.

gcc/testsuite/ChangeLog:

	PR fortran/60576
	* gfortran.dg/asan/assumed_rank_26.f90: New test.
---
 gcc/fortran/trans-array.cc                    | 32 ++++++++++++++-
 gcc/fortran/trans-expr.cc                     | 34 +++++++++++++++-
 .../gfortran.dg/asan/assumed_rank_26.f90      | 39 +++++++++++++++++++
 3 files changed, 103 insertions(+), 2 deletions(-)
 create mode 100644 gcc/testsuite/gfortran.dg/asan/assumed_rank_26.f90

diff --git a/gcc/fortran/trans-array.cc b/gcc/fortran/trans-array.cc
index cb55e5082d4..91c1cdc1e75 100644
--- a/gcc/fortran/trans-array.cc
+++ b/gcc/fortran/trans-array.cc
@@ -9505,7 +9505,37 @@ gfc_conv_array_parameter (gfc_se *se, gfc_expr *expr, bool g77,
 	      gfc_conv_descriptor_offset_set (&block, arr, gfc_index_zero_node);
 	      se->expr = arr;
 	    }
-	  gfc_class_array_data_assign (&block, tmp, se->expr, true);
+	  if (expr->rank == -1)
+	    {
+	      /* Assumed-rank actual argument: the caller only allocates storage
+		 for dtype.rank dimensions.  Copying GFC_MAX_DIMENSIONS dim
+		 entries would read past the physical end of the descriptor.
+		 Copy the header fields explicitly and use a runtime-sized
+		 memcpy for the dim[] entries.  PR fortran/60576.  */
+	      tree rank, dim_field, dim_size, copy_size, dst_ptr, src_ptr;
+
+	      gfc_conv_descriptor_data_set (&block, tmp,
+					    gfc_conv_descriptor_data_get (se->expr));
+	      gfc_conv_descriptor_offset_set (&block, tmp,
+					      gfc_conv_descriptor_offset_get (se->expr));
+	      gfc_add_modify (&block, gfc_conv_descriptor_dtype (tmp),
+			      gfc_conv_descriptor_dtype (se->expr));
+	      rank = fold_convert (size_type_node,
+				   gfc_conv_descriptor_rank (se->expr));
+	      dim_field = gfc_get_descriptor_dimension (se->expr);
+	      dim_size = TYPE_SIZE_UNIT (TREE_TYPE (TREE_TYPE (dim_field)));
+	      copy_size = fold_build2_loc (input_location, MULT_EXPR,
+					   size_type_node, rank, dim_size);
+	      dst_ptr = gfc_build_addr_expr (pvoid_type_node,
+					     gfc_get_descriptor_dimension (tmp));
+	      src_ptr = gfc_build_addr_expr (pvoid_type_node, dim_field);
+	      gfc_add_expr_to_block (&block,
+		  build_call_expr_loc (input_location,
+				       builtin_decl_explicit (BUILT_IN_MEMCPY),
+				       3, dst_ptr, src_ptr, copy_size));
+	    }
+	  else
+	    gfc_class_array_data_assign (&block, tmp, se->expr, true);
 
 	  /* Handle optional.  */
 	  if (fsym && fsym->attr.optional && sym && sym->attr.optional)
diff --git a/gcc/fortran/trans-expr.cc b/gcc/fortran/trans-expr.cc
index 8acee12e9c2..d56ad33dc1f 100644
--- a/gcc/fortran/trans-expr.cc
+++ b/gcc/fortran/trans-expr.cc
@@ -1028,7 +1028,39 @@ gfc_conv_derived_to_class (gfc_se *parmse, gfc_expr *e, gfc_symbol *fsym,
 	    {
 	      *derived_array
 		= gfc_create_var (TREE_TYPE (parmse->expr), "array");
-	      gfc_add_modify (&block, *derived_array, parmse->expr);
+	      if (e->rank == -1)
+		{
+		  /* Assumed-rank actual: parmse->expr physically holds only
+		     dtype.rank dims; a full struct assign reads past the end.
+		     Copy field-by-field with a runtime-sized dim[] memcpy.
+		     PR fortran/60576.  */
+		  tree rank, dim_field, dim_size, copy_size, dst_ptr, src_ptr;
+
+		  gfc_conv_descriptor_data_set
+		    (&block, *derived_array,
+		     gfc_conv_descriptor_data_get (parmse->expr));
+		  gfc_conv_descriptor_offset_set
+		    (&block, *derived_array,
+		     gfc_conv_descriptor_offset_get (parmse->expr));
+		  gfc_add_modify (&block,
+				  gfc_conv_descriptor_dtype (*derived_array),
+				  gfc_conv_descriptor_dtype (parmse->expr));
+		  rank = fold_convert (size_type_node,
+				       gfc_conv_descriptor_rank (parmse->expr));
+		  dim_field = gfc_get_descriptor_dimension (parmse->expr);
+		  dim_size = TYPE_SIZE_UNIT (TREE_TYPE (TREE_TYPE (dim_field)));
+		  copy_size = fold_build2_loc (input_location, MULT_EXPR,
+					       size_type_node, rank, dim_size);
+		  dst_ptr = gfc_build_addr_expr
+		    (pvoid_type_node, gfc_get_descriptor_dimension (*derived_array));
+		  src_ptr = gfc_build_addr_expr (pvoid_type_node, dim_field);
+		  gfc_add_expr_to_block (&block,
+		      build_call_expr_loc (input_location,
+					   builtin_decl_explicit (BUILT_IN_MEMCPY),
+					   3, dst_ptr, src_ptr, copy_size));
+		}
+	      else
+		gfc_add_modify (&block, *derived_array, parmse->expr);
 	    }
 
 	  if (optional)
diff --git a/gcc/testsuite/gfortran.dg/asan/assumed_rank_26.f90 b/gcc/testsuite/gfortran.dg/asan/assumed_rank_26.f90
new file mode 100644
index 00000000000..d2a0e10a047
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/asan/assumed_rank_26.f90
@@ -0,0 +1,39 @@
+! { dg-do run }
+!
+! PR fortran/60576
+!
+! Stack buffer overflow when passing an assumed-rank type(T) dummy argument
+! to an assumed-rank class(T) dummy argument.  The caller only allocates
+! storage for dtype.rank dimensions in the descriptor; generating a full
+! GFC_MAX_DIMENSIONS copy caused a stack-buffer-overflow detected by ASan.
+
+implicit none
+type t
+  integer :: i
+end type
+
+type(T) :: at(2:3,2:4)
+integer :: i = 0
+
+call bar(at)
+if (i /= 2) STOP 1
+
+contains
+  subroutine bar(x)
+    type(t) :: x(..)
+    if (lbound(x,1) /= 1 .or. lbound(x,2) /= 1) STOP 2
+    if (size(x) /= 6) STOP 3
+    if (size(x,1) /= 2 .or. size(x,2) /= 3) STOP 4
+    if (ubound(x,1) /= 2 .or. ubound(x,2) /= 3) STOP 5
+    i = i + 1
+    call foo(x)
+  end subroutine
+  subroutine foo(x)
+    class(t) :: x(..)
+    if (lbound(x,1) /= 1 .or. lbound(x,2) /= 1) STOP 6
+    if (size(x) /= 6) STOP 7
+    if (size(x,1) /= 2 .or. size(x,2) /= 3) STOP 8
+    if (ubound(x,1) /= 2 .or. ubound(x,2) /= 3) STOP 9
+    i = i + 1
+  end subroutine
+end
-- 
2.54.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.