diff mbox series

elf: handle addition overflow in _dl_find_object_update_1 [BZ #32345]

Message ID 20241110112252.616754-1-aurelien@aurel32.net
State New
Headers show
Series elf: handle addition overflow in _dl_find_object_update_1 [BZ #32345] | expand

Commit Message

Aurelien Jarno Nov. 10, 2024, 11:22 a.m. UTC
Theoretically, current_used + count can wrap around to zero without
undefined behaviour. This is caught by GCC 14+ on hppa, which determines
from there that target_seg could be be NULL when remaining_to_add is
zero, which in turns causes a -Wstringop-overflow warning:

 In file included from ../include/atomic.h:49,
                  from dl-find_object.c:20:
 In function '_dlfo_update_init_seg',
     inlined from '_dl_find_object_update_1' at dl-find_object.c:689:30,
     inlined from '_dl_find_object_update' at dl-find_object.c:805:13:
 ../sysdeps/unix/sysv/linux/hppa/atomic-machine.h:44:4: error: '__atomic_store_4' writing 4 bytes into a region of size 0 overflows the destination [-Werror=stringop-overflow=]
    44 |    __atomic_store_n ((mem), (val), __ATOMIC_RELAXED);                        \
       |    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 dl-find_object.c:644:3: note: in expansion of macro 'atomic_store_relaxed'
   644 |   atomic_store_relaxed (&seg->size, new_seg_size);
       |   ^~~~~~~~~~~~~~~~~~~~
 In function '_dl_find_object_update':
 cc1: note: destination object is likely at address zero

Fix that by doing the addition using __builtin_add_overflow and
returning false in the unlikely case an overflow happens.

Thanks to Andreas Schwab for the investigation.

Closes: BZ #32345
Signed-off-by: Aurelien Jarno <aurelien@aurel32.net>
---
 elf/dl-find_object.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

Comments

Florian Weimer Nov. 10, 2024, 9:23 p.m. UTC | #1
* Aurelien Jarno:

> Theoretically, current_used + count can wrap around to zero without
> undefined behaviour.

Sorry, I don't see how.  These are counts of link maps (among other
things).  Link maps have byte sizes larger than 1, so two counts of
allocated link maps always fit into a size_t value without wrapping
around.

> This is caught by GCC 14+ on hppa, which determines
> from there that target_seg could be be NULL when remaining_to_add is
> zero, which in turns causes a -Wstringop-overflow warning:
>
>  In file included from ../include/atomic.h:49,
>                   from dl-find_object.c:20:
>  In function '_dlfo_update_init_seg',
>      inlined from '_dl_find_object_update_1' at dl-find_object.c:689:30,
>      inlined from '_dl_find_object_update' at dl-find_object.c:805:13:
>  ../sysdeps/unix/sysv/linux/hppa/atomic-machine.h:44:4: error: '__atomic_store_4' writing 4 bytes into a region of size 0 overflows the destination [-Werror=stringop-overflow=]
>     44 |    __atomic_store_n ((mem), (val), __ATOMIC_RELAXED);                        \
>        |    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>  dl-find_object.c:644:3: note: in expansion of macro 'atomic_store_relaxed'
>    644 |   atomic_store_relaxed (&seg->size, new_seg_size);
>        |   ^~~~~~~~~~~~~~~~~~~~
>  In function '_dl_find_object_update':
>  cc1: note: destination object is likely at address zero

What is HPPA doing that triggers this warning?  Clone some path?

Maybe we can tell the compiler that the path is unreachable (with a
suitable comment)?

  if (__builtin_add_overflow(current_used, count, &remaining_to_add))
    __builtin_unreachable ();
John David Anglin Nov. 11, 2024, 12:33 a.m. UTC | #2
Patch fixes build on hppa.  But I agree with Florian that the overflow is impossible.
I think Florian's suggestion will work.

Dave

On 2024-11-10 6:22 a.m., Aurelien Jarno wrote:
> Theoretically, current_used + count can wrap around to zero without
> undefined behaviour. This is caught by GCC 14+ on hppa, which determines
> from there that target_seg could be be NULL when remaining_to_add is
> zero, which in turns causes a -Wstringop-overflow warning:
>
>   In file included from ../include/atomic.h:49,
>                    from dl-find_object.c:20:
>   In function '_dlfo_update_init_seg',
>       inlined from '_dl_find_object_update_1' at dl-find_object.c:689:30,
>       inlined from '_dl_find_object_update' at dl-find_object.c:805:13:
>   ../sysdeps/unix/sysv/linux/hppa/atomic-machine.h:44:4: error: '__atomic_store_4' writing 4 bytes into a region of size 0 overflows the destination [-Werror=stringop-overflow=]
>      44 |    __atomic_store_n ((mem), (val), __ATOMIC_RELAXED);                        \
>         |    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>   dl-find_object.c:644:3: note: in expansion of macro 'atomic_store_relaxed'
>     644 |   atomic_store_relaxed (&seg->size, new_seg_size);
>         |   ^~~~~~~~~~~~~~~~~~~~
>   In function '_dl_find_object_update':
>   cc1: note: destination object is likely at address zero
>
> Fix that by doing the addition using __builtin_add_overflow and
> returning false in the unlikely case an overflow happens.
>
> Thanks to Andreas Schwab for the investigation.
>
> Closes: BZ #32345
> Signed-off-by: Aurelien Jarno <aurelien@aurel32.net>
> ---
>   elf/dl-find_object.c | 6 +++++-
>   1 file changed, 5 insertions(+), 1 deletion(-)
>
> diff --git a/elf/dl-find_object.c b/elf/dl-find_object.c
> index 449302eda3..c51a9711c9 100644
> --- a/elf/dl-find_object.c
> +++ b/elf/dl-find_object.c
> @@ -660,7 +660,11 @@ _dl_find_object_update_1 (struct link_map **loaded, size_t count)
>   
>     struct dlfo_mappings_segment *target_seg
>       = _dlfo_loaded_mappings[!active_idx];
> -  size_t remaining_to_add = current_used + count;
> +  size_t remaining_to_add;
> +
> +  if (__glibc_unlikely (__builtin_add_overflow(current_used, count, &remaining_to_add)))
> +    /* Out of memory if remaining_to_add does not fit in a size_t type */
> +    return false;
>   
>     /* Ensure that the new segment chain has enough space.  */
>     {
Andreas Schwab Nov. 11, 2024, 9:04 a.m. UTC | #3
On Nov 10 2024, Aurelien Jarno wrote:

> diff --git a/elf/dl-find_object.c b/elf/dl-find_object.c
> index 449302eda3..c51a9711c9 100644
> --- a/elf/dl-find_object.c
> +++ b/elf/dl-find_object.c
> @@ -660,7 +660,11 @@ _dl_find_object_update_1 (struct link_map **loaded, size_t count)
>  
>    struct dlfo_mappings_segment *target_seg
>      = _dlfo_loaded_mappings[!active_idx];
> -  size_t remaining_to_add = current_used + count;
> +  size_t remaining_to_add;
> +
> +  if (__glibc_unlikely (__builtin_add_overflow(current_used, count, &remaining_to_add)))
> +    /* Out of memory if remaining_to_add does not fit in a size_t type */
> +    return false;

Does it work to assert that remaining_to_add != 0?
Aurelien Jarno Nov. 11, 2024, 12:50 p.m. UTC | #4
On 2024-11-10 22:23, Florian Weimer wrote:
> * Aurelien Jarno:
> 
> > Theoretically, current_used + count can wrap around to zero without
> > undefined behaviour.
> 
> Sorry, I don't see how.  These are counts of link maps (among other
> things).  Link maps have byte sizes larger than 1, so two counts of
> allocated link maps always fit into a size_t value without wrapping
> around.

Theoretically means from the C code point of view, but I agree with you
that it can't happen when looking at the whole picture (that GCC can't
see).

