https://gcc.gnu.org/g:0235a09b822088b8bea3bc3fccbea3a8fc773d29
commit r17-5793-g0235a09b822088b8bea3bc3fccbea3a8fc773d29 Author: Jerry DeLisle <[email protected]> Date: Wed Oct 28 13:27:21 2026 -0400 libgfortran: [PR127751] caf_shmem: catch overflowing coarray sizes caf_register multiplied the per-image size by the number of images in size_t without checking for overflow. A large request could wrap to a small size, succeed with STAT=0, and leave the coarray far too small. The size arithmetic now saturates at SIZE_MAX, and the allocator rejects sizes too large to round up to a power of two, so the request fails through STAT= as an exhausted pool does. The buffer of a collective reduction had the same overflow on the number of images; its size now saturates as well, and the failure is reported through STAT= and ERRMSG= of the collective. Assisted-by: Claude Opus 5.5 PR libfortran/127751 libgfortran/ChangeLog: * caf/shmem.c (mul_size_sat): New function. (_gfortran_caf_register): Use it for all size computations and saturate the alignment of the size. * caf/shmem/allocator.c (allocator_shared_malloc): Return a null pointer for sizes that cannot be rounded up to a power of two. * caf/shmem/collective_subroutine.c (collsub_reduce_array): Saturate the buffer size on overflow. gcc/testsuite/ChangeLog: * gfortran.dg/coarray/alloc_pool_exhausted_2.f90: New test. Diff: --- .../gfortran.dg/coarray/alloc_pool_exhausted_2.f90 | 25 ++++++++++++++++ libgfortran/caf/shmem.c | 34 +++++++++++++++------- libgfortran/caf/shmem/allocator.c | 4 +++ libgfortran/caf/shmem/collective_subroutine.c | 11 +++++-- 4 files changed, 61 insertions(+), 13 deletions(-) diff --git a/gcc/testsuite/gfortran.dg/coarray/alloc_pool_exhausted_2.f90 b/gcc/testsuite/gfortran.dg/coarray/alloc_pool_exhausted_2.f90 new file mode 100644 index 000000000000..28faea352d1a --- /dev/null +++ b/gcc/testsuite/gfortran.dg/coarray/alloc_pool_exhausted_2.f90 @@ -0,0 +1,25 @@ +! { dg-do run } +! PR libfortran/127751 +! +! ALLOCATE of a coarray whose total size over all images does not fit in +! size_t reports a nonzero STAT= instead of allocating a wrapped-around size. + +program alloc_pool_exhausted_2 + use iso_c_binding, only: c_intptr_t + implicit none + ! 2**62 bytes per image on 64-bit targets, 2**30 on 32-bit targets + integer(8), parameter :: n = 2_8**(bit_size (0_c_intptr_t) - 5) + integer(8), allocatable :: a(:)[:] + integer :: st, nb + + ! With four images the total size wraps to zero + if (num_images () < 4) stop + allocate (a(n)[*], stat=st) + if (st == 0) stop 1 + allocate (a(10)[*], stat=st) + if (st /= 0) stop 2 + a = this_image () + sync all + nb = mod (this_image (), num_images ()) + 1 + if (a(10)[nb] /= nb) stop 3 +end program alloc_pool_exhausted_2 diff --git a/libgfortran/caf/shmem.c b/libgfortran/caf/shmem.c index 964c7449e028..27aa9e081219 100644 --- a/libgfortran/caf/shmem.c +++ b/libgfortran/caf/shmem.c @@ -196,6 +196,15 @@ _gfortran_caf_num_images (caf_team_t team, int32_t *team_number) return caf_current_team->u.image_info->image_map_size; } +/* Return A * B, or SIZE_MAX on overflow. The allocator rejects SIZE_MAX, + so a saturated size makes the allocation fail through STAT=. */ + +static size_t +mul_size_sat (size_t a, size_t b) +{ + size_t r; + return __builtin_mul_overflow (a, b, &r) ? SIZE_MAX : r; +} void _gfortran_caf_register (size_t size, caf_register_t type, caf_token_t *token, @@ -257,7 +266,7 @@ _gfortran_caf_register (size_t size, caf_register_t type, caf_token_t *token, break; case CAF_REGTYPE_EVENT_ALLOC: case CAF_REGTYPE_EVENT_STATIC: - size *= sizeof (void *); + size = mul_size_sat (size, sizeof (void *)); break; default: break; @@ -266,7 +275,9 @@ _gfortran_caf_register (size_t size, caf_register_t type, caf_token_t *token, if (type != CAF_REGTYPE_COARRAY_ALLOC_ALLOCATE_ONLY) *token = malloc (sizeof (struct caf_shmem_token)); - size = alignto (size, sizeof (ptrdiff_t)); + /* Saturate like mul_size_sat. */ + size = size > SIZE_MAX - sizeof (ptrdiff_t) + ? SIZE_MAX : alignto (size, sizeof (ptrdiff_t)); switch (type) { case CAF_REGTYPE_LOCK_STATIC: @@ -281,13 +292,14 @@ _gfortran_caf_register (size_t size, caf_register_t type, caf_token_t *token, #if defined(WIN32) || defined(__CYGWIN__) /* On Windows mutexes are not an object stored in the shmem but identified by an id. */ - alloc_size = size * caf_current_team->u.image_info->image_count.count; + alloc_size = mul_size_sat ( + size, caf_current_team->u.image_info->image_count.count); #else alloc_size = size; #endif - addr = alloc_get_memory_by_id_created (&local->ai, - alloc_size * sizeof (caf_shmem_lock_t), - next_memid, &created); + addr = alloc_get_memory_by_id_created ( + &local->ai, mul_size_sat (alloc_size, sizeof (caf_shmem_lock_t)), + next_memid, &created); if (created) { @@ -306,12 +318,13 @@ _gfortran_caf_register (size_t size, caf_register_t type, caf_token_t *token, case CAF_REGTYPE_EVENT_ALLOC: { bool created; + size_t alloc_size = mul_size_sat (size, local->total_num_images); allocator_lock (&local->ai.alloc); - mem = alloc_get_memory_by_id_created ( - &local->ai, size * local->total_num_images, next_memid, &created); + mem = alloc_get_memory_by_id_created (&local->ai, alloc_size, next_memid, + &created); if (created) - memset (mem, 0, size * local->total_num_images); + memset (mem, 0, alloc_size); allocator_unlock (&local->ai.alloc); } break; @@ -334,7 +347,8 @@ _gfortran_caf_register (size_t size, caf_register_t type, caf_token_t *token, break; default: /* Each image's part is at its index among all images. */ - mem = alloc_get_memory_by_id (&local->ai, size * local->total_num_images, + mem = alloc_get_memory_by_id (&local->ai, + mul_size_sat (size, local->total_num_images), next_memid); break; } diff --git a/libgfortran/caf/shmem/allocator.c b/libgfortran/caf/shmem/allocator.c index 104a8586052b..ed60869506d6 100644 --- a/libgfortran/caf/shmem/allocator.c +++ b/libgfortran/caf/shmem/allocator.c @@ -91,6 +91,10 @@ allocator_shared_malloc (allocator *a, size_t size) size_t act_size; int bucket_list_index; + /* Sizes beyond the largest power of two of size_t cannot be served. */ + if (size > (size_t) 1 << (sizeof (size_t) * __CHAR_BIT__ - 1)) + return SHMPTR_NULL; + sz = next_power_of_two (size); act_size = sz > sizeof (bucket) ? sz : sizeof (bucket); bucket_list_index = __builtin_clzl (act_size); diff --git a/libgfortran/caf/shmem/collective_subroutine.c b/libgfortran/caf/shmem/collective_subroutine.c index 2fef8ae50141..0c91f44a43e5 100644 --- a/libgfortran/caf/shmem/collective_subroutine.c +++ b/libgfortran/caf/shmem/collective_subroutine.c @@ -362,6 +362,7 @@ collsub_reduce_array (gfc_descriptor_t *desc, int result_image, int imoffset; index_type elem_size; index_type this_image_size_bytes; + size_t buf_size; void *this_image_buf, *roll_iter, *src_iter; ass_op assign; const int this_img_id = caf_current_team->index; @@ -373,9 +374,13 @@ collsub_reduce_array (gfc_descriptor_t *desc, int result_image, elem_size = GFC_DESCRIPTOR_SIZE (desc); this_image_size_bytes = elem_size * pi.num_elem; - if (!get_collsub_buf (this_image_size_bytes - * caf_current_team->u.image_info->image_map_size, - &buffer)) + /* Saturate at SIZE_MAX so the allocation fails instead of wrapping. */ + if (__builtin_mul_overflow ((size_t) this_image_size_bytes, + (size_t) caf_current_team->u.image_info + ->image_map_size, + &buf_size)) + buf_size = SIZE_MAX; + if (!get_collsub_buf (buf_size, &buffer)) return collsub_no_memory (); this_image_buf = buffer + this_image_size_bytes * this_img_id;
