forked from rpms/glibc
		
	Avoid some free (NULL) calls during __tls_get_addr (RHEL-39415)
This commit is contained in:
		
							parent
							
								
									93f17de82b
								
							
						
					
					
						commit
						87d167a431
					
				
							
								
								
									
										282
									
								
								SOURCES/glibc-RHEL-39415-2.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										282
									
								
								SOURCES/glibc-RHEL-39415-2.patch
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,282 @@ | ||||
| commit 5397764c8c8435324294b885ad42ed9a73a68d1f | ||||
| Author: Florian Weimer <fweimer@redhat.com> | ||||
| Date:   Mon Jun 24 16:00:43 2024 +0200 | ||||
| 
 | ||||
|     elf: Support recursive use of dynamic TLS in interposed malloc | ||||
|      | ||||
|     It turns out that quite a few applications use bundled mallocs that | ||||
|     have been built to use global-dynamic TLS (instead of the recommended | ||||
|     initial-exec TLS).  The previous workaround from | ||||
|     commit afe42e935b3ee97bac9a7064157587777259c60e ("elf: Avoid some | ||||
|     free (NULL) calls in _dl_update_slotinfo") does not fix all | ||||
|     encountered cases unfortunatelly. | ||||
|      | ||||
|     This change avoids the TLS generation update for recursive use | ||||
|     of TLS from a malloc that was called during a TLS update.  This | ||||
|     is possible because an interposed malloc has a fixed module ID and | ||||
|     TLS slot.  (It cannot be unloaded.)  If an initially-loaded module ID | ||||
|     is encountered in __tls_get_addr and the dynamic linker is already | ||||
|     in the middle of a TLS update, use the outdated DTV, thus avoiding | ||||
|     another call into malloc.  It's still necessary to update the | ||||
|     DTV to the most recent generation, to get out of the slow path, | ||||
|     which is why the check for recursion is needed. | ||||
|      | ||||
|     The bookkeeping is done using a global counter instead of per-thread | ||||
|     flag because TLS access in the dynamic linker is tricky. | ||||
|      | ||||
|     All this will go away once the dynamic linker stops using malloc | ||||
|     for TLS, likely as part of a change that pre-allocates all TLS | ||||
|     during pthread_create/dlopen. | ||||
|      | ||||
|     Fixes commit d2123d68275acc0f061e73d5f86ca504e0d5a344 ("elf: Fix slow | ||||
|     tls access after dlopen [BZ #19924]"). | ||||
| 
 | ||||
| diff --git a/elf/dl-tls.c b/elf/dl-tls.c
 | ||||
| index 231171b72c21828f..720921b4d3b9df17 100644
 | ||||
| --- a/elf/dl-tls.c
 | ||||
| +++ b/elf/dl-tls.c
 | ||||
| @@ -75,6 +75,31 @@
 | ||||
|  /* Default for dl_tls_static_optional.  */ | ||||
|  #define OPTIONAL_TLS 512 | ||||
|   | ||||
| +/* Used to count the number of threads currently executing dynamic TLS
 | ||||
| +   updates.  Used to avoid recursive malloc calls in __tls_get_addr
 | ||||
| +   for an interposed malloc that uses global-dynamic TLS (which is not
 | ||||
| +   recommended); see _dl_tls_allocate_active checks.  This could be a
 | ||||
| +   per-thread flag, but would need TLS access in the dynamic linker.  */
 | ||||
| +unsigned int _dl_tls_threads_in_update;
 | ||||
| +
 | ||||
| +static inline void
 | ||||
| +_dl_tls_allocate_begin (void)
 | ||||
| +{
 | ||||
| +  atomic_fetch_add_relaxed (&_dl_tls_threads_in_update, 1);
 | ||||
| +}
 | ||||
| +
 | ||||
| +static inline void
 | ||||
| +_dl_tls_allocate_end (void)
 | ||||
| +{
 | ||||
| +  atomic_fetch_add_relaxed (&_dl_tls_threads_in_update, -1);
 | ||||
| +}
 | ||||
| +
 | ||||
| +static inline bool
 | ||||
| +_dl_tls_allocate_active (void)
 | ||||
| +{
 | ||||
| +  return atomic_load_relaxed (&_dl_tls_threads_in_update) > 0;
 | ||||
| +}
 | ||||
| +
 | ||||
|  /* Compute the static TLS surplus based on the namespace count and the | ||||
|     TLS space that can be used for optimizations.  */ | ||||
|  static inline int | ||||
| @@ -431,12 +456,18 @@ _dl_allocate_tls_storage (void)
 | ||||
|    size += TLS_PRE_TCB_SIZE; | ||||
|  #endif | ||||
|   | ||||
| -  /* Perform the allocation.  Reserve space for the required alignment
 | ||||
| -     and the pointer to the original allocation.  */
 | ||||
| +  /* Reserve space for the required alignment and the pointer to the
 | ||||
| +     original allocation.  */
 | ||||
|    size_t alignment = GLRO (dl_tls_static_align); | ||||
| +
 | ||||
| +  /* Perform the allocation.  */
 | ||||
| +  _dl_tls_allocate_begin ();
 | ||||
|    void *allocated = malloc (size + alignment + sizeof (void *)); | ||||
|    if (__glibc_unlikely (allocated == NULL)) | ||||
| -    return NULL;
 | ||||
| +    {
 | ||||
| +      _dl_tls_allocate_end ();
 | ||||
| +      return NULL;
 | ||||
| +    }
 | ||||
|   | ||||
|    /* Perform alignment and allocate the DTV.  */ | ||||
|  #if TLS_TCB_AT_TP | ||||
| @@ -472,6 +503,8 @@ _dl_allocate_tls_storage (void)
 | ||||
|    result = allocate_dtv (result); | ||||
|    if (result == NULL) | ||||
|      free (allocated); | ||||
| +
 | ||||
| +  _dl_tls_allocate_end ();
 | ||||
|    return result; | ||||
|  } | ||||
|   | ||||
| @@ -489,6 +522,7 @@ _dl_resize_dtv (dtv_t *dtv, size_t max_modid)
 | ||||
|    size_t newsize = max_modid + DTV_SURPLUS; | ||||
|    size_t oldsize = dtv[-1].counter; | ||||
|   | ||||
| +  _dl_tls_allocate_begin ();
 | ||||
|    if (dtv == GL(dl_initial_dtv)) | ||||
|      { | ||||
|        /* This is the initial dtv that was either statically allocated in | ||||
| @@ -508,6 +542,7 @@ _dl_resize_dtv (dtv_t *dtv, size_t max_modid)
 | ||||
|        if (newp == NULL) | ||||
|  	oom (); | ||||
|      } | ||||
| +  _dl_tls_allocate_end ();
 | ||||
|   | ||||
|    newp[0].counter = newsize; | ||||
|   | ||||
| @@ -682,7 +717,9 @@ allocate_dtv_entry (size_t alignment, size_t size)
 | ||||
|    if (powerof2 (alignment) && alignment <= _Alignof (max_align_t)) | ||||
|      { | ||||
|        /* The alignment is supported by malloc.  */ | ||||
| +      _dl_tls_allocate_begin ();
 | ||||
|        void *ptr = malloc (size); | ||||
| +      _dl_tls_allocate_end ();
 | ||||
|        return (struct dtv_pointer) { ptr, ptr }; | ||||
|      } | ||||
|   | ||||
| @@ -694,7 +731,10 @@ allocate_dtv_entry (size_t alignment, size_t size)
 | ||||
|   | ||||
|    /* Perform the allocation.  This is the pointer we need to free | ||||
|       later.  */ | ||||
| +  _dl_tls_allocate_begin ();
 | ||||
|    void *start = malloc (alloc_size); | ||||
| +  _dl_tls_allocate_end ();
 | ||||
| +
 | ||||
|    if (start == NULL) | ||||
|      return (struct dtv_pointer) {}; | ||||
|   | ||||
| @@ -832,7 +872,11 @@ _dl_update_slotinfo (unsigned long int req_modid, size_t new_gen)
 | ||||
|  		 free implementation.  Checking here papers over at | ||||
|  		 least some dynamic TLS usage by interposed mallocs.  */ | ||||
|  	      if (dtv[modid].pointer.to_free != NULL) | ||||
| -		free (dtv[modid].pointer.to_free);
 | ||||
| +		{
 | ||||
| +		  _dl_tls_allocate_begin ();
 | ||||
| +		  free (dtv[modid].pointer.to_free);
 | ||||
| +		  _dl_tls_allocate_end ();
 | ||||
| +		}
 | ||||
|  	      dtv[modid].pointer.val = TLS_DTV_UNALLOCATED; | ||||
|  	      dtv[modid].pointer.to_free = NULL; | ||||
|   | ||||
| @@ -959,13 +1003,25 @@ __tls_get_addr (GET_ADDR_ARGS)
 | ||||
|       module, but the global generation counter is easier to check (which | ||||
|       must be synchronized up to the generation of the accessed module by | ||||
|       user code doing the TLS access so relaxed mo read is enough).  */ | ||||
| -  size_t gen = atomic_load_relaxed (&GL(dl_tls_generation));
 | ||||
| +    size_t gen = atomic_load_relaxed (&GL(dl_tls_generation));
 | ||||
|    if (__glibc_unlikely (dtv[0].counter != gen)) | ||||
|      { | ||||
| -      /* Update DTV up to the global generation, see CONCURRENCY NOTES
 | ||||
| -         in _dl_update_slotinfo.  */
 | ||||
| -      gen = atomic_load_acquire (&GL(dl_tls_generation));
 | ||||
| -      return update_get_addr (GET_ADDR_PARAM, gen);
 | ||||
| +      if (_dl_tls_allocate_active ()
 | ||||
| +	  && GET_ADDR_MODULE < _dl_tls_initial_modid_limit)
 | ||||
| +	  /* This is a reentrant __tls_get_addr call, but we can
 | ||||
| +	     satisfy it because it's an initially-loaded module ID.
 | ||||
| +	     These TLS slotinfo slots do not change, so the
 | ||||
| +	     out-of-date generation counter does not matter.  However,
 | ||||
| +	     if not in a TLS update, still update_get_addr below, to
 | ||||
| +	     get off the slow path eventually.  */
 | ||||
| +	;
 | ||||
| +      else
 | ||||
| +	{
 | ||||
| +	  /* Update DTV up to the global generation, see CONCURRENCY NOTES
 | ||||
| +	     in _dl_update_slotinfo.  */
 | ||||
| +	  gen = atomic_load_acquire (&GL(dl_tls_generation));
 | ||||
| +	  return update_get_addr (GET_ADDR_PARAM, gen);
 | ||||
| +	}
 | ||||
|      } | ||||
|   | ||||
|    void *p = dtv[GET_ADDR_MODULE].pointer.val; | ||||
| @@ -975,7 +1031,7 @@ __tls_get_addr (GET_ADDR_ARGS)
 | ||||
|   | ||||
|    return (char *) p + GET_ADDR_OFFSET; | ||||
|  } | ||||
| -#endif
 | ||||
| +#endif /* SHARED */
 | ||||
|   | ||||
|   | ||||
|  /* Look up the module's TLS block as for __tls_get_addr, | ||||
| @@ -1024,6 +1080,25 @@ _dl_tls_get_addr_soft (struct link_map *l)
 | ||||
|    return data; | ||||
|  } | ||||
|   | ||||
| +size_t _dl_tls_initial_modid_limit;
 | ||||
| +
 | ||||
| +void
 | ||||
| +_dl_tls_initial_modid_limit_setup (void)
 | ||||
| +{
 | ||||
| +  struct dtv_slotinfo_list *listp = GL(dl_tls_dtv_slotinfo_list);
 | ||||
| +  size_t idx;
 | ||||
| +  for (idx = 0; idx < listp->len; ++idx)
 | ||||
| +    {
 | ||||
| +      struct link_map *l = listp->slotinfo[idx].map;
 | ||||
| +      if (l == NULL
 | ||||
| +	  /* The object can be unloaded, so its modid can be
 | ||||
| +	     reassociated.  */
 | ||||
| +	  || !(l->l_type == lt_executable || l->l_type == lt_library))
 | ||||