> > This is caught by GCC 14+ on hppa, which determines
> > from there that target_seg could be be NULL when remaining_to_add is
> > zero, which in turns causes a -Wstringop-overflow warning:
> >
> >  In file included from ../include/atomic.h:49,
> >                   from dl-find_object.c:20:
> >  In function '_dlfo_update_init_seg',
> >      inlined from '_dl_find_object_update_1' at dl-find_object.c:689:30,
> >      inlined from '_dl_find_object_update' at dl-find_object.c:805:13:
> >  ../sysdeps/unix/sysv/linux/hppa/atomic-machine.h:44:4: error: '__atomic_store_4' writing 4 bytes into a region of size 0 overflows the destination [-Werror=stringop-overflow=]
> >     44 |    __atomic_store_n ((mem), (val), __ATOMIC_RELAXED);                        \
> >        |    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> >  dl-find_object.c:644:3: note: in expansion of macro 'atomic_store_relaxed'
> >    644 |   atomic_store_relaxed (&seg->size, new_seg_size);
> >        |   ^~~~~~~~~~~~~~~~~~~~
> >  In function '_dl_find_object_update':
> >  cc1: note: destination object is likely at address zero
> 
> What is HPPA doing that triggers this warning?  Clone some path?

Not clear to me why this is limited to HPPA. I only know this issue is
new in GCC 14.

