diff --git a/glibc-RHEL-75809-2.patch b/glibc-RHEL-75809-2.patch new file mode 100644 index 0000000..15d865e --- /dev/null +++ b/glibc-RHEL-75809-2.patch @@ -0,0 +1,232 @@ +commit 36fcdfbbc5463e55581fec67141df3493fb81f7e +Author: Florian Weimer +Date: Fri Jan 24 08:04:23 2025 +0100 + + Revert "stdlib: Support malloc-managed environ arrays for compatibility" + + This reverts commit b62759db04b8ed7f829c06f1d7c3b8fb70616493. + + Reason for revert: Incompatible with “env -i” and coreutils (bug 32588). + + Reviewed-by: H.J. Lu + +diff --git a/csu/init-first.c b/csu/init-first.c +index 77b5b4941beb3a73..a2cb456ccf9ac5e6 100644 +--- a/csu/init-first.c ++++ b/csu/init-first.c +@@ -61,7 +61,6 @@ _init_first (int argc, char **argv, char **envp) + __libc_argc = argc; + __libc_argv = argv; + __environ = envp; +- __environ_startup = envp; + + #ifndef SHARED + /* First the initialization which normally would be done by the +diff --git a/csu/libc-start.c b/csu/libc-start.c +index 260027c2396e1f52..d784de0f0bdd70c8 100644 +--- a/csu/libc-start.c ++++ b/csu/libc-start.c +@@ -244,7 +244,6 @@ LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL), + char **ev = &argv[argc + 1]; + + __environ = ev; +- __environ_startup = ev; + + /* Store the lowest stack address. This is done in ld.so if this is + the code for the DSO. */ +diff --git a/include/unistd.h b/include/unistd.h +index ada957f9d04d272a..e241603b8131a9e9 100644 +--- a/include/unistd.h ++++ b/include/unistd.h +@@ -203,9 +203,6 @@ libc_hidden_proto (__tcsetpgrp) + extern int __libc_enable_secure attribute_relro; + rtld_hidden_proto (__libc_enable_secure) + +-/* Original value of __environ. Initialized by _init_first (dynamic) +- or __libc_start_main (static). */ +-extern char **__environ_startup attribute_hidden; + + /* Various internal function. */ + extern void __libc_check_standard_fds (void) attribute_hidden; +diff --git a/posix/environ.c b/posix/environ.c +index 2430b47d8eee148c..a0ed0d80eab207f8 100644 +--- a/posix/environ.c ++++ b/posix/environ.c +@@ -10,5 +10,3 @@ weak_alias (__environ, environ) + /* The SVR4 ABI says `_environ' will be the name to use + in case the user overrides the weak alias `environ'. */ + weak_alias (__environ, _environ) +- +-char **__environ_startup; +diff --git a/stdlib/Makefile b/stdlib/Makefile +index 217600ba60e3c7d4..ff1418f5bb2ea5c9 100644 +--- a/stdlib/Makefile ++++ b/stdlib/Makefile +@@ -312,7 +312,6 @@ tests := \ + tst-setcontext9 \ + tst-setcontext10 \ + tst-setcontext11 \ +- tst-setenv-malloc \ + tst-stdbit-Wconversion \ + tst-stdbit-builtins \ + tst-stdc_bit_ceil \ +diff --git a/stdlib/setenv.c b/stdlib/setenv.c +index 79982aa12ac20078..d12401ca77cee5a7 100644 +--- a/stdlib/setenv.c ++++ b/stdlib/setenv.c +@@ -191,52 +191,52 @@ __add_to_environ (const char *name, const char *value, const char *combined, + ep[1] = NULL; + else + { +- /* We cannot use __environ as is and need a larger allocation. */ +- +- if (start_environ == __environ_startup +- || __environ_is_from_array_list (start_environ)) +- { +- /* Allocate a new array, managed in the list. */ +- struct environ_array *target_array +- = __environ_new_array (required_size); +- if (target_array == NULL) +- { +- UNLOCK; +- return -1; +- } +- result_environ = &target_array->array[0]; +- +- /* Copy over the __environ array contents. This code +- handles the case start_environ == ep == NULL, too. */ +- size_t i; +- for (i = 0; start_environ + i < ep; ++i) +- /* Regular store because unless there has been direct +- manipulation of the environment, target_array is still +- a private copy. */ +- result_environ[i] = atomic_load_relaxed (start_environ + i); +- } ++ /* We cannot use __environ as is and need to copy over the ++ __environ contents into an array managed via ++ __environ_array_list. */ ++ ++ struct environ_array *target_array; ++ if (__environ_array_list != NULL ++ && required_size <= __environ_array_list->allocated) ++ /* Existing array has enough room. Contents is copied below. */ ++ target_array = __environ_array_list; + else + { +- /* Otherwise the application installed its own pointer. +- Historically, this pointer was managed using realloc. +- Continue doing so. This disables multi-threading +- support. */ +- result_environ = __libc_reallocarray (start_environ, +- required_size, +- sizeof (*result_environ)); +- if (result_environ == NULL) ++ /* Allocate a new array. */ ++ target_array = __environ_new_array (required_size); ++ if (target_array == NULL) + { + UNLOCK; + return -1; + } + } + ++ /* Copy over the __environ array contents. This forward ++ copy slides backwards part of the array if __environ ++ points into target_array->array. This happens if an ++ application makes an assignment like: ++ ++ environ = &environ[1]; ++ ++ The forward copy avoids clobbering values that still ++ needing copying. This code handles the case ++ start_environ == ep == NULL, too. */ ++ size_t i; ++ for (i = 0; start_environ + i < ep; ++i) ++ /* Regular store because unless there has been direct ++ manipulation of the environment, target_array is still ++ a private copy. */ ++ target_array->array[i] = atomic_load_relaxed (start_environ + i); ++ + /* This is the new place where we should add the element. */ +- ep = result_environ + (required_size - 2); ++ ep = target_array->array + i; + + /* Add the null terminator in case there was a pointer there + previously. */ + ep[1] = NULL; ++ ++ /* And __environ should be repointed to our array. */ ++ result_environ = &target_array->array[0]; + } + } + +diff --git a/stdlib/tst-setenv-malloc.c b/stdlib/tst-setenv-malloc.c +deleted file mode 100644 +index 18a9d36842e67aa5..0000000000000000 +--- a/stdlib/tst-setenv-malloc.c ++++ /dev/null +@@ -1,64 +0,0 @@ +-/* Test using setenv with a malloc-allocated environ variable. +- Copyright (C) 2025 Free Software Foundation, Inc. +- This file is part of the GNU C Library. +- +- The GNU C Library is free software; you can redistribute it and/or +- modify it under the terms of the GNU Lesser General Public +- License as published by the Free Software Foundation; either +- version 2.1 of the License, or (at your option) any later version. +- +- The GNU C Library is distributed in the hope that it will be useful, +- but WITHOUT ANY WARRANTY; without even the implied warranty of +- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- Lesser General Public License for more details. +- +- You should have received a copy of the GNU Lesser General Public +- License along with the GNU C Library; if not, see +- . */ +- +-/* This test is not in the scope for POSIX or any other standard, but +- some applications assume that environ is a heap-allocated pointer +- after a call to setenv on an empty environment. */ +- +-#include +-#include +-#include +-#include +- +-static const char *original_path; +-static char **save_environ; +- +-static void +-rewrite_environ (void) +-{ +- save_environ = environ; +- environ = xmalloc (sizeof (*environ)); +- *environ = NULL; +- TEST_COMPARE (setenv ("A", "1", 1), 0); +- TEST_COMPARE (setenv ("B", "2", 1), 0); +- TEST_VERIFY (environ != save_environ); +- TEST_COMPARE_STRING (environ[0], "A=1"); +- TEST_COMPARE_STRING (environ[1], "B=2"); +- TEST_COMPARE_STRING (environ[2], NULL); +- TEST_COMPARE_STRING (getenv ("PATH"), NULL); +- free (environ); +- environ = save_environ; +- TEST_COMPARE_STRING (getenv ("PATH"), original_path); +-} +- +-static int +-do_test (void) +-{ +- original_path = getenv ("PATH"); +- rewrite_environ (); +- +- /* Test again after reallocated the environment due to an initial +- setenv call. */ +- TEST_COMPARE (setenv ("TST_SETENV_MALLOC", "1", 1), 0); +- TEST_VERIFY (environ != save_environ); +- rewrite_environ (); +- +- return 0; +-} +- +-#include diff --git a/glibc-RHEL-75809-3.patch b/glibc-RHEL-75809-3.patch new file mode 100644 index 0000000..aff1b42 --- /dev/null +++ b/glibc-RHEL-75809-3.patch @@ -0,0 +1,299 @@ +commit 12b4a1fc6ecfc278a87159164bdf1d682deb18e2 +Author: Florian Weimer +Date: Fri Jan 24 10:40:28 2025 +0100 + + stdlib: Re-implement free (environ) compatibility kludge for setenv + + For the originally failing application (userhelper from usermode), + it is not actually necessary to call realloc on the environ + pointer. Yes, there will be a memory leak because the application + assigns a heap-allocated pointer to environ that it never frees, + but this leak was always there: the old realloc-based setenv had + a hidden internal variable, last_environ, that was used in a similar + way to __environ_array_list. The application is not impacted by + the leak anyway because the relevant operations do not happen in + a loop. + + The change here just uses a separte heap allocation and points + environ to that. This means that if an application calls + free (environ) and restores the environ pointer to the value + at process start, and does not modify the environment further, + nothing bad happens. + + This change should not invalidate any previous testing that went into + the original getenv thread safety change, commit 7a61e7f557a97ab597d6 + ("stdlib: Make getenv thread-safe in more cases"). + + The new test cases are modeled in part on the env -i use case from + bug 32588 (with !DO_MALLOC && !DO_EARLY_SETENV), and the previous + stdlib/tst-setenv-malloc test. The DO_MALLOC && !DO_EARLY_SETENV + case in the new test should approximate what userhelper from the + usermode package does. + + Reviewed-by: Carlos O'Donell + +diff --git a/stdlib/Makefile b/stdlib/Makefile +index ff1418f5bb2ea5c9..f4dec9be46a573b9 100644 +--- a/stdlib/Makefile ++++ b/stdlib/Makefile +@@ -275,6 +275,10 @@ tests := \ + tst-canon-bz26341 \ + tst-cxa_atexit \ + tst-environ \ ++ tst-environ-change-1 \ ++ tst-environ-change-2 \ ++ tst-environ-change-3 \ ++ tst-environ-change-4 \ + tst-getenv-signal \ + tst-getenv-thread \ + tst-getenv-unsetenv \ +diff --git a/stdlib/setenv.c b/stdlib/setenv.c +index d12401ca77cee5a7..20b0e1673c9557de 100644 +--- a/stdlib/setenv.c ++++ b/stdlib/setenv.c +@@ -118,24 +118,21 @@ __environ_new_array (size_t required_size) + else + new_size = __environ_array_list->allocated * 2; + +- size_t new_size_in_bytes; +- if (__builtin_mul_overflow (new_size, sizeof (char *), +- &new_size_in_bytes) +- || __builtin_add_overflow (new_size_in_bytes, +- offsetof (struct environ_array, +- array), +- &new_size_in_bytes)) ++ /* Zero-initialize everything, so that getenv can only ++ observe valid or null pointers. */ ++ char **new_array = calloc (new_size, sizeof (*new_array)); ++ if (new_array == NULL) ++ return NULL; ++ ++ struct environ_array *target_array = malloc (sizeof (*target_array)); ++ if (target_array == NULL) + { +- __set_errno (ENOMEM); ++ free (new_array); + return NULL; + } + +- /* Zero-initialize everything, so that getenv can only +- observe valid or null pointers. */ +- struct environ_array *target_array = calloc (1, new_size_in_bytes); +- if (target_array == NULL) +- return NULL; + target_array->allocated = new_size; ++ target_array->array = new_array; + assert (new_size >= target_array->allocated); + + /* Put it onto the list. */ +@@ -236,7 +233,7 @@ __add_to_environ (const char *name, const char *value, const char *combined, + ep[1] = NULL; + + /* And __environ should be repointed to our array. */ +- result_environ = &target_array->array[0]; ++ result_environ = target_array->array; + } + } + +@@ -403,6 +400,7 @@ __libc_setenv_freemem (void) + /* Clear all backing arrays. */ + while (__environ_array_list != NULL) + { ++ free (__environ_array_list->array); + void *ptr = __environ_array_list; + __environ_array_list = __environ_array_list->next; + free (ptr); +diff --git a/stdlib/setenv.h b/stdlib/setenv.h +index 036f4274aa29b722..42b86fff1008bc81 100644 +--- a/stdlib/setenv.h ++++ b/stdlib/setenv.h +@@ -29,9 +29,18 @@ + of environment values used before. */ + struct environ_array + { +- struct environ_array *next; /* Previously used environment array. */ ++ /* The actual environment array. Use a separate allocation (and not ++ a flexible array member) so that calls like free (environ) that ++ have been encountered in some applications do not crash ++ immediately. With such a call, if the application restores the ++ original environ pointer at process start and does not modify the ++ environment again, a use-after-free situation only occurs during ++ __libc_freeres, which is only called during memory debugging. ++ With subsequent setenv calls, there is still heap corruption, but ++ that happened with the old realloc-based implementation, too. */ ++ char **array; + size_t allocated; /* Number of allocated array elments. */ +- char *array[]; /* The actual environment array. */ ++ struct environ_array *next; /* Previously used environment array. */ + }; + + /* After initialization, and until the user resets environ (perhaps by +@@ -44,7 +53,7 @@ static inline bool + __environ_is_from_array_list (char **ep) + { + struct environ_array *eal = atomic_load_relaxed (&__environ_array_list); +- return eal != NULL && &eal->array[0] == ep; ++ return eal != NULL && eal->array == ep; + } + + /* Counter for detecting concurrent modification in unsetenv. +diff --git a/stdlib/tst-environ-change-1.c b/stdlib/tst-environ-change-1.c +new file mode 100644 +index 0000000000000000..4241ad4c63ea2e33 +--- /dev/null ++++ b/stdlib/tst-environ-change-1.c +@@ -0,0 +1,3 @@ ++#define DO_EARLY_SETENV 0 ++#define DO_MALLOC 0 ++#include "tst-environ-change-skeleton.c" +diff --git a/stdlib/tst-environ-change-2.c b/stdlib/tst-environ-change-2.c +new file mode 100644 +index 0000000000000000..b20be124902125e8 +--- /dev/null ++++ b/stdlib/tst-environ-change-2.c +@@ -0,0 +1,3 @@ ++#define DO_EARLY_SETENV 0 ++#define DO_MALLOC 1 ++#include "tst-environ-change-skeleton.c" +diff --git a/stdlib/tst-environ-change-3.c b/stdlib/tst-environ-change-3.c +new file mode 100644 +index 0000000000000000..e77996a6cb0ac601 +--- /dev/null ++++ b/stdlib/tst-environ-change-3.c +@@ -0,0 +1,3 @@ ++#define DO_EARLY_SETENV 1 ++#define DO_MALLOC 0 ++#include "tst-environ-change-skeleton.c" +diff --git a/stdlib/tst-environ-change-4.c b/stdlib/tst-environ-change-4.c +new file mode 100644 +index 0000000000000000..633ef7bda84eb2a8 +--- /dev/null ++++ b/stdlib/tst-environ-change-4.c +@@ -0,0 +1,3 @@ ++#define DO_EARLY_SETENV 1 ++#define DO_MALLOC 1 ++#include "tst-environ-change-skeleton.c" +diff --git a/stdlib/tst-environ-change-skeleton.c b/stdlib/tst-environ-change-skeleton.c +new file mode 100644 +index 0000000000000000..c9b02844369207d9 +--- /dev/null ++++ b/stdlib/tst-environ-change-skeleton.c +@@ -0,0 +1,118 @@ ++/* Test deallocation of the environ pointer. ++ Copyright (C) 2025 Free Software Foundation, Inc. ++ This file is part of the GNU C Library. ++ ++ The GNU C Library is free software; you can redistribute it and/or ++ modify it under the terms of the GNU Lesser General Public ++ License as published by the Free Software Foundation; either ++ version 2.1 of the License, or (at your option) any later version. ++ ++ The GNU C Library is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ Lesser General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public ++ License along with the GNU C Library; if not, see ++ . */ ++ ++/* This test is not in the scope for POSIX or any other standard, but ++ some applications assume that environ is a heap-allocated pointer ++ after a call to setenv on an empty environment. They also try to ++ save and restore environ in an attempt to undo a temporary ++ modification of the environment array, but this does not work if ++ setenv was called before. ++ ++ Before including this file, these macros need to be defined ++ to 0 or 1: ++ ++ DO_EARLY_SETENV If 1, perform a setenv call before changing environ. ++ DO_MALLOC If 1, use a heap pointer for the empty environment. ++ ++ Note that this test will produce errors under valgrind and other ++ memory tracers that call __libc_freeres because free (environ) ++ deallocates a pointer still used internally. */ ++ ++#include ++#include ++#include ++#include ++ ++static void ++check_rewritten (void) ++{ ++ TEST_COMPARE_STRING (environ[0], "tst_environ_change_a=1"); ++ TEST_COMPARE_STRING (environ[1], "tst_environ_change_b=2"); ++ TEST_COMPARE_STRING (environ[2], NULL); ++ TEST_COMPARE_STRING (getenv ("tst_environ_change_a"), "1"); ++ TEST_COMPARE_STRING (getenv ("tst_environ_change_b"), "2"); ++ TEST_COMPARE_STRING (getenv ("tst_environ_change_early"), NULL); ++ TEST_COMPARE_STRING (getenv ("PATH"), NULL); ++} ++ ++static int ++do_test (void) ++{ ++ TEST_COMPARE_STRING (getenv ("tst_environ_change_a"), NULL); ++ TEST_COMPARE_STRING (getenv ("tst_environ_change_b"), NULL); ++ TEST_COMPARE_STRING (getenv ("tst_environ_change_early_setenv"), NULL); ++#if DO_EARLY_SETENV ++ TEST_COMPARE (setenv ("tst_environ_change_early_setenv", "1", 1), 0); ++#else ++ /* Must come back after environ reset. */ ++ char *original_path = xstrdup (getenv ("PATH")); ++#endif ++ ++ char **save_environ = environ; ++#if DO_MALLOC ++ environ = xmalloc (sizeof (*environ)); ++#else ++ char *environ_array[1]; ++ environ = environ_array; ++#endif ++ *environ = NULL; ++ TEST_COMPARE (setenv ("tst_environ_change_a", "1", 1), 0); ++ TEST_COMPARE (setenv ("tst_environ_change_b", "2", 1), 0); ++#if !DO_EARLY_SETENV ++ /* Early setenv results in reuse of the heap-allocated environ array ++ that does not change as more pointers are added to it. */ ++ TEST_VERIFY (environ != save_environ); ++#endif ++ check_rewritten (); ++ ++ bool check_environ = true; ++#if DO_MALLOC ++ /* Disable further checks if the free call clobbers the environ ++ contents. Whether that is the case depends on the internal ++ setenv allocation policy and the heap layout. */ ++ check_environ = environ != save_environ; ++ /* Invalid: Causes internal use-after-free condition. Yet this has ++ to be supported for compatibility with some applications. */ ++ free (environ); ++#endif ++ ++ environ = save_environ; ++ ++#if DO_EARLY_SETENV ++ /* With an early setenv, the internal environ array was overwritten. ++ Historically, this triggered a use-after-free problem because of ++ the use of realloc internally in setenv, but it may appear as if ++ the original environment had been restored. In the current code, ++ we can only support this if the free (environ) above call did not ++ clobber the array, otherwise getenv will see invalid pointers. ++ Due to the use-after-free, invalid pointers could be seen with ++ the old implementation as well, but the triggering conditions ++ were different. */ ++ if (check_environ) ++ check_rewritten (); ++#else ++ TEST_VERIFY (check_environ); ++ TEST_COMPARE_STRING (getenv ("PATH"), original_path); ++ TEST_COMPARE_STRING (getenv ("tst_environ_change_a"), NULL); ++ TEST_COMPARE_STRING (getenv ("tst_environ_change_b"), NULL); ++#endif ++ ++ return 0; ++} ++ ++#include diff --git a/glibc.spec b/glibc.spec index 63699a8..a6e5f3f 100644 --- a/glibc.spec +++ b/glibc.spec @@ -145,7 +145,7 @@ Version: %{glibcversion} # - It allows using the Release number without the %%dist tag in the dependency # generator to make the generated requires interchangeable between Rawhide # and ELN (.elnYY < .fcXX). -%global baserelease 36 +%global baserelease 37 Release: %{baserelease}%{?dist} # Licenses: @@ -506,6 +506,8 @@ Patch188: glibc-upstream-2.39-145.patch Patch189: glibc-upstream-2.39-146.patch Patch190: glibc-RHEL-75809.patch Patch191: glibc-RHEL-75555.patch +Patch192: glibc-RHEL-75809-2.patch +Patch193: glibc-RHEL-75809-3.patch ############################################################################## # Continued list of core "glibc" package information: @@ -2501,6 +2503,9 @@ update_gconv_modules_cache () %endif %changelog +* Fri Jan 24 2025 Florian Weimer - 2.39-37 +- setenv: Rework free(environ) compatibility support (RHEL-75809) + * Thu Jan 23 2025 Florian Weimer - 2.39-36 - CVE-2025-0577: vDSO getrandom predictable randomness after fork (RHEL-75555)