From 3720984950d353033518a5a43d2d03744418a4e1 Mon Sep 17 00:00:00 2001 From: Florian Weimer Date: Wed, 21 May 2025 16:57:12 +0200 Subject: [PATCH] CVE-2025-4802: static setuid dlopen may search LD_LIBRARY_PATH (RHEL-92697) Resolves: RHEL-92697 --- glibc-RHEL-92697-1.patch | 95 +++++++++++++++++++++++ glibc-RHEL-92697-2.patch | 56 ++++++++++++++ glibc-RHEL-92697-3.patch | 41 ++++++++++ glibc-RHEL-92697-4.patch | 158 +++++++++++++++++++++++++++++++++++++++ glibc-RHEL-92697-5.patch | 42 +++++++++++ glibc-RHEL-92697-6.patch | 105 ++++++++++++++++++++++++++ glibc.spec | 11 ++- 7 files changed, 507 insertions(+), 1 deletion(-) create mode 100644 glibc-RHEL-92697-1.patch create mode 100644 glibc-RHEL-92697-2.patch create mode 100644 glibc-RHEL-92697-3.patch create mode 100644 glibc-RHEL-92697-4.patch create mode 100644 glibc-RHEL-92697-5.patch create mode 100644 glibc-RHEL-92697-6.patch diff --git a/glibc-RHEL-92697-1.patch b/glibc-RHEL-92697-1.patch new file mode 100644 index 0000000..0cc023a --- /dev/null +++ b/glibc-RHEL-92697-1.patch @@ -0,0 +1,95 @@ +commit 5451fa962cd0a90a0e2ec1d8910a559ace02bba0 +Author: Adhemerval Zanella +Date: Mon Nov 6 17:25:49 2023 -0300 + + elf: Ignore LD_LIBRARY_PATH and debug env var for setuid for static + + It mimics the ld.so behavior. + + Checked on x86_64-linux-gnu. + Reviewed-by: Siddhesh Poyarekar + +Conflicts: + elf/dl-support.c + (tunables are still optional downstream, + LD_PROFILE_OUTPUT handling is still present + for static binaries) + +diff --git a/elf/dl-support.c b/elf/dl-support.c +index a2e45e7b14e3a6b9..1fea55c443505890 100644 +--- a/elf/dl-support.c ++++ b/elf/dl-support.c +@@ -272,8 +272,6 @@ _dl_non_dynamic_init (void) + _dl_main_map.l_phdr = GL(dl_phdr); + _dl_main_map.l_phnum = GL(dl_phnum); + +- _dl_verbose = *(getenv ("LD_WARN") ?: "") == '\0' ? 0 : 1; +- + /* Set up the data structures for the system-supplied DSO early, + so they can influence _dl_init_paths. */ + setup_vdso (NULL, NULL); +@@ -281,6 +279,30 @@ _dl_non_dynamic_init (void) + /* With vDSO setup we can initialize the function pointers. */ + setup_vdso_pointers (); + ++ if (__libc_enable_secure) ++ { ++ static const char unsecure_envvars[] = ++ UNSECURE_ENVVARS ++#ifdef EXTRA_UNSECURE_ENVVARS ++ EXTRA_UNSECURE_ENVVARS ++#endif ++ ; ++ const char *cp = unsecure_envvars; ++ ++ while (cp < unsecure_envvars + sizeof (unsecure_envvars)) ++ { ++ __unsetenv (cp); ++ cp = strchr (cp, '\0') + 1; ++ } ++ ++#if !HAVE_TUNABLES ++ if (__access ("/etc/suid-debug", F_OK) != 0) ++ __unsetenv ("MALLOC_CHECK_"); ++#endif ++} ++ ++ _dl_verbose = *(getenv ("LD_WARN") ?: "") == '\0' ? 0 : 1; ++ + /* Initialize the data structures for the search paths for shared + objects. */ + _dl_init_paths (getenv ("LD_LIBRARY_PATH"), "LD_LIBRARY_PATH", +@@ -298,32 +320,11 @@ _dl_non_dynamic_init (void) + _dl_dynamic_weak = *(getenv ("LD_DYNAMIC_WEAK") ?: "") == '\0'; + + _dl_profile_output = getenv ("LD_PROFILE_OUTPUT"); ++ + if (_dl_profile_output == NULL || _dl_profile_output[0] == '\0') + _dl_profile_output + = &"/var/tmp\0/var/profile"[__libc_enable_secure ? 9 : 0]; + +- if (__libc_enable_secure) +- { +- static const char unsecure_envvars[] = +- UNSECURE_ENVVARS +-#ifdef EXTRA_UNSECURE_ENVVARS +- EXTRA_UNSECURE_ENVVARS +-#endif +- ; +- const char *cp = unsecure_envvars; +- +- while (cp < unsecure_envvars + sizeof (unsecure_envvars)) +- { +- __unsetenv (cp); +- cp = (const char *) __rawmemchr (cp, '\0') + 1; +- } +- +-#if !HAVE_TUNABLES +- if (__access ("/etc/suid-debug", F_OK) != 0) +- __unsetenv ("MALLOC_CHECK_"); +-#endif +- } +- + #ifdef DL_PLATFORM_INIT + DL_PLATFORM_INIT; + #endif diff --git a/glibc-RHEL-92697-2.patch b/glibc-RHEL-92697-2.patch new file mode 100644 index 0000000..0d837a4 --- /dev/null +++ b/glibc-RHEL-92697-2.patch @@ -0,0 +1,56 @@ +commit f0c09fe61678df6f7f18fe1ebff074e62fa5ca7a +Author: Florian Weimer +Date: Tue May 20 19:36:02 2025 +0200 + + support: Use const char * argument in support_capture_subprogram_self_sgid + + The function does not modify the passed-in string, so make this clear + via the prototype. + + Reviewed-by: Carlos O'Donell + +diff --git a/support/capture_subprocess.h b/support/capture_subprocess.h +index 4be430f099bcc8cd..4229300d4b0aab29 100644 +--- a/support/capture_subprocess.h ++++ b/support/capture_subprocess.h +@@ -44,8 +44,7 @@ struct support_capture_subprocess support_capture_subprogram + /* Copy the running program into a setgid binary and run it with CHILD_ID + argument. If execution is successful, return the exit status of the child + program, otherwise return a non-zero failure exit code. */ +-int support_capture_subprogram_self_sgid +- (char *child_id); ++int support_capture_subprogram_self_sgid (const char *child_id); + + /* Deallocate the subprocess data captured by + support_capture_subprocess. */ +diff --git a/support/support_capture_subprocess.c b/support/support_capture_subprocess.c +index 0bacf6dbc23b0732..6e6ac8bbb7f64859 100644 +--- a/support/support_capture_subprocess.c ++++ b/support/support_capture_subprocess.c +@@ -109,7 +109,7 @@ support_capture_subprogram (const char *file, char *const argv[]) + safely make it SGID with the TARGET group ID. Then runs the + executable. */ + static int +-copy_and_spawn_sgid (char *child_id, gid_t gid) ++copy_and_spawn_sgid (const char *child_id, gid_t gid) + { + char *dirname = xasprintf ("%s/tst-tunables-setuid.%jd", + test_dir, (intmax_t) getpid ()); +@@ -172,7 +172,7 @@ copy_and_spawn_sgid (char *child_id, gid_t gid) + ret = 0; + infd = outfd = -1; + +- char * const args[] = {execname, child_id, NULL}; ++ char * const args[] = {execname, (char *) child_id, NULL}; + + status = support_subprogram_wait (args[0], args); + +@@ -199,7 +199,7 @@ err: + } + + int +-support_capture_subprogram_self_sgid (char *child_id) ++support_capture_subprogram_self_sgid (const char *child_id) + { + gid_t target = 0; + const int count = 64; diff --git a/glibc-RHEL-92697-3.patch b/glibc-RHEL-92697-3.patch new file mode 100644 index 0000000..8946433 --- /dev/null +++ b/glibc-RHEL-92697-3.patch @@ -0,0 +1,41 @@ +commit d0b8aa6de4529231fadfe604ac2c434e559c2d9e +Author: Florian Weimer +Date: Mon Dec 23 13:57:55 2024 +0100 + + support: Add support_record_failure_barrier + + This can be used to stop execution after a TEST_COMPARE_BLOB + failure, for example. + +diff --git a/support/check.h b/support/check.h +index 8e045dd9c0c36b4c..6b8b70a10961db6c 100644 +--- a/support/check.h ++++ b/support/check.h +@@ -207,6 +207,9 @@ void support_record_failure_reset (void); + failures or not. */ + int support_record_failure_is_failed (void); + ++/* Terminate the process if any failures have been encountered so far. */ ++void support_record_failure_barrier (void); ++ + __END_DECLS + + #endif /* SUPPORT_CHECK_H */ +diff --git a/support/support_record_failure.c b/support/support_record_failure.c +index 903da401555fc83a..c0bd489637990b70 100644 +--- a/support/support_record_failure.c ++++ b/support/support_record_failure.c +@@ -112,3 +112,13 @@ support_record_failure_is_failed (void) + synchronization for reliable test error reporting anyway. */ + return __atomic_load_n (&state->failed, __ATOMIC_RELAXED); + } ++ ++void ++support_record_failure_barrier (void) ++{ ++ if (__atomic_load_n (&state->failed, __ATOMIC_RELAXED)) ++ { ++ puts ("error: exiting due to previous errors"); ++ exit (1); ++ } ++} diff --git a/glibc-RHEL-92697-4.patch b/glibc-RHEL-92697-4.patch new file mode 100644 index 0000000..fd4dc60 --- /dev/null +++ b/glibc-RHEL-92697-4.patch @@ -0,0 +1,158 @@ +commit d8f7a79335b0d861c12c42aec94c04cd5bb181e2 +Author: Florian Weimer +Date: Tue May 20 19:36:02 2025 +0200 + + elf: Test case for bug 32976 (CVE-2025-4802) + + Check that LD_LIBRARY_PATH is ignored for AT_SECURE statically + linked binaries, using support_capture_subprogram_self_sgid. + + Reviewed-by: Carlos O'Donell + +Conflicts: + elf/Makefile + (test list differences) + +diff --git a/elf/Makefile b/elf/Makefile +index f5b89c7deaf167f2..41adea8d1c6d13ca 100644 +--- a/elf/Makefile ++++ b/elf/Makefile +@@ -260,6 +260,7 @@ tests-static-normal := \ + tst-array1-static \ + tst-array5-static \ + tst-dl-iter-static \ ++ tst-dlopen-sgid \ + tst-dst-static \ + tst-env-setuid \ + tst-env-setuid-tunables \ +@@ -776,6 +777,7 @@ modules-names = \ + tst-dlmopen-gethostbyname-mod \ + tst-dlmopen-twice-mod1 \ + tst-dlmopen-twice-mod2 \ ++ tst-dlopen-sgid-mod \ + tst-dlopen-tlsreinitmod1 \ + tst-dlopen-tlsreinitmod2 \ + tst-dlopen-tlsreinitmod3 \ +@@ -2892,3 +2894,5 @@ tst-tls22-mod1.so-no-z-defs = yes + tst-tls22-mod1-gnu2.so-no-z-defs = yes + tst-tls22-mod2.so-no-z-defs = yes + tst-tls22-mod2-gnu2.so-no-z-defs = yes ++ ++$(objpfx)tst-dlopen-sgid.out: $(objpfx)tst-dlopen-sgid-mod.so +diff --git a/elf/tst-dlopen-sgid-mod.c b/elf/tst-dlopen-sgid-mod.c +new file mode 100644 +index 0000000000000000..5eb79eef485da4c9 +--- /dev/null ++++ b/elf/tst-dlopen-sgid-mod.c +@@ -0,0 +1 @@ ++/* Opening this object should not succeed. */ +diff --git a/elf/tst-dlopen-sgid.c b/elf/tst-dlopen-sgid.c +new file mode 100644 +index 0000000000000000..47829a405e90b6b9 +--- /dev/null ++++ b/elf/tst-dlopen-sgid.c +@@ -0,0 +1,104 @@ ++/* Test case for ignored LD_LIBRARY_PATH in static startug (bug 32976). ++ 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 ++ . */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* This is the name of our test object. Use a custom module for ++ testing, so that this object does not get picked up from the system ++ path. */ ++static const char dso_name[] = "tst-dlopen-sgid-mod.so"; ++ ++/* Used to mark the recursive invocation. */ ++static const char magic_argument[] = "run-actual-test"; ++ ++static int ++do_test (void) ++{ ++/* Pathname of the directory that receives the shared objects this ++ test attempts to load. */ ++ char *libdir = support_create_temp_directory ("tst-dlopen-sgid-"); ++ ++ /* This is supposed to be ignored and stripped. */ ++ TEST_COMPARE (setenv ("LD_LIBRARY_PATH", libdir, 1), 0); ++ ++ /* Copy of libc.so.6. */ ++ { ++ char *from = xasprintf ("%s/%s", support_objdir_root, LIBC_SO); ++ char *to = xasprintf ("%s/%s", libdir, LIBC_SO); ++ add_temp_file (to); ++ support_copy_file (from, to); ++ free (to); ++ free (from); ++ } ++ ++ /* Copy of the test object. */ ++ { ++ char *from = xasprintf ("%s/elf/%s", support_objdir_root, dso_name); ++ char *to = xasprintf ("%s/%s", libdir, dso_name); ++ add_temp_file (to); ++ support_copy_file (from, to); ++ free (to); ++ free (from); ++ } ++ ++ TEST_COMPARE (support_capture_subprogram_self_sgid (magic_argument), 0); ++ ++ free (libdir); ++ ++ return 0; ++} ++ ++static void ++alternative_main (int argc, char **argv) ++{ ++ if (argc == 2 && strcmp (argv[1], magic_argument) == 0) ++ { ++ if (getgid () == getegid ()) ++ /* This can happen if the file system is mounted nosuid. */ ++ FAIL_UNSUPPORTED ("SGID failed: GID and EGID match (%jd)\n", ++ (intmax_t) getgid ()); ++ ++ /* Should be removed due to SGID. */ ++ TEST_COMPARE_STRING (getenv ("LD_LIBRARY_PATH"), NULL); ++ ++ TEST_VERIFY (dlopen (dso_name, RTLD_NOW) == NULL); ++ { ++ const char *message = dlerror (); ++ TEST_COMPARE_STRING (message, ++ "tst-dlopen-sgid-mod.so:" ++ " cannot open shared object file:" ++ " No such file or directory"); ++ } ++ ++ support_record_failure_barrier (); ++ exit (EXIT_SUCCESS); ++ } ++} ++ ++#define PREPARE alternative_main ++#include diff --git a/glibc-RHEL-92697-5.patch b/glibc-RHEL-92697-5.patch new file mode 100644 index 0000000..78a1a20 --- /dev/null +++ b/glibc-RHEL-92697-5.patch @@ -0,0 +1,42 @@ +commit 35fc356fa3b4f485bd3ba3114c9f774e5df7d3c2 +Author: Florian Weimer +Date: Wed May 21 08:43:32 2025 +0200 + + elf: Fix subprocess status handling for tst-dlopen-sgid (bug 32987) + + This should really move into support_capture_subprogram_self_sgid. + + Reviewed-by: Sam James + +diff --git a/elf/tst-dlopen-sgid.c b/elf/tst-dlopen-sgid.c +index 47829a405e90b6b9..5688b79f2e870b1d 100644 +--- a/elf/tst-dlopen-sgid.c ++++ b/elf/tst-dlopen-sgid.c +@@ -26,6 +26,8 @@ + #include + #include + #include ++#include ++#include + #include + + /* This is the name of our test object. Use a custom module for +@@ -66,10 +68,16 @@ do_test (void) + free (from); + } + +- TEST_COMPARE (support_capture_subprogram_self_sgid (magic_argument), 0); +- + free (libdir); + ++ int status = support_capture_subprogram_self_sgid (magic_argument); ++ ++ if (WEXITSTATUS (status) == EXIT_UNSUPPORTED) ++ return EXIT_UNSUPPORTED; ++ ++ if (!WIFEXITED (status)) ++ FAIL_EXIT1 ("Unexpected exit status %d from child process\n", status); ++ + return 0; + } + diff --git a/glibc-RHEL-92697-6.patch b/glibc-RHEL-92697-6.patch new file mode 100644 index 0000000..e67ad64 --- /dev/null +++ b/glibc-RHEL-92697-6.patch @@ -0,0 +1,105 @@ +commit 2f769cec448d84a62b7dd0d4ff56978fe22c0cd6 +Author: Florian Weimer +Date: Wed May 21 16:47:34 2025 +0200 + + support: Pick group in support_capture_subprogram_self_sgid if UID == 0 + + When running as root, it is likely that we can run under any group. + Pick a harmless group from /etc/group in this case. + + Reviewed-by: Carlos O'Donell + +diff --git a/support/support_capture_subprocess.c b/support/support_capture_subprocess.c +index 6e6ac8bbb7f64859..755ee13553b3d253 100644 +--- a/support/support_capture_subprocess.c ++++ b/support/support_capture_subprocess.c +@@ -21,7 +21,11 @@ + + #include + #include ++#include ++#include ++#include + #include ++#include + #include + #include + #include +@@ -198,10 +202,48 @@ err: + return status; + } + ++/* Returns true if a group with NAME has been found, and writes its ++ GID to *TARGET. */ ++static bool ++find_sgid_group (gid_t *target, const char *name) ++{ ++ /* Do not use getgrname_r because it does not work in statically ++ linked binaries if the system libc is different. */ ++ FILE *fp = fopen ("/etc/group", "rce"); ++ if (fp == NULL) ++ return false; ++ __fsetlocking (fp, FSETLOCKING_BYCALLER); ++ ++ bool ok = false; ++ struct scratch_buffer buf; ++ scratch_buffer_init (&buf); ++ while (true) ++ { ++ struct group grp; ++ struct group *result = NULL; ++ int status = fgetgrent_r (fp, &grp, buf.data, buf.length, &result); ++ if (status == 0 && result != NULL) ++ { ++ if (strcmp (result->gr_name, name) == 0) ++ { ++ *target = result->gr_gid; ++ ok = true; ++ break; ++ } ++ } ++ else if (errno != ERANGE) ++ break; ++ else if (!scratch_buffer_grow (&buf)) ++ break; ++ } ++ scratch_buffer_free (&buf); ++ fclose (fp); ++ return ok; ++} ++ + int + support_capture_subprogram_self_sgid (const char *child_id) + { +- gid_t target = 0; + const int count = 64; + gid_t groups[count]; + +@@ -213,6 +255,7 @@ support_capture_subprogram_self_sgid (const char *child_id) + (intmax_t) getuid ()); + + gid_t current = getgid (); ++ gid_t target = current; + for (int i = 0; i < ret; ++i) + { + if (groups[i] != current) +@@ -222,9 +265,16 @@ support_capture_subprogram_self_sgid (const char *child_id) + } + } + +- if (target == 0) +- FAIL_UNSUPPORTED("Could not find a suitable GID for user %jd\n", +- (intmax_t) getuid ()); ++ if (target == current) ++ { ++ /* If running as root, try to find a harmless group for SGID. */ ++ if (getuid () != 0 ++ || (!find_sgid_group (&target, "nogroup") ++ && !find_sgid_group (&target, "bin") ++ && !find_sgid_group (&target, "daemon"))) ++ FAIL_UNSUPPORTED("Could not find a suitable GID for user %jd\n", ++ (intmax_t) getuid ()); ++ } + + return copy_and_spawn_sgid (child_id, target); + } diff --git a/glibc.spec b/glibc.spec index 4f5c1dc..926450e 100644 --- a/glibc.spec +++ b/glibc.spec @@ -157,7 +157,7 @@ end \ Summary: The GNU libc libraries Name: glibc Version: %{glibcversion} -Release: 194%{?dist} +Release: 195%{?dist} # In general, GPLv2+ is used by programs, LGPLv2+ is used for # libraries. @@ -1205,6 +1205,12 @@ Patch897: glibc-RHEL-61558-1.patch Patch898: glibc-RHEL-61558-2.patch Patch899: glibc-RHEL-61558-3.patch Patch900: glibc-RHEL-61558-4.patch +Patch901: glibc-RHEL-92697-1.patch +Patch902: glibc-RHEL-92697-2.patch +Patch903: glibc-RHEL-92697-3.patch +Patch904: glibc-RHEL-92697-4.patch +Patch905: glibc-RHEL-92697-5.patch +Patch906: glibc-RHEL-92697-6.patch ############################################################################## # Continued list of core "glibc" package information: @@ -3198,6 +3204,9 @@ update_gconv_modules_cache () %endif %changelog +* Wed May 21 2025 Florian Weimer - 2.34-195 +- CVE-2025-4802: static setuid dlopen may search LD_LIBRARY_PATH (RHEL-92697) + * Mon May 19 2025 DJ Delorie - 2.34-194 - manual: Improve the documentation of the Thread APIs (RHEL-61558)