> Maybe we can tell the compiler that the path is unreachable (with a
> suitable comment)?
> 
>   if (__builtin_add_overflow(current_used, count, &remaining_to_add))
>     __builtin_unreachable ();

Unfortunately this does not work. On the other hand this one works:

  if (remaining_to_add == 0)
    __builtin_unreachable ();
Aurelien Jarno Nov. 11, 2024, 12:53 p.m. UTC | #5
On 2024-11-11 10:04, Andreas Schwab wrote:
> On Nov 10 2024, Aurelien Jarno wrote:
> 
> > diff --git a/elf/dl-find_object.c b/elf/dl-find_object.c
> > index 449302eda3..c51a9711c9 100644
> > --- a/elf/dl-find_object.c
> > +++ b/elf/dl-find_object.c
> > @@ -660,7 +660,11 @@ _dl_find_object_update_1 (struct link_map **loaded, size_t count)
> >  
> >    struct dlfo_mappings_segment *target_seg
> >      = _dlfo_loaded_mappings[!active_idx];
> > -  size_t remaining_to_add = current_used + count;
> > +  size_t remaining_to_add;
> > +
> > +  if (__glibc_unlikely (__builtin_add_overflow(current_used, count, &remaining_to_add)))
> > +    /* Out of memory if remaining_to_add does not fit in a size_t type */
> > +    return false;
> 
> Does it work to assert that remaining_to_add != 0?

Yep, that works. However Joseph commented in the bug report that we want
glibc to build even with -DNDEBUG, so an assert is not enough. As
answered to Florian, this one works:

  if (remaining_to_add == 0)
    __builtin_unreachable ();
diff mbox series

Patch

diff --git a/elf/dl-find_object.c b/elf/dl-find_object.c
index 449302eda3..c51a9711c9 100644
--- a/elf/dl-find_object.c
+++ b/elf/dl-find_object.c
@@ -660,7 +660,11 @@  _dl_find_object_update_1 (struct link_map **loaded, size_t count)
 
   struct dlfo_mappings_segment *target_seg
     = _dlfo_loaded_mappings[!active_idx];
-  size_t remaining_to_add = current_used + count;
+  size_t remaining_to_add;
+
+  if (__glibc_unlikely (__builtin_add_overflow(current_used, count, &remaining_to_add)))
+    /* Out of memory if remaining_to_add does not fit in a size_t type */
+    return false;
 
   /* Ensure that the new segment chain has enough space.  */
   {