| +	break;
 | ||||
| +    }
 | ||||
| +  _dl_tls_initial_modid_limit = idx;
 | ||||
| +}
 | ||||
| +
 | ||||
|   | ||||
|  void | ||||
|  _dl_add_to_slotinfo (struct link_map *l, bool do_add) | ||||
| @@ -1056,9 +1131,11 @@ _dl_add_to_slotinfo (struct link_map *l, bool do_add)
 | ||||
|  	 the first slot.  */ | ||||
|        assert (idx == 0); | ||||
|   | ||||
| +      _dl_tls_allocate_begin ();
 | ||||
|        listp = (struct dtv_slotinfo_list *) | ||||
|  	malloc (sizeof (struct dtv_slotinfo_list) | ||||
|  		+ TLS_SLOTINFO_SURPLUS * sizeof (struct dtv_slotinfo)); | ||||
| +      _dl_tls_allocate_end ();
 | ||||
|        if (listp == NULL) | ||||
|  	{ | ||||
|  	  /* We ran out of memory while resizing the dtv slotinfo list.  */ | ||||
| diff --git a/elf/rtld.c b/elf/rtld.c
 | ||||
| index d973c385b312ea16..15a01f3b175ac065 100644
 | ||||
| --- a/elf/rtld.c
 | ||||
| +++ b/elf/rtld.c
 | ||||
| @@ -801,6 +801,8 @@ init_tls (size_t naudit)
 | ||||
|      _dl_fatal_printf ("\ | ||||
|  cannot allocate TLS data structures for initial thread\n"); | ||||
|   | ||||
| +  _dl_tls_initial_modid_limit_setup ();
 | ||||
| +
 | ||||
|    /* Store for detection of the special case by __tls_get_addr | ||||
|       so it knows not to pass this dtv to the normal realloc.  */ | ||||
|    GL(dl_initial_dtv) = GET_DTV (tcbp); | ||||
| diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
 | ||||
| index 7964e133e4930e88..8bc42ba147ad4c47 100644
 | ||||
| --- a/sysdeps/generic/ldsodefs.h
 | ||||
| +++ b/sysdeps/generic/ldsodefs.h
 | ||||
| @@ -1308,6 +1308,20 @@ extern struct link_map *_dl_update_slotinfo (unsigned long int req_modid,
 | ||||
|  					     size_t gen) | ||||
|       attribute_hidden; | ||||
|   | ||||
| +/* The last TLS module ID that is initially loaded, plus 1.  TLS
 | ||||
| +   addresses for modules with IDs lower than that can be obtained from
 | ||||
| +   the DTV even if its generation is outdated.  */
 | ||||
| +extern size_t _dl_tls_initial_modid_limit attribute_hidden attribute_relro;
 | ||||
| +
 | ||||
| +/* Compute _dl_tls_initial_modid_limit.  To be called after initial
 | ||||
| +   relocation.  */
 | ||||
| +void _dl_tls_initial_modid_limit_setup (void) attribute_hidden;
 | ||||
| +
 | ||||
| +/* Number of threads currently in a TLS update.  This is used to
 | ||||
| +   detect reentrant __tls_get_addr calls without a per-thread
 | ||||
| +   flag.  */
 | ||||
| +extern unsigned int _dl_tls_threads_in_update attribute_hidden;
 | ||||
| +
 | ||||
|  /* Look up the module's TLS block as for __tls_get_addr, | ||||
|     but never touch anything.  Return null if it's not allocated yet.  */ | ||||
|  extern void *_dl_tls_get_addr_soft (struct link_map *l) attribute_hidden; | ||||
| diff --git a/sysdeps/x86_64/dl-tls.c b/sysdeps/x86_64/dl-tls.c
 | ||||
| index 4ded8dd6b94edc81..7abfb18413b1d06c 100644
 | ||||
| --- a/sysdeps/x86_64/dl-tls.c
 | ||||
| +++ b/sysdeps/x86_64/dl-tls.c
 | ||||
| @@ -41,7 +41,10 @@ __tls_get_addr_slow (GET_ADDR_ARGS)
 | ||||
|    dtv_t *dtv = THREAD_DTV (); | ||||
|   | ||||
|    size_t gen = atomic_load_acquire (&GL(dl_tls_generation)); | ||||
| -  if (__glibc_unlikely (dtv[0].counter != gen))
 | ||||
| +  if (__glibc_unlikely (dtv[0].counter != gen)
 | ||||
| +      /* See comment in __tls_get_addr in elf/dl-tls.c.  */
 | ||||
| +      && !(_dl_tls_allocate_active ()
 | ||||
| +           && GET_ADDR_MODULE < _dl_tls_initial_modid_limit))
 | ||||
|      return update_get_addr (GET_ADDR_PARAM, gen); | ||||
|   | ||||
|    return tls_get_addr_tail (GET_ADDR_PARAM, dtv, NULL); | ||||
							
								
								
									
										28
									
								
								SOURCES/glibc-RHEL-39415.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								SOURCES/glibc-RHEL-39415.patch
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| Try to cover for incorrect dynamic TLS usage in some malloc | ||||
| implementations. | ||||
| 
 | ||||
| Upstream discussion: | ||||
| 
 | ||||
|   New TLS usage in libgcc_s.so.1, compatibility impact | ||||
|   <https://inbox.sourceware.org/gcc/8734v1ieke.fsf@oldenburg.str.redhat.com/> | ||||
| 
 | ||||
| diff --git a/elf/dl-tls.c b/elf/dl-tls.c
 | ||||
| index b9dc56e81a3b43db..231171b72c21828f 100644
 | ||||
| --- a/elf/dl-tls.c
 | ||||
| +++ b/elf/dl-tls.c
 | ||||
| @@ -825,7 +825,14 @@ _dl_update_slotinfo (unsigned long int req_modid, size_t new_gen)
 | ||||
|  		 dtv entry free it.  Note: this is not AS-safe.  */ | ||||
|  	      /* XXX Ideally we will at some point create a memory | ||||
|  		 pool.  */ | ||||
| -	      free (dtv[modid].pointer.to_free);
 | ||||
| +	      /* Avoid calling free on a null pointer.  Some mallocs
 | ||||
| +		 incorrectly use dynamic TLS, and depending on how the
 | ||||
| +		 free function was compiled, it could call
 | ||||
| +		 __tls_get_addr before the null pointer check in the
 | ||||
| +		 free implementation.  Checking here papers over at
 | ||||
| +		 least some dynamic TLS usage by interposed mallocs.  */
 | ||||
| +	      if (dtv[modid].pointer.to_free != NULL)
 | ||||
| +		free (dtv[modid].pointer.to_free);
 | ||||
|  	      dtv[modid].pointer.val = TLS_DTV_UNALLOCATED; | ||||
|  	      dtv[modid].pointer.to_free = NULL; | ||||
|   | ||||
| @ -155,7 +155,7 @@ end \ | ||||
| Summary: The GNU libc libraries | ||||
| Name: glibc | ||||
| Version: %{glibcversion} | ||||
| Release: 100%{?dist}.2 | ||||
| Release: 100%{?dist}.2.alma.1 | ||||
| 
 | ||||
| # In general, GPLv2+ is used by programs, LGPLv2+ is used for | ||||
| # libraries. | ||||
| @ -813,6 +813,8 @@ Patch576: glibc-RHEL-34318-1.patch | ||||
| Patch577: glibc-RHEL-34318-2.patch | ||||
| Patch578: glibc-RHEL-34318-3.patch | ||||
| Patch579: glibc-RHEL-34318-4.patch | ||||
| Patch580: glibc-RHEL-39415.patch | ||||
| Patch581: glibc-RHEL-39415-2.patch | ||||
| 
 | ||||
| ############################################################################## | ||||
| # Continued list of core "glibc" package information: | ||||
| @ -2971,6 +2973,9 @@ update_gconv_modules_cache () | ||||
| %endif | ||||
| 
 | ||||
| %changelog | ||||
| * Fri Jul 05 2024 Andrew Lukoshko <alukoshko@almalinux.org> - 2.34-100.2.alma.1 | ||||
| - Avoid some free (NULL) calls during __tls_get_addr (RHEL-39415) | ||||
| 
 | ||||
| * Mon Apr 29 2024 Florian Weimer <fweimer@redhat.com> - 2.34-100.2 | ||||
| - CVE-2024-33599: nscd: buffer overflow in netgroup cache (RHEL-34318) | ||||
| - CVE-2024-33600: nscd: null pointer dereferences in netgroup cache | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user