PR libfortran/103634 - Runtime crash with PACK on zero-sized arrays

Message ID trinity-44c88958-25da-4d91-a3a5-09dbff59d448-1639080318194@3c-app-gmx-bs60
State New
Headers
Series PR libfortran/103634 - Runtime crash with PACK on zero-sized arrays |

Commit Message

Harald Anlauf Dec. 9, 2021, 8:05 p.m. UTC
  Dear all,

I had thought that we had fixed this in the past (see PR31001),
but it did fail for me with all gcc versions I have tried (7-12)
for a slightly more elaborate case as in the old testcase.

The loop in pack_internal did try to access the first element of
the array argument to PACK even if one (or more) extents were zero.
This is not good.

Solution: check the extents and return early.  (We already do a
related check for the vector argument if present).

Regtested on x86_64-pc-linux-gnu.  OK for mainline?

As this segfaults on valid code at runtime: I am considering
backporting this, if there are no objections.

Thanks,
Harald
  

Comments

Mikael Morin Dec. 9, 2021, 8:37 p.m. UTC | #1
Hello,

On 09/12/2021 21:05, Harald Anlauf via Fortran wrote:
> Dear all,
> 
> I had thought that we had fixed this in the past (see PR31001),
> but it did fail for me with all gcc versions I have tried (7-12)
> for a slightly more elaborate case as in the old testcase.
> 
> The loop in pack_internal did try to access the first element of
> the array argument to PACK even if one (or more) extents were zero.
> This is not good.
> 
> Solution: check the extents and return early.  (We already do a
> related check for the vector argument if present).

If there is a vector argument, aren’t we supposed to copy it to the result ?
There is something else to pay attention for, the early return should 
come at least after the return array bounds have been set.  In the 
testcase an array with the correct bounds has been allocated beforehand 
to hold the return value, but it’s not always the case.

For what it’s worth, the non-generic variant in pack.m4 (or in 
pack_{i,f,c}{1,2,4,8,10,16}.c) has a zero extent check and it clears the 
source ptr in that case, which makes it setup the return array and then 
jump to the vector copy at the end of the function.
  
Harald Anlauf Dec. 13, 2021, 8:25 p.m. UTC | #2
Hi Mikael,

Am 09.12.21 um 21:37 schrieb Mikael Morin:
> Hello,
>
> On 09/12/2021 21:05, Harald Anlauf via Fortran wrote:
>> Dear all,
>>
>> I had thought that we had fixed this in the past (see PR31001),
>> but it did fail for me with all gcc versions I have tried (7-12)
>> for a slightly more elaborate case as in the old testcase.
>>
>> The loop in pack_internal did try to access the first element of
>> the array argument to PACK even if one (or more) extents were zero.
>> This is not good.
>>
>> Solution: check the extents and return early.  (We already do a
>> related check for the vector argument if present).
>
> If there is a vector argument, aren’t we supposed to copy it to the
> result ?
> There is something else to pay attention for, the early return should
> come at least after the return array bounds have been set.  In the
> testcase an array with the correct bounds has been allocated beforehand
> to hold the return value, but it’s not always the case.

you are absolutely right, I had gotten that wrong.

> For what it’s worth, the non-generic variant in pack.m4 (or in
> pack_{i,f,c}{1,2,4,8,10,16}.c) has a zero extent check and it clears the
> source ptr in that case, which makes it setup the return array and then
> jump to the vector copy at the end of the function.
>

The code is so similar (for good reason) that it makes sense to keep
it synchronous.  I added code for 'zero_sized' array with the minor
difference that I made it boolean instead of integer.

I also extended the testcase so that it exercises PACK/pack_internal
a little, for argument 'vector' present as well as not.  (There are
existing tests for intrinsic types, but not for the issue at hand).

Regtested again, and checked the testcase (against other compilers
and also with valgrind).

OK now?

Thanks,
Harald
  
Harald Anlauf Dec. 13, 2021, 8:27 p.m. UTC | #3
Works better with patch attached...

Am 13.12.21 um 21:25 schrieb Harald Anlauf via Gcc-patches:
> Hi Mikael,
>
> Am 09.12.21 um 21:37 schrieb Mikael Morin:
>> Hello,
>>
>> On 09/12/2021 21:05, Harald Anlauf via Fortran wrote:
>>> Dear all,
>>>
>>> I had thought that we had fixed this in the past (see PR31001),
>>> but it did fail for me with all gcc versions I have tried (7-12)
>>> for a slightly more elaborate case as in the old testcase.
>>>
>>> The loop in pack_internal did try to access the first element of
>>> the array argument to PACK even if one (or more) extents were zero.
>>> This is not good.
>>>
>>> Solution: check the extents and return early.  (We already do a
>>> related check for the vector argument if present).
>>
>> If there is a vector argument, aren’t we supposed to copy it to the
>> result ?
>> There is something else to pay attention for, the early return should
>> come at least after the return array bounds have been set.  In the
>> testcase an array with the correct bounds has been allocated beforehand
>> to hold the return value, but it’s not always the case.
>
> you are absolutely right, I had gotten that wrong.
>
>> For what it’s worth, the non-generic variant in pack.m4 (or in
>> pack_{i,f,c}{1,2,4,8,10,16}.c) has a zero extent check and it clears the
>> source ptr in that case, which makes it setup the return array and then
>> jump to the vector copy at the end of the function.
>>
>
> The code is so similar (for good reason) that it makes sense to keep
> it synchronous.  I added code for 'zero_sized' array with the minor
> difference that I made it boolean instead of integer.
>
> I also extended the testcase so that it exercises PACK/pack_internal
> a little, for argument 'vector' present as well as not.  (There are
> existing tests for intrinsic types, but not for the issue at hand).
>
> Regtested again, and checked the testcase (against other compilers
> and also with valgrind).
>
> OK now?
>
> Thanks,
> Harald
>
  
Mikael Morin Dec. 14, 2021, 11:50 a.m. UTC | #4
Le 13/12/2021 à 21:27, Harald Anlauf via Fortran a écrit :
> Works better with patch attached...
> 
> Am 13.12.21 um 21:25 schrieb Harald Anlauf via Gcc-patches:
>>
>> The code is so similar (for good reason) that it makes sense to keep
>> it synchronous.  I added code for 'zero_sized' array with the minor
>> difference that I made it boolean instead of integer.
>>
>> I also extended the testcase so that it exercises PACK/pack_internal
>> a little, for argument 'vector' present as well as not.  (There are
>> existing tests for intrinsic types, but not for the issue at hand).
>>
>> Regtested again, and checked the testcase (against other compilers
>> and also with valgrind).
>>
>> OK now?
>>
Yes, thanks.
  

Patch

From dfa1e1ac5d8e43f1ca8f13b64330825581174f36 Mon Sep 17 00:00:00 2001
From: Harald Anlauf <anlauf@gmx.de>
Date: Thu, 9 Dec 2021 20:55:08 +0100
Subject: [PATCH] Fortran: PACK intrinsic should not try to read from
 zero-sized array

libgfortran/ChangeLog:

	PR libfortran/103634
	* intrinsics/pack_generic.c (pack_internal): Handle case when the
	array argument of PACK has one extent of size zero to avoid
	invalid reads.

gcc/testsuite/ChangeLog:

	PR libfortran/103634
	* gfortran.dg/zero_sized_13.f90: New test.
---
 gcc/testsuite/gfortran.dg/zero_sized_13.f90 | 20 ++++++++++++++++++++
 libgfortran/intrinsics/pack_generic.c       |  4 ++++
 2 files changed, 24 insertions(+)
 create mode 100644 gcc/testsuite/gfortran.dg/zero_sized_13.f90

diff --git a/gcc/testsuite/gfortran.dg/zero_sized_13.f90 b/gcc/testsuite/gfortran.dg/zero_sized_13.f90
new file mode 100644
index 00000000000..5620514334c
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/zero_sized_13.f90
@@ -0,0 +1,20 @@ 
+! { dg-do run }
+! PR libfortran/103634 - Runtime crash with PACK on zero-sized arrays
+
+program p
+  implicit none
+  type t
+     real :: r(24) = -99.
+  end type
+  type(t), allocatable :: new(:), old(:)
+  logical, allocatable :: mask(:)
+  integer              :: n, m
+! m = 1    ! works
+  m = 0    ! failed with SIGSEGV in pack_internal
+  allocate (old(m), mask(m))
+  mask(:) = .false.
+  n = count (mask)
+  allocate (new(n))
+  new(:) = pack (old, mask)
+  print *, size (new)
+end
diff --git a/libgfortran/intrinsics/pack_generic.c b/libgfortran/intrinsics/pack_generic.c
index cad2fbbfbcd..f629e0e8469 100644
--- a/libgfortran/intrinsics/pack_generic.c
+++ b/libgfortran/intrinsics/pack_generic.c
@@ -126,6 +126,10 @@  pack_internal (gfc_array_char *ret, const gfc_array_char *array,
   if (mstride[0] == 0)
     mstride[0] = mask_kind;

+  for (n = 0; n < dim; n++)
+    if (extent[n] == 0)
+      return;
+
   if (ret->base_addr == NULL || unlikely (compile_options.bounds_check))
     {
       /* Count the elements, either for allocating memory or
--
2.26.2