import CS glibc-2.34-276.el9

This commit is contained in:
AlmaLinux RelEng Bot 2026-08-24 09:51:50 -04:00
parent e0b67080b6
commit 4c2db18501
54 changed files with 9445 additions and 2 deletions

View File

@ -0,0 +1,24 @@
commit fc72b6d7d818ab2868920af956d1542d03342a4d
Author: Andreas Schwab <schwab@suse.de>
Date: Tue Aug 1 17:01:37 2023 +0200
iconv: restore verbosity with unrecognized encoding names (bug 30694)
Commit 91927b7c76 ("Rewrite iconv option parsing [BZ #19519]") changed the
iconv program to call __gconv_open directly instead of the iconv_open
wrapper, but the former does not set errno. Update the caller to
interpret the return codes like iconv_open does.
diff --git a/iconv/iconv_prog.c b/iconv/iconv_prog.c
index 08ea99d6adf6ea86..f199db6020284ad1 100644
--- a/iconv/iconv_prog.c
+++ b/iconv/iconv_prog.c
@@ -201,7 +201,7 @@ main (int argc, char *argv[])
if (res != __GCONV_OK)
{
- if (errno == EINVAL)
+ if (res == __GCONV_NOCONV || res == __GCONV_NODB)
{
/* Try to be nice with the user and tell her which of the
two encoding names is wrong. This is possible because

View File

@ -0,0 +1,59 @@
commit 559010e471acb3cb292615b71b248aba73e5c2fe
Author: Dragan Stanojević (Nevidljivi) <neville.ravenwood@gmail.com>
Date: Wed Feb 7 16:31:04 2024 +0100
localedata: hr_HR: change currency to EUR/€
Resolves: BZ # 29845
Conflicts:
localedata/locales/hr_HR
(locale definitions do not use UTF-8 downstream)
diff --git a/localedata/locales/hr_HR b/localedata/locales/hr_HR
index 029a3794e2a17dcb..a13370461eede9c1 100644
--- a/localedata/locales/hr_HR
+++ b/localedata/locales/hr_HR
@@ -24,8 +24,8 @@ tel ""
fax ""
language "Croatian"
territory "Croatia"
-revision "2.3"
-date "2016-04-16"
+revision "2.4"
+date "2022-12-03"
category "i18n:2012";LC_IDENTIFICATION
category "i18n:2012";LC_CTYPE
@@ -171,8 +171,8 @@ nostr "ne"
END LC_MESSAGES
LC_MONETARY
-int_curr_symbol "HRK "
-currency_symbol "kn"
+int_curr_symbol "EUR "
+currency_symbol "<U20AC>"
mon_decimal_point ","
mon_thousands_sep "."
mon_grouping 3;3
diff --git a/stdlib/tst-strfmon_l.c b/stdlib/tst-strfmon_l.c
index 603d4b0c3d91818b..65313cd14b61dfe4 100644
--- a/stdlib/tst-strfmon_l.c
+++ b/stdlib/tst-strfmon_l.c
@@ -181,12 +181,12 @@ static const struct locale_pair tests[] =
"hr_HR.UTF-8",
{
{
- "HRK 1.234.567,89", "1.234.567,89 kn",
- "HRK 1234567,89", "1234567,89 kn"
+ "EUR 1.234.567,89", "1.234.567,89 €",
+ "EUR 1234567,89", "1234567,89 €"
},
{
- "-HRK 1.234.567,89", "-1.234.567,89 kn",
- "-HRK 1234567,89", "-1234567,89 kn"
+ "-EUR 1.234.567,89", "-1.234.567,89 €",
+ "-EUR 1234567,89", "-1234567,89 €"
}
}
},

View File

@ -0,0 +1,46 @@
commit 63716823dbad9482e09972907ae98e9cb00f9b86
Author: Sunil K Pandey <sunil.k.pandey@intel.com>
Date: Tue Dec 9 08:57:44 2025 -0800
nptl: Optimize trylock for high cache contention workloads (BZ #33704)
Check lock availability before acquisition to reduce cache line
bouncing. Significantly improves trylock throughput on multi-core
systems under heavy contention.
Tested on x86_64.
Fixes BZ #33704.
Co-authored-by: Alex M Wells <alex.m.wells@intel.com>
Reviewed-by: Wilco Dijkstra <Wilco.Dijkstra@arm.com>
Conflicts:
nptl/pthread_mutex_trylock.c
Updated for minor context difference
diff -Nrup a/nptl/pthread_mutex_trylock.c b/nptl/pthread_mutex_trylock.c
--- a/nptl/pthread_mutex_trylock.c 2021-08-01 21:33:43.000000000 -0400
+++ b/nptl/pthread_mutex_trylock.c 2026-02-06 13:29:01.508899356 -0500
@@ -49,7 +49,8 @@ ___pthread_mutex_trylock (pthread_mutex_
return 0;
}
- if (lll_trylock (mutex->__data.__lock) == 0)
+ if (atomic_load_relaxed (&(mutex->__data.__lock)) == 0
+ && lll_trylock (mutex->__data.__lock) == 0)
{
/* Record the ownership. */
mutex->__data.__owner = id;
@@ -72,7 +73,10 @@ ___pthread_mutex_trylock (pthread_mutex_
/*FALL THROUGH*/
case PTHREAD_MUTEX_ADAPTIVE_NP:
case PTHREAD_MUTEX_ERRORCHECK_NP:
- if (lll_trylock (mutex->__data.__lock) != 0)
+ /* Mutex type is already loaded, lock check overhead should
+ be minimal. */
+ if (atomic_load_relaxed (&(mutex->__data.__lock)) != 0
+ || lll_trylock (mutex->__data.__lock) != 0)
break;
/* Record the ownership. */

View File

@ -0,0 +1,52 @@
commit 4a133885a7c8ae7ebe34e36fcdb353f8e94c810f
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Mon Nov 6 17:25:45 2023 -0300
elf: Ignore LD_PROFILE for setuid binaries
Loader does not ignore LD_PROFILE in secure-execution mode (different
than man-page states [1]), rather it uses a different path
(/var/profile) and ignore LD_PROFILE_OUTPUT.
Allowing secure-execution profiling is already a non good security
boundary, since it enables different code paths and extra OS access by
the process. But by ignoring LD_PROFILE_OUTPUT, the resulting profile
file might also be acceded in a racy manner since the file name does not
use any process-specific information (such as pid, timing, etc.).
Another side-effect is it forces lazy binding even on libraries that
might be with DF_BIND_NOW.
[1] https://man7.org/linux/man-pages/man8/ld.so.8.html
Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
Conflicts:
elf/Makefile
elf/tst-env-setuid.c
(test does not have LD_PROFILE support downstream)
diff --git a/elf/rtld.c b/elf/rtld.c
index 1fbfc65a38e19110..44546e8e158e7b9c 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -2761,10 +2761,6 @@ process_envvars (struct dl_main_state *state)
char *envline;
char *debug_output = NULL;
- /* This is the default place for profiling data file. */
- GLRO(dl_profile_output)
- = &"/var/tmp\0/var/profile"[__libc_enable_secure ? 9 : 0];
-
while ((envline = _dl_next_ld_env_entry (&runp)) != NULL)
{
size_t len = 0;
@@ -2813,7 +2809,8 @@ process_envvars (struct dl_main_state *state)
}
/* Which shared object shall be profiled. */
- if (memcmp (envline, "PROFILE", 7) == 0 && envline[8] != '\0')
+ if (!__libc_enable_secure
+ && memcmp (envline, "PROFILE", 7) == 0 && envline[8] != '\0')
GLRO(dl_profile) = &envline[8];
break;

View File

@ -0,0 +1,243 @@
commit 1e1ad714ee9a663eda0e2bffad1d9f258b00a4e9
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Mon May 6 13:18:47 2024 -0300
support: Add envp argument to support_capture_subprogram
So tests can specify a list of environment variables.
Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
Conflicts:
elf/tst-tunables.c
elf/tst-tunables-enable_secure.c
sysdeps/x86/tst-hwcap-tunables.c
(files do not exist downstream)
diff --git a/elf/tst-audit18.c b/elf/tst-audit18.c
index 841251dd7003aa7d..cec93e269ca0b4ef 100644
--- a/elf/tst-audit18.c
+++ b/elf/tst-audit18.c
@@ -79,7 +79,7 @@ do_test (int argc, char *argv[])
setenv ("LD_AUDIT", "tst-auditmod18.so", 0);
struct support_capture_subprocess result
- = support_capture_subprogram (spargv[0], spargv);
+ = support_capture_subprogram (spargv[0], spargv, NULL);
support_capture_subprocess_check (&result, "tst-audit18", 0, sc_allow_stderr);
struct
diff --git a/elf/tst-audit19b.c b/elf/tst-audit19b.c
index 70bfe4eadf5ee845..88d99a416bbe93dd 100644
--- a/elf/tst-audit19b.c
+++ b/elf/tst-audit19b.c
@@ -69,7 +69,7 @@ do_test (int argc, char *argv[])
setenv ("LD_AUDIT", "tst-auditmod18b.so", 0);
struct support_capture_subprocess result
- = support_capture_subprogram (spargv[0], spargv);
+ = support_capture_subprogram (spargv[0], spargv, NULL);
support_capture_subprocess_check (&result, "tst-audit18b", 0, sc_allow_stderr);
bool find_symbind = false;
diff --git a/elf/tst-audit22.c b/elf/tst-audit22.c
index 4e97be3be0c6a2e3..6aa18af948afa9c5 100644
--- a/elf/tst-audit22.c
+++ b/elf/tst-audit22.c
@@ -83,7 +83,7 @@ do_test (int argc, char *argv[])
setenv ("LD_AUDIT", "tst-auditmod22.so", 0);
struct support_capture_subprocess result
- = support_capture_subprogram (spargv[0], spargv);
+ = support_capture_subprogram (spargv[0], spargv, NULL);
support_capture_subprocess_check (&result, "tst-audit22", 0, sc_allow_stderr);
/* The respawned process should always print the vDSO address (otherwise it
diff --git a/elf/tst-audit23.c b/elf/tst-audit23.c
index 1b76336595fcd301..7786a74e648aeb6f 100644
--- a/elf/tst-audit23.c
+++ b/elf/tst-audit23.c
@@ -87,7 +87,7 @@ do_one_test (int argc, char *argv[], bool pass_dlclose_flag)
setenv ("LD_AUDIT", "tst-auditmod23.so", 0);
struct support_capture_subprocess result
- = support_capture_subprogram (spargv[0], spargv);
+ = support_capture_subprogram (spargv[0], spargv, NULL);
support_capture_subprocess_check (&result, "tst-audit22", 0, sc_allow_stderr);
{
diff --git a/elf/tst-audit25a.c b/elf/tst-audit25a.c
index b209ee820f2f2a02..cdd4f2ce2b54d622 100644
--- a/elf/tst-audit25a.c
+++ b/elf/tst-audit25a.c
@@ -77,7 +77,7 @@ do_test (int argc, char *argv[])
{
struct support_capture_subprocess result
- = support_capture_subprogram (spargv[0], spargv);
+ = support_capture_subprogram (spargv[0], spargv, NULL);
support_capture_subprocess_check (&result, "tst-audit25a", 0,
sc_allow_stderr);
@@ -102,7 +102,7 @@ do_test (int argc, char *argv[])
{
setenv ("LD_BIND_NOW", "1", 0);
struct support_capture_subprocess result
- = support_capture_subprogram (spargv[0], spargv);
+ = support_capture_subprogram (spargv[0], spargv, NULL);
support_capture_subprocess_check (&result, "tst-audit25a", 0,
sc_allow_stderr);
diff --git a/elf/tst-audit25b.c b/elf/tst-audit25b.c
index 9b8665d5171b7c6b..939f4d6188368540 100644
--- a/elf/tst-audit25b.c
+++ b/elf/tst-audit25b.c
@@ -76,7 +76,7 @@ do_test (int argc, char *argv[])
{
struct support_capture_subprocess result
- = support_capture_subprogram (spargv[0], spargv);
+ = support_capture_subprogram (spargv[0], spargv, NULL);
support_capture_subprocess_check (&result, "tst-audit25a", 0,
sc_allow_stderr);
@@ -102,7 +102,7 @@ do_test (int argc, char *argv[])
{
setenv ("LD_BIND_NOW", "1", 0);
struct support_capture_subprocess result
- = support_capture_subprogram (spargv[0], spargv);
+ = support_capture_subprogram (spargv[0], spargv, NULL);
support_capture_subprocess_check (&result, "tst-audit25a", 0,
sc_allow_stderr);
diff --git a/elf/tst-glibc-hwcaps-2-cache.c b/elf/tst-glibc-hwcaps-2-cache.c
index 81ab44ff78ddbb57..af91476ccafeecff 100644
--- a/elf/tst-glibc-hwcaps-2-cache.c
+++ b/elf/tst-glibc-hwcaps-2-cache.c
@@ -32,7 +32,7 @@ main (int argc, char **argv)
/* Run ldconfig to populate the cache. */
char *command = xasprintf ("%s/ldconfig", support_install_rootsbindir);
struct support_capture_subprocess result =
- support_capture_subprogram (command, &((char *) { NULL }));
+ support_capture_subprogram (command, &((char *) { NULL }), NULL);
support_capture_subprocess_check (&result, "ldconfig", 0, sc_allow_none);
free (command);
diff --git a/elf/tst-rtld-run-static.c b/elf/tst-rtld-run-static.c
index b2650e85ffbcdc20..f05c00eb7b76958b 100644
--- a/elf/tst-rtld-run-static.c
+++ b/elf/tst-rtld-run-static.c
@@ -30,7 +30,7 @@ do_test (void)
{
char *argv[] = { (char *) "ld.so", ldconfig_path, (char *) "--help", NULL };
struct support_capture_subprocess cap
- = support_capture_subprogram (support_objdir_elf_ldso, argv);
+ = support_capture_subprogram (support_objdir_elf_ldso, argv, NULL);
support_capture_subprocess_check (&cap, "no --argv0", 0, sc_allow_stdout);
puts ("info: output without --argv0:");
puts (cap.out.buffer);
@@ -46,7 +46,7 @@ do_test (void)
ldconfig_path, (char *) "--help", NULL
};
struct support_capture_subprocess cap
- = support_capture_subprogram (support_objdir_elf_ldso, argv);
+ = support_capture_subprogram (support_objdir_elf_ldso, argv, NULL);
support_capture_subprocess_check (&cap, "with --argv0", 0, sc_allow_stdout);
puts ("info: output with --argv0:");
puts (cap.out.buffer);
diff --git a/support/capture_subprocess.h b/support/capture_subprocess.h
index 8cbdca3b9dfb41ba..57bb941e7d1e5c84 100644
--- a/support/capture_subprocess.h
+++ b/support/capture_subprocess.h
@@ -35,11 +35,12 @@ struct support_capture_subprocess
struct support_capture_subprocess support_capture_subprocess
(void (*callback) (void *), void *closure);
-/* Issue FILE with ARGV arguments by using posix_spawn and capture standard
- output, standard error, and the exit status. The out.buffer and err.buffer
- are handle as support_capture_subprocess. */
+/* Issue FILE with ARGV arguments and ENVP environments by using posix_spawn
+ and capture standard output, standard error, and the exit status. If
+ ENVP is NULL the current environment variable is used. The out.buffer and
+ err.buffer are handle by support_capture_subprocess. */
struct support_capture_subprocess support_capture_subprogram
- (const char *file, char *const argv[]);
+ (const char *file, char *const argv[], char *const envp[]);
/* Copy the running program into a setgid binary and run it with
CHILD_ID argument. If the program exits with a non-zero status,
diff --git a/support/subprocess.h b/support/subprocess.h
index 8fbb895353d61965..8274a2b22bb10296 100644
--- a/support/subprocess.h
+++ b/support/subprocess.h
@@ -33,10 +33,11 @@ struct support_subprocess
struct support_subprocess support_subprocess
(void (*callback) (void *), void *closure);
-/* Issue FILE with ARGV arguments by using posix_spawn and return is PID, a
- pipe redirected to STDOUT, and a pipe redirected to STDERR. */
+/* Issue FILE with ARGV arguments and ENVP environments by using posix_spawn
+ and return is PID, a pipe redirected to STDOUT, and a pipe redirected to
+ STDERR. If ENVP is NULL the current environment variable is used. */
struct support_subprocess support_subprogram
- (const char *file, char *const argv[]);
+ (const char *file, char *const argv[], char *const envp[]);
/* Invoke program FILE with ARGV arguments by using posix_spawn and wait for it
to complete. Return program exit status. */
diff --git a/support/support_capture_subprocess.c b/support/support_capture_subprocess.c
index 8dc95f8aa723b6bc..cbc695106483ab54 100644
--- a/support/support_capture_subprocess.c
+++ b/support/support_capture_subprocess.c
@@ -98,13 +98,14 @@ support_capture_subprocess (void (*callback) (void *), void *closure)
}
struct support_capture_subprocess
-support_capture_subprogram (const char *file, char *const argv[])
+support_capture_subprogram (const char *file, char *const argv[],
+ char *const envp[])
{
struct support_capture_subprocess result;
xopen_memstream (&result.out);
xopen_memstream (&result.err);
- struct support_subprocess proc = support_subprogram (file, argv);
+ struct support_subprocess proc = support_subprogram (file, argv, envp);
support_capture_poll (&result, &proc);
return result;
diff --git a/support/support_subprocess.c b/support/support_subprocess.c
index a2fef394d42ea4f9..b692a7f8b178502d 100644
--- a/support/support_subprocess.c
+++ b/support/support_subprocess.c
@@ -69,7 +69,7 @@ support_subprocess (void (*callback) (void *), void *closure)
}
struct support_subprocess
-support_subprogram (const char *file, char *const argv[])
+support_subprogram (const char *file, char *const argv[], char *const envp[])
{
struct support_subprocess result = support_subprocess_init ();
@@ -84,7 +84,8 @@ support_subprogram (const char *file, char *const argv[])
xposix_spawn_file_actions_addclose (&fa, result.stdout_pipe[1]);
xposix_spawn_file_actions_addclose (&fa, result.stderr_pipe[1]);
- result.pid = xposix_spawn (file, &fa, NULL, argv, environ);
+ result.pid = xposix_spawn (file, &fa, NULL, argv,
+ envp == NULL ? environ : envp);
xclose (result.stdout_pipe[1]);
xclose (result.stderr_pipe[1]);
diff --git a/support/tst-support_capture_subprocess.c b/support/tst-support_capture_subprocess.c
index 8145548982a935cb..756fb75d195cdf8a 100644
--- a/support/tst-support_capture_subprocess.c
+++ b/support/tst-support_capture_subprocess.c
@@ -238,7 +238,7 @@ do_subprogram (const struct test *test)
args[argc] = NULL;
TEST_VERIFY (argc < argv_size);
- return support_capture_subprogram (args[0], args);
+ return support_capture_subprogram (args[0], args, NULL);
}
enum test_type

View File

@ -0,0 +1,56 @@
commit 7b543dcdf97d07fd4346feb17916e08fe83ad0ae
Author: Florian Weimer <fweimer@redhat.com>
Date: Thu Jan 15 22:29:46 2026 +0100
elf: Ignore LD_PROFILE if LD_PROFILE_OUTPUT is not set (bug 33797)
The previous default for LD_PROFILE_OUTPUT, /var/tmp, is insecure
because it's typically a 1777 directory, and other systems could
place malicious files there which interfere with execution.
Requiring the user to specify a profiling directory mitigates
the impact of bug 33797. Clear LD_PROFILE_OUTPUT alongside
with LD_PROFILE.
Rework the test not to use predictable file names.
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Conflicts:
elf/rtld.c
(different implementation of environment variable filtering
downstream)
elf/tst-env-setuid.c
(no LD_PROFILE test downstream)
diff -Nrup a/elf/rtld.c b/elf/rtld.c
--- a/elf/rtld.c 2026-02-25 14:11:17.505620756 -0500
+++ b/elf/rtld.c 2026-02-25 14:14:43.355916730 -0500
@@ -2974,6 +2974,15 @@ process_envvars (struct dl_main_state *s
/* We use standard output if opening the file failed. */
GLRO(dl_debug_fd) = STDOUT_FILENO;
}
+
+ /* There is no fixed, safe directory to store profiling data, so
+ activate LD_PROFILE only if LD_PROFILE_OUTPUT is set as well. */
+ if (GLRO(dl_profile) != NULL && GLRO(dl_profile_output) == NULL)
+ {
+ _dl_error_printf ("\
+warning: LD_PROFILE ignored because LD_PROFILE_OUTPUT not specified\n");
+ GLRO(dl_profile) = NULL;
+ }
}
#if HP_TIMING_INLINE
diff --git a/sysdeps/generic/unsecvars.h b/sysdeps/generic/unsecvars.h
index 5ea8a4a259ef753c..0b84642f71ae9351 100644
--- a/sysdeps/generic/unsecvars.h
+++ b/sysdeps/generic/unsecvars.h
@@ -21,6 +21,7 @@
"LD_ORIGIN_PATH\0" \
"LD_PRELOAD\0" \
"LD_PROFILE\0" \
+ "LD_PROFILE_OUTPUT\0" \
"LD_SHOW_AUXV\0" \
"LD_USE_LOAD_BIAS\0" \
"LOCALDOMAIN\0" \

View File

@ -0,0 +1,28 @@
commit 34d98aea6c1eaf7750a992bae55b2bca24898eab
Author: Florian Weimer <fweimer@redhat.com>
Date: Sat Jan 24 10:29:22 2026 +0100
support: Fix memory leaks in support_subprogram, support_subprogram_wait
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/support/support_subprocess.c b/support/support_subprocess.c
index b692a7f8b178502d..ec0c069470a586ac 100644
--- a/support/support_subprocess.c
+++ b/support/support_subprocess.c
@@ -86,6 +86,7 @@ support_subprogram (const char *file, char *const argv[], char *const envp[])
result.pid = xposix_spawn (file, &fa, NULL, argv,
envp == NULL ? environ : envp);
+ posix_spawn_file_actions_destroy (&fa);
xclose (result.stdout_pipe[1]);
xclose (result.stderr_pipe[1]);
@@ -102,6 +103,7 @@ support_subprogram_wait (const char *file, char *const argv[])
struct support_subprocess res = support_subprocess_init ();
res.pid = xposix_spawn (file, &fa, NULL, argv, environ);
+ posix_spawn_file_actions_destroy (&fa);
return support_process_wait (&res);
}

View File

@ -0,0 +1,49 @@
commit e8502182f09211663c1583960442eb6ff502a33e
Author: Florian Weimer <fweimer@redhat.com>
Date: Sat Jan 24 10:29:22 2026 +0100
support: Add support_hardcoded_paths_in_test
It indicates whether glibc was configured with
--enable-hardcoded-path-in-tests.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff -Nrup a/support/Makefile b/support/Makefile
--- a/support/Makefile 2026-02-23 07:44:09.189237390 -0500
+++ b/support/Makefile 2026-02-23 07:49:33.975521231 -0500
@@ -252,6 +252,12 @@ CFLAGS-support_paths.c = \
-DROOTSBINDIR_PATH=\"$(rootsbindir)\" \
-DCOMPLOCALEDIR_PATH=\"$(complocaledir)\"
+ifeq ($(build-hardcoded-path-in-tests),yes)
+CFLAGS-support_paths.c += -DHARDCODED_PATHS_IN_TEST=true
+else
+CFLAGS-support_paths.c += -DHARDCODED_PATHS_IN_TEST=false
+endif
+
# In support_timespec_check_in_range we may be passed a very tight
# range for which we should produce a correct result for expected
# being withing the observed range. The code uses double internally
diff -Nrup a/support/support.h b/support/support.h
--- a/support/support.h 2026-02-23 07:44:09.127896391 -0500
+++ b/support/support.h 2026-02-23 07:52:57.800614726 -0500
@@ -144,6 +144,9 @@ extern const char support_install_rootsb
/* Corresponds to the install's compiled locale directory. */
extern const char support_complocaledir_prefix[];
+/* If true, glibc was configured with --enable-hardcoded-path-in-tests. */
+extern const bool support_hardcoded_paths_in_test;
+
/* Copies the file at the path FROM to TO. If TO does not exist, it
is created. If TO is a regular file, it is truncated before
copying. The file mode is copied, but the permissions are not. */
diff -Nrup a/support/support_paths.c b/support/support_paths.c
--- a/support/support_paths.c 2021-08-01 21:33:43.000000000 -0400
+++ b/support/support_paths.c 2026-02-23 07:56:12.231942401 -0500
@@ -92,3 +92,5 @@ const char support_complocaledir_prefix[
#else
# error please -DCOMPLOCALEDIR_PATH=something in the Makefile
#endif
+
+const bool support_hardcoded_paths_in_test = HARDCODED_PATHS_IN_TEST;

View File

@ -0,0 +1,40 @@
commit 458a6a2b935f60a25a136846fe8b7a4723296dda
Author: Florian Weimer <fweimer@redhat.com>
Date: Sat Jan 24 10:29:39 2026 +0100
support: Reinitialize containers if /etc is present
This prevents test failures because configuration file leftovers
unexpectedly change glibc for future tests. Whether this
triggers depends on test execution order.
Adding postclean.req files manually (before this change) appears
too error-prone.
Reviewed-by: DJ Delorie <dj@redhat.com>
diff --git a/support/test-container.c b/support/test-container.c
index a9c9926c21f3a21e..f2853f6a71475420 100644
--- a/support/test-container.c
+++ b/support/test-container.c
@@ -134,7 +134,9 @@ int verbose = 0;
- 'mkdirp': A minimal "mkdir -p FILE" command.
* mytest.root/postclean.req causes fresh rsync (with delete) after
- test if present
+ test if present. If /etc is present, the testroot is cleaned,
+ too. This prevents further tests from using special
+ configurations in /etc from previous tests.
* mytest.root/ldconfig.run causes ldconfig to be issued prior
test execution (to setup the initial ld.so.cache).
@@ -868,7 +870,8 @@ main (int argc, char **argv)
if (strrchr (so_base, '/') != NULL)
strrchr (so_base, '/')[1] = 0;
- if (file_exists (concat (command_root, "/postclean.req", NULL)))
+ if (file_exists (concat (command_root, "/postclean.req", NULL))
+ || file_exists (concat (command_root, "/etc", NULL)))
do_postclean = 1;
if (file_exists (concat (command_root, "/ldconfig.run", NULL)))

View File

@ -0,0 +1,505 @@
commit 229f65f5f322609283c7104c80c8af6434dff628
Author: Florian Weimer <fweimer@redhat.com>
Date: Mon Feb 2 21:15:48 2026 +0100
support: Add support_spawn_wrap and related functionality
It allows us to write test cases in C that run tests with
dynamic linker wrapping.
The iconv test case was auto-generated. The posix_spawn usage
is mechanical, and the interface it tests is newly added in this
commit, so this should be acceptable.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Conflicts:
support/Makefile
(Remove duplicate entry for support_stack_alloc)
diff -Nrup a/support/Makefile b/support/Makefile
--- a/support/Makefile 2026-02-23 11:21:06.705964233 -0500
+++ b/support/Makefile 2026-02-23 11:46:54.560737380 -0500
@@ -90,7 +90,7 @@ libsupport-routines = \
support_shared_allocate \
support_small_stack_thread_attribute \
support_socket_so_timestamp_time64 \
- support_stack_alloc \
+ support_spawn_wrap \
support_stack_alloc \
support_stat_nanoseconds \
support_subprocess \
@@ -258,6 +258,11 @@ else
CFLAGS-support_paths.c += -DHARDCODED_PATHS_IN_TEST=false
endif
+CFLAGS-support_spawn_wrap.c += \
+ '-DRUN_PROGRAM_ENV=$(patsubst %, ELEMENT ("%"), $(run-program-env))' \
+ '-DRTLD_PREFIX=$(patsubst %, ELEMENT ("%"), $(rtld-prefix))' \
+ # CFLAGS-support_spawn_wrap.c
+
# In support_timespec_check_in_range we may be passed a very tight
# range for which we should produce a correct result for expected
# being withing the observed range. The code uses double internally
@@ -343,6 +348,7 @@ tests = \
tst-support_quote_string \
tst-support_readdir \
tst-support_record_failure \
+ tst-support_spawn_wrap \
tst-test_compare \
tst-test_compare_blob \
tst-test_compare_string \
diff -Nrup a/support/subprocess.h b/support/subprocess.h
--- a/support/subprocess.h 2021-08-01 21:33:43.000000000 -0400
+++ b/support/subprocess.h 2026-02-23 11:22:26.578465391 -0500
@@ -51,4 +51,36 @@ int support_process_wait (struct support
then with a SIGKILL. Return the status as for waitpid call. */
int support_process_terminate (struct support_subprocess *proc);
+/* Arguments to pass to posix_spawn and related functions to run a
+ process under the built glibc. This overrides the dynamic linker,
+ its search path, and other search paths, such as for locales. */
+struct support_spawn_wrapped
+{
+ const char *path;
+ char *const *argv;
+ char *const *envp;
+};
+
+enum support_spawn_wrap_flags
+ {
+ /* Always wrap the invocation, even if test binaries are linked
+ with overridden the default paths to point into the build tree
+ (--enable-hardcoded-path-in-tests). Can be used to run
+ non-test binaries. */
+ support_spawn_wrap_force = 1 << 0,
+ };
+
+/* Wrap the invocation for invoking testing. PATH is the program
+ path. If ARGV is null, no arguments are passed. If ENVP is null,
+ environ is used instead. The result must not be modified. It is a
+ deep copy of the inputs. */
+struct support_spawn_wrapped *support_spawn_wrap (const char *path,
+ char *const argv[],
+ char *const envp[],
+ enum
+ support_spawn_wrap_flags);
+
+/* Deallocate the result of support_spawn_wrap. */
+void support_spawn_wrapped_free (struct support_spawn_wrapped *);
+
#endif
diff -Nrup a/support/support_spawn_wrap.c b/support/support_spawn_wrap.c
--- a/support/support_spawn_wrap.c 1969-12-31 19:00:00.000000000 -0500
+++ b/support/support_spawn_wrap.c 2026-02-23 11:22:26.578713214 -0500
@@ -0,0 +1,171 @@
+/* Wrap a subprocess invocation with an ld.so invocation.
+ Copyright (C) 2026 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <array_length.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/subprocess.h>
+#include <support/support.h>
+#include <unistd.h>
+
+#define ELEMENT(s) s,
+static const char *const rtld_prefix[] = { RTLD_PREFIX "--argv0" };
+static const char *const run_program_env[] = { RUN_PROGRAM_ENV };
+#undef ELEMENT
+
+/* Return a newly allocated argument vector, with ld.so wrapping per
+ rtld_prefix applied if WRAP is true. */
+static char *const *
+rewrite_argv (const char *path, char *const argv[], bool wrap)
+{
+ char *const substitute[] = { (char *) path, NULL};
+ if (argv == NULL)
+ argv = substitute;
+ TEST_VERIFY (argv[0] != NULL);
+
+ size_t length;
+ for (length = 0; argv[length] != 0; ++length)
+ ;
+ /* Potential wrapping, injected path, and null terminator. */
+ length += array_length (rtld_prefix) + 1 + 1;
+
+ char **result = xcalloc (length, sizeof (result));
+
+ size_t inpos = 0;
+ size_t outpos = 0;
+ if (wrap)
+ {
+ for (size_t i = 0; i < array_length (rtld_prefix); ++i)
+ {
+ TEST_VERIFY (outpos < length);
+ result[outpos++] = xstrdup (rtld_prefix[i]);
+ }
+
+ /* --argv0 argument. */
+ TEST_VERIFY (outpos < length);
+ result[outpos++] = xstrdup (argv[0]);
+ inpos = 1;
+
+ /* Path to program as used by ld.so. */
+ TEST_VERIFY (outpos < length);
+ result[outpos++] = xstrdup (path);
+ }
+
+ for (; argv[inpos] != NULL; ++inpos)
+ {
+ TEST_VERIFY (outpos < length);
+ result[outpos++] = xstrdup (argv[inpos]);
+ }
+
+ TEST_VERIFY (outpos < length);
+ return result;
+
+}
+
+/* Return a newly allocated, rewritten environment, with the settings
+ from run_program_env. */
+static char *const *
+rewrite_env (char *const envp[])
+{
+ if (envp == NULL)
+ envp = environ;
+
+ size_t length;
+ for (length = 0; envp[length] != 0; ++length)
+ ;
+ length += array_length (run_program_env) + 1;
+
+ /* Set to true if an element of run_program_env is copied. This is
+ used to avoid adding it again. */
+ bool copied[array_length (run_program_env)] = { false, };
+
+ char **result = xcalloc (length, sizeof (result));
+ size_t outpos = 0;
+ for (size_t inpos = 0; envp[inpos] != NULL; ++inpos)
+ {
+ const char *to_copy = envp[inpos];
+ /* If there is no assignment operator, this environment string
+ cannot be overridden. */
+ const char *envp_assign = strchr (to_copy, '=');
+ if (envp_assign != NULL)
+ {
+ size_t length_with_assign = envp_assign - to_copy + 1;
+ for (size_t i = 0; i < array_length (run_program_env); ++i)
+ {
+ if (strncmp (to_copy, run_program_env[i], length_with_assign)
+ == 0 && !copied[i])
+ {
+ to_copy = run_program_env[i];
+ copied[i] = true;
+ break;
+ }
+ }
+ }
+ TEST_VERIFY (outpos < length);
+ result[outpos++] = xstrdup (to_copy);
+ }
+
+ for (size_t i = 0; i < array_length (run_program_env); ++i)
+ {
+ TEST_VERIFY (strchr (run_program_env[i], '=') != 0);
+ if (!copied[i])
+ {
+ TEST_VERIFY (outpos < length);
+ result[outpos++] = xstrdup (run_program_env[i]);
+ }
+ }
+
+ TEST_VERIFY (outpos < length);
+ return result;
+}
+
+struct support_spawn_wrapped *
+support_spawn_wrap (const char *path,
+ char *const argv[],
+ char *const envp[],
+ enum support_spawn_wrap_flags flags)
+{
+ if (flags != 0)
+ TEST_COMPARE (flags, support_spawn_wrap_force);
+ bool force = flags & support_spawn_wrap_force;
+ bool wrap = force || !support_hardcoded_paths_in_test;
+
+ struct support_spawn_wrapped *result = xmalloc (sizeof (*result));
+ if (wrap)
+ result->path = xstrdup (support_objdir_elf_ldso);
+ else
+ result->path = xstrdup (path);
+ result->argv = rewrite_argv (path, argv, wrap);
+ result->envp = rewrite_env (envp);
+ return result;
+}
+
+void
+support_spawn_wrapped_free (struct support_spawn_wrapped *wrapped)
+{
+ free ((char *) wrapped->path);
+ for (size_t i = 0; wrapped->argv[i] != NULL; ++i)
+ free (wrapped->argv[i]);
+ free ((char **) wrapped->argv);
+ for (size_t i = 0; wrapped->envp[i] != NULL; ++i)
+ free (wrapped->envp[i]);
+ free ((char **) wrapped->envp);
+ free (wrapped);
+}
diff -Nrup a/support/tst-support_spawn_wrap.c b/support/tst-support_spawn_wrap.c
--- a/support/tst-support_spawn_wrap.c 1969-12-31 19:00:00.000000000 -0500
+++ b/support/tst-support_spawn_wrap.c 2026-02-23 11:22:26.578942243 -0500
@@ -0,0 +1,235 @@
+/* Tests for support_spawn_wrap.
+ Copyright (C) 2026 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <getopt.h>
+#include <spawn.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/capture_subprocess.h>
+#include <support/check.h>
+#include <support/subprocess.h>
+#include <support/support.h>
+#include <support/xspawn.h>
+#include <support/xunistd.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+/* Return true if running via an explicit ld.so invocation. */
+static bool
+running_via_ldso (void)
+{
+ char *self = realpath ("/proc/self/exe", NULL);
+ if (self == NULL)
+ FAIL_UNSUPPORTED ("/proc/self/exe not available");
+ char *ldso = realpath (support_objdir_elf_ldso, NULL);
+ TEST_VERIFY_EXIT (ldso != NULL);
+ bool result = strcmp (self, ldso) == 0;
+ free (ldso);
+ free (self);
+ return result;
+}
+
+static void
+test_subprocess (int argc, char **argv)
+{
+ {
+ char *argc_env = getenv ("argc");
+ if (argc_env != NULL)
+ {
+ char *argc_arg = xasprintf ("%d", argc);
+ TEST_COMPARE_STRING (argc_arg, argc_env);
+ free (argc_arg);
+ }
+ }
+
+ if (argc >= 2 && strcmp (argv[1], "alpha") == 0)
+ {
+ TEST_COMPARE (argc, 4);
+ TEST_COMPARE_STRING (argv[0], "program");
+ TEST_COMPARE_STRING (argv[2], "beta");
+ TEST_COMPARE_STRING (argv[3], "gamma");
+ TEST_COMPARE_STRING (argv[4], NULL);
+ }
+ else if (argc >= 2 && strcmp (argv[1], "check-env") == 0)
+ printf ("%d %s\n", argc, getenv ("extra"));
+ else if (argc >= 2 && strcmp (argv[1], "check-ld.so") == 0)
+ TEST_VERIFY (running_via_ldso ());
+}
+
+/* The "recurse" environment variable and the --recurse option
+ indicate recursive invocation. */
+static int flag_recurse;
+#define CMDLINE_OPTIONS \
+ { "recurse", no_argument, &flag_recurse, 1 }, \
+ /* CMDLINE_OPTION */
+
+static void
+prepare (int argc, char **argv)
+{
+ if (getenv ("recurse") != NULL || flag_recurse)
+ {
+ test_subprocess (argc, argv);
+ support_record_failure_barrier ();
+ exit (0);
+ }
+}
+
+#define PREPARE prepare
+
+/* Test wrapping of a non-test program (iconv). This uses posix_spawn
+ directly, mainly for illustrative purposes. */
+void
+test_iconv (void)
+{
+ char *iconv_prog = xasprintf ("%s/iconv/iconv_prog", support_objdir_root);
+ char *argv[] = { (char *) "iconv", (char *) "-f", (char *) "UTF-8",
+ (char *) "-t", (char *) "ISO-8859-1", NULL };
+ struct support_spawn_wrapped *w
+ = support_spawn_wrap (iconv_prog, argv, NULL, support_spawn_wrap_force);
+
+ /* Set up pipes for stdin, stdout, and stderr. */
+ int stdin_pipe[2];
+ xpipe (stdin_pipe);
+ int stdout_pipe[2];
+ xpipe (stdout_pipe);
+ int stderr_pipe[2];
+ xpipe (stderr_pipe);
+
+ posix_spawn_file_actions_t fa;
+ posix_spawn_file_actions_init (&fa);
+ xposix_spawn_file_actions_adddup2 (&fa, stdin_pipe[0], STDIN_FILENO);
+ xposix_spawn_file_actions_addclose (&fa, stdin_pipe[0]);
+ xposix_spawn_file_actions_addclose (&fa, stdin_pipe[1]);
+ xposix_spawn_file_actions_adddup2 (&fa, stdout_pipe[1], STDOUT_FILENO);
+ xposix_spawn_file_actions_addclose (&fa, stdout_pipe[0]);
+ xposix_spawn_file_actions_addclose (&fa, stdout_pipe[1]);
+ xposix_spawn_file_actions_adddup2 (&fa, stderr_pipe[1], STDERR_FILENO);
+ xposix_spawn_file_actions_addclose (&fa, stderr_pipe[0]);
+ xposix_spawn_file_actions_addclose (&fa, stderr_pipe[1]);
+
+ pid_t pid = xposix_spawn (w->path, &fa, NULL, w->argv, w->envp);
+ posix_spawn_file_actions_destroy (&fa);
+
+ xclose (stdin_pipe[0]);
+ xclose (stdout_pipe[1]);
+ xclose (stderr_pipe[1]);
+
+ /* Write UTF-8 encoding of "äöü\n" to stdin. */
+ xwrite (stdin_pipe[1], "\xc3\xa4\xc3\xb6\xc3\xbc\n", 7);
+ xclose (stdin_pipe[1]);
+
+ /* Read the converted output from the pipe. */
+ char buf[16];
+ ssize_t ret = read (stdout_pipe[0], buf, sizeof (buf));
+ xclose (stdout_pipe[0]);
+
+ /* ISO-8859-1 encoding of "äöü\n". */
+ TEST_COMPARE_BLOB (buf, ret, "\xe4\xf6\xfc\n", 4);
+
+ int status;
+ xwaitpid (pid, &status, 0);
+ TEST_COMPARE (status, 0);
+
+ /* Check that nothing has been written to stderr. */
+ ret = read (stderr_pipe[0], buf, sizeof (buf));
+ TEST_COMPARE (ret, 0);
+ xclose (stderr_pipe[0]);
+
+ support_spawn_wrapped_free (w);
+ free (iconv_prog);
+}
+
+static int
+do_test (void)
+{
+ char *program = xasprintf ("%s/support/tst-support_spawn_wrap",
+ support_objdir_root);
+
+ {
+ char *env[] = { (char *) "recurse=", (char *) "argc=1", NULL };
+ struct support_spawn_wrapped *w
+ = support_spawn_wrap (program, NULL, env, 0);
+ struct support_capture_subprocess proc
+ = support_capture_subprogram (w->path, w->argv, w->envp);
+ support_capture_subprocess_check (&proc, "no arguments", 0, sc_allow_none);
+ support_capture_subprocess_free (&proc);
+ support_spawn_wrapped_free (w);
+ }
+
+ {
+ char *argv[] = { (char *) "program", (char *) "--recurse", NULL };
+ struct support_spawn_wrapped *w
+ = support_spawn_wrap (program, argv, NULL, 0);
+ struct support_capture_subprocess proc
+ = support_capture_subprogram (w->path, w->argv, w->envp);
+ support_capture_subprocess_check (&proc, "default envvironment", 0,
+ sc_allow_none);
+ support_capture_subprocess_free (&proc);
+ support_spawn_wrapped_free (w);
+ }
+
+ {
+ char *argv[] = { (char *) "program", (char *) "alpha", (char *) "beta",
+ (char *) "gamma", NULL };
+ char *env[] = { (char *) "recurse=", (char *) "argc=4", NULL };
+ struct support_spawn_wrapped *w
+ = support_spawn_wrap (program, argv, env, 0);
+ struct support_capture_subprocess proc
+ = support_capture_subprogram (w->path, w->argv, w->envp);
+ support_capture_subprocess_check (&proc, "3 arguments", 0, sc_allow_none);
+ support_capture_subprocess_free (&proc);
+ support_spawn_wrapped_free (w);
+ }
+
+ {
+ char *argv[] = { (char *) "program", (char *) "check-env", NULL };
+ char *env[] = { (char *) "recurse=", (char *) "argc=2",
+ (char *) "extra=17", NULL };
+ struct support_spawn_wrapped *w
+ = support_spawn_wrap (program, argv, env, 0);
+ struct support_capture_subprocess proc
+ = support_capture_subprogram (w->path, w->argv, w->envp);
+ TEST_COMPARE_STRING (proc.out.buffer, "2 17\n");
+ support_capture_subprocess_check (&proc, "check-env", 0, sc_allow_stdout);
+ support_capture_subprocess_free (&proc);
+ support_spawn_wrapped_free (w);
+ }
+
+ test_iconv ();
+
+ /* This may trigger EXIT_UNSUPPORTED, so run this before the tests
+ that rely on running_via_ldso. */
+ TEST_COMPARE (!running_via_ldso (), support_hardcoded_paths_in_test);
+
+ {
+ char *argv[] = { (char *) "program", (char *) "check-ld.so", NULL };
+ char *env[] = { (char *) "recurse=", NULL };
+ struct support_spawn_wrapped *w
+ = support_spawn_wrap (program, argv, env, support_spawn_wrap_force);
+ struct support_capture_subprocess proc
+ = support_capture_subprogram (w->path, w->argv, w->envp);
+ support_capture_subprocess_check (&proc, "check-ld.so", 0, sc_allow_none);
+ support_capture_subprocess_free (&proc);
+ support_spawn_wrapped_free (w);
+ }
+
+ free (program);
+ return 0;
+}
+
+#include <support/test-driver.c>

View File

@ -0,0 +1,168 @@
commit 364426a59ee30ee3e528e5b5cae36b5dee045320
Author: Florian Weimer <fweimer@redhat.com>
Date: Mon Feb 2 21:15:48 2026 +0100
elf: Add test case for LD_PROFILE/LD_PROFILE_OUTPUT interaction
This verifies that LD_PROFILE is correctly ignored if LD_PROFILE_OUTPUT
is not set.
The test was initially auto-generated, then heavily edited and re-edited
for brevity and clarity. The test uses glibc-specific interfaces
(including one that did not exist at all a couple of hours ago), so
this should be unproblematic.
diff -Nrup a/elf/Makefile b/elf/Makefile
--- a/elf/Makefile 2026-02-23 12:26:54.652168428 -0500
+++ b/elf/Makefile 2026-02-23 12:30:15.356267040 -0500
@@ -442,6 +442,7 @@ tests += \
tst-initorder \
tst-initorder2 \
tst-latepthread \
+ tst-ld_profile \
tst-main1 \
tst-nodelete2 \
tst-nodelete-dlclose \
diff -Nrup a/elf/tst-ld_profile.c b/elf/tst-ld_profile.c
--- a/elf/tst-ld_profile.c 1969-12-31 19:00:00.000000000 -0500
+++ b/elf/tst-ld_profile.c 2026-02-23 12:27:49.770154596 -0500
@@ -0,0 +1,139 @@
+/* Test LD_PROFILE/LD_PROFILE_OUTPUT interaction (bug 33797).
+ Copyright (C) 2026 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <gnu/lib-names.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/capture_subprocess.h>
+#include <support/check.h>
+#include <support/subprocess.h>
+#include <support/support.h>
+#include <support/temp_file.h>
+#include <unistd.h>
+
+/* Expected profile file path (based on LD_PROFILE_OUTPUT and LIBC_SO). */
+static char *profile_file_path;
+
+/* Path to this test program for recursive invocation. */
+static char *program;
+
+/* LD_PROFILE_OUTPUT environment variable setting. */
+static char *ld_profile_output_env;
+
+/* Run the test program with the specified environment array. */
+static struct support_capture_subprocess
+run_test_program (char *env[])
+{
+ /* Make sure the the potential output file does not exist. */
+ unlink (profile_file_path);
+
+/* Command line arguments for recursive invocation. This turns the
+ test program in a no-op (with LD_PROFILE output the only side effect). */
+ static char *recurse_argv[] =
+ {
+ (char *) "tst-ld_profile", (char *) "recurse", NULL
+ };
+ struct support_spawn_wrapped *w
+ = support_spawn_wrap (program, recurse_argv, env, 0);
+ struct support_capture_subprocess proc
+ = support_capture_subprogram (w->path, w->argv, w->envp);
+ support_spawn_wrapped_free (w);
+
+ return proc;
+}
+
+static void
+test_profile_without_output (void)
+{
+ char *env[] = { (char *) "LD_PROFILE=" LIBC_SO, NULL };
+ struct support_capture_subprocess proc = run_test_program (env);
+ const char *expected_warning =
+ "warning: LD_PROFILE ignored because LD_PROFILE_OUTPUT not specified\n";
+ TEST_COMPARE_STRING (proc.err.buffer, expected_warning);
+ support_capture_subprocess_check (&proc,
+ "LD_PROFILE without LD_PROFILE_OUTPUT",
+ 0, sc_allow_stderr);
+ support_capture_subprocess_free (&proc);
+
+ TEST_VERIFY (access (profile_file_path, F_OK) != 0);
+ /* Also check the old /var/tmp path. */
+ TEST_VERIFY (access ("/var/tmp/" LIBC_SO ".profile", F_OK) != 0);
+}
+
+static void
+test_profile_with_output (void)
+{
+ char *env[] = { (char *) "LD_PROFILE=" LIBC_SO, ld_profile_output_env, NULL };
+ struct support_capture_subprocess proc = run_test_program (env);
+ support_capture_subprocess_check (&proc, "LD_PROFILE with LD_PROFILE_OUTPUT",
+ 0, sc_allow_none);
+ support_capture_subprocess_free (&proc);
+
+ /* This asserts that the file was created. */
+ TEST_COMPARE (unlink (profile_file_path), 0);
+}
+
+static void
+test_output_without_profile (void)
+{
+ char *env[] = { ld_profile_output_env, NULL };
+ struct support_capture_subprocess proc = run_test_program (env);
+ support_capture_subprocess_check (&proc,
+ "LD_PROFILE_OUTPUT without LD_PROFILE",
+ 0, sc_allow_none);
+ support_capture_subprocess_free (&proc);
+
+ TEST_VERIFY (access (profile_file_path, F_OK) != 0);
+}
+
+static void
+prepare (int argc, char **argv)
+{
+ /* Do nothing on recursive invocation. */
+ if (argc >= 2 && strcmp (argv[1], "recurse") == 0)
+ exit (0);
+}
+
+#define PREPARE prepare
+
+static int
+do_test (void)
+{
+ if (access ("/var/tmp/" LIBC_SO ".profile", F_OK) == 0)
+ FAIL_UNSUPPORTED ("/var/tmp/" LIBC_SO ".profile exists");
+
+ /* Temporary directory for the profile output. */
+ char *profile_output_dir = support_create_temp_directory ("tst-ld_profile");
+ profile_file_path = xasprintf ("%s/%s.profile", profile_output_dir, LIBC_SO);
+ ld_profile_output_env = xasprintf ("LD_PROFILE_OUTPUT=%s",
+ profile_output_dir);
+ program = xasprintf ("%s/elf/tst-ld_profile", support_objdir_root);
+
+ test_profile_without_output ();
+ test_profile_with_output ();
+ test_output_without_profile ();
+
+ free (program);
+ free (ld_profile_output_env);
+ free (profile_file_path);
+ free (profile_output_dir);
+
+ return 0;
+}
+
+#include <support/test-driver.c>

View File

@ -0,0 +1,52 @@
commit 55e85c1e48c2aae71c0b5907fd22a3e9b978b6e8
Author: H.J. Lu <hjl.tools@gmail.com>
Date: Fri Jul 18 17:03:04 2025 -0700
io/tst-stat.c: Use a temporary directory for symlink test
Call support_create_temp_directory to create a temporary directory for
symlink test, instead of a fixed file in the glibc source tree, to avoid
the race condition when there are more than one glibc tests running at the
same time with the same glibc source tree. This fixes BZ #33178.
Signed-off-by: H.J. Lu <hjl.tools@gmail.com>
Reviewed-by: Andreas K. Huettel <dilfridge@gentoo.org>
diff --git a/io/tst-stat.c b/io/tst-stat.c
index 269c62d29e7e93fa..af31b4b39d3d65a8 100644
--- a/io/tst-stat.c
+++ b/io/tst-stat.c
@@ -27,6 +27,7 @@
#include <sys/sysmacros.h>
#include <stdio.h>
#include <unistd.h>
+#include <stdlib.h>
static void
stat_check (int fd, const char *path, struct stat *st)
@@ -78,7 +79,8 @@ static int
do_test (void)
{
char *path;
- const char *linkame = "tst-fstat.linkname";
+ char *tempdir = support_create_temp_directory ("tst-stat-");
+ char *linkname = xasprintf ("%s/tst-fstat.linkname", tempdir);
int fd = create_temp_file ("tst-fstat.", &path);
TEST_VERIFY_EXIT (fd >= 0);
support_write_file_string (path, "abc");
@@ -122,9 +124,12 @@ do_test (void)
}
}
- TEST_COMPARE (symlink ("tst-fstat.target", linkame), 0);
- add_temp_file (linkame);
- fstatat_link (linkame, &st);
+ TEST_COMPARE (symlink ("tst-fstat.target", linkname), 0);
+ add_temp_file (linkname);
+ fstatat_link (linkname, &st);
+
+ free (linkname);
+ free (tempdir);
return 0;
}

View File

@ -0,0 +1,206 @@
commit 0981c03c2752b5f12ede24e6e696d5a29f7c6396
Author: Frédéric Bérat <fberat@redhat.com>
Date: Wed Apr 29 15:53:51 2026 +0200
libio: Fix gconv module reference counter overflow in swscanf
The swscanf family of functions creates a wide-oriented FILE stream
on the stack. Initialization of this stream invokes `_IO_fwide`, which
clones the global locale's gconv transformation steps via
`__wcsmbs_clone_conv`. This increments the reference counter (`__counter`)
of the gconv module.
Because the FILE stream is stack-allocated, `fclose` cannot be called,
and so `__gconv_release_step` is never invoked. The counter leaks,
eventually hitting the 32-bit integer overflow limit and aborting the
process.
To resolve this, we introduce `_IO_wstrfile_fclose_stack`, a dedicated
cleanup function for stack-allocated FILE streams. This function invokes
`_IO_FINISH` and correctly releases the gconv steps via
`__gconv_release_step` without attempting to `free` the FILE pointer.
This cleanup function is then hooked into all variants of swscanf right
before they return.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Conflicts:
sysdeps/ieee754/ldbl-opt/nldbl-compat.c
(isoc23 variants not present downstream)
sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc23_swscanf.c
(not present downstream)
sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc23_vswscanf.c
(not present downstream)
wcsmbs/isoc23_swscanf.c
(not present downstream)
wcsmbs/isoc23_vswscanf.c
(not present downstream)
diff --git a/libio/iofwide.c b/libio/iofwide.c
index a7f29fa0b9693cc3..972f154365e88bb3 100644
--- a/libio/iofwide.c
+++ b/libio/iofwide.c
@@ -257,3 +257,18 @@ __libio_codecvt_length (struct _IO_codecvt *codecvt, __mbstate_t *statep,
return result;
}
+
+void
+_IO_wstrfile_fclose_stack (FILE *fp)
+{
+ _IO_FINISH (fp);
+ if (fp->_mode > 0)
+ {
+ struct _IO_codecvt *cc = fp->_codecvt;
+
+ __libc_lock_lock (__gconv_lock);
+ __gconv_release_step (cc->__cd_in.step);
+ __gconv_release_step (cc->__cd_out.step);
+ __libc_lock_unlock (__gconv_lock);
+ }
+}
diff --git a/libio/iovswscanf.c b/libio/iovswscanf.c
index 35bddc0daeca917f..284c61615ebddd47 100644
--- a/libio/iovswscanf.c
+++ b/libio/iovswscanf.c
@@ -38,6 +38,8 @@ __vswscanf (const wchar_t *string, const wchar_t *format, va_list args)
_IO_strfile sf;
struct _IO_wide_data wd;
FILE *f = _IO_strfile_readw (&sf, &wd, string);
- return __vfwscanf_internal (f, format, args, 0);
+ int done = __vfwscanf_internal (f, format, args, 0);
+ _IO_wstrfile_fclose_stack (f);
+ return done;
}
ldbl_weak_alias (__vswscanf, vswscanf)
diff --git a/libio/libioP.h b/libio/libioP.h
index 50570f89de5a7010..76fd7853dc087bf9 100644
--- a/libio/libioP.h
+++ b/libio/libioP.h
@@ -594,6 +594,7 @@ extern FILE* _IO_new_file_fopen (FILE *, const char *, const char *,
int);
extern void _IO_no_init (FILE *, int, int, struct _IO_wide_data *,
const struct _IO_jump_t *) __THROW;
+extern void _IO_wstrfile_fclose_stack (FILE *) attribute_hidden;
extern void _IO_new_file_init_internal (struct _IO_FILE_plus *)
__THROW attribute_hidden;
extern FILE* _IO_new_file_setbuf (FILE *, char *, ssize_t);
diff --git a/libio/swscanf.c b/libio/swscanf.c
index 88a19144342cdc38..cb3795599c6f70c6 100644
--- a/libio/swscanf.c
+++ b/libio/swscanf.c
@@ -37,7 +37,7 @@ __swscanf (const wchar_t *s, const wchar_t *format, ...)
va_start (arg, format);
done = __vfwscanf_internal (f, format, arg, 0);
va_end (arg);
-
+ _IO_wstrfile_fclose_stack (f);
return done;
}
ldbl_strong_alias (__swscanf, swscanf)
diff --git a/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc99_swscanf.c b/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc99_swscanf.c
index ef8c1317dcd7f5e5..96bf2ac3559186e7 100644
--- a/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc99_swscanf.c
+++ b/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc99_swscanf.c
@@ -34,7 +34,7 @@ ___ieee128_isoc99_swscanf (const wchar_t *string, const wchar_t *format, ...)
va_start (ap, format);
done = __vfwscanf_internal (fp, format, ap, mode_flags);
va_end (ap);
-
+ _IO_wstrfile_fclose_stack (fp);
return done;
}
strong_alias (___ieee128_isoc99_swscanf, __isoc99_swscanfieee128)
diff --git a/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc99_vswscanf.c b/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc99_vswscanf.c
index f77bb64638a4717f..8fa10ee2e22e238f 100644
--- a/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc99_vswscanf.c
+++ b/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc99_vswscanf.c
@@ -27,6 +27,8 @@ ___ieee128_isoc99_vswscanf (wchar_t *string, const wchar_t *format, va_list ap)
struct _IO_wide_data wd;
FILE *fp = _IO_strfile_readw (&sf, &wd, string);
int mode_flags = SCANF_ISOC99_A | SCANF_LDBL_USES_FLOAT128;
- return __vfwscanf_internal (fp, format, ap, mode_flags);
+ int done = __vfwscanf_internal (fp, format, ap, mode_flags);
+ _IO_wstrfile_fclose_stack (fp);
+ return done;
}
strong_alias (___ieee128_isoc99_vswscanf, __isoc99_vswscanfieee128)
diff --git a/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-swscanf.c b/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-swscanf.c
index 04ee5419200298b1..509eaa663d0a9a33 100644
--- a/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-swscanf.c
+++ b/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-swscanf.c
@@ -34,7 +34,7 @@ ___ieee128_swscanf (const wchar_t *string, const wchar_t *format, ...)
done = __vfwscanf_internal (fp, format, ap,
SCANF_LDBL_USES_FLOAT128);
va_end (ap);
-
+ _IO_wstrfile_fclose_stack (fp);
return done;
}
strong_alias (___ieee128_swscanf, __swscanfieee128)
diff --git a/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-vswscanf.c b/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-vswscanf.c
index 7aebc5f1c12939b6..18aa6deaacdde682 100644
--- a/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-vswscanf.c
+++ b/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-vswscanf.c
@@ -27,6 +27,8 @@ ___ieee128_vswscanf (const wchar_t *string, const wchar_t *format,
_IO_strfile sf;
struct _IO_wide_data wd;
FILE *fp = _IO_strfile_readw (&sf, &wd, string);
- return __vfwscanf_internal (fp, format, ap, SCANF_LDBL_USES_FLOAT128);
+ int done = __vfwscanf_internal (fp, format, ap, SCANF_LDBL_USES_FLOAT128);
+ _IO_wstrfile_fclose_stack (fp);
+ return done;
}
strong_alias (___ieee128_vswscanf, __vswscanfieee128)
diff --git a/sysdeps/ieee754/ldbl-opt/nldbl-compat.c b/sysdeps/ieee754/ldbl-opt/nldbl-compat.c
index a6c5c49ecb151bf5..81d53a4cf30acb89 100644
--- a/sysdeps/ieee754/ldbl-opt/nldbl-compat.c
+++ b/sysdeps/ieee754/ldbl-opt/nldbl-compat.c
@@ -388,7 +388,9 @@ __nldbl_vswscanf (const wchar_t *s, const wchar_t *fmt, va_list ap)
struct _IO_wide_data wd;
FILE *f = _IO_strfile_readw (&sf, &wd, s);
- return __vfwscanf_internal (f, fmt, ap, SCANF_LDBL_IS_DBL);
+ int ret = __vfwscanf_internal (f, fmt, ap, SCANF_LDBL_IS_DBL);
+ _IO_wstrfile_fclose_stack (f);
+ return ret;
}
libc_hidden_def (__nldbl_vswscanf)
@@ -952,7 +954,9 @@ __nldbl___isoc99_vswscanf (const wchar_t *s, const wchar_t *fmt, va_list ap)
struct _IO_wide_data wd;
FILE *f = _IO_strfile_readw (&sf, &wd, s);
- return __vfwscanf_internal (f, fmt, ap, SCANF_LDBL_IS_DBL | SCANF_ISOC99_A);
+ int ret = __vfwscanf_internal (f, fmt, ap, SCANF_LDBL_IS_DBL | SCANF_ISOC99_A);
+ _IO_wstrfile_fclose_stack (f);
+ return ret;
}
libc_hidden_def (__nldbl___isoc99_vswscanf)
diff --git a/wcsmbs/isoc99_swscanf.c b/wcsmbs/isoc99_swscanf.c
index bb6d8f2035d8e4a4..3fda1f7be695b31e 100644
--- a/wcsmbs/isoc99_swscanf.c
+++ b/wcsmbs/isoc99_swscanf.c
@@ -32,6 +32,6 @@ __isoc99_swscanf (const wchar_t *s, const wchar_t *format, ...)
va_start (arg, format);
done = __vfwscanf_internal (f, format, arg, SCANF_ISOC99_A);
va_end (arg);
-
+ _IO_wstrfile_fclose_stack (f);
return done;
}
diff --git a/wcsmbs/isoc99_vswscanf.c b/wcsmbs/isoc99_vswscanf.c
index 3cd0a28c21ffa36f..1df90438416d3bb7 100644
--- a/wcsmbs/isoc99_vswscanf.c
+++ b/wcsmbs/isoc99_vswscanf.c
@@ -33,6 +33,8 @@ __isoc99_vswscanf (const wchar_t *string, const wchar_t *format, va_list args)
_IO_strfile sf;
struct _IO_wide_data wd;
FILE *f = _IO_strfile_readw (&sf, &wd, string);
- return __vfwscanf_internal (f, format, args, SCANF_ISOC99_A);
+ int done = __vfwscanf_internal (f, format, args, SCANF_ISOC99_A);
+ _IO_wstrfile_fclose_stack (f);
+ return done;
}
libc_hidden_def (__isoc99_vswscanf)

View File

@ -0,0 +1,57 @@
libio: Fix gconv module reference counter overflow in vswprintf
The vswprintf family of functions (swprintf, vswprintf and their
fortified/ldbl-compat wrappers) creates a wide-oriented FILE stream
on the stack via _IO_no_init with _IO_wstrn_jumps, then calls
_IO_fwide which clones the gconv transformation steps via
__wcsmbs_clone_conv, incrementing the __counter reference counter.
Because the FILE is stack-allocated, fclose is never called, and the
counter is never decremented. In a long-running process calling
swprintf repeatedly with a non-builtin locale (e.g. en_US.UTF-8),
the counter eventually overflows, triggering a fatal abort.
This is the same class of bug fixed upstream for swscanf in commit
0981c03c2752b5f12ede24e6e696d5a29f7c6396. Upstream resolved the
swprintf path architecturally by reworking the printf subsystem to
use struct __wprintf_buffer instead of FILE * (commit 118816de3383),
eliminating the _IO_fwide call entirely. That rework is too large
to backport to glibc 2.34.
Instead, apply the same pattern as the swscanf fix: call
_IO_wstrfile_fclose_stack after __vfwprintf_internal returns to
release the gconv steps before the stack FILE goes out of scope.
All ldbl-compat wrappers (ieee128, nldbl) call __vswprintf_internal,
so they are covered by this single change.
Note: _IO_wstrfile_fclose_stack calls _IO_FINISH which invokes
_IO_wstr_finish, and that NULLs out _IO_buf_base. Therefore the
cleanup must happen after the overflow check (which compares
_IO_buf_base against overflow_buf) and after string termination
(which dereferences _IO_write_ptr).
diff --git a/libio/vswprintf.c b/libio/vswprintf.c
index b7036f1480733ac6..accbb8516f860680 100644
--- a/libio/vswprintf.c
+++ b/libio/vswprintf.c
@@ -111,13 +111,17 @@ __vswprintf_internal (wchar_t *string, size_t maxlen, const wchar_t *format,
ret = __vfwprintf_internal ((FILE *) &sf.f._sbf, format, args, mode_flags);
if (sf.f._sbf._f._wide_data->_IO_buf_base == sf.overflow_buf)
- /* ISO C99 requires swprintf/vswprintf to return an error if the
- output does not fit in the provided buffer. */
- return -1;
+ {
+ /* ISO C99 requires swprintf/vswprintf to return an error if the
+ output does not fit in the provided buffer. */
+ _IO_wstrfile_fclose_stack ((FILE *) &sf.f._sbf);
+ return -1;
+ }
/* Terminate the string. */
*sf.f._sbf._f._wide_data->_IO_write_ptr = '\0';
+ _IO_wstrfile_fclose_stack ((FILE *) &sf.f._sbf);
return ret;
}

View File

@ -0,0 +1,123 @@
commit 1cc165baed20b6589ef2f2c8c0e0415139bbb151
Author: Frédéric Bérat <fberat@redhat.com>
Date: Wed Apr 29 13:26:38 2026 +0200
test: Add gconv refcount leak test for swscanf
Add a new internal test, `tst-wcsmbs-clone-overflow`, to verify correct
gconv module reference counting. The Makefile is updated to include this
test in the `tests-internal` list and ensure it runs with generated locales.
This test specifically checks that the `__counter` for `gconv_fcts->towc`
does not leak references when `swscanf` is used with a stack-allocated
wide character stream. It ensures that `_IO_wstrfile_fclose_stack`
properly decrements the module reference counter, preventing a module
from staying loaded indefinitely due to unreleased references.
Assisted-by: LLM
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Conflicts:
wcsmbs/Makefile
(test-c8rtomb, test-mbrtoc8, tst-wscanf-to_inpunct not present
downstream)
wcsmbs/tst-wcsmbs-clone-overflow.c
(struct lc_ctype_data not present downstream; use
loc->private.ctype directly instead)
diff --git a/wcsmbs/Makefile b/wcsmbs/Makefile
index 0e5fe909a83c7a13..f74b06d9749eaa02 100644
--- a/wcsmbs/Makefile
+++ b/wcsmbs/Makefile
@@ -59,6 +59,12 @@ tests := tst-wcstof wcsmbs-tst1 tst-wcsnlen tst-btowc tst-mbrtowc \
# This test runs for a long time.
xtests += test-wcsncmp-nonarray
+tests-internal += \
+ tst-wcsmbs-clone-overflow
+
+tests-static += \
+ tst-wcsmbs-clone-overflow
+
include ../Rules
@@ -77,6 +83,7 @@ $(objpfx)tst-wcstol-locale.out: $(gen-locales)
$(objpfx)tst-wcstod-nan-locale.out: $(gen-locales)
$(objpfx)tst-c16-surrogate.out: $(gen-locales)
$(objpfx)tst-c32-state.out: $(gen-locales)
+$(objpfx)tst-wcsmbs-clone-overflow.out: $(gen-locales)
endif
$(objpfx)tst-wcstod-round: $(libm)
diff --git a/wcsmbs/tst-wcsmbs-clone-overflow.c b/wcsmbs/tst-wcsmbs-clone-overflow.c
new file mode 100644
index 0000000000000000..412e1c54085c68d5
--- /dev/null
+++ b/wcsmbs/tst-wcsmbs-clone-overflow.c
@@ -0,0 +1,65 @@
+/* Test for gconv module reference counter leak.
+ Copyright (C) 2026 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <locale.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <support/check.h>
+#include <support/support.h>
+
+/* Internal headers for accessing the gconv structures. */
+#include <locale/localeinfo.h>
+#include <iconv/gconv_int.h>
+#include <wcsmbs/wcsmbsload.h>
+
+static int
+do_test (void)
+{
+ if (setlocale (LC_ALL, "de_DE.ISO-8859-1") == NULL)
+ FAIL_EXIT1 ("setlocale failed, check if de_DE.ISO-8859-1 is generated");
+
+ wchar_t buf[32] = L"123";
+ int j;
+
+ /* First iteration initializes the gconv functions internally. */
+ if (swscanf (buf, L"%d", &j) < 1)
+ FAIL_EXIT1 ("swscanf failed");
+
+ /* Retrieve the current gconv_fcts from the LC_CTYPE locale data. */
+ struct __locale_data *loc = _NL_CURRENT_DATA (LC_CTYPE);
+ const struct gconv_fcts *fcts = loc->private.ctype;
+
+ TEST_VERIFY_EXIT (fcts != NULL);
+ TEST_VERIFY_EXIT (fcts->towc != NULL);
+
+ /* Capture the reference counter. */
+ int initial_counter = fcts->towc->__counter;
+
+ /* Perform a second iteration of swscanf. If the stack-allocated FILE
+ leaks the gconv reference, the counter will increment. */
+ if (swscanf (buf, L"%d", &j) < 1)
+ FAIL_EXIT1 ("swscanf failed");
+
+ /* The counter should be unchanged, as _IO_wstrfile_fclose_stack should
+ have decremented it correctly. */
+ TEST_COMPARE (fcts->towc->__counter, initial_counter);
+
+ return 0;
+}
+
+#include <support/test-driver.c>

View File

@ -0,0 +1,146 @@
commit 9ef37798fa7dc2b10926742e3f3bd304a17a9ad1
Author: Frédéric Bérat <fberat@redhat.com>
Date: Tue May 26 13:29:57 2026 +0200
test: Fix and stabilize tst-wcsmbs-clone-overflow test
The test tst-wcsmbs-clone-overflow was initially added to tests-static.
However, this causes the test to be unstable because gconv modules
dynamically load libc.so. Any discrepancy between the statically linked
version and the dynamically loaded one can lead to a crash.
By removing the test from tests-static, it relies on dynamic linking,
safely bypassing the dlopen crash. Since the test is now dynamically
linked, it cannot use the internal thread-local symbol
_NL_CURRENT_DATA(LC_CTYPE) because _nl_current_LC_CTYPE is hidden in
libc.so, leading to undefined references. Thus, the test now uses
newlocale and uselocale, safely extracting the locale data from the
returned locale_t object.
Furthermore, using newlocale requires the gconv-modules configuration to
be built and available so that the ISO8859-1.so module can be
dynamically loaded. Otherwise, glibc falls back to the built-in C locale
conversions, leaving __shlib_handle as NULL and silently bypassing the
reference counter increment.
A new Makefile fragment, gen-gconv-modules.mk, is introduced to ensure
the gconv-modules are built before the test runs, and an explicit check
for __shlib_handle != NULL is added to the test.
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Conflicts:
wcsmbs/Makefile
(test-c8rtomb, test-mbrtoc8, tst-wscanf-to_inpunct not present
downstream)
wcsmbs/tst-wcsmbs-clone-overflow.c
(struct lc_ctype_data not present downstream; use
loc->private.ctype directly instead)
diff --git a/gen-gconv-modules.mk b/gen-gconv-modules.mk
new file mode 100644
index 0000000000000000..046721a7a7bf460c
--- /dev/null
+++ b/gen-gconv-modules.mk
@@ -0,0 +1,6 @@
+# defines target $(gen-gconv-modules) that ensures gconv-modules are available
+
+gen-gconv-modules := $(common-objpfx)iconvdata/gconv-modules
+
+$(gen-gconv-modules):
+ $(MAKE) -C ../iconvdata subdir=iconvdata $@
diff --git a/localedata/Makefile b/localedata/Makefile
index 56de42e9262decd3..0cc02c430d96d804 100644
--- a/localedata/Makefile
+++ b/localedata/Makefile
@@ -204,7 +204,7 @@ install-others := $(addprefix $(inst_i18ndir)/, \
$(locales))
endif
-tests: $(objdir)/iconvdata/gconv-modules
+tests: $(gen-gconv-modules)
tests-static += tst-langinfo-newlocale-static tst-langinfo-setlocale-static
@@ -315,6 +315,7 @@ LOCALES := \
$(NULL)
include ../gen-locales.mk
+include ../gen-gconv-modules.mk
$(objpfx)tst-iconv-math-trans.out: $(gen-locales)
endif
@@ -486,6 +487,3 @@ $(objpfx)mtrace-tst-leaks.out: $(objpfx)tst-leaks.out
bug-setlocale1-ENV-only = LOCPATH=$(objpfx) LC_CTYPE=de_DE.UTF-8
bug-setlocale1-static-ENV-only = $(bug-setlocale1-ENV-only)
-
-$(objdir)/iconvdata/gconv-modules:
- $(MAKE) -C ../iconvdata subdir=iconvdata $@
diff --git a/wcsmbs/Makefile b/wcsmbs/Makefile
index f74b06d9749eaa02..bb03d92576700a99 100644
--- a/wcsmbs/Makefile
+++ b/wcsmbs/Makefile
@@ -62,16 +62,13 @@ xtests += test-wcsncmp-nonarray
tests-internal += \
tst-wcsmbs-clone-overflow
-tests-static += \
- tst-wcsmbs-clone-overflow
-
-
include ../Rules
ifeq ($(run-built-tests),yes)
LOCALES := de_DE.ISO-8859-1 de_DE.UTF-8 en_US.ANSI_X3.4-1968 hr_HR.ISO-8859-2 \
ja_JP.EUC-JP zh_TW.EUC-TW tr_TR.UTF-8 tr_TR.ISO-8859-9
include ../gen-locales.mk
+include ../gen-gconv-modules.mk
$(objpfx)tst-btowc.out: $(gen-locales)
$(objpfx)tst-c16c32-1.out: $(gen-locales)
@@ -83,7 +80,7 @@ $(objpfx)tst-wcstol-locale.out: $(gen-locales)
$(objpfx)tst-wcstod-nan-locale.out: $(gen-locales)
$(objpfx)tst-c16-surrogate.out: $(gen-locales)
$(objpfx)tst-c32-state.out: $(gen-locales)
-$(objpfx)tst-wcsmbs-clone-overflow.out: $(gen-locales)
+$(objpfx)tst-wcsmbs-clone-overflow.out: $(gen-locales) $(gen-gconv-modules)
endif
$(objpfx)tst-wcstod-round: $(libm)
diff --git a/wcsmbs/tst-wcsmbs-clone-overflow.c b/wcsmbs/tst-wcsmbs-clone-overflow.c
index 412e1c54085c68d5..6d8b643f48aa7775 100644
--- a/wcsmbs/tst-wcsmbs-clone-overflow.c
+++ b/wcsmbs/tst-wcsmbs-clone-overflow.c
@@ -30,8 +30,11 @@
static int
do_test (void)
{
- if (setlocale (LC_ALL, "de_DE.ISO-8859-1") == NULL)
- FAIL_EXIT1 ("setlocale failed, check if de_DE.ISO-8859-1 is generated");
+ locale_t loc_obj = newlocale (LC_ALL_MASK, "de_DE.ISO-8859-1", NULL);
+ if (loc_obj == NULL)
+ FAIL_EXIT1 ("newlocale failed, check if de_DE.ISO-8859-1 is generated");
+
+ uselocale (loc_obj);
wchar_t buf[32] = L"123";
int j;
@@ -41,7 +44,7 @@ do_test (void)
FAIL_EXIT1 ("swscanf failed");
/* Retrieve the current gconv_fcts from the LC_CTYPE locale data. */
- struct __locale_data *loc = _NL_CURRENT_DATA (LC_CTYPE);
+ struct __locale_data *loc = loc_obj->__locales[LC_CTYPE];
const struct gconv_fcts *fcts = loc->private.ctype;
TEST_VERIFY_EXIT (fcts != NULL);
@@ -50,6 +53,9 @@ do_test (void)
/* Capture the reference counter. */
int initial_counter = fcts->towc->__counter;
+ if (fcts->towc->__shlib_handle == NULL)
+ FAIL_EXIT1 ("__shlib_handle is NULL!");
+
/* Perform a second iteration of swscanf. If the stack-allocated FILE
leaks the gconv reference, the counter will increment. */
if (swscanf (buf, L"%d", &j) < 1)

View File

@ -0,0 +1,111 @@
test: Add gconv refcount leak test for swprintf
Add a new internal test, tst-wcsmbs-clone-overflow-swprintf, to verify
correct gconv module reference counting for the swprintf path.
This is the swprintf counterpart to tst-wcsmbs-clone-overflow (which
tests swscanf). The vswprintf/swprintf family creates a wide-oriented
stack-allocated FILE stream via _IO_no_init and _IO_fwide, which clones
the gconv transformation steps. This test verifies that the __counter
for gconv_fcts->towc does not leak references after swprintf returns,
confirming that _IO_wstrfile_fclose_stack properly releases the gconv
steps.
diff --git a/wcsmbs/Makefile b/wcsmbs/Makefile
index bb03d92576700a99..252630457146de35 100644
--- a/wcsmbs/Makefile
+++ b/wcsmbs/Makefile
@@ -60,7 +60,8 @@ tests := tst-wcstof wcsmbs-tst1 tst-wcsnlen tst-btowc tst-mbrtowc \
xtests += test-wcsncmp-nonarray
tests-internal += \
- tst-wcsmbs-clone-overflow
+ tst-wcsmbs-clone-overflow \
+ tst-wcsmbs-clone-overflow-swprintf
include ../Rules
@@ -81,6 +82,7 @@ $(objpfx)tst-wcstod-nan-locale.out: $(gen-locales)
$(objpfx)tst-c16-surrogate.out: $(gen-locales)
$(objpfx)tst-c32-state.out: $(gen-locales)
$(objpfx)tst-wcsmbs-clone-overflow.out: $(gen-locales) $(gen-gconv-modules)
+$(objpfx)tst-wcsmbs-clone-overflow-swprintf.out: $(gen-locales) $(gen-gconv-modules)
endif
$(objpfx)tst-wcstod-round: $(libm)
diff --git a/wcsmbs/tst-wcsmbs-clone-overflow-swprintf.c b/wcsmbs/tst-wcsmbs-clone-overflow-swprintf.c
new file mode 100644
index 0000000000000000..daccb08e6352a943
--- /dev/null
+++ b/wcsmbs/tst-wcsmbs-clone-overflow-swprintf.c
@@ -0,0 +1,70 @@
+/* Test for gconv module reference counter leak in swprintf.
+ Copyright (C) 2026 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <locale.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <support/check.h>
+#include <support/support.h>
+
+/* Internal headers for accessing the gconv structures. */
+#include <locale/localeinfo.h>
+#include <iconv/gconv_int.h>
+#include <wcsmbs/wcsmbsload.h>
+
+static int
+do_test (void)
+{
+ locale_t loc_obj = newlocale (LC_ALL_MASK, "de_DE.ISO-8859-1", NULL);
+ if (loc_obj == NULL)
+ FAIL_EXIT1 ("newlocale failed, check if de_DE.ISO-8859-1 is generated");
+
+ uselocale (loc_obj);
+
+ wchar_t buf[32];
+
+ /* First iteration initializes the gconv functions internally. */
+ if (swprintf (buf, sizeof (buf) / sizeof (buf[0]), L"%d", 123) < 0)
+ FAIL_EXIT1 ("swprintf failed");
+
+ /* Retrieve the current gconv_fcts from the LC_CTYPE locale data. */
+ struct __locale_data *loc = loc_obj->__locales[LC_CTYPE];
+ const struct gconv_fcts *fcts = loc->private.ctype;
+
+ TEST_VERIFY_EXIT (fcts != NULL);
+ TEST_VERIFY_EXIT (fcts->towc != NULL);
+
+ /* Capture the reference counter. */
+ int initial_counter = fcts->towc->__counter;
+
+ if (fcts->towc->__shlib_handle == NULL)
+ FAIL_EXIT1 ("__shlib_handle is NULL!");
+
+ /* Perform a second iteration of swprintf. If the stack-allocated FILE
+ leaks the gconv reference, the counter will increment. */
+ if (swprintf (buf, sizeof (buf) / sizeof (buf[0]), L"%d", 456) < 0)
+ FAIL_EXIT1 ("swprintf failed");
+
+ /* The counter should be unchanged, as _IO_wstrfile_fclose_stack should
+ have decremented it correctly. */
+ TEST_COMPARE (fcts->towc->__counter, initial_counter);
+
+ return 0;
+}
+
+#include <support/test-driver.c>

View File

@ -0,0 +1,25 @@
commit 0d83b349fa7340475406b2fe933c7467e4584091
Author: Andreas Schwab <schwab@suse.de>
Date: Wed Mar 15 11:44:24 2023 +0100
getlogin_r: fix missing fallback if loginuid is unset (bug 30235)
When /proc/self/loginuid is not set, we should still fall back to using
the traditional utmp lookup, instead of failing right away.
diff --git a/sysdeps/unix/sysv/linux/getlogin_r.c b/sysdeps/unix/sysv/linux/getlogin_r.c
index dc0ca388b20d42c3..ff60fbbc726d7e7a 100644
--- a/sysdeps/unix/sysv/linux/getlogin_r.c
+++ b/sysdeps/unix/sysv/linux/getlogin_r.c
@@ -59,10 +59,7 @@ __getlogin_r_loginuid (char *name, size_t namesize)
value of, (uid_t) -1, so check if that value is set and return early to
avoid making unneeded nss lookups. */
if (uid == (uid_t) -1)
- {
- __set_errno (ENXIO);
- return ENXIO;
- }
+ return -1;
struct passwd pwd;
struct passwd *tpwd;

View File

@ -0,0 +1,112 @@
commit 7bb859f4198d0be19c31a9937eae4f6c2c9a079e
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Feb 13 09:02:07 2026 +0100
nss: Introduce dedicated struct nss_database_for_fork type
The initialized field in struct nss_database_data is rather confusing
because it is not used by the regular NSS code, only by the fork
state synchronization code. Introduce a separate type and place
the initialized field there.
Reviewed-by: Sam James <sam@gentoo.org>
diff --git a/nss/nss_database.c b/nss/nss_database.c
index e807e9d84ca03680..293e8457d70c00ba 100644
--- a/nss/nss_database.c
+++ b/nss/nss_database.c
@@ -56,7 +56,6 @@ global_state_allocate (void *closure)
{
result->data.nsswitch_conf.size = -1; /* Force reload. */
memset (result->data.services, 0, sizeof (result->data.services));
- result->data.initialized = true;
result->data.reload_disabled = false;
__libc_lock_init (result->lock);
result->root_ino = 0;
@@ -451,8 +450,8 @@ nss_database_check_reload_and_get (struct nss_database_state *local,
/* Avoid overwriting the global configuration until we have loaded
everything successfully. Otherwise, if the file change
information changes back to what is in the global configuration,
- the lookups would use the partially-written configuration. */
- struct nss_database_data staging = { .initialized = true, };
+ the lookups would use the partially-written configuration. */
+ struct nss_database_data staging = { };
bool ok = nss_database_reload (&staging, &initial);
@@ -503,7 +502,7 @@ __nss_database_freeres (void)
}
void
-__nss_database_fork_prepare_parent (struct nss_database_data *data)
+__nss_database_fork_prepare_parent (struct nss_database_for_fork *data)
{
/* Do not use allocate_once to trigger loading unnecessarily. */
struct nss_database_state *local = atomic_load_acquire (&global_database_state);
@@ -515,20 +514,21 @@ __nss_database_fork_prepare_parent (struct nss_database_data *data)
because it avoids acquiring the lock during the actual
fork. */
__libc_lock_lock (local->lock);
- *data = local->data;
+ data->data = local->data;
__libc_lock_unlock (local->lock);
+ data->initialized = true;
}
}
void
-__nss_database_fork_subprocess (struct nss_database_data *data)
+__nss_database_fork_subprocess (struct nss_database_for_fork *data)
{
struct nss_database_state *local = atomic_load_acquire (&global_database_state);
if (data->initialized)
{
/* Restore the state at the point of the fork. */
assert (local != NULL);
- local->data = *data;
+ local->data = data->data;
__libc_lock_init (local->lock);
}
else if (local != NULL)
diff --git a/nss/nss_database.h b/nss/nss_database.h
index d4b23b5295110c1c..349a793a6ef011e4 100644
--- a/nss/nss_database.h
+++ b/nss/nss_database.h
@@ -73,15 +73,21 @@ struct nss_database_data
struct file_change_detection nsswitch_conf;
nss_action_list services[NSS_DATABASE_COUNT];
int reload_disabled; /* Actually bool; int for atomic access. */
- bool initialized;
+};
+
+/* Use to store a consistent state snapshot across fork. */
+struct nss_database_for_fork
+{
+ bool initialized; /* Set to true if the data field below is initialized. */
+ struct nss_database_data data;
};
/* Called by fork in the parent process, before forking. */
-void __nss_database_fork_prepare_parent (struct nss_database_data *data)
+void __nss_database_fork_prepare_parent (struct nss_database_for_fork *)
attribute_hidden;
/* Called by fork in the new subprocess, after forking. */
-void __nss_database_fork_subprocess (struct nss_database_data *data)
+void __nss_database_fork_subprocess (struct nss_database_for_fork *)
attribute_hidden;
#endif /* _NSS_DATABASE_H */
diff --git a/posix/fork.c b/posix/fork.c
index 5a5baf63aa971b43..b996f4c63acc4e4b 100644
--- a/posix/fork.c
+++ b/posix/fork.c
@@ -50,7 +50,7 @@ __libc_fork (void)
lastrun = __run_prefork_handlers (multiple_threads);
- struct nss_database_data nss_database_data;
+ struct nss_database_for_fork nss_database_data;
/* If we are not running multiple threads, we do not have to
preserve lock state. If fork runs from a signal handler, only

View File

@ -0,0 +1,71 @@
commit 28660f4b45afa8921c2faebaec2846f95f670ba0
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Feb 13 09:02:07 2026 +0100
Linux: In getlogin_r, use utmp fallback only for specific errors
Most importantly, if getwpuid_r fails, it does not make sense to retry
via utmp because the user ID obtained from there is less reliable than
the one from /proc/self/loginuid.
Reviewed-by: Sam James <sam@gentoo.org>
diff --git a/sysdeps/unix/sysv/linux/getlogin_r.c b/sysdeps/unix/sysv/linux/getlogin_r.c
index ff60fbbc726d7e7a..85e54b3879ff986a 100644
--- a/sysdeps/unix/sysv/linux/getlogin_r.c
+++ b/sysdeps/unix/sysv/linux/getlogin_r.c
@@ -37,7 +37,12 @@ __getlogin_r_loginuid (char *name, size_t namesize)
{
int fd = __open_nocancel ("/proc/self/loginuid", O_RDONLY);
if (fd == -1)
- return -1;
+ {
+ if (errno == ENOENT)
+ /* Trigger utmp fallback. */
+ return -1;
+ return errno;
+ }
/* We are reading a 32-bit number. 12 bytes are enough for the text
representation. If not, something is wrong. */
@@ -45,6 +50,8 @@ __getlogin_r_loginuid (char *name, size_t namesize)
ssize_t n = TEMP_FAILURE_RETRY (__read_nocancel (fd, uidbuf,
sizeof (uidbuf)));
__close_nocancel_nostatus (fd);
+ if (n < 0)
+ return errno;
uid_t uid;
char *endp;
@@ -53,12 +60,13 @@ __getlogin_r_loginuid (char *name, size_t namesize)
|| (uidbuf[n] = '\0',
uid = strtoul (uidbuf, &endp, 10),
endp == uidbuf || *endp != '\0'))
- return -1;
+ return EINVAL;
/* If there is no login uid, linux sets /proc/self/loginid to the sentinel
value of, (uid_t) -1, so check if that value is set and return early to
avoid making unneeded nss lookups. */
if (uid == (uid_t) -1)
+ /* Trigger utmp fallback. */
return -1;
struct passwd pwd;
@@ -78,9 +86,14 @@ __getlogin_r_loginuid (char *name, size_t namesize)
}
}
- if (res != 0 || tpwd == NULL)
+ if (res != 0)
+ {
+ result = res;
+ goto out;
+ }
+ if (tpwd == NULL)
{
- result = -1;
+ result = ENOENT;
goto out;
}

View File

@ -0,0 +1,405 @@
commit 5b713b49443eb6a4e54e50e2f0147105f86dab02
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Feb 13 09:02:07 2026 +0100
nss: Missing checks in __nss_configure_lookup, __nss_database_get (bug 28940)
This avoids a null pointer dereference in the
nss_database_check_reload_and_get function, and assertion failures.
Reviewed-by: Sam James <sam@gentoo.org>
Conflicts:
nss/Makefile
(fixup context)
diff --git a/nss/Makefile b/nss/Makefile
index dbdba5bf815136ef..4d128da0a366301f 100644
--- a/nss/Makefile
+++ b/nss/Makefile
@@ -62,6 +62,7 @@ tests := \
test-digits-dots \
test-netdb \
tst-nss-getpwent \
+ tst-nss-malloc-failure-getlogin_r \
tst-nss-test1 \
tst-nss-test2 \
tst-nss-test4 \
diff --git a/nss/nss_database.c b/nss/nss_database.c
index 293e8457d70c00ba..251e47fdcf1ecf54 100644
--- a/nss/nss_database.c
+++ b/nss/nss_database.c
@@ -250,9 +250,12 @@ __nss_configure_lookup (const char *dbname, const char *service_line)
/* Force any load/cache/read whatever to happen, so we can override
it. */
- __nss_database_get (db, &result);
+ if (!__nss_database_get (db, &result))
+ return -1;
local = nss_database_state_get ();
+ if (local == NULL)
+ return -1;
result = __nss_action_parse (service_line);
if (result == NULL)
@@ -477,6 +480,8 @@ bool
__nss_database_get (enum nss_database db, nss_action_list *actions)
{
struct nss_database_state *local = nss_database_state_get ();
+ if (local == NULL)
+ return false;
return nss_database_check_reload_and_get (local, actions, db);
}
libc_hidden_def (__nss_database_get)
diff --git a/nss/tst-nss-malloc-failure-getlogin_r.c b/nss/tst-nss-malloc-failure-getlogin_r.c
new file mode 100644
index 0000000000000000..0e2985ad57b07fce
--- /dev/null
+++ b/nss/tst-nss-malloc-failure-getlogin_r.c
@@ -0,0 +1,345 @@
+/* Test NSS/getlogin_r with injected allocation failures (bug 28940).
+ Copyright (C) 2026 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <errno.h>
+#include <getopt.h>
+#include <malloc.h>
+#include <netdb.h>
+#include <nss.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/namespace.h>
+#include <support/support.h>
+#include <support/xstdio.h>
+#include <unistd.h>
+
+/* This test calls getpwuid_r via getlogin_r (on Linux).
+
+ This test uses the NSS system configuration to exercise that code
+ path. It means that it can fail (crash) if malloc failure is not
+ handled by NSS modules for the passwd database. */
+
+/* Data structure allocated via MAP_SHARED, so that writes from the
+ subprocess are visible. */
+struct shared_data
+{
+ /* Number of tracked allocations performed so far. */
+ volatile unsigned int allocation_count;
+
+ /* If this number is reached, one allocation fails. */
+ volatile unsigned int failing_allocation;
+
+ /* The number of allocations performed during initialization
+ (before the actual getlogin_r call). */
+ volatile unsigned int init_allocation_count;
+
+ /* Error code of an expected getlogin_r failure. */
+ volatile int expected_failure;
+
+ /* The subprocess stores the expected name here. */
+ char name[100];
+};
+
+/* Allocation count in shared mapping. */
+static struct shared_data *shared;
+
+/* Returns true if a failure should be injected for this allocation. */
+static bool
+fail_this_allocation (void)
+{
+ if (shared != NULL)
+ {
+ unsigned int count = shared->allocation_count;
+ shared->allocation_count = count + 1;
+ return count == shared->failing_allocation;
+ }
+ else
+ return false;
+}
+
+/* Failure-injecting wrappers for allocation functions used by glibc. */
+
+void *
+malloc (size_t size)
+{
+ if (fail_this_allocation ())
+ {
+ errno = ENOMEM;
+ return NULL;
+ }
+ extern __typeof (malloc) __libc_malloc;
+ return __libc_malloc (size);
+}
+
+void *
+calloc (size_t a, size_t b)
+{
+ if (fail_this_allocation ())
+ {
+ errno = ENOMEM;
+ return NULL;
+ }
+ extern __typeof (calloc) __libc_calloc;
+ return __libc_calloc (a, b);
+}
+
+void *
+realloc (void *ptr, size_t size)
+{
+ if (fail_this_allocation ())
+ {
+ errno = ENOMEM;
+ return NULL;
+ }
+ extern __typeof (realloc) __libc_realloc;
+ return __libc_realloc (ptr, size);
+}
+
+/* No-op subprocess to verify that support_isolate_in_subprocess does
+ not perform any heap allocations. */
+static void
+no_op (void *ignored)
+{
+}
+
+/* Perform a getlogin_r call in a subprocess, to obtain the number of
+ allocations used and the expected result of a successful call. */
+static void
+initialize (void *configure_lookup)
+{
+ shared->init_allocation_count = 0;
+ if (configure_lookup != NULL)
+ {
+ TEST_COMPARE (__nss_configure_lookup ("passwd", configure_lookup), 0);
+ shared->init_allocation_count = shared->allocation_count;
+ }
+
+ shared->name[0] = '\0';
+ int ret = getlogin_r (shared->name, sizeof (shared->name));
+ if (ret != 0)
+ {
+ printf ("info: getlogin_r failed: %s (%d)\n",
+ strerrorname_np (ret), ret);
+ shared->expected_failure = ret;
+ }
+ else
+ {
+ shared->expected_failure = 0;
+ if (shared->name[0] == '\0')
+ FAIL ("error: getlogin_r succeeded without result\n");
+ else
+ printf ("info: getlogin_r: \"%s\"\n", shared->name);
+ }
+}
+
+/* Perform getlogin_r in a subprocess with fault injection. */
+static void
+test_in_subprocess (void *configure_lookup)
+{
+ if (configure_lookup != NULL
+ && __nss_configure_lookup ("passwd", configure_lookup) < 0)
+ {
+ printf ("info: __nss_configure_lookup failed: %s (%d)\n",
+ strerrorname_np (errno), errno);
+ TEST_COMPARE (errno, ENOMEM);
+ TEST_VERIFY (shared->allocation_count <= shared->init_allocation_count);
+ return;
+ }
+
+ unsigned int inject_at = shared->failing_allocation;
+ char name[sizeof (shared->name)] = "name not set";
+ int ret = getlogin_r (name, sizeof (name));
+ shared->failing_allocation = ~0U;
+
+ if (ret == 0)
+ {
+ TEST_COMPARE (shared->expected_failure, 0);
+ TEST_COMPARE_STRING (name, shared->name);
+ }
+ else
+ {
+ printf ("info: allocation %u failure results in error %s (%d)\n",
+ inject_at, strerrorname_np (ret), ret);
+
+ if (ret != ENOMEM)
+ {
+ if (shared->expected_failure != 0)
+ TEST_COMPARE (ret, shared->expected_failure);
+ else if (configure_lookup == NULL)
+ /* The ENOENT failure can happen due to an issue related
+ to bug 22041: dlopen failure does not result in ENOMEM. */
+ TEST_COMPARE (ret, ENOENT);
+ else
+ FAIL ("unexpected getlogin_r error");
+ }
+ }
+
+ if (shared->expected_failure == 0)
+ {
+ /* The second call should succeed. */
+ puts ("info: about to perform second getlogin_r call");
+ ret = getlogin_r (name, sizeof (name));
+ if (configure_lookup == NULL)
+ {
+ /* This check can fail due to bug 22041 if the malloc error
+ injection causes a failure internally in dlopen. */
+ if (ret != 0)
+ {
+ printf ("warning: second getlogin_r call failed with %s (%d)\n",
+ strerrorname_np (ret), ret);
+ TEST_COMPARE (ret, ENOENT);
+ }
+ }
+ else
+ /* If __nss_configure_lookup has been called, the error caching
+ bug does not happen because nss_files is built-in, and the
+ second getlogin_r is expected to succeed. */
+ TEST_COMPARE (ret, 0);
+ if (ret == 0)
+ TEST_COMPARE_STRING (name, shared->name);
+ }
+}
+
+/* Set by the --failing-allocation command line option. Together with
+ --direct, this can be used to trigger an allocation failure in the
+ original process, which may help with debugging. */
+static int option_failing_allocation = -1;
+
+/* Set by --override, to be used with --failing-allocation. Turns on
+ the __nss_configure_lookup call for passwd/files, which is disabled
+ by default. */
+static int option_override = 0;
+
+static int
+do_test (void)
+{
+ char files[] = "files";
+
+ if (option_failing_allocation >= 0)
+ {
+ /* The test was invoked with --failing-allocation. Perform just
+ one test, using the original nsswitch.conf. This is a
+ condensed version of the probing/testing loop below. */
+ printf ("info: testing with failing allocation %d\n",
+ option_failing_allocation);
+ shared = support_shared_allocate (sizeof (*shared));
+ shared->failing_allocation = ~0U;
+ char *configure_lookup = option_override ? files : NULL;
+ support_isolate_in_subprocess (initialize, configure_lookup);
+ shared->allocation_count = 0;
+ shared->failing_allocation = option_failing_allocation;
+ test_in_subprocess (configure_lookup); /* No subprocess. */
+ support_shared_free (shared);
+ shared = NULL;
+ return 0;
+ }
+
+ bool any_success = false;
+
+ for (int do_configure_lookup = 0; do_configure_lookup < 2;
+ ++do_configure_lookup)
+ {
+ if (do_configure_lookup)
+ puts ("info: testing with nsswitch.conf override");
+ else
+ puts ("info: testing with original nsswitch.conf");
+
+ char *configure_lookup = do_configure_lookup ? files : NULL;
+
+ shared = support_shared_allocate (sizeof (*shared));
+
+ /* Disable fault injection. */
+ shared->failing_allocation = ~0U;
+
+ support_isolate_in_subprocess (no_op, NULL);
+ TEST_COMPARE (shared->allocation_count, 0);
+
+ support_isolate_in_subprocess (initialize, configure_lookup);
+
+ if (shared->name[0] != '\0')
+ any_success = true;
+
+ /* The number of allocations in the successful case. Once the
+ number of expected allocations is exceeded, injecting further
+ failures does not make a difference (assuming that the number
+ of malloc calls is deterministic). */
+ unsigned int maximum_allocation_count = shared->allocation_count;
+ printf ("info: initial getlogin_r performed %u allocations\n",
+ maximum_allocation_count);
+
+ for (unsigned int inject_at = 0; inject_at <= maximum_allocation_count;
+ ++inject_at)
+ {
+ printf ("info: running fault injection at allocation %u\n",
+ inject_at);
+ shared->allocation_count = 0;
+ shared->failing_allocation = inject_at;
+ support_isolate_in_subprocess (test_in_subprocess, configure_lookup);
+ }
+
+ support_shared_free (shared);
+ shared = NULL;
+ }
+
+ {
+ FILE *fp = fopen (_PATH_NSSWITCH_CONF, "r");
+ if (fp == NULL)
+ printf ("info: no %s file\n", _PATH_NSSWITCH_CONF);
+ else
+ {
+ printf ("info: %s contents follows\n", _PATH_NSSWITCH_CONF);
+ int last_ch = '\n';
+ while (true)
+ {
+ int ch = fgetc (fp);
+ if (ch == EOF)
+ break;
+ putchar (ch);
+ last_ch = ch;
+ }
+ if (last_ch != '\n')
+ putchar ('\n');
+ printf ("(end of %s contents)\n", _PATH_NSSWITCH_CONF);
+ xfclose (fp);
+ }
+ }
+
+ support_record_failure_barrier ();
+
+ if (!any_success)
+ FAIL_UNSUPPORTED ("no successful getlogin_r calls");
+
+ return 0;
+}
+
+static void
+cmdline_process (int c)
+{
+ if (c == 'F')
+ option_failing_allocation = atoi (optarg);
+}
+
+#define CMDLINE_OPTIONS \
+ { "failing-allocation", required_argument, NULL, 'F' }, \
+ { "override", no_argument, &option_override, 1 },
+
+#define CMDLINE_PROCESS cmdline_process
+
+#include <support/test-driver.c>

View File

@ -0,0 +1,249 @@
commit 8ca2fe7e96c0ccf04d32d7002d7a6d9edcb9f8ee
Author: Sergey Kolosov <skolosov@redhat.com>
Date: Fri Oct 10 17:15:27 2025 +0200
resolv: Add tests for getaddrinfo returning EAI_AGAIN [BZ #16849]
This patch adds two tests that verify correct behavior of getaddrinfo
when DNS resolution fails with a temporary error. Both tests ensure
that getaddrinfo returns EAI_AGAIN in cases where no valid address can
be resolved due to network or resolver failure.
* tst-getaddrinfo-eai-again.c
Runs inside the glibc test-container without any DNS server
configured. The test performs queries using AF_INET, AF_INET6,
and AF_UNSPEC and verifies that getaddrinfo returns EAI_AGAIN
when resolution fails.
* tst-getaddrinfo-eai-again-timeout.c
Runs outside of the container but uses the resolv_test framework
to simulate network failures. The test covers two failure modes:
- No response from the server (resolv_response_drop)
- Zero-length reply from the server
In both cases, getaddrinfo is expected to return EAI_AGAIN.
Reviewed-by: Florian Weimer <fweimer@redhat.com>
diff --git a/resolv/Makefile b/resolv/Makefile
index f3f5c260d0b7471f..cb1ff182dfdd4572 100644
--- a/resolv/Makefile
+++ b/resolv/Makefile
@@ -80,7 +80,10 @@ routines_no_fortify += \
# routines_no_fortify
tests = tst-aton tst-leaks tst-inet_ntop
-tests-container = tst-leaks2
+tests-container += \
+ tst-getaddrinfo-eai-again \
+ tst-leaks2 \
+ # tests-container
tests-internal += tst-inet_aton_exact
@@ -136,6 +139,7 @@ tests-static += tst-ns_rr_cursor
# These tests need libdl.
ifeq (yes,$(build-shared))
tests += \
+ tst-getaddrinfo-eai-again-timeout \
tst-resolv-ai_idn \
tst-resolv-ai_idn-latin1 \
tst-resolv-ai_idn-nolibidn2 \
@@ -268,6 +272,8 @@ $(objpfx)mtrace-tst-resolv-res_ninit.out: $(objpfx)tst-resolv-res_ninit.out
$(objpfx)tst-bug18665-tcp: $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-bug18665: $(objpfx)libresolv.so $(shared-thread-library)
+$(objpfx)tst-getaddrinfo-eai-again-timeout: \
+ $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-resolv-ai_idn: $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-resolv-ai_idn-latin1: \
$(objpfx)libresolv.so $(shared-thread-library)
diff --git a/resolv/tst-getaddrinfo-eai-again-timeout.c b/resolv/tst-getaddrinfo-eai-again-timeout.c
new file mode 100644
index 0000000000000000..ec4a6563b7b5ae51
--- /dev/null
+++ b/resolv/tst-getaddrinfo-eai-again-timeout.c
@@ -0,0 +1,122 @@
+/* Test for BZ #16849. Verify that getaddrinfo correctly returns
+ EAI_AGAIN when DNS resolution fails due to timeout or malformed
+ responses.
+
+ This test uses two simulated failure modes:
+ - The DNS server does not respond at all (resolv_response_drop).
+ - The DNS server responds with a zero-length packet.
+
+ 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <netdb.h>
+#include <resolv.h>
+#include <stdbool.h>
+#include <support/check.h>
+#include <support/resolv_test.h>
+
+/* Track whether the callbacks were actually invoked. */
+static volatile bool response_called_drop = false;
+static volatile bool response_called_zero_len = false;
+
+/* Simulate a DNS server that sends a zero-length response. */
+static void
+response_zero_len (const struct resolv_response_context *ctx,
+ struct resolv_response_builder *b,
+ const char *qname, uint16_t qclass, uint16_t qtype)
+{
+ response_called_zero_len = true;
+ /* Do nothing — zero-length reply. */
+}
+
+/* Simulate a DNS server that drops the request. */
+static void
+response_drop (const struct resolv_response_context *ctx,
+ struct resolv_response_builder *b,
+ const char *qname, uint16_t qclass, uint16_t qtype)
+{
+ response_called_drop = true;
+ resolv_response_drop (b);
+}
+
+/* Query getaddrinfo for multiple families and expect EAI_AGAIN. */
+static void
+query_host (const char *host_name)
+{
+ int family[] = { AF_INET, AF_INET6, AF_UNSPEC };
+ const char *family_names[] = { "AF_INET", "AF_INET6", "AF_UNSPEC" };
+
+ for (int i = 0; i < 3; i++)
+ {
+ struct addrinfo hints =
+ {
+ .ai_socktype = 0,
+ .ai_protocol = 0,
+ .ai_family = family[i],
+ .ai_flags = 0,
+ };
+ struct addrinfo *result;
+ int res = getaddrinfo (host_name, NULL, &hints, &result);
+ if (res != EAI_AGAIN)
+ FAIL_EXIT1 ("getaddrinfo (%s, %s) returned %s, expected EAI_AGAIN",
+ host_name, family_names[i], gai_strerror (res));
+ }
+}
+
+/* Simulate DNS server dropping all queries. */
+static void
+test_drop (void)
+{
+ struct resolv_test *aux = resolv_test_start
+ ((struct resolv_redirect_config)
+ {
+ .response_callback = response_drop,
+ });
+ /* Reduce default timeout to make the test run faster. */
+ _res.retrans = 1;
+ _res.retry = 1;
+ query_host ("site.example");
+ resolv_test_end (aux);
+}
+
+/* Simulate DNS server sending zero-length responses. */
+static void
+test_zero_len_packet (void)
+{
+ struct resolv_test *aux = resolv_test_start
+ ((struct resolv_redirect_config)
+ {
+ .response_callback = response_zero_len,
+ });
+ query_host ("site.example");
+ resolv_test_end (aux);
+}
+
+static int
+do_test (void)
+{
+ test_drop ();
+ test_zero_len_packet ();
+
+ if (!response_called_drop)
+ FAIL_EXIT1 ("response_drop callback was not called");
+ if (!response_called_zero_len)
+ FAIL_EXIT1 ("response_zero_len callback was not called");
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/resolv/tst-getaddrinfo-eai-again.c b/resolv/tst-getaddrinfo-eai-again.c
new file mode 100644
index 0000000000000000..21daa6c1682d1156
--- /dev/null
+++ b/resolv/tst-getaddrinfo-eai-again.c
@@ -0,0 +1,56 @@
+/* Test for BZ #16849. Verify that getaddrinfo correctly returns
+ EAI_AGAIN error code if DNS query fails due to a network failure.
+
+ 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <netdb.h>
+#include <support/check.h>
+
+/* Query getaddrinfo with various address families and verify that
+ it returns EAI_AGAIN when DNS resolution fails. */
+static void
+query_host (const char *host_name)
+{
+ int family[] = { AF_INET, AF_INET6, AF_UNSPEC };
+ const char *family_names[] = { "AF_INET", "AF_INET6", "AF_UNSPEC" };
+
+ for (int i = 0; i < 3; i++)
+ {
+ struct addrinfo hints =
+ {
+ .ai_socktype = 0,
+ .ai_protocol = 0,
+ .ai_family = family[i],
+ .ai_flags = 0,
+ };
+ struct addrinfo *result;
+ int res = getaddrinfo (host_name, NULL, &hints, &result);
+ if (res != EAI_AGAIN)
+ FAIL_EXIT1 ("getaddrinfo (%s, %s) returned %s, expected EAI_AGAIN",
+ host_name, family_names[i], gai_strerror (res));
+ }
+}
+
+static int
+do_test (void)
+{
+ query_host ("site.example");
+ return 0;
+}
+
+#include <support/test-driver.c>

View File

@ -0,0 +1,258 @@
commit c995686e2cbe2a3ab2a11877a61c14a2e1fc35cb
Author: Florian Weimer <fweimer@redhat.com>
Date: Tue Mar 3 18:48:47 2026 +0100
support: no_override_resolv_conf_search flag for resolver test framework
It is required to test "search ." in /etc/resolv.conf files. The
default is to override the search path isolate from unexpected
settings in the test execution environment.
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Conflicts:
resolv/Makefile
(tst-resolv-invalid-cname dependencies were added
in a different place on the 2.34 upstream branch)
diff --git a/resolv/Makefile b/resolv/Makefile
index cb1ff182dfdd4572..116ab6a1574eb1ff 100644
--- a/resolv/Makefile
+++ b/resolv/Makefile
@@ -83,6 +83,7 @@ tests = tst-aton tst-leaks tst-inet_ntop
tests-container += \
tst-getaddrinfo-eai-again \
tst-leaks2 \
+ tst-resolv-no-search \
# tests-container
tests-internal += tst-inet_aton_exact
@@ -294,10 +295,11 @@ $(objpfx)tst-resolv-res_init-multi: $(objpfx)libresolv.so \
$(shared-thread-library)
$(objpfx)tst-resolv-res_init-thread: $(objpfx)libresolv.so \
$(shared-thread-library)
-$(objpfx)tst-resolv-noaaaa: $(objpfx)libresolv.so $(shared-thread-library)
-$(objpfx)tst-resolv-noaaaa-vc: $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-resolv-invalid-cname: $(objpfx)libresolv.so \
$(shared-thread-library)
+$(objpfx)tst-resolv-no-search: $(objpfx)libresolv.so $(shared-thread-library)
+$(objpfx)tst-resolv-noaaaa: $(objpfx)libresolv.so $(shared-thread-library)
+$(objpfx)tst-resolv-noaaaa-vc: $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-resolv-nondecimal: $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-resolv-qtypes: $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-resolv-rotate: $(objpfx)libresolv.so $(shared-thread-library)
diff --git a/resolv/tst-resolv-no-search.c b/resolv/tst-resolv-no-search.c
new file mode 100644
index 0000000000000000..29701d4772507507
--- /dev/null
+++ b/resolv/tst-resolv-no-search.c
@@ -0,0 +1,174 @@
+/* Test using "search ." in /etc/resolv.conf.
+ Copyright (C) 2026 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <netdb.h>
+#include <resolv.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/check_nss.h>
+#include <support/namespace.h>
+#include <support/resolv_test.h>
+#include <support/support.h>
+
+/* Check that plain res_init loads the configuration as expected. */
+static void
+test_res_init (void *ignored)
+{
+ res_init ();
+ TEST_COMPARE_STRING (_res.dnsrch[0], ".");
+ TEST_COMPARE_STRING (_res.dnsrch[1], NULL);
+}
+
+static void
+response (const struct resolv_response_context *ctx,
+ struct resolv_response_builder *b,
+ const char *qname, uint16_t qclass, uint16_t qtype)
+{
+ TEST_VERIFY_EXIT (qclass == C_IN);
+ TEST_COMPARE (ctx->server_index, 0);
+
+ if (strncmp (qname, "does-not-exist", strlen ("does-not-exist")) == 0)
+ {
+ resolv_response_init (b, (struct resolv_response_flags)
+ { .rcode = ns_r_nxdomain });
+ resolv_response_add_question (b, qname, qclass, qtype);
+ return;
+ }
+
+ resolv_response_init (b, (struct resolv_response_flags) { });
+ resolv_response_add_question (b, qname, qclass, qtype);
+ resolv_response_section (b, ns_s_an);
+
+ resolv_response_open_record (b, qname, qclass, qtype, 0);
+ switch (qtype)
+ {
+ case T_A:
+ {
+ char ipv4[4] = {192, 0, 2, 17};
+ resolv_response_add_data (b, &ipv4, sizeof (ipv4));
+ }
+ break;
+ case T_AAAA:
+ {
+ char ipv6[16]
+ = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};
+ resolv_response_add_data (b, &ipv6, sizeof (ipv6));
+ }
+ break;
+ }
+ resolv_response_close_record (b);
+}
+
+static void
+check_h (const char *name, int family, const char *expected)
+{
+ if (family == AF_INET)
+ {
+ char *query = xasprintf ("gethostbyname (\"%s\")", name);
+ check_hostent (query, gethostbyname (name), expected);
+ free (query);
+ }
+ {
+ char *query = xasprintf ("gethostbyname2 (\"%s\", %d)", name, family);
+ check_hostent (query, gethostbyname2 (name, family), expected);
+ free (query);
+ }
+}
+
+static void
+check_ai (const char *name, int family, const char *expected)
+{
+ struct addrinfo hints = { .ai_family = family, .ai_socktype = SOCK_STREAM, };
+ struct addrinfo *ai;
+ char *query = xasprintf ("%s:80 [%d]", name, hints.ai_family);
+ int ret = getaddrinfo (name, "80", &hints, &ai);
+ check_addrinfo (query, ai, ret, expected);
+ if (ret == 0)
+ freeaddrinfo (ai);
+ free (query);
+}
+
+static int
+do_test (void)
+{
+ support_isolate_in_subprocess (test_res_init, NULL);
+
+ struct resolv_test *aux = resolv_test_start
+ ((struct resolv_redirect_config)
+ {
+ .response_callback = response,
+ .no_override_resolv_conf_search = true,
+ });
+
+ check_h ("www.example", AF_INET,
+ "name: www.example\n"
+ "address: 192.0.2.17\n");
+ check_h ("www.example", AF_INET6,
+ "name: www.example\n"
+ "address: 2001:db8::1\n");
+ check_ai ("www.example", AF_UNSPEC,
+ "address: STREAM/TCP 192.0.2.17 80\n"
+ "address: STREAM/TCP 2001:db8::1 80\n");
+ check_ai ("www.example", AF_INET,
+ "address: STREAM/TCP 192.0.2.17 80\n");
+ check_ai ("www.example", AF_INET6,
+ "address: STREAM/TCP 2001:db8::1 80\n");
+ check_h ("does-not-exist.example", AF_INET,
+ "error: HOST_NOT_FOUND\n");
+ check_h ("does-not-exist.example", AF_INET6,
+ "error: HOST_NOT_FOUND\n");
+ check_ai ("does-not-exist.example", AF_UNSPEC,
+ "error: Name or service not known\n");
+ check_ai ("does-not-exist.example", AF_INET,
+ "error: Name or service not known\n");
+ check_ai ("does-not-exist.example", AF_INET6,
+ "error: Name or service not known\n");
+
+ /* With trailing dot. */
+ check_h ("www.example.", AF_INET,
+ "name: www.example\n"
+ "address: 192.0.2.17\n");
+ check_h ("www.example.", AF_INET6,
+ "name: www.example\n"
+ "address: 2001:db8::1\n");
+ check_ai ("www.example.", AF_UNSPEC,
+ "address: STREAM/TCP 192.0.2.17 80\n"
+ "address: STREAM/TCP 2001:db8::1 80\n");
+ check_ai ("www.example.", AF_INET,
+ "address: STREAM/TCP 192.0.2.17 80\n");
+ check_ai ("www.example.", AF_INET6,
+ "address: STREAM/TCP 2001:db8::1 80\n");
+ check_h ("does-not-exist.example.", AF_INET,
+ "error: HOST_NOT_FOUND\n");
+ check_h ("does-not-exist.example.", AF_INET6,
+ "error: HOST_NOT_FOUND\n");
+ check_ai ("does-not-exist.example.", AF_UNSPEC,
+ "error: Name or service not known\n");
+ check_ai ("does-not-exist.example.", AF_INET,
+ "error: Name or service not known\n");
+ check_ai ("does-not-exist.example.", AF_INET6,
+ "error: Name or service not known\n");
+
+ resolv_test_end (aux);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/resolv/tst-resolv-no-search.root/etc/resolv.conf b/resolv/tst-resolv-no-search.root/etc/resolv.conf
new file mode 100644
index 0000000000000000..5ace648869a31edb
--- /dev/null
+++ b/resolv/tst-resolv-no-search.root/etc/resolv.conf
@@ -0,0 +1 @@
+search .
diff --git a/support/resolv_test.c b/support/resolv_test.c
index 73d20572f3b843c7..7272334b78fc85f1 100644
--- a/support/resolv_test.c
+++ b/support/resolv_test.c
@@ -1098,6 +1098,9 @@ resolv_test_init (void)
static void
set_search_path (struct resolv_redirect_config config)
{
+ if (config.no_override_resolv_conf_search)
+ return;
+
memset (_res.defdname, 0, sizeof (_res.defdname));
memset (_res.dnsrch, 0, sizeof (_res.dnsrch));
diff --git a/support/resolv_test.h b/support/resolv_test.h
index ddf967449e398710..e0a17ebf23c34880 100644
--- a/support/resolv_test.h
+++ b/support/resolv_test.h
@@ -96,6 +96,9 @@ struct resolv_redirect_config
domain name as well. */
const char *search[7];
+ /* If true, do not override the search path loaded from /etc/resolv.conf. */
+ bool no_override_resolv_conf_search;
+
/* Number of servers to activate in resolv. 0 means the default,
resolv_max_test_servers. */
int nscount;

View File

@ -0,0 +1,98 @@
commit dc9ca785a5fe2059a9b04ab336520d463d9a715b
Author: Carlos Peón Costa <carlospeon@gmail.com>
Date: Tue Mar 3 18:48:47 2026 +0100
resolv: Avoid duplicate query if search list contains '.' (bug 33804)
Co-authored-by: Florian Weimer <fweimer@redhat.com>
Signed-off-by: Florian Weimer <fweimer@redhat.com>
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
diff --git a/resolv/res_query.c b/resolv/res_query.c
index 1d2c81737bc889c9..bae7b3c23d778a7d 100644
--- a/resolv/res_query.c
+++ b/resolv/res_query.c
@@ -358,7 +358,7 @@ __res_context_search (struct resolv_context *ctx,
char tmp[NS_MAXDNAME];
u_int dots;
int trailing_dot, ret, saved_herrno;
- int got_nodata = 0, got_servfail = 0, root_on_list = 0;
+ int got_nodata = 0, got_servfail = 0;
int tried_as_is = 0;
int searched = 0;
@@ -437,8 +437,11 @@ __res_context_search (struct resolv_context *ctx,
domain. */
if (dname[0] == '.')
dname++;
- if (dname[0] == '\0')
- root_on_list++;
+ if (dname[0] == '\0') {
+ if (tried_as_is)
+ continue;
+ tried_as_is++;
+ }
ret = __res_context_querydomain
(ctx, name, dname, class, type,
@@ -510,7 +513,7 @@ __res_context_search (struct resolv_context *ctx,
* unless RES_NOTLDQUERY is set and there were no dots.
*/
if ((dots || !searched || (statp->options & RES_NOTLDQUERY) == 0)
- && !(tried_as_is || root_on_list)) {
+ && !tried_as_is) {
ret = __res_context_querydomain
(ctx, name, NULL, class, type,
answer, anslen, answerp, answerp2, nanswerp2,
diff --git a/resolv/tst-resolv-no-search.c b/resolv/tst-resolv-no-search.c
index 29701d4772507507..7d78d4044cf50e48 100644
--- a/resolv/tst-resolv-no-search.c
+++ b/resolv/tst-resolv-no-search.c
@@ -27,6 +27,11 @@
#include <support/resolv_test.h>
#include <support/support.h>
+/* Used to check for duplicated queries (bug 33804). POSIX does not
+ explicitly say that socket calls (as used in the resolver tests)
+ provide synchronization. */
+static _Atomic unsigned int query_count;
+
/* Check that plain res_init loads the configuration as expected. */
static void
test_res_init (void *ignored)
@@ -43,6 +48,7 @@ response (const struct resolv_response_context *ctx,
{
TEST_VERIFY_EXIT (qclass == C_IN);
TEST_COMPARE (ctx->server_index, 0);
+ ++query_count;
if (strncmp (qname, "does-not-exist", strlen ("does-not-exist")) == 0)
{
@@ -82,12 +88,16 @@ check_h (const char *name, int family, const char *expected)
if (family == AF_INET)
{
char *query = xasprintf ("gethostbyname (\"%s\")", name);
+ query_count = 0;
check_hostent (query, gethostbyname (name), expected);
+ TEST_COMPARE (query_count, 1);
free (query);
}
{
char *query = xasprintf ("gethostbyname2 (\"%s\", %d)", name, family);
+ query_count = 0;
check_hostent (query, gethostbyname2 (name, family), expected);
+ TEST_COMPARE (query_count, 1);
free (query);
}
}
@@ -98,8 +108,10 @@ check_ai (const char *name, int family, const char *expected)
struct addrinfo hints = { .ai_family = family, .ai_socktype = SOCK_STREAM, };
struct addrinfo *ai;
char *query = xasprintf ("%s:80 [%d]", name, hints.ai_family);
+ query_count = 0;
int ret = getaddrinfo (name, "80", &hints, &ai);
check_addrinfo (query, ai, ret, expected);
+ TEST_COMPARE (query_count, family == AF_UNSPEC ? 2 : 1);
if (ret == 0)
freeaddrinfo (ai);
free (query);

View File

@ -0,0 +1,369 @@
commit 5729e0e9af590807df66a3db688008f9547bce9f
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Fri Feb 10 14:09:10 2023 -0300
iconv: Remove _STRING_ARCH_unaligned usage for get/set macros
And use a packed structure instead. The compiler generates optimized
unaligned code if the architecture supports it.
Checked on x86_64-linux-gnu and i686-linux-gnu.
Reviewed-by: Wilco Dijkstra <Wilco.Dijkstra@arm.com>
diff --git a/iconv/gconv_int.h b/iconv/gconv_int.h
index 499bc6bfa54fe37d..360924cc0394d86b 100644
--- a/iconv/gconv_int.h
+++ b/iconv/gconv_int.h
@@ -27,6 +27,34 @@
__BEGIN_DECLS
+/* We have to provide support for machines which are not able to handled
+ unaligned memory accesses. Some of the character encodings have
+ representations with a fixed width of 2 or 4 bytes. */
+#define get16(addr) \
+({ \
+ const struct { uint16_t r; } __attribute__ ((__packed__)) *__ptr \
+ = (__typeof(__ptr))(addr); \
+ __ptr->r; \
+})
+#define get32(addr) \
+({ \
+ const struct { uint32_t r; } __attribute__ ((__packed__)) *__ptr \
+ = (__typeof(__ptr))(addr); \
+ __ptr->r; \
+})
+
+#define put16(addr, val) \
+do { \
+ struct { uint16_t r; } __attribute__ ((__packed__)) *__ptr \
+ = (__typeof(__ptr))(addr); \
+ __ptr->r = val; \
+} while (0)
+#define put32(addr, val) \
+do { \
+ struct { uint32_t r; } __attribute__ ((__packed__)) *__ptr \
+ = (__typeof(__ptr))(addr); \
+ __ptr->r = val; \
+} while (0)
/* Structure for alias definition. Simply two strings. */
struct gconv_alias
diff --git a/iconv/loop.c b/iconv/loop.c
index 7193e8f20104bf84..851caa9d42d45a47 100644
--- a/iconv/loop.c
+++ b/iconv/loop.c
@@ -58,75 +58,10 @@
#include <stddef.h>
#include <libc-diag.h>
-/* We have to provide support for machines which are not able to handled
- unaligned memory accesses. Some of the character encodings have
- representations with a fixed width of 2 or 4 bytes. But if we cannot
- access unaligned memory we still have to read byte-wise. */
#undef FCTNAME2
#if _STRING_ARCH_unaligned || !defined DEFINE_UNALIGNED
-/* We can handle unaligned memory access. */
-# define get16(addr) *((const uint16_t *) (addr))
-# define get32(addr) *((const uint32_t *) (addr))
-
-/* We need no special support for writing values either. */
-# define put16(addr, val) *((uint16_t *) (addr)) = (val)
-# define put32(addr, val) *((uint32_t *) (addr)) = (val)
-
# define FCTNAME2(name) name
#else
-/* Distinguish between big endian and little endian. */
-# if __BYTE_ORDER == __LITTLE_ENDIAN
-# define get16(addr) \
- (((const unsigned char *) (addr))[1] << 8 \
- | ((const unsigned char *) (addr))[0])
-# define get32(addr) \
- (((((const unsigned char *) (addr))[3] << 8 \
- | ((const unsigned char *) (addr))[2]) << 8 \
- | ((const unsigned char *) (addr))[1]) << 8 \
- | ((const unsigned char *) (addr))[0])
-
-# define put16(addr, val) \
- ({ uint16_t __val = (val); \
- ((unsigned char *) (addr))[0] = __val; \
- ((unsigned char *) (addr))[1] = __val >> 8; \
- (void) 0; })
-# define put32(addr, val) \
- ({ uint32_t __val = (val); \
- ((unsigned char *) (addr))[0] = __val; \
- __val >>= 8; \
- ((unsigned char *) (addr))[1] = __val; \
- __val >>= 8; \
- ((unsigned char *) (addr))[2] = __val; \
- __val >>= 8; \
- ((unsigned char *) (addr))[3] = __val; \
- (void) 0; })
-# else
-# define get16(addr) \
- (((const unsigned char *) (addr))[0] << 8 \
- | ((const unsigned char *) (addr))[1])
-# define get32(addr) \
- (((((const unsigned char *) (addr))[0] << 8 \
- | ((const unsigned char *) (addr))[1]) << 8 \
- | ((const unsigned char *) (addr))[2]) << 8 \
- | ((const unsigned char *) (addr))[3])
-
-# define put16(addr, val) \
- ({ uint16_t __val = (val); \
- ((unsigned char *) (addr))[1] = __val; \
- ((unsigned char *) (addr))[0] = __val >> 8; \
- (void) 0; })
-# define put32(addr, val) \
- ({ uint32_t __val = (val); \
- ((unsigned char *) (addr))[3] = __val; \
- __val >>= 8; \
- ((unsigned char *) (addr))[2] = __val; \
- __val >>= 8; \
- ((unsigned char *) (addr))[1] = __val; \
- __val >>= 8; \
- ((unsigned char *) (addr))[0] = __val; \
- (void) 0; })
-# endif
-
# define FCTNAME2(name) name##_unaligned
#endif
#define FCTNAME(name) FCTNAME2(name)
@@ -352,10 +287,6 @@ FCTNAME (LOOPFCT) (struct __gconv_step *step,
#if !defined DEFINE_UNALIGNED && !_STRING_ARCH_unaligned \
&& MIN_NEEDED_INPUT != 1 && MAX_NEEDED_INPUT % MIN_NEEDED_INPUT == 0 \
&& MIN_NEEDED_OUTPUT != 1 && MAX_NEEDED_OUTPUT % MIN_NEEDED_OUTPUT == 0
-# undef get16
-# undef get32
-# undef put16
-# undef put32
# undef unaligned
# define DEFINE_UNALIGNED
@@ -537,8 +468,4 @@ gconv_btowc (struct __gconv_step *step, unsigned char c)
#undef LOOP_NEED_STATE
#undef LOOP_NEED_FLAGS
#undef LOOP_NEED_DATA
-#undef get16
-#undef get32
-#undef put16
-#undef put32
#undef unaligned
diff --git a/iconv/skeleton.c b/iconv/skeleton.c
index 0ddc16ad1d7e0184..6992701808079ec2 100644
--- a/iconv/skeleton.c
+++ b/iconv/skeleton.c
@@ -205,73 +205,6 @@
#endif
-/* Define macros which can access unaligned buffers. These macros are
- supposed to be used only in code outside the inner loops. For the inner
- loops we have other definitions which allow optimized access. */
-#if _STRING_ARCH_unaligned
-/* We can handle unaligned memory access. */
-# define get16u(addr) *((const uint16_t *) (addr))
-# define get32u(addr) *((const uint32_t *) (addr))
-
-/* We need no special support for writing values either. */
-# define put16u(addr, val) *((uint16_t *) (addr)) = (val)
-# define put32u(addr, val) *((uint32_t *) (addr)) = (val)
-#else
-/* Distinguish between big endian and little endian. */
-# if __BYTE_ORDER == __LITTLE_ENDIAN
-# define get16u(addr) \
- (((const unsigned char *) (addr))[1] << 8 \
- | ((const unsigned char *) (addr))[0])
-# define get32u(addr) \
- (((((const unsigned char *) (addr))[3] << 8 \
- | ((const unsigned char *) (addr))[2]) << 8 \
- | ((const unsigned char *) (addr))[1]) << 8 \
- | ((const unsigned char *) (addr))[0])
-
-# define put16u(addr, val) \
- ({ uint16_t __val = (val); \
- ((unsigned char *) (addr))[0] = __val; \
- ((unsigned char *) (addr))[1] = __val >> 8; \
- (void) 0; })
-# define put32u(addr, val) \
- ({ uint32_t __val = (val); \
- ((unsigned char *) (addr))[0] = __val; \
- __val >>= 8; \
- ((unsigned char *) (addr))[1] = __val; \
- __val >>= 8; \
- ((unsigned char *) (addr))[2] = __val; \
- __val >>= 8; \
- ((unsigned char *) (addr))[3] = __val; \
- (void) 0; })
-# else
-# define get16u(addr) \
- (((const unsigned char *) (addr))[0] << 8 \
- | ((const unsigned char *) (addr))[1])
-# define get32u(addr) \
- (((((const unsigned char *) (addr))[0] << 8 \
- | ((const unsigned char *) (addr))[1]) << 8 \
- | ((const unsigned char *) (addr))[2]) << 8 \
- | ((const unsigned char *) (addr))[3])
-
-# define put16u(addr, val) \
- ({ uint16_t __val = (val); \
- ((unsigned char *) (addr))[1] = __val; \
- ((unsigned char *) (addr))[0] = __val >> 8; \
- (void) 0; })
-# define put32u(addr, val) \
- ({ uint32_t __val = (val); \
- ((unsigned char *) (addr))[3] = __val; \
- __val >>= 8; \
- ((unsigned char *) (addr))[2] = __val; \
- __val >>= 8; \
- ((unsigned char *) (addr))[1] = __val; \
- __val >>= 8; \
- ((unsigned char *) (addr))[0] = __val; \
- (void) 0; })
-# endif
-#endif
-
-
/* For conversions from a fixed width character set to another fixed width
character set we can define RESET_INPUT_BUFFER in a very fast way. */
#if !defined RESET_INPUT_BUFFER && !defined SAVE_RESET_STATE
diff --git a/iconvdata/iso-2022-jp-3.c b/iconvdata/iso-2022-jp-3.c
index 220d8d1cf174fdd7..97e735c755ab8093 100644
--- a/iconvdata/iso-2022-jp-3.c
+++ b/iconvdata/iso-2022-jp-3.c
@@ -93,7 +93,7 @@ enum
if (__glibc_likely (outbuf + 4 <= outend)) \
{ \
/* Write out the last character. */ \
- put32u (outbuf, ch); \
+ put32 (outbuf, ch); \
outbuf += 4; \
data->__statep->__count &= 7; \
data->__statep->__count |= ASCII_set; \
diff --git a/iconvdata/unicode.c b/iconvdata/unicode.c
index 525abe48b94118e5..ee19b2041c7d4fc5 100644
--- a/iconvdata/unicode.c
+++ b/iconvdata/unicode.c
@@ -52,10 +52,10 @@
return (inptr == inend \
? __GCONV_EMPTY_INPUT : __GCONV_INCOMPLETE_INPUT); \
\
- if (get16u (inptr) == BOM) \
+ if (get16 (inptr) == BOM) \
/* Simply ignore the BOM character. */ \
*inptrp = inptr += 2; \
- else if (get16u (inptr) == BOM_OE) \
+ else if (get16 (inptr) == BOM_OE) \
{ \
data->__flags |= __GCONV_SWAP; \
*inptrp = inptr += 2; \
@@ -68,7 +68,7 @@
if (__glibc_unlikely (outbuf + 2 > outend)) \
return __GCONV_FULL_OUTPUT; \
\
- put16u (outbuf, BOM); \
+ put16 (outbuf, BOM); \
outbuf += 2; \
} \
swap = data->__flags & __GCONV_SWAP;
diff --git a/iconvdata/utf-16.c b/iconvdata/utf-16.c
index 63cf43a09b4abf5d..87c59e9ecf1602ac 100644
--- a/iconvdata/utf-16.c
+++ b/iconvdata/utf-16.c
@@ -56,10 +56,10 @@
return (inptr == inend \
? __GCONV_EMPTY_INPUT : __GCONV_INCOMPLETE_INPUT); \
\
- if (get16u (inptr) == BOM) \
+ if (get16 (inptr) == BOM) \
/* Simply ignore the BOM character. */ \
*inptrp = inptr += 2; \
- else if (get16u (inptr) == BOM_OE) \
+ else if (get16 (inptr) == BOM_OE) \
{ \
data->__flags |= __GCONV_SWAP; \
*inptrp = inptr += 2; \
@@ -71,7 +71,7 @@
if (__glibc_unlikely (outbuf + 2 > outend)) \
return __GCONV_FULL_OUTPUT; \
\
- put16u (outbuf, BOM); \
+ put16 (outbuf, BOM); \
outbuf += 2; \
} \
} \
diff --git a/iconvdata/utf-32.c b/iconvdata/utf-32.c
index 4fbd7bc18fa2d0c9..060f77230b0101fe 100644
--- a/iconvdata/utf-32.c
+++ b/iconvdata/utf-32.c
@@ -52,10 +52,10 @@
return (inptr == inend \
? __GCONV_EMPTY_INPUT : __GCONV_INCOMPLETE_INPUT); \
\
- if (get32u (inptr) == BOM) \
+ if (get32 (inptr) == BOM) \
/* Simply ignore the BOM character. */ \
*inptrp = inptr += 4; \
- else if (get32u (inptr) == BOM_OE) \
+ else if (get32 (inptr) == BOM_OE) \
{ \
data->__flags |= __GCONV_SWAP; \
*inptrp = inptr += 4; \
@@ -69,7 +69,7 @@
if (__glibc_unlikely (outbuf + 4 > outend)) \
return __GCONV_FULL_OUTPUT; \
\
- put32u (outbuf, BOM); \
+ put32 (outbuf, BOM); \
outbuf += 4; \
} \
else if (__builtin_expect (data->__invocation_counter == 0, 0) \
diff --git a/sysdeps/s390/utf16-utf32-z9.c b/sysdeps/s390/utf16-utf32-z9.c
index 93895f9db8004f72..7d3de99f440a8ab4 100644
--- a/sysdeps/s390/utf16-utf32-z9.c
+++ b/sysdeps/s390/utf16-utf32-z9.c
@@ -177,7 +177,7 @@ gconv_end (struct __gconv_step *data)
if (__glibc_unlikely (outbuf + 2 > outend)) \
return __GCONV_FULL_OUTPUT; \
\
- put16u (outbuf, BOM_UTF16); \
+ put16 (outbuf, BOM_UTF16); \
outbuf += 2; \
} \
else \
@@ -186,7 +186,7 @@ gconv_end (struct __gconv_step *data)
if (__glibc_unlikely (outbuf + 4 > outend)) \
return __GCONV_FULL_OUTPUT; \
\
- put32u (outbuf, BOM_UTF32); \
+ put32 (outbuf, BOM_UTF32); \
outbuf += 4; \
} \
}
diff --git a/sysdeps/s390/utf8-utf16-z9.c b/sysdeps/s390/utf8-utf16-z9.c
index 1ff16b5bec4c10fd..615ebaac13a4c0a7 100644
--- a/sysdeps/s390/utf8-utf16-z9.c
+++ b/sysdeps/s390/utf8-utf16-z9.c
@@ -217,7 +217,7 @@ gconv_end (struct __gconv_step *data)
if (__glibc_unlikely (outbuf + 2 > outend)) \
return __GCONV_FULL_OUTPUT; \
\
- put16u (outbuf, BOM_UTF16); \
+ put16 (outbuf, BOM_UTF16); \
outbuf += 2; \
}
diff --git a/sysdeps/s390/utf8-utf32-z9.c b/sysdeps/s390/utf8-utf32-z9.c
index f1f4d3c89e6469c1..364558c0e2bed517 100644
--- a/sysdeps/s390/utf8-utf32-z9.c
+++ b/sysdeps/s390/utf8-utf32-z9.c
@@ -217,7 +217,7 @@ gconv_end (struct __gconv_step *data)
if (__glibc_unlikely (outbuf + 4 > outend)) \
return __GCONV_FULL_OUTPUT; \
\
- put32u (outbuf, BOM); \
+ put32 (outbuf, BOM); \
outbuf += 4; \
}

View File

@ -0,0 +1,694 @@
commit 3e20ddade31d9c392d8ccf7ec902172f4bb01c2b
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Fri Feb 10 16:37:36 2023 -0300
iconv: Remove _STRING_ARCH_unaligned usage
Use put/get macros __builtin_bswap32 instead. It allows to remove
the unaligned routines, the compiler will generate unaligned access
if the ABI allows it.
Checked on x86_64-linux-gnu and i686-linux-gnu.
Reviewed-by: Wilco Dijkstra <Wilco.Dijkstra@arm.com>
diff --git a/iconv/gconv_simple.c b/iconv/gconv_simple.c
index 882d3642e445b107..d368284411c79282 100644
--- a/iconv/gconv_simple.c
+++ b/iconv/gconv_simple.c
@@ -87,69 +87,22 @@ internal_ucs4_loop (struct __gconv_step *step,
#if __BYTE_ORDER == __LITTLE_ENDIAN
/* Sigh, we have to do some real work. */
size_t cnt;
- uint32_t *outptr32 = (uint32_t *) outptr;
-
- for (cnt = 0; cnt < n_convert; ++cnt, inptr += 4)
- *outptr32++ = bswap_32 (*(const uint32_t *) inptr);
-
- *inptrp = inptr;
- *outptrp = (unsigned char *) outptr32;
-#elif __BYTE_ORDER == __BIG_ENDIAN
- /* Simply copy the data. */
- *inptrp = inptr + n_convert * 4;
- *outptrp = __mempcpy (outptr, inptr, n_convert * 4);
-#else
-# error "This endianness is not supported."
-#endif
-
- /* Determine the status. */
- if (*inptrp == inend)
- result = __GCONV_EMPTY_INPUT;
- else if (*outptrp + 4 > outend)
- result = __GCONV_FULL_OUTPUT;
- else
- result = __GCONV_INCOMPLETE_INPUT;
-
- return result;
-}
-
-#if !_STRING_ARCH_unaligned
-static inline int
-__attribute ((always_inline))
-internal_ucs4_loop_unaligned (struct __gconv_step *step,
- struct __gconv_step_data *step_data,
- const unsigned char **inptrp,
- const unsigned char *inend,
- unsigned char **outptrp,
- const unsigned char *outend,
- size_t *irreversible)
-{
- const unsigned char *inptr = *inptrp;
- unsigned char *outptr = *outptrp;
- size_t n_convert = MIN (inend - inptr, outend - outptr) / 4;
- int result;
-
-# if __BYTE_ORDER == __LITTLE_ENDIAN
- /* Sigh, we have to do some real work. */
- size_t cnt;
for (cnt = 0; cnt < n_convert; ++cnt, inptr += 4, outptr += 4)
{
- outptr[0] = inptr[3];
- outptr[1] = inptr[2];
- outptr[2] = inptr[1];
- outptr[3] = inptr[0];
+ uint32_t val = get32 (inptr);
+ put32 (outptr, __builtin_bswap32 (val));
}
*inptrp = inptr;
*outptrp = outptr;
-# elif __BYTE_ORDER == __BIG_ENDIAN
+#elif __BYTE_ORDER == __BIG_ENDIAN
/* Simply copy the data. */
*inptrp = inptr + n_convert * 4;
*outptrp = __mempcpy (outptr, inptr, n_convert * 4);
-# else
-# error "This endianess is not supported."
-# endif
+#else
+# error "This endianness is not supported."
+#endif
/* Determine the status. */
if (*inptrp == inend)
@@ -161,7 +114,6 @@ internal_ucs4_loop_unaligned (struct __gconv_step *step,
return result;
}
-#endif
static inline int
@@ -243,12 +195,9 @@ ucs4_internal_loop (struct __gconv_step *step,
for (; inptr + 4 <= inend && outptr + 4 <= outend; inptr += 4)
{
- uint32_t inval;
-
+ uint32_t inval = get32 (inptr);
#if __BYTE_ORDER == __LITTLE_ENDIAN
- inval = bswap_32 (*(const uint32_t *) inptr);
-#else
- inval = *(const uint32_t *) inptr;
+ inval = __builtin_bswap32 (inval);
#endif
if (__glibc_unlikely (inval > 0x7fffffff))
@@ -273,7 +222,7 @@ ucs4_internal_loop (struct __gconv_step *step,
return __gconv_mark_illegal_input (step_data);
}
- *((uint32_t *) outptr) = inval;
+ put32 (outptr, inval);
outptr += sizeof (uint32_t);
}
@@ -291,75 +240,6 @@ ucs4_internal_loop (struct __gconv_step *step,
return result;
}
-#if !_STRING_ARCH_unaligned
-static inline int
-__attribute ((always_inline))
-ucs4_internal_loop_unaligned (struct __gconv_step *step,
- struct __gconv_step_data *step_data,
- const unsigned char **inptrp,
- const unsigned char *inend,
- unsigned char **outptrp,
- const unsigned char *outend,
- size_t *irreversible)
-{
- int flags = step_data->__flags;
- const unsigned char *inptr = *inptrp;
- unsigned char *outptr = *outptrp;
- int result;
-
- for (; inptr + 4 <= inend && outptr + 4 <= outend; inptr += 4)
- {
- if (__glibc_unlikely (inptr[0] > 0x80))
- {
- /* The value is too large. We don't try transliteration here since
- this is not an error because of the lack of possibilities to
- represent the result. This is a genuine bug in the input since
- UCS4 does not allow such values. */
- if (irreversible == NULL)
- /* We are transliterating, don't try to correct anything. */
- return __GCONV_ILLEGAL_INPUT;
-
- if (flags & __GCONV_IGNORE_ERRORS)
- {
- /* Just ignore this character. */
- ++*irreversible;
- continue;
- }
-
- *inptrp = inptr;
- *outptrp = outptr;
- return __GCONV_ILLEGAL_INPUT;
- }
-
-# if __BYTE_ORDER == __LITTLE_ENDIAN
- outptr[3] = inptr[0];
- outptr[2] = inptr[1];
- outptr[1] = inptr[2];
- outptr[0] = inptr[3];
-# else
- outptr[0] = inptr[0];
- outptr[1] = inptr[1];
- outptr[2] = inptr[2];
- outptr[3] = inptr[3];
-# endif
- outptr += 4;
- }
-
- *inptrp = inptr;
- *outptrp = outptr;
-
- /* Determine the status. */
- if (*inptrp == inend)
- result = __GCONV_EMPTY_INPUT;
- else if (*outptrp + 4 > outend)
- result = __GCONV_FULL_OUTPUT;
- else
- result = __GCONV_INCOMPLETE_INPUT;
-
- return result;
-}
-#endif
-
static inline int
__attribute ((always_inline))
@@ -454,11 +334,12 @@ internal_ucs4le_loop (struct __gconv_step *step,
#if __BYTE_ORDER == __BIG_ENDIAN
/* Sigh, we have to do some real work. */
size_t cnt;
- uint32_t *outptr32 = (uint32_t *) outptr;
- for (cnt = 0; cnt < n_convert; ++cnt, inptr += 4)
- *outptr32++ = bswap_32 (*(const uint32_t *) inptr);
- outptr = (unsigned char *) outptr32;
+ for (cnt = 0; cnt < n_convert; ++cnt, inptr += 4, outptr += 4)
+ {
+ uint32_t val = get32 (inptr);
+ put32 (outptr, __builtin_bswap32 (val));
+ }
*inptrp = inptr;
*outptrp = outptr;
@@ -481,59 +362,6 @@ internal_ucs4le_loop (struct __gconv_step *step,
return result;
}
-#if !_STRING_ARCH_unaligned
-static inline int
-__attribute ((always_inline))
-internal_ucs4le_loop_unaligned (struct __gconv_step *step,
- struct __gconv_step_data *step_data,
- const unsigned char **inptrp,
- const unsigned char *inend,
- unsigned char **outptrp,
- const unsigned char *outend,
- size_t *irreversible)
-{
- const unsigned char *inptr = *inptrp;
- unsigned char *outptr = *outptrp;
- size_t n_convert = MIN (inend - inptr, outend - outptr) / 4;
- int result;
-
-# if __BYTE_ORDER == __BIG_ENDIAN
- /* Sigh, we have to do some real work. */
- size_t cnt;
-
- for (cnt = 0; cnt < n_convert; ++cnt, inptr += 4, outptr += 4)
- {
- outptr[0] = inptr[3];
- outptr[1] = inptr[2];
- outptr[2] = inptr[1];
- outptr[3] = inptr[0];
- }
-
- *inptrp = inptr;
- *outptrp = outptr;
-# elif __BYTE_ORDER == __LITTLE_ENDIAN
- /* Simply copy the data. */
- *inptrp = inptr + n_convert * 4;
- *outptrp = __mempcpy (outptr, inptr, n_convert * 4);
-# else
-# error "This endianess is not supported."
-# endif
-
- /* Determine the status. */
- if (*inptrp == inend)
- result = __GCONV_EMPTY_INPUT;
- else if (*inptrp + 4 > inend)
- result = __GCONV_INCOMPLETE_INPUT;
- else
- {
- assert (*outptrp + 4 > outend);
- result = __GCONV_FULL_OUTPUT;
- }
-
- return result;
-}
-#endif
-
static inline int
__attribute ((always_inline))
@@ -613,12 +441,9 @@ ucs4le_internal_loop (struct __gconv_step *step,
for (; inptr + 4 <= inend && outptr + 4 <= outend; inptr += 4)
{
- uint32_t inval;
-
+ uint32_t inval = get32 (inptr);
#if __BYTE_ORDER == __BIG_ENDIAN
- inval = bswap_32 (*(const uint32_t *) inptr);
-#else
- inval = *(const uint32_t *) inptr;
+ inval = __builtin_bswap32 (inval);
#endif
if (__glibc_unlikely (inval > 0x7fffffff))
@@ -643,7 +468,7 @@ ucs4le_internal_loop (struct __gconv_step *step,
return __gconv_mark_illegal_input (step_data);
}
- *((uint32_t *) outptr) = inval;
+ put32 (outptr, inval);
outptr += sizeof (uint32_t);
}
@@ -664,79 +489,6 @@ ucs4le_internal_loop (struct __gconv_step *step,
return result;
}
-#if !_STRING_ARCH_unaligned
-static inline int
-__attribute ((always_inline))
-ucs4le_internal_loop_unaligned (struct __gconv_step *step,
- struct __gconv_step_data *step_data,
- const unsigned char **inptrp,
- const unsigned char *inend,
- unsigned char **outptrp,
- const unsigned char *outend,
- size_t *irreversible)
-{
- int flags = step_data->__flags;
- const unsigned char *inptr = *inptrp;
- unsigned char *outptr = *outptrp;
- int result;
-
- for (; inptr + 4 <= inend && outptr + 4 <= outend; inptr += 4)
- {
- if (__glibc_unlikely (inptr[3] > 0x80))
- {
- /* The value is too large. We don't try transliteration here since
- this is not an error because of the lack of possibilities to
- represent the result. This is a genuine bug in the input since
- UCS4 does not allow such values. */
- if (irreversible == NULL)
- /* We are transliterating, don't try to correct anything. */
- return __GCONV_ILLEGAL_INPUT;
-
- if (flags & __GCONV_IGNORE_ERRORS)
- {
- /* Just ignore this character. */
- ++*irreversible;
- continue;
- }
-
- *inptrp = inptr;
- *outptrp = outptr;
- return __GCONV_ILLEGAL_INPUT;
- }
-
-# if __BYTE_ORDER == __BIG_ENDIAN
- outptr[3] = inptr[0];
- outptr[2] = inptr[1];
- outptr[1] = inptr[2];
- outptr[0] = inptr[3];
-# else
- outptr[0] = inptr[0];
- outptr[1] = inptr[1];
- outptr[2] = inptr[2];
- outptr[3] = inptr[3];
-# endif
-
- outptr += 4;
- }
-
- *inptrp = inptr;
- *outptrp = outptr;
-
- /* Determine the status. */
- if (*inptrp == inend)
- result = __GCONV_EMPTY_INPUT;
- else if (*inptrp + 4 > inend)
- result = __GCONV_INCOMPLETE_INPUT;
- else
- {
- assert (*outptrp + 4 > outend);
- result = __GCONV_FULL_OUTPUT;
- }
-
- return result;
-}
-#endif
-
static inline int
__attribute ((always_inline))
diff --git a/iconv/loop.c b/iconv/loop.c
index 851caa9d42d45a47..696caa212aaec4bd 100644
--- a/iconv/loop.c
+++ b/iconv/loop.c
@@ -59,12 +59,7 @@
#include <libc-diag.h>
#undef FCTNAME2
-#if _STRING_ARCH_unaligned || !defined DEFINE_UNALIGNED
-# define FCTNAME2(name) name
-#else
-# define FCTNAME2(name) name##_unaligned
-#endif
-#define FCTNAME(name) FCTNAME2(name)
+#define FCTNAME(name) name
/* We need at least one byte for the next round. */
@@ -282,20 +277,9 @@ FCTNAME (LOOPFCT) (struct __gconv_step *step,
}
-/* Include the file a second time to define the function to handle
- unaligned access. */
-#if !defined DEFINE_UNALIGNED && !_STRING_ARCH_unaligned \
- && MIN_NEEDED_INPUT != 1 && MAX_NEEDED_INPUT % MIN_NEEDED_INPUT == 0 \
- && MIN_NEEDED_OUTPUT != 1 && MAX_NEEDED_OUTPUT % MIN_NEEDED_OUTPUT == 0
-# undef unaligned
-
-# define DEFINE_UNALIGNED
-# include "loop.c"
-# undef DEFINE_UNALIGNED
-#else
-# if MAX_NEEDED_INPUT > 1
-# define SINGLE(fct) SINGLE2 (fct)
-# define SINGLE2(fct) fct##_single
+#if MAX_NEEDED_INPUT > 1
+# define SINGLE(fct) SINGLE2 (fct)
+# define SINGLE2(fct) fct##_single
static inline int
__attribute ((always_inline))
SINGLE(LOOPFCT) (struct __gconv_step *step,
@@ -305,37 +289,37 @@ SINGLE(LOOPFCT) (struct __gconv_step *step,
size_t *irreversible EXTRA_LOOP_DECLS)
{
mbstate_t *state = step_data->__statep;
-# ifdef LOOP_NEED_FLAGS
+# ifdef LOOP_NEED_FLAGS
int flags = step_data->__flags;
-# endif
-# ifdef LOOP_NEED_DATA
+# endif
+# ifdef LOOP_NEED_DATA
void *data = step->__data;
-# endif
+# endif
int result = __GCONV_OK;
unsigned char bytebuf[MAX_NEEDED_INPUT];
const unsigned char *inptr = *inptrp;
unsigned char *outptr = *outptrp;
size_t inlen;
-# ifdef INIT_PARAMS
+# ifdef INIT_PARAMS
INIT_PARAMS;
-# endif
+# endif
-# ifdef UNPACK_BYTES
+# ifdef UNPACK_BYTES
UNPACK_BYTES
-# else
+# else
/* Add the bytes from the state to the input buffer. */
assert ((state->__count & 7) <= sizeof (state->__value));
for (inlen = 0; inlen < (size_t) (state->__count & 7); ++inlen)
bytebuf[inlen] = state->__value.__wchb[inlen];
-# endif
+# endif
/* Are there enough bytes in the input buffer? */
if (MIN_NEEDED_INPUT > 1
&& __builtin_expect (inptr + (MIN_NEEDED_INPUT - inlen) > inend, 0))
{
*inptrp = inend;
-# ifdef STORE_REST
+# ifdef STORE_REST
/* Building with -O3 GCC emits a `array subscript is above array
bounds' warning. GCC BZ #64739 has been opened for this. */
@@ -350,14 +334,14 @@ SINGLE(LOOPFCT) (struct __gconv_step *step,
inend = &bytebuf[inlen];
STORE_REST
-# else
+# else
/* We don't have enough input for another complete input
character. */
size_t inlen_after = inlen + (inend - inptr);
assert (inlen_after <= sizeof (state->__value.__wchb));
for (; inlen < inlen_after; inlen++)
state->__value.__wchb[inlen] = *inptr++;
-# endif
+# endif
return __GCONV_INCOMPLETE_INPUT;
}
@@ -403,11 +387,11 @@ SINGLE(LOOPFCT) (struct __gconv_step *step,
result = __GCONV_OK;
/* Clear the state buffer. */
-# ifdef CLEAR_STATE
+# ifdef CLEAR_STATE
CLEAR_STATE;
-# else
+# else
state->__count &= ~7;
-# endif
+# endif
}
else if (result == __GCONV_INCOMPLETE_INPUT)
{
@@ -416,11 +400,11 @@ SINGLE(LOOPFCT) (struct __gconv_step *step,
assert (inend != &bytebuf[MAX_NEEDED_INPUT]);
*inptrp += inend - bytebuf - (state->__count & 7);
-# ifdef STORE_REST
+# ifdef STORE_REST
inptrp = &inptr;
STORE_REST
-# else
+# else
/* We don't have enough input for another complete input
character. */
assert (inend - inptr > (state->__count & ~7));
@@ -429,14 +413,13 @@ SINGLE(LOOPFCT) (struct __gconv_step *step,
for (inlen = 0; inlen < inend - inptr; inlen++)
state->__value.__wchb[inlen] = inptr[inlen];
inptr = inend;
-# endif
+# endif
}
return result;
}
-# undef SINGLE
-# undef SINGLE2
-# endif
+# undef SINGLE
+# undef SINGLE2
# ifdef ONEBYTE_BODY
@@ -468,4 +451,3 @@ gconv_btowc (struct __gconv_step *step, unsigned char c)
#undef LOOP_NEED_STATE
#undef LOOP_NEED_FLAGS
#undef LOOP_NEED_DATA
-#undef unaligned
diff --git a/iconv/skeleton.c b/iconv/skeleton.c
index 6992701808079ec2..e7933ae333a5eae5 100644
--- a/iconv/skeleton.c
+++ b/iconv/skeleton.c
@@ -451,33 +451,6 @@ FUNCTION_NAME (struct __gconv_step *step, struct __gconv_step_data *data,
size_t lirreversible = 0;
size_t *lirreversiblep = irreversible ? &lirreversible : NULL;
- /* The following assumes that encodings, which have a variable length
- what might unalign a buffer even though it is an aligned in the
- beginning, either don't have the minimal number of bytes as a divisor
- of the maximum length or have a minimum length of 1. This is true
- for all known and supported encodings.
- We use && instead of || to combine the subexpression for the FROM
- encoding and for the TO encoding, because usually one of them is
- INTERNAL, for which the subexpression evaluates to 1, but INTERNAL
- buffers are always aligned correctly. */
-#define POSSIBLY_UNALIGNED \
- (!_STRING_ARCH_unaligned \
- && (((FROM_LOOP_MIN_NEEDED_FROM != 1 \
- && FROM_LOOP_MAX_NEEDED_FROM % FROM_LOOP_MIN_NEEDED_FROM == 0) \
- && (FROM_LOOP_MIN_NEEDED_TO != 1 \
- && FROM_LOOP_MAX_NEEDED_TO % FROM_LOOP_MIN_NEEDED_TO == 0)) \
- || ((TO_LOOP_MIN_NEEDED_FROM != 1 \
- && TO_LOOP_MAX_NEEDED_FROM % TO_LOOP_MIN_NEEDED_FROM == 0) \
- && (TO_LOOP_MIN_NEEDED_TO != 1 \
- && TO_LOOP_MAX_NEEDED_TO % TO_LOOP_MIN_NEEDED_TO == 0))))
-#if POSSIBLY_UNALIGNED
- int unaligned;
-# define GEN_unaligned(name) GEN_unaligned2 (name)
-# define GEN_unaligned2(name) name##_unaligned
-#else
-# define unaligned 0
-#endif
-
#ifdef PREPARE_LOOP
PREPARE_LOOP
#endif
@@ -517,18 +490,6 @@ FUNCTION_NAME (struct __gconv_step *step, struct __gconv_step_data *data,
}
#endif
-#if POSSIBLY_UNALIGNED
- unaligned =
- ((FROM_DIRECTION
- && ((uintptr_t) inptr % FROM_LOOP_MIN_NEEDED_FROM != 0
- || ((data->__flags & __GCONV_IS_LAST)
- && (uintptr_t) outbuf % FROM_LOOP_MIN_NEEDED_TO != 0)))
- || (!FROM_DIRECTION
- && (((data->__flags & __GCONV_IS_LAST)
- && (uintptr_t) outbuf % TO_LOOP_MIN_NEEDED_TO != 0)
- || (uintptr_t) inptr % TO_LOOP_MIN_NEEDED_FROM != 0)));
-#endif
-
while (1)
{
/* Remember the start value for this round. */
@@ -546,34 +507,14 @@ FUNCTION_NAME (struct __gconv_step *step, struct __gconv_step_data *data,
SAVE_RESET_STATE (1);
#endif
- if (__glibc_likely (!unaligned))
- {
- if (FROM_DIRECTION)
- /* Run the conversion loop. */
- status = FROM_LOOP (step, data, inptrp, inend, &outbuf, outend,
- lirreversiblep EXTRA_LOOP_ARGS);
- else
- /* Run the conversion loop. */
- status = TO_LOOP (step, data, inptrp, inend, &outbuf, outend,
- lirreversiblep EXTRA_LOOP_ARGS);
- }
-#if POSSIBLY_UNALIGNED
+ if (FROM_DIRECTION)
+ /* Run the conversion loop. */
+ status = FROM_LOOP (step, data, inptrp, inend, &outbuf, outend,
+ lirreversiblep EXTRA_LOOP_ARGS);
else
- {
- if (FROM_DIRECTION)
- /* Run the conversion loop. */
- status = GEN_unaligned (FROM_LOOP) (step, data, inptrp, inend,
- &outbuf, outend,
- lirreversiblep
- EXTRA_LOOP_ARGS);
- else
- /* Run the conversion loop. */
- status = GEN_unaligned (TO_LOOP) (step, data, inptrp, inend,
- &outbuf, outend,
- lirreversiblep
- EXTRA_LOOP_ARGS);
- }
-#endif
+ /* Run the conversion loop. */
+ status = TO_LOOP (step, data, inptrp, inend, &outbuf, outend,
+ lirreversiblep EXTRA_LOOP_ARGS);
/* If we were called as part of an error handling module we
don't do anything else here. */
@@ -638,41 +579,18 @@ FUNCTION_NAME (struct __gconv_step *step, struct __gconv_step_data *data,
SAVE_RESET_STATE (0);
#endif
- if (__glibc_likely (!unaligned))
- {
- if (FROM_DIRECTION)
- /* Run the conversion loop. */
- nstatus = FROM_LOOP (step, data, inptrp, inend,
- &outbuf, outerr,
- lirreversiblep
- EXTRA_LOOP_ARGS);
- else
- /* Run the conversion loop. */
- nstatus = TO_LOOP (step, data, inptrp, inend,
- &outbuf, outerr,
- lirreversiblep
- EXTRA_LOOP_ARGS);
- }
-#if POSSIBLY_UNALIGNED
+ if (FROM_DIRECTION)
+ /* Run the conversion loop. */
+ nstatus = FROM_LOOP (step, data, inptrp, inend,
+ &outbuf, outerr,
+ lirreversiblep
+ EXTRA_LOOP_ARGS);
else
- {
- if (FROM_DIRECTION)
- /* Run the conversion loop. */
- nstatus = GEN_unaligned (FROM_LOOP) (step, data,
- inptrp, inend,
- &outbuf,
- outerr,
- lirreversiblep
- EXTRA_LOOP_ARGS);
- else
- /* Run the conversion loop. */
- nstatus = GEN_unaligned (TO_LOOP) (step, data,
- inptrp, inend,
- &outbuf, outerr,
- lirreversiblep
- EXTRA_LOOP_ARGS);
- }
-#endif
+ /* Run the conversion loop. */
+ nstatus = TO_LOOP (step, data, inptrp, inend,
+ &outbuf, outerr,
+ lirreversiblep
+ EXTRA_LOOP_ARGS);
/* We must run out of output buffer space in this
rerun. */

View File

@ -0,0 +1,326 @@
commit d6f08d1cf027f4eb2ba289a6cc66853722d4badc
Author: Florian Weimer <fweimer@redhat.com>
Date: Thu Apr 16 19:13:43 2026 +0200
Use pending character state in IBM1390, IBM1399 character sets (CVE-2026-4046)
Follow the example in iso-2022-jp-3.c and use the __count state
variable to store the pending character. This avoids restarting
the conversion if the output buffer ends between two 4-byte UCS-4
code points, so that the assert reported in the bug can no longer
happen.
Even though the fix is applied to ibm1364.c, the change is only
effective for the two HAS_COMBINED codecs for IBM1390, IBM1399.
The test case was mostly auto-generated using
claude-4.6-opus-high-thinking, and composer-2-fast shows up in the
log as well. During review, gpt-5.4-xhigh flagged that the original
version of the test case was not exercising the new character
flush logic.
This fixes bug 33980.
Assisted-by: LLM
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Conflicts:
iconvdata/Makefile
(fixup context)
diff --git a/iconvdata/Makefile b/iconvdata/Makefile
index 25bd004e7f92a994..798f9be86d376aeb 100644
--- a/iconvdata/Makefile
+++ b/iconvdata/Makefile
@@ -75,7 +75,8 @@ ifeq (yes,$(build-shared))
tests = bug-iconv1 bug-iconv2 tst-loading tst-e2big tst-iconv4 bug-iconv4 \
tst-iconv6 bug-iconv5 bug-iconv6 tst-iconv7 bug-iconv8 bug-iconv9 \
bug-iconv10 bug-iconv11 bug-iconv12 tst-iconv-big5-hkscs-to-2ucs4 \
- bug-iconv13 bug-iconv14 bug-iconv15 tst-iconv-iso-2022-cn-ext
+ bug-iconv13 bug-iconv14 bug-iconv15 \
+ tst-iconv-iso-2022-cn-ext tst-bug33980
ifeq ($(have-thread-library),yes)
tests += bug-iconv3
endif
@@ -332,6 +333,8 @@ $(objpfx)bug-iconv15.out: $(addprefix $(objpfx), $(gconv-modules)) \
$(addprefix $(objpfx),$(modules.so))
$(objpfx)tst-iconv-iso-2022-cn-ext.out: $(addprefix $(objpfx), $(gconv-modules)) \
$(addprefix $(objpfx),$(modules.so))
+$(objpfx)tst-bug33980.out: $(addprefix $(objpfx), $(gconv-modules)) \
+ $(addprefix $(objpfx),$(modules.so))
$(objpfx)iconv-test.out: run-iconv-test.sh \
$(addprefix $(objpfx), $(gconv-modules)) \
diff --git a/iconvdata/ibm1364.c b/iconvdata/ibm1364.c
index 18f1aa4f6da4b021..c3e9b7c881eaa1d9 100644
--- a/iconvdata/ibm1364.c
+++ b/iconvdata/ibm1364.c
@@ -68,12 +68,29 @@
/* Since this is a stateful encoding we have to provide code which resets
the output state to the initial state. This has to be done during the
- flushing. */
+ flushing. For the to-internal direction (FROM_DIRECTION is true),
+ there may be a pending character that needs flushing. */
#define EMIT_SHIFT_TO_INIT \
if ((data->__statep->__count & ~7) != sb) \
{ \
if (FROM_DIRECTION) \
- data->__statep->__count &= 7; \
+ { \
+ uint32_t ch = data->__statep->__count >> 7; \
+ if (__glibc_unlikely (ch != 0)) \
+ { \
+ if (__glibc_unlikely (outend - outbuf < 4)) \
+ status = __GCONV_FULL_OUTPUT; \
+ else \
+ { \
+ put32 (outbuf, ch); \
+ outbuf += 4; \
+ /* Clear character and db bit. */ \
+ data->__statep->__count &= 7; \
+ } \
+ } \
+ else \
+ data->__statep->__count &= 7; \
+ } \
else \
{ \
/* We are not in the initial state. To switch back we have \
@@ -100,11 +117,13 @@
*curcsp = save_curcs
-/* Current codeset type. */
+/* Current codeset type. The bit is stored in the __count variable of
+ the conversion state. If the db bit is set, bit 7 and above store
+ a pending UCS-4 code point if non-zero. */
enum
{
- sb = 0,
- db = 64
+ sb = 0, /* Single byte mode. */
+ db = 64 /* Double byte mode. */
};
@@ -120,21 +139,29 @@ enum
} \
else \
{ \
- /* This is a combined character. Make sure we have room. */ \
- if (__glibc_unlikely (outptr + 8 > outend)) \
- { \
- result = __GCONV_FULL_OUTPUT; \
- break; \
- } \
- \
const struct divide *cmbp \
= &DB_TO_UCS4_COMB[ch - __TO_UCS4_COMBINED_MIN]; \
assert (cmbp->res1 != 0 && cmbp->res2 != 0); \
\
put32 (outptr, cmbp->res1); \
outptr += 4; \
- put32 (outptr, cmbp->res2); \
- outptr += 4; \
+ \
+ /* See whether we have room for the second character. */ \
+ if (outend - outptr >= 4) \
+ { \
+ put32 (outptr, cmbp->res2); \
+ outptr += 4; \
+ } \
+ else \
+ { \
+ /* Otherwise store only the first character now, and \
+ put the second one into the queue. */ \
+ curcs |= cmbp->res2 << 7; \
+ inptr += 2; \
+ /* Tell the caller why we terminate the loop. */ \
+ result = __GCONV_FULL_OUTPUT; \
+ break; \
+ } \
} \
}
#else
@@ -154,7 +181,20 @@ enum
#define LOOPFCT FROM_LOOP
#define BODY \
{ \
- uint32_t ch = *inptr; \
+ uint32_t ch; \
+ \
+ ch = curcs >> 7; \
+ if (__glibc_unlikely (ch != 0)) \
+ { \
+ put32 (outptr, ch); \
+ outptr += 4; \
+ /* Remove the pending character, but preserve state bits. */ \
+ curcs &= (1 << 7) - 1; \
+ continue; \
+ } \
+ \
+ /* Otherwise read the next input byte. */ \
+ ch = *inptr; \
\
if (__builtin_expect (ch, 0) == SO) \
{ \
diff --git a/iconvdata/tst-bug33980.c b/iconvdata/tst-bug33980.c
new file mode 100644
index 0000000000000000..c9693e0efebe4eae
--- /dev/null
+++ b/iconvdata/tst-bug33980.c
@@ -0,0 +1,153 @@
+/* Test for bug 33980: combining characters in IBM1390/IBM1399.
+ Copyright (C) 2026 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <alloc_buffer.h>
+#include <errno.h>
+#include <iconv.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <support/check.h>
+#include <support/next_to_fault.h>
+#include <support/support.h>
+
+/* Run iconv in a loop with a small output buffer of OUTBUFSIZE bytes
+ starting at OUTBUF. OUTBUF should be right before an unmapped page
+ so that writing past the end will fault. Skip SHIFT bytes at the
+ start of the input and output, to exercise different buffer
+ alignment. TRUNCATE indicates skipped bytes at the end of
+ input (0 and 1 a valid). */
+static void
+test_one (const char *encoding, unsigned int shift, unsigned int truncate,
+ char *outbuf, size_t outbufsize)
+{
+ /* In IBM1390 and IBM1399, the DBCS code 0xECB5 expands to two
+ Unicode code points when translated. */
+ static char input[] =
+ {
+ /* 8 letters X. */
+ 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7,
+ /* SO, 0xECB5, SI: shift to DBCS, special character, shift back. */
+ 0x0e, 0xec, 0xb5, 0x0f
+ };
+
+ /* Expected output after UTF-8 conversion. */
+ static char expected[] =
+ {
+ 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X',
+ /* U+304B (HIRAGANA LETTER KA). */
+ 0xe3, 0x81, 0x8b,
+ /* U+309A (COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK). */
+ 0xe3, 0x82, 0x9a
+ };
+
+ iconv_t cd = iconv_open ("UTF-8", encoding);
+ TEST_VERIFY_EXIT (cd != (iconv_t) -1);
+
+ char result_storage[64];
+ struct alloc_buffer result_buf
+ = alloc_buffer_create (result_storage, sizeof (result_storage));
+
+ char *inptr = &input[shift];
+ size_t inleft = sizeof (input) - shift - truncate;
+
+ while (inleft > 0)
+ {
+ char *outptr = outbuf;
+ size_t outleft = outbufsize;
+ size_t inleft_before = inleft;
+
+ size_t ret = iconv (cd, &inptr, &inleft, &outptr, &outleft);
+ size_t produced = outptr - outbuf;
+ alloc_buffer_copy_bytes (&result_buf, outbuf, produced);
+
+ if (ret == (size_t) -1 && errno == E2BIG)
+ {
+ if (produced == 0 && inleft == inleft_before)
+ {
+ /* Output buffer too small to make progress. This is
+ expected for very small output buffer sizes. */
+ TEST_VERIFY_EXIT (outbufsize < 3);
+ break;
+ }
+ continue;
+ }
+ if (ret == (size_t) -1)
+ FAIL_EXIT1 ("%s (outbufsize %zu): iconv: %m", encoding, outbufsize);
+ break;
+ }
+
+ /* Flush any pending state (e.g. a buffered combined character).
+ With outbufsize < 3, we could not store the first character, so
+ the second character did not become pending, and there is nothing
+ to flush. */
+ {
+ char *outptr = outbuf;
+ size_t outleft = outbufsize;
+
+ size_t ret = iconv (cd, NULL, NULL, &outptr, &outleft);
+ TEST_VERIFY_EXIT (ret == 0);
+ size_t produced = outptr - outbuf;
+ alloc_buffer_copy_bytes (&result_buf, outbuf, produced);
+
+ /* Second flush does not provide more data. */
+ outptr = outbuf;
+ outleft = outbufsize;
+ ret = iconv (cd, NULL, NULL, &outptr, &outleft);
+ TEST_VERIFY_EXIT (ret == 0);
+ TEST_VERIFY (outptr == outbuf);
+ }
+
+ TEST_VERIFY_EXIT (!alloc_buffer_has_failed (&result_buf));
+ size_t result_used
+ = sizeof (result_storage) - alloc_buffer_size (&result_buf);
+
+ if (outbufsize >= 3)
+ {
+ TEST_COMPARE (inleft, 0);
+ TEST_COMPARE (result_used, sizeof (expected) - shift);
+ TEST_COMPARE_BLOB (result_storage, result_used,
+ &expected[shift], sizeof (expected) - shift);
+ }
+ else
+ /* If the buffer is too small, only the leading X could be converted. */
+ TEST_COMPARE (result_used, 8 - shift);
+
+ TEST_VERIFY_EXIT (iconv_close (cd) == 0);
+}
+
+static int
+do_test (void)
+{
+ struct support_next_to_fault ntf
+ = support_next_to_fault_allocate (8);
+
+ for (int shift = 0; shift <= 8; ++shift)
+ for (int truncate = 0; truncate < 2; ++truncate)
+ for (size_t outbufsize = 1; outbufsize <= 8; outbufsize++)
+ {
+ char *outbuf = ntf.buffer + ntf.length - outbufsize;
+ test_one ("IBM1390", shift, truncate, outbuf, outbufsize);
+ test_one ("IBM1399", shift, truncate, outbuf, outbufsize);
+ }
+
+ support_next_to_fault_free (&ntf);
+ return 0;
+}
+
+#include <support/test-driver.c>

View File

@ -0,0 +1,184 @@
commit d8997716a1ca22cf038eac86ed286830ba9818cc
Author: DJ Delorie <dj@redhat.com>
Date: Wed Apr 1 17:52:25 2026 -0400
nss: fix __get_default_domain logic
Fix logic bug in __nss_get_default_domain that prevents
proper initialization.
Because this function is not exposed, the test case must link
against the object directly.
Bug origin commit: 64d1e08ea822bf47cb2796ad0f727136227f983c
Co-authored-by: Florian Weimer <fweimer@redhat.com>
Reviewed-by: Frédéric Bérat <fberat@redhat.com>
Conflicts:
nss/Makefile
(fixup context)
diff --git a/nss/Makefile b/nss/Makefile
index 4d128da0a366301f..4d22ef2661fb9b27 100644
--- a/nss/Makefile
+++ b/nss/Makefile
@@ -61,6 +61,7 @@ tests := \
bug17079 \
test-digits-dots \
test-netdb \
+ tst-default-domain \
tst-nss-getpwent \
tst-nss-malloc-failure-getlogin_r \
tst-nss-test1 \
@@ -226,6 +227,8 @@ endif
$(objpfx)tst-nss-files-alias-leak.out: $(objpfx)libnss_files.so
$(objpfx)tst-nss-files-alias-truncated.out: $(objpfx)libnss_files.so
+$(objpfx)tst-default-domain: $(objpfx)nisdomain.os
+
tst-nss-gai-hv2-canonname-ENV = \
MALLOC_TRACE=$(objpfx)tst-nss-gai-hv2-canonname.mtrace \
LD_PRELOAD=$(common-objpfx)/malloc/libc_malloc_debug.so
diff --git a/nss/nss_compat/nisdomain.c b/nss/nss_compat/nisdomain.c
index 838fd95820a47767..899b323f63d90076 100644
--- a/nss/nss_compat/nisdomain.c
+++ b/nss/nss_compat/nisdomain.c
@@ -36,7 +36,7 @@ __nss_get_default_domain (char **outdomain)
__libc_lock_lock (domainname_lock);
- if (domainname[0] != '\0')
+ if (domainname[0] == '\0')
{
if (getdomainname (domainname, MAXDOMAINNAMELEN) < 0)
result = errno;
diff --git a/nss/tst-default-domain.c b/nss/tst-default-domain.c
new file mode 100644
index 0000000000000000..2fa9153d8802d4c3
--- /dev/null
+++ b/nss/tst-default-domain.c
@@ -0,0 +1,123 @@
+/* Basic test of __nss_get_default_domain
+ Copyright (C) 2026 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <unistd.h>
+#include <string.h>
+#include <dlfcn.h>
+
+#include "nss_compat/nisdomain.h"
+
+#include <support/test-driver.h>
+#include <support/support.h>
+#include <support/namespace.h>
+#include <support/check.h>
+
+char unset_domain[] = "unset_domain";
+char new_domain[] = "new_domain";
+
+/* This function checks the __nss_get_default_domain() function in
+ nss_compat/nssdomain.c. Because this is an internal function to
+ libnss_compat.so, the Makefile will link that object to this test
+ case directly. */
+
+static int
+do_test (void)
+{
+ char *domain_name;
+ char buf[1024];
+
+ /* We need to be in a network namespace so we can change the domain
+ name without interfering with the host system. */
+ support_become_root ();
+ support_enter_network_namespace ();
+ if (!support_in_uts_namespace ())
+ return EXIT_UNSUPPORTED;
+
+ /* First pass: set an empty domain and make sure it's returned
+ correctly. This should not be cached. */
+
+ /* Set the domain name to a known value. */
+ TEST_VERIFY (setdomainname ("", 0) == 0);
+
+ /* Make sure it got set. */
+ TEST_VERIFY (getdomainname (buf, sizeof(buf)) == 0);
+ TEST_COMPARE_STRING (buf, "");
+
+ /* Set this to a known "unknown" value so we can detect if it's not
+ changed. */
+ domain_name = unset_domain;
+
+ /* This is the function we're testing. */
+ TEST_VERIFY (__nss_get_default_domain (& domain_name) == 0);
+
+ /* Make sure the correct domain name is returned. */
+ TEST_VERIFY (domain_name != NULL);
+ TEST_COMPARE_STRING (domain_name, "");
+
+ /* Second pass: set a non-empty domain and make sure it's returned
+ correctly. This works because the empty domain is not
+ cached. */
+
+ /* Set the domain name to a known value. */
+ TEST_VERIFY (setdomainname (new_domain, strlen (new_domain)) == 0);
+
+ /* Make sure it got set. */
+ TEST_VERIFY (getdomainname (buf, sizeof(buf)) == 0);
+ TEST_COMPARE_STRING (buf, new_domain);
+
+ /* Set this to a known "unknown" value so we can detect if it's not
+ changed. */
+ domain_name = unset_domain;
+
+ /* This is the function we're testing. */
+ TEST_VERIFY (__nss_get_default_domain (& domain_name) == 0);
+
+ /* Make sure the correct domain name is returned. */
+ TEST_VERIFY (domain_name != NULL);
+ TEST_COMPARE_STRING (domain_name, new_domain);
+
+ /* The function caches the name, so check it twice. */
+ TEST_VERIFY (__nss_get_default_domain (& domain_name) == 0);
+
+ TEST_VERIFY (domain_name != NULL);
+ TEST_COMPARE_STRING (domain_name, new_domain);
+
+ /* Third pass: set an empty domain again but expect the cached
+ value. */
+
+ /* Set the domain name to a known value. */
+ TEST_VERIFY (setdomainname ("", 0) == 0);
+
+ /* Make sure it got set. */
+ TEST_VERIFY (getdomainname (buf, sizeof(buf)) == 0);
+ TEST_COMPARE_STRING (buf, "");
+
+ /* Set this to a known "unknown" value so we can detect if it's not
+ changed. */
+ domain_name = unset_domain;
+
+ /* This is the function we're testing. */
+ TEST_VERIFY (__nss_get_default_domain (& domain_name) == 0);
+
+ TEST_VERIFY (domain_name != NULL);
+ TEST_COMPARE_STRING (domain_name, new_domain);
+
+ return 0;
+}
+
+#include <support/test-driver.c>

View File

@ -0,0 +1,234 @@
commit 5663ab0b833df187b15e7bb4b18173e22beb8bd1
Author: Carlos O'Donell <carlos@redhat.com>
Date: Fri Mar 20 16:43:33 2026 -0400
resolv: Count records correctly (CVE-2026-4437)
The answer section boundary was previously ignored, and the code in
getanswer_ptr would iterate past the last resource record, but not
beyond the end of the returned data. This could lead to subsequent data
being interpreted as answer records, thus violating the DNS
specification. Such resource records could be maliciously crafted and
hidden from other tooling, but processed by the glibc stub resolver and
acted upon by the application. While we trust the data returned by the
configured recursive resolvers, we should not trust its format and
should validate it as required. It is a security issue to incorrectly
process the DNS protocol.
A regression test is added for response section crossing.
No regressions on x86_64-linux-gnu.
Reviewed-by: Collin Funk <collin.funk1@gmail.com>
(cherry picked from commit 9f5f18aab40ec6b61fa49a007615e6077e9a979b)
diff --git a/resolv/Makefile b/resolv/Makefile
index 116ab6a1574eb1ff..649c1254c30d40c6 100644
--- a/resolv/Makefile
+++ b/resolv/Makefile
@@ -108,6 +108,7 @@ tests += \
tst-resolv-basic \
tst-resolv-binary \
tst-resolv-byaddr \
+ tst-resolv-dns-section \
tst-resolv-edns \
tst-resolv-invalid-cname \
tst-resolv-network \
@@ -119,6 +120,7 @@ tests += \
tst-resolv-semi-failure \
tst-resolv-short-response \
tst-resolv-trailing \
+ # tests
# This test calls __res_context_send directly, which is not exported
# from libresolv.
@@ -288,6 +290,8 @@ $(objpfx)tst-resolv-aliases: $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-resolv-basic: $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-resolv-binary: $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-resolv-byaddr: $(objpfx)libresolv.so $(shared-thread-library)
+$(objpfx)tst-resolv-dns-section: $(objpfx)libresolv.so \
+ $(shared-thread-library)
$(objpfx)tst-resolv-edns: $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-resolv-network: $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-resolv-res_init: $(objpfx)libresolv.so
diff --git a/resolv/nss_dns/dns-host.c b/resolv/nss_dns/dns-host.c
index 3d261b6810bba5c9..54d6e2822d3454ca 100644
--- a/resolv/nss_dns/dns-host.c
+++ b/resolv/nss_dns/dns-host.c
@@ -821,7 +821,7 @@ getanswer_ptr (unsigned char *packet, size_t packetlen,
/* expected_name may be updated to point into this buffer. */
unsigned char name_buffer[NS_MAXCDNAME];
- while (ancount > 0)
+ for (; ancount > 0; --ancount)
{
struct ns_rr_wire rr;
if (!__ns_rr_cursor_next (&c, &rr))
diff --git a/resolv/tst-resolv-dns-section.c b/resolv/tst-resolv-dns-section.c
new file mode 100644
index 0000000000000000..1171baef51e3cc36
--- /dev/null
+++ b/resolv/tst-resolv-dns-section.c
@@ -0,0 +1,162 @@
+/* Test handling of invalid section transitions (bug 34014).
+ Copyright (C) 2022-2026 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <array_length.h>
+#include <errno.h>
+#include <netdb.h>
+#include <resolv.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/format_nss.h>
+#include <support/resolv_test.h>
+#include <support/support.h>
+
+/* Name of test, and the second section type. */
+struct item {
+ const char *test;
+ int ns_section;
+};
+
+static const struct item test_items[] =
+ {
+ { "Test crossing from ns_s_an to ns_s_ar.", ns_s_ar },
+ { "Test crossing from ns_s_an to ns_s_an.", ns_s_ns },
+
+ { NULL, 0 },
+ };
+
+/* The response is designed to contain the following:
+ - An Answer section with one T_PTR record that is skipped.
+ - A second section with a semantically invalid T_PTR record.
+ The original defect is that the response parsing would cross
+ section boundaries and handle the additional section T_PTR
+ as if it were an answer. A conforming implementation would
+ stop as soon as it reaches the end of the section. */
+static void
+response (const struct resolv_response_context *ctx,
+ struct resolv_response_builder *b,
+ const char *qname, uint16_t qclass, uint16_t qtype)
+{
+ TEST_COMPARE (qclass, C_IN);
+
+ /* We only test PTR. */
+ TEST_COMPARE (qtype, T_PTR);
+
+ unsigned int count;
+ char *tail = NULL;
+
+ if (strstr (qname, "in-addr.arpa") != NULL
+ && sscanf (qname, "%u.%ms", &count, &tail) == 2)
+ TEST_COMPARE_STRING (tail, "0.168.192.in-addr.arpa");
+ else if (sscanf (qname, "%x.%ms", &count, &tail) == 2)
+ {
+ TEST_COMPARE_STRING (tail, "\
+0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa");
+ }
+ else
+ FAIL_EXIT1 ("invalid QNAME: %s\n", qname);
+ free (tail);
+
+ /* We have a bounded number of possible tests. */
+ TEST_VERIFY (count >= 0);
+ TEST_VERIFY (count <= 15);
+
+ struct resolv_response_flags flags = {};
+ resolv_response_init (b, flags);
+ resolv_response_add_question (b, qname, qclass, qtype);
+ resolv_response_section (b, ns_s_an);
+
+ /* Actual answer record, but the wrong name (skipped). */
+ resolv_response_open_record (b, "1.0.0.10.in-addr.arpa", qclass, qtype, 60);
+
+ /* Record the answer. */
+ resolv_response_add_name (b, "test.ptr.example.net");
+ resolv_response_close_record (b);
+
+ /* Add a second section to test section boundary crossing. */
+ resolv_response_section (b, test_items[count].ns_section);
+ /* Semantically incorrect, but hide a T_PTR entry. */
+ resolv_response_open_record (b, qname, qclass, qtype, 60);
+ resolv_response_add_name (b, "wrong.ptr.example.net");
+ resolv_response_close_record (b);
+}
+
+
+/* Perform one check using a reverse lookup. */
+static void
+check_reverse (int af, int count)
+{
+ TEST_VERIFY (af == AF_INET || af == AF_INET6);
+ TEST_VERIFY (count < array_length (test_items));
+
+ char addr[sizeof (struct in6_addr)] = { 0 };
+ socklen_t addrlen;
+ if (af == AF_INET)
+ {
+ addr[0] = (char) 192;
+ addr[1] = (char) 168;
+ addr[2] = (char) 0;
+ addr[3] = (char) count;
+ addrlen = 4;
+ }
+ else
+ {
+ addr[0] = 0x20;
+ addr[1] = 0x01;
+ addr[2] = 0x0d;
+ addr[3] = 0xb8;
+ addr[4] = addr[5] = addr[6] = addr[7] = 0x0;
+ addr[8] = addr[9] = addr[10] = addr[11] = 0x0;
+ addr[12] = 0x0;
+ addr[13] = 0x0;
+ addr[14] = 0x0;
+ addr[15] = count;
+ addrlen = 16;
+ }
+
+ h_errno = 0;
+ struct hostent *answer = gethostbyaddr (addr, addrlen, af);
+ TEST_VERIFY (answer == NULL);
+ TEST_VERIFY (h_errno == NO_RECOVERY);
+ if (answer != NULL)
+ printf ("error: unexpected success: %s\n",
+ support_format_hostent (answer));
+}
+
+static int
+do_test (void)
+{
+ struct resolv_test *obj = resolv_test_start
+ ((struct resolv_redirect_config)
+ {
+ .response_callback = response
+ });
+
+ for (int i = 0; test_items[i].test != NULL; i++)
+ {
+ check_reverse (AF_INET, i);
+ check_reverse (AF_INET6, i);
+ }
+
+ resolv_test_end (obj);
+
+ return 0;
+}
+
+#include <support/test-driver.c>

View File

@ -0,0 +1,318 @@
commit 9344c796f7a4ac8f2c59de63f3e1e936b51e817d
Author: Carlos O'Donell <carlos@redhat.com>
Date: Fri Mar 20 17:14:33 2026 -0400
resolv: Check hostname for validity (CVE-2026-4438)
The processed hostname in getanswer_ptr should be correctly checked to
avoid invalid characters from being allowed, including shell
metacharacters. It is a security issue to fail to check the returned
hostname for validity.
A regression test is added for invalid metacharacters and other cases
of invalid or valid characters.
No regressions on x86_64-linux-gnu.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
(cherry picked from commit e10977481f4db4b2a3ce34fa4c3a1e26651ae312)
Conflicts:
resolv/Makefile
(fixup context)
diff --git a/resolv/Makefile b/resolv/Makefile
index 649c1254c30d40c6..aa25c71f1eea9c28 100644
--- a/resolv/Makefile
+++ b/resolv/Makefile
@@ -111,6 +111,7 @@ tests += \
tst-resolv-dns-section \
tst-resolv-edns \
tst-resolv-invalid-cname \
+ tst-resolv-invalid-ptr \
tst-resolv-network \
tst-resolv-noaaaa \
tst-resolv-noaaaa-vc \
@@ -302,6 +303,8 @@ $(objpfx)tst-resolv-res_init-thread: $(objpfx)libresolv.so \
$(objpfx)tst-resolv-invalid-cname: $(objpfx)libresolv.so \
$(shared-thread-library)
$(objpfx)tst-resolv-no-search: $(objpfx)libresolv.so $(shared-thread-library)
+$(objpfx)tst-resolv-invalid-ptr: $(objpfx)libresolv.so \
+ $(shared-thread-library)
$(objpfx)tst-resolv-noaaaa: $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-resolv-noaaaa-vc: $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-resolv-nondecimal: $(objpfx)libresolv.so $(shared-thread-library)
diff --git a/resolv/nss_dns/dns-host.c b/resolv/nss_dns/dns-host.c
index 54d6e2822d3454ca..2016131b24a55145 100644
--- a/resolv/nss_dns/dns-host.c
+++ b/resolv/nss_dns/dns-host.c
@@ -867,7 +867,7 @@ getanswer_ptr (unsigned char *packet, size_t packetlen,
char hname[MAXHOSTNAMELEN + 1];
if (__ns_name_unpack (c.begin, c.end, rr.rdata,
name_buffer, sizeof (name_buffer)) < 0
- || !__res_binary_hnok (expected_name)
+ || !__res_binary_hnok (name_buffer)
|| __ns_name_ntop (name_buffer, hname, sizeof (hname)) < 0)
{
*h_errnop = NO_RECOVERY;
diff --git a/resolv/tst-resolv-invalid-ptr.c b/resolv/tst-resolv-invalid-ptr.c
new file mode 100644
index 0000000000000000..0c802ab96780efb0
--- /dev/null
+++ b/resolv/tst-resolv-invalid-ptr.c
@@ -0,0 +1,255 @@
+/* Test handling of invalid T_PTR results (bug 34015).
+ Copyright (C) 2022-2026 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <array_length.h>
+#include <errno.h>
+#include <netdb.h>
+#include <resolv.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/format_nss.h>
+#include <support/resolv_test.h>
+#include <support/support.h>
+
+/* Name of test, the answer, the expected error return, and if we
+ expect the call to fail. */
+struct item {
+ const char *test;
+ const char *answer;
+ int expected;
+ bool fail;
+};
+
+static const struct item test_items[] =
+ {
+ /* Test for invalid characters. */
+ { "Invalid use of \"|\"",
+ "test.|.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \"&\"",
+ "test.&.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \";\"",
+ "test.;.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \"<\"",
+ "test.<.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \">\"",
+ "test.>.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \"(\"",
+ "test.(.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \")\"",
+ "test.).ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \"$\"",
+ "test.$.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \"`\"",
+ "test.`.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \"\\\"",
+ "test.\\.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \"\'\"",
+ "test.'.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \"\"\"",
+ "test.\".ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \" \"",
+ "test. .ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \"\\t\"",
+ "test.\t.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \"\\n\"",
+ "test.\n.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \"\\r\"",
+ "test.\r.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \"*\"",
+ "test.*.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \"?\"",
+ "test.?.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \"[\"",
+ "test.[.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \"]\"",
+ "test.].ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \",\"",
+ "test.,.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \"~\"",
+ "test.~.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \":\"",
+ "test.:.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \"!\"",
+ "test.!.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \"@\"",
+ "test.@.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \"#\"",
+ "test.#.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \"%\"",
+ "test.%%.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of \"^\"",
+ "test.^.ptr.example", NO_RECOVERY, true },
+
+ /* Test for invalid UTF-8 characters (2-byte, 4-byte, 6-byte). */
+ { "Invalid use of UTF-8 (2-byte, U+00C0-U+00C2)",
+ "ÁÂÃ.test.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of UTF-8 (4-byte, U+0750-U+0752)",
+ "ݐݑݒ.test.ptr.example", NO_RECOVERY, true },
+ { "Invalid use of UTF-8 (6-byte, U+0904-U+0906)",
+ "ऄअआ.test.ptr.example", NO_RECOVERY, true },
+
+ /* Test for "-" which may be valid depending on position. */
+ { "Invalid leading \"-\"",
+ "-test.ptr.example", NO_RECOVERY, true },
+ { "Valid trailing \"-\"",
+ "test-.ptr.example", 0, false },
+ { "Valid mid-label use of \"-\"",
+ "te-st.ptr.example", 0, false },
+
+ /* Test for "_" which is always valid in any position. */
+ { "Valid leading use of \"_\"",
+ "_test.ptr.example", 0, false },
+ { "Valid mid-label use of \"_\"",
+ "te_st.ptr.example", 0, false },
+ { "Valid trailing use of \"_\"",
+ "test_.ptr.example", 0, false },
+
+ /* Sanity test the broader set [A-Za-z0-9_-] of valid characters. */
+ { "Valid \"[A-Z]\"",
+ "test.ABCDEFGHIJKLMNOPQRSTUVWXYZ.ptr.example", 0, false },
+ { "Valid \"[a-z]\"",
+ "test.abcdefghijklmnopqrstuvwxyz.ptr.example", 0, false },
+ { "Valid \"[0-9]\"",
+ "test.0123456789.ptr.example", 0, false },
+ { "Valid mixed use of \"[A-Za-z0-9_-]\"",
+ "test.012abcABZ_-.ptr.example", 0, false },
+ };
+
+static void
+response (const struct resolv_response_context *ctx,
+ struct resolv_response_builder *b,
+ const char *qname, uint16_t qclass, uint16_t qtype)
+{
+ TEST_COMPARE (qclass, C_IN);
+
+ /* We only test PTR. */
+ TEST_COMPARE (qtype, T_PTR);
+
+ unsigned int count, count1;
+ char *tail = NULL;
+
+ /* The test implementation can handle up to 255 tests. */
+ if (strstr (qname, "in-addr.arpa") != NULL
+ && sscanf (qname, "%u.%ms", &count, &tail) == 2)
+ TEST_COMPARE_STRING (tail, "0.168.192.in-addr.arpa");
+ else if (sscanf (qname, "%x.%x.%ms", &count, &count1, &tail) == 3)
+ {
+ TEST_COMPARE_STRING (tail, "\
+0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa");
+ count |= count1 << 4;
+ }
+ else
+ FAIL_EXIT1 ("invalid QNAME: %s\n", qname);
+ free (tail);
+
+ /* Cross check. Count has a fixed bound (soft limit). */
+ TEST_VERIFY (count >= 0 && count <= 255);
+
+ /* We have a fixed number of tests (hard limit). */
+ TEST_VERIFY_EXIT (count < array_length (test_items));
+
+ struct resolv_response_flags flags = {};
+ resolv_response_init (b, flags);
+ resolv_response_add_question (b, qname, qclass, qtype);
+ resolv_response_section (b, ns_s_an);
+
+ /* Actual answer record. */
+ resolv_response_open_record (b, qname, qclass, qtype, 60);
+
+ /* Record the answer. */
+ resolv_response_add_name (b, test_items[count].answer);
+ resolv_response_close_record (b);
+}
+
+/* Perform one check using a reverse lookup. */
+static void
+check_reverse (int af, int count)
+{
+ TEST_VERIFY (af == AF_INET || af == AF_INET6);
+ TEST_VERIFY_EXIT (count < array_length (test_items));
+
+ /* Generate an address to query for each test. */
+ char addr[sizeof (struct in6_addr)] = { 0 };
+ socklen_t addrlen;
+ if (af == AF_INET)
+ {
+ addr[0] = (char) 192;
+ addr[1] = (char) 168;
+ addr[2] = (char) 0;
+ addr[3] = (char) count;
+ addrlen = 4;
+ }
+ else
+ {
+ addr[0] = 0x20;
+ addr[1] = 0x01;
+ addr[2] = 0x0d;
+ addr[3] = 0xb8;
+ addr[4] = addr[5] = addr[6] = addr[7] = 0x0;
+ addr[8] = addr[9] = addr[10] = addr[11] = 0x0;
+ addr[12] = 0x0;
+ addr[13] = 0x0;
+ addr[14] = 0x0;
+ addr[15] = (char) count;
+ addrlen = 16;
+ }
+
+ h_errno = 0;
+ struct hostent *answer = gethostbyaddr (addr, addrlen, af);
+
+ /* Verify h_errno is as expected. */
+ TEST_COMPARE (h_errno, test_items[count].expected);
+ if (h_errno != test_items[count].expected)
+ /* And print more information if it's not. */
+ printf ("INFO: %s\n", test_items[count].test);
+
+ if (test_items[count].fail)
+ {
+ /* We expected a failure so verify answer is NULL. */
+ TEST_VERIFY (answer == NULL);
+ /* If it's not NULL we should print out what we received. */
+ if (answer != NULL)
+ printf ("error: unexpected success: %s\n",
+ support_format_hostent (answer));
+ }
+ else
+ /* We don't expect a failure so answer must be valid. */
+ TEST_COMPARE_STRING (answer->h_name, test_items[count].answer);
+}
+
+static int
+do_test (void)
+{
+ struct resolv_test *obj = resolv_test_start
+ ((struct resolv_redirect_config)
+ {
+ .response_callback = response
+ });
+
+ for (int i = 0; i < array_length (test_items); i++)
+ {
+ check_reverse (AF_INET, i);
+ check_reverse (AF_INET6, i);
+ }
+ resolv_test_end (obj);
+
+ return 0;
+}
+
+#include <support/test-driver.c>

View File

@ -0,0 +1,126 @@
commit 839898777226a3ed88c0859f25ffe712519b4ead
Author: Rocket Ma <marocketbd@gmail.com>
Date: Fri Apr 17 23:48:41 2026 -0700
stdio-common: Fix buffer overflow in scanf %mc [BZ #34008]
* stdio-common/vfscanf-internal.c: When enlarging allocated buffer with
format %mc or %mC, glibc allocates one byte less, leading to
user-controlled one byte overflow. This commit fixes BZ #34008, or
CVE-2026-5450.
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Signed-off-by: Rocket Ma <marocketbd@gmail.com>
Reviewed-by: H.J. Lu <hjl.tools@gmail.com>
Conflicts:
stdio-common/Makefile
(usual test differences)
diff -Nrup a/stdio-common/Makefile b/stdio-common/Makefile
--- a/stdio-common/Makefile 2026-06-16 08:24:01.348860456 -0400
+++ b/stdio-common/Makefile 2026-06-16 08:18:22.791126117 -0400
@@ -290,6 +290,7 @@ tests := \
tst-vfprintf-mbs-prec \
tst-vfprintf-user-type \
tst-vfprintf-width-prec-alloc \
+ tst-vfscanf-bz34008 \
tst-wc-printf \
tstdiomisc \
tstgetln \
@@ -441,6 +442,9 @@ tst-printf-bz18872-ENV = MALLOC_TRACE=$(
tst-vfprintf-width-prec-ENV = \
MALLOC_TRACE=$(objpfx)tst-vfprintf-width-prec.mtrace \
LD_PRELOAD=$(common-objpfx)/malloc/libc_malloc_debug.so
+tst-vfscanf-bz34008-ENV = \
+ MALLOC_CHECK_=3 \
+ LD_PRELOAD=$(common-objpfx)/malloc/libc_malloc_debug.so
tst-printf-bz25691-ENV = \
MALLOC_TRACE=$(objpfx)tst-printf-bz25691.mtrace \
LD_PRELOAD=$(common-objpfx)/malloc/libc_malloc_debug.so
diff --git a/stdio-common/tst-vfscanf-bz34008.c b/stdio-common/tst-vfscanf-bz34008.c
new file mode 100644
index 0000000000..48371c8a3d
--- /dev/null
+++ b/stdio-common/tst-vfscanf-bz34008.c
@@ -0,0 +1,48 @@
+/* Regression test for vfscanf %Nmc out-of-bound write (BZ #34008)
+ Copyright (C) 2026 The GNU Toolchain Authors.
+ 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
+ <https://www.gnu.org/licenses/>. */
+
+#include "malloc/mcheck.h"
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <wchar.h>
+#include <stdlib.h>
+#include <malloc.h>
+#include <support/check.h>
+
+#define WIDTH 0x410
+#define SCANFSTR "%1040mc"
+static int
+do_test (void)
+{
+ mcheck_pedantic (NULL);
+ char *input = malloc (WIDTH + 1);
+ TEST_VERIFY (input != NULL);
+ memset (input, 'A', WIDTH);
+ input[WIDTH] = '\0';
+
+ char *buf = NULL;
+ TEST_VERIFY (sscanf (input, SCANFSTR, &buf) != -1);
+ TEST_VERIFY (buf != NULL);
+
+ free (buf);
+ free (input);
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/stdio-common/vfscanf-internal.c b/stdio-common/vfscanf-internal.c
index 59fc8208aa..3d11ac261e 100644
--- a/stdio-common/vfscanf-internal.c
+++ b/stdio-common/vfscanf-internal.c
@@ -855,8 +855,7 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
{
/* Enlarge the buffer. */
size_t newsize
- = strsize
- + (strsize >= width ? width - 1 : strsize);
+ = strsize + (strsize >= width ? width : strsize);
str = (char *) realloc (*strptr, newsize);
if (str == NULL)
@@ -929,7 +928,7 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
&& wstr == (wchar_t *) *strptr + strsize)
{
size_t newsize
- = strsize + (strsize > width ? width - 1 : strsize);
+ = strsize + (strsize >= width ? width : strsize);
/* Enlarge the buffer. */
wstr = (wchar_t *) realloc (*strptr,
newsize * sizeof (wchar_t));
@@ -984,7 +983,7 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
&& wstr == (wchar_t *) *strptr + strsize)
{
size_t newsize
- = strsize + (strsize > width ? width - 1 : strsize);
+ = strsize + (strsize >= width ? width : strsize);
/* Enlarge the buffer. */
wstr = (wchar_t *) realloc (*strptr,
newsize * sizeof (wchar_t));

View File

@ -0,0 +1,80 @@
commit b866ef29773b22a1343ff9084374775114350b78
Author: Maciej W. Rozycki <macro@redhat.com>
Date: Wed May 27 12:57:10 2026 -0400
support: Implement 'xfmemopen' for seamless 'fmemopen' use
Add 'xfmemopen' wrapper for seamless 'fmemopen' use in tests, following
'xfopen', 'xfclose', etc., and providing a standardized error reporting
facility.
Reviewed-by: Florian Weimer <fweimer@redhat.com>
(cherry picked from commit fe709cc24578ecfd2ff5b07e10e3829fcb55075b)
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Conflicts:
support/Makefile
(usual test differences)
support/xstdio.h
(adjust for downstream context difference)
diff -Nrup a/support/Makefile b/support/Makefile
--- a/support/Makefile 2026-06-16 10:34:13.241874132 -0400
+++ b/support/Makefile 2026-06-16 10:37:06.085172353 -0400
@@ -135,6 +135,7 @@ libsupport-routines = \
xfchmod \
xfclose \
xfdopendir \
+ xfmemopen \
xfopen \
xfork \
xfread \
diff --git a/support/xfmemopen.c b/support/xfmemopen.c
new file mode 100644
index 0000000000..f1dbc72c67
--- /dev/null
+++ b/support/xfmemopen.c
@@ -0,0 +1,31 @@
+/* fmemopen with error checking.
+ 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <support/xstdio.h>
+
+#include <support/check.h>
+#include <stdlib.h>
+
+FILE *
+xfmemopen (void *mem, size_t len, const char *mode)
+{
+ FILE *fp = fmemopen (mem, len, mode);
+ if (fp == NULL)
+ FAIL_EXIT1 ("fmemopen (mode \"%s\"): %m", mode);
+ return fp;
+}
diff -Nrup a/support/xstdio.h b/support/xstdio.h
--- a/support/xstdio.h 2026-06-16 10:34:10.450856660 -0400
+++ b/support/xstdio.h 2026-06-16 10:39:05.792825780 -0400
@@ -26,6 +26,7 @@ __BEGIN_DECLS
FILE *xfopen (const char *path, const char *mode);
void xfclose (FILE *);
+FILE *xfmemopen (void *mem, size_t len, const char *mode);
void xfread (void *ptr, size_t size, size_t nmemb, FILE *stream);
/* Read a line from FP, using getline. *BUFFER must be NULL, or a

View File

@ -0,0 +1,457 @@
commit 97926e9017f3faeaacce9337f1288460f5e6ec7d
Author: Maciej W. Rozycki <macro@redhat.com>
Date: Wed May 27 12:57:10 2026 -0400
stdio-common: Reject insufficient character data in scanf [BZ #12701]
Reject invalid formatted scanf character data with the 'c' conversion
where there is not enough input available to satisfy the field width
requested. It is required by ISO C that this conversion matches a
sequence of characters of exactly the number specified by the field
width and it is also already documented as such in our own manual:
"It reads precisely the next N characters, and fails if it cannot get
that many."
Currently a matching success is instead incorrectly produced where the
EOF condition is encountered before the required number of characters
has been retrieved, and the characters actually obtained are stored in
the buffer provided.
Add test cases accordingly and remove placeholders from 'c' conversion
input data for the existing scanf tests.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
[This is a modified version of commit 2b16c76609, which tests for the
old behavior and only includes the test cases, for older branches
and downstream backports - DJ]
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Conflicts:
localedata/Makefile
stdio-common/Makefile
(usual test differences)
diff -Nrup a/localedata/Makefile b/localedata/Makefile
--- a/localedata/Makefile 2026-06-16 09:15:19.295653341 -0400
+++ b/localedata/Makefile 2026-06-16 09:11:51.111414458 -0400
@@ -160,6 +160,7 @@ tests = \
bug-iconv-trans \
bug-setlocale1 \
bug-usesetlocale \
+ tst-bz12701-lc \
tst-c-utf8-consistency \
tst-digits \
tst-iconv-math-trans \
diff --git a/localedata/tst-bz12701-lc.c b/localedata/tst-bz12701-lc.c
new file mode 100644
index 0000000000..23c2ab7d2a
--- /dev/null
+++ b/localedata/tst-bz12701-lc.c
@@ -0,0 +1,218 @@
+/* Verify scanf field width handling with the 'lc' conversion (BZ #12701).
+ Copyright (C) 2025-2026 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <locale.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <wchar.h>
+
+#include <libc-diag.h>
+#include <support/check.h>
+#include <support/next_to_fault.h>
+#include <support/xstdio.h>
+
+/* Compare character-wise the initial part of the wide character object
+ pointed to by WS corresponding to wide characters obtained by the
+ conversion of first N bytes of the multibyte character object pointed
+ to by S. */
+
+static int
+tst_bz12701_lc_memcmp (const wchar_t *ds, const char *s, size_t n)
+{
+ size_t nc = mbsnrtowcs (NULL, &s, n, 0, NULL);
+
+ struct support_next_to_fault ntf;
+ ntf = support_next_to_fault_allocate (nc * sizeof (wchar_t));
+ wchar_t *ss = (wchar_t *) ntf.buffer;
+
+ mbsnrtowcs (ss, &s, n, nc, NULL);
+ int r = wmemcmp (ds, ss, nc);
+
+ support_next_to_fault_free (&ntf);
+
+ return r;
+}
+
+/* Verify various aspects of field width handling, including the data
+ obtained, the number of bytes consumed, and the stream position. */
+
+static int
+do_test (void)
+{
+ if (setlocale (LC_ALL, "pl_PL.UTF-8") == NULL)
+ FAIL_EXIT1 ("setlocale (LC_ALL, \"pl_PL.UTF-8\")");
+
+ /* Part of a tongue-twister in Polish, which says:
+ "On a rainy morning cuckoos and warblers, rather than starting
+ on earthworms, stuffed themselves fasted with the flesh of cress." */
+ static const char s[126] = "Dżdżystym rankiem gżegżółki i piegże, "
+ "zamiast wziąć się za dżdżownice, "
+ "nażarły się na czczo miąższu rzeżuchy";
+
+ const char *sp = s;
+ size_t nc;
+ TEST_VERIFY_EXIT ((nc = mbsnrtowcs (NULL, &sp, sizeof (s), 0, NULL)) == 108);
+
+ struct support_next_to_fault ntfo, ntfi;
+ ntfo = support_next_to_fault_allocate (nc * sizeof (wchar_t));
+ ntfi = support_next_to_fault_allocate (sizeof (s));
+ wchar_t *e = (wchar_t *) ntfo.buffer + nc;
+ char *b = ntfi.buffer;
+
+ wchar_t *c;
+ FILE *f;
+ int ic;
+ int n;
+ int i;
+
+ memcpy (ntfi.buffer, s, sizeof (s));
+
+ ic = i = 0;
+ f = xfmemopen (b, sizeof (s), "r");
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ /* Avoid: "warning: zero width in gnu_scanf format [-Werror=format=]". */
+ DIAG_PUSH_NEEDS_COMMENT;
+ DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wformat");
+ TEST_VERIFY_EXIT (fscanf (f, "%0lc%n", c, &n) == 1);
+ DIAG_POP_NEEDS_COMMENT;
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 1;
+ i += n;
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 2);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 1;
+ i += n;
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%1lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 1;
+ i += n;
+
+ c = e - 2;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 3);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 2;
+ i += n;
+
+ c = e - 4;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%4lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 4);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 4;
+ i += n;
+
+ c = e - 8;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%8lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 8);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 8;
+ i += n;
+
+ c = e - 16;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%16lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 20);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 16;
+ i += n;
+
+ c = e - 32;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%32lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 38);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 32;
+ i += n;
+
+ c = e - (nc - ic);
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_COMPARE (fscanf (f, "%64lc%n", c, &n), 1);
+ TEST_COMPARE (n , 49);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, sizeof (s) - i) == 0);
+
+ TEST_VERIFY_EXIT (ftell (f) == sizeof (s));
+ TEST_VERIFY_EXIT (feof (f) != 0);
+
+ xfclose (f);
+
+ ic = i = 0;
+ f = xfmemopen (b, 3, "r");
+
+ c = e - 2;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 3);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 2;
+ i += n;
+
+ c = e - (nc - ic);
+ TEST_VERIFY_EXIT (feof (f) == 0);
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2lc%n", c, &n) == EOF);
+ TEST_VERIFY_EXIT (n == 3);
+
+ TEST_VERIFY_EXIT (ftell (f) == 3);
+ TEST_VERIFY_EXIT (feof (f) != 0);
+
+ xfclose (f);
+
+ ic = i = 0;
+ f = xfmemopen (b, 3, "r");
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 1;
+ i += n;
+
+ c = e - (nc - ic);
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 2);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, 3 - i) == 0);
+
+ TEST_VERIFY_EXIT (ftell (f) == 3);
+ TEST_VERIFY_EXIT (feof (f) != 0);
+
+ xfclose (f);
+
+ support_next_to_fault_free (&ntfi);
+ support_next_to_fault_free (&ntfo);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
diff -Nrup a/stdio-common/Makefile b/stdio-common/Makefile
--- a/stdio-common/Makefile 2026-06-16 09:15:19.320447732 -0400
+++ b/stdio-common/Makefile 2026-06-16 09:14:09.814624522 -0400
@@ -217,6 +217,7 @@ tests := \
tllformat \
tst-bz11319 \
tst-bz11319-fortify2 \
+ tst-bz12701-c \
tst-cookie \
tst-fclose-devzero \
tst-fclose-offset \
diff --git a/stdio-common/tst-bz12701-c.c b/stdio-common/tst-bz12701-c.c
new file mode 100644
index 0000000000..4f3616fbfd
--- /dev/null
+++ b/stdio-common/tst-bz12701-c.c
@@ -0,0 +1,169 @@
+/* Verify scanf field width handling with the 'c' conversion (BZ #12701).
+ Copyright (C) 2025-2026 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <stdio.h>
+#include <string.h>
+
+#include <libc-diag.h>
+#include <support/check.h>
+#include <support/next_to_fault.h>
+#include <support/xstdio.h>
+
+/* Verify various aspects of field width handling, including the data
+ obtained, the number of bytes consumed, and the stream position. */
+
+static int
+do_test (void)
+{
+ static const char s[43] = "The quick brown fox jumps over the lazy dog";
+ struct support_next_to_fault ntfo, ntfi;
+ ntfo = support_next_to_fault_allocate (sizeof (s));
+ ntfi = support_next_to_fault_allocate (sizeof (s));
+ char *e = ntfo.buffer + sizeof (s);
+ char *b = ntfi.buffer;
+
+ char *c;
+ FILE *f;
+ int n;
+ int i;
+
+ memcpy (ntfi.buffer, s, sizeof (s));
+
+ i = 0;
+ f = xfmemopen (b, sizeof (s), "r");
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ /* Avoid: "warning: zero width in gnu_scanf format [-Werror=format=]". */
+ DIAG_PUSH_NEEDS_COMMENT;
+ DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wformat");
+ TEST_VERIFY_EXIT (fscanf (f, "%0c%n", c, &n) == 1);
+ DIAG_POP_NEEDS_COMMENT;
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%1c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - 2;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 2);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - 4;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%4c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 4);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - 8;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%8c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 8);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - 16;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%16c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 16);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - (sizeof (s) - i);
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%32c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 10);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, sizeof (s) - i) == 0);
+
+ TEST_VERIFY_EXIT (ftell (f) == sizeof (s));
+ TEST_VERIFY_EXIT (feof (f) != 0);
+
+ xfclose (f);
+
+ i = 0;
+ f = xfmemopen (b, 3, "r");
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - 2;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 2);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - (3 - i);
+ TEST_VERIFY_EXIT (feof (f) == 0);
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2c%n", c, &n) == EOF);
+ TEST_VERIFY_EXIT (n == 2);
+
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (feof (f) != 0);
+
+ xfclose (f);
+
+ i = 0;
+ f = xfmemopen (b, 3, "r");
+
+ c = e - 2;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 2);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - (3 - i);
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, 3 - i) == 0);
+
+ TEST_VERIFY_EXIT (ftell (f) == 3);
+ TEST_VERIFY_EXIT (feof (f) != 0);
+
+ xfclose (f);
+
+ support_next_to_fault_free (&ntfi);
+ support_next_to_fault_free (&ntfo);
+
+ return 0;
+}
+
+#include <support/test-driver.c>

View File

@ -0,0 +1,193 @@
commit 6cebb0b80fd783e442a8ad27c3f52cde52a9cac7
Author: DJ Delorie <dj@redhat.com>
Date: Wed May 27 12:57:10 2026 -0400
stdio-common: Allow partially-filled %mc buffers [BZ #12701]
This is a backwards-compatible alternative to the main solution to
the %mc part of 12701. The allocated buffer is expanded to the
requested size and NUL padded, but truncated reads are allowed.
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Conflicts:
localedata/Makefile
stdio-common/Makefile
(usual test differences)
diff -Nrup a/localedata/Makefile b/localedata/Makefile
--- a/localedata/Makefile 2026-06-16 10:00:37.802495527 -0400
+++ b/localedata/Makefile 2026-06-16 09:59:09.766677507 -0400
@@ -161,6 +161,7 @@ tests = \
bug-setlocale1 \
bug-usesetlocale \
tst-bz12701-lc \
+ tst-bz12701-lc2 \
tst-c-utf8-consistency \
tst-digits \
tst-iconv-math-trans \
diff --git a/localedata/tst-bz12701-lc2.c b/localedata/tst-bz12701-lc2.c
new file mode 100644
index 0000000000..b24e86df0b
--- /dev/null
+++ b/localedata/tst-bz12701-lc2.c
@@ -0,0 +1,47 @@
+/* Verify scanf memory handling with the 'c' conversion (BZ #12701).
+ Copyright (C) 2026 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <stdio.h>
+#include <malloc.h>
+#include <string.h>
+
+#include <libc-diag.h>
+#include <support/check.h>
+#include <support/next_to_fault.h>
+#include <support/xstdio.h>
+
+static int
+do_test (void)
+{
+ wchar_t *c = NULL;
+ int i;
+
+ TEST_VERIFY (sscanf ("1234", "%30mlc", &c) == 1);
+
+ TEST_VERIFY (c != NULL);
+ TEST_COMPARE_BLOB (c, 5 * sizeof (wchar_t),
+ L"1234\0", 5 * sizeof (wchar_t));
+ for (i = 5; i < 30; i ++)
+ TEST_VERIFY (c[i] == L'\0');
+
+ TEST_VERIFY (malloc_usable_size (c) >= 30 * sizeof(wchar_t));
+
+ return 0;
+}
+
+#include <support/test-driver.c>
diff -Nrup a/stdio-common/Makefile b/stdio-common/Makefile
--- a/stdio-common/Makefile 2026-06-16 10:00:37.803143281 -0400
+++ b/stdio-common/Makefile 2026-06-16 09:59:54.974421920 -0400
@@ -218,6 +218,7 @@ tests := \
tst-bz11319 \
tst-bz11319-fortify2 \
tst-bz12701-c \
+ tst-bz12701-c2 \
tst-cookie \
tst-fclose-devzero \
tst-fclose-offset \
diff --git a/stdio-common/tst-bz12701-c2.c b/stdio-common/tst-bz12701-c2.c
new file mode 100644
index 0000000000..5f9ca7c592
--- /dev/null
+++ b/stdio-common/tst-bz12701-c2.c
@@ -0,0 +1,46 @@
+/* Verify scanf memory handling with the 'c' conversion (BZ #12701).
+ Copyright (C) 2026 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <stdio.h>
+#include <malloc.h>
+#include <string.h>
+
+#include <libc-diag.h>
+#include <support/check.h>
+#include <support/next_to_fault.h>
+#include <support/xstdio.h>
+
+static int
+do_test (void)
+{
+ char *c = NULL;
+ int i;
+
+ TEST_VERIFY (sscanf ("1234", "%30mc", &c) == 1);
+
+ TEST_VERIFY (c != NULL);
+ TEST_COMPARE_BLOB (c, 5, "1234\0", 5);
+ for (i = 5; i < 30; i ++)
+ TEST_VERIFY (c[i] == '\0');
+
+ TEST_VERIFY (malloc_usable_size (c) >= 30);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/stdio-common/vfscanf-internal.c b/stdio-common/vfscanf-internal.c
index 17b5565d0f..90a1886951 100644
--- a/stdio-common/vfscanf-internal.c
+++ b/stdio-common/vfscanf-internal.c
@@ -780,9 +780,9 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
conv_error (); \
} while (0)
#ifdef COMPILE_WSCANF
- STRING_ARG (str, char, 100);
+ STRING_ARG (str, char, (width > 0 ? width : 1));
#else
- STRING_ARG (str, char, (width > 1024 ? 1024 : width));
+ STRING_ARG (str, char, (width > 0 ? width : 1));
#endif
c = inchar ();
@@ -891,6 +891,11 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
if (!(flags & SUPPRESS))
{
+ /* If the buffer isn't completely filled, pad it with NULs. */
+ if (flags & MALLOC)
+ while (width-- > 0)
+ *str++ = '\0';
+
if ((flags & MALLOC) && str - *strptr != strsize)
{
char *cp = (char *) realloc (*strptr, str - *strptr);
@@ -908,7 +913,7 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
if (width == -1)
width = 1;
- STRING_ARG (wstr, wchar_t, (width > 1024 ? 1024 : width));
+ STRING_ARG (wstr, wchar_t, (width > 0 ? width : 1));
c = inchar ();
if (__glibc_unlikely (c == EOF))
@@ -1044,6 +1049,11 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
if (!(flags & SUPPRESS))
{
+ /* If the buffer isn't completely filled, pad it with NULs. */
+ if (flags & MALLOC)
+ while (width-- > 0)
+ *wstr++ = L'\0';
+
if ((flags & MALLOC) && wstr - (wchar_t *) *strptr != strsize)
{
wchar_t *cp = (wchar_t *) realloc (*strptr,

View File

@ -0,0 +1,117 @@
commit e9325bd7d04aacc45cf39505e279b1ca9de22c08
Author: Florian Weimer <fweimer@redhat.com>
Date: Tue Jun 9 07:28:02 2026 +0200
iconv: Suppress intermediate errors with //TRANSLIT (bug 34236)
When tentatively converting characters on behalf of
__gconv_transliterate, do not create a persistent error. Just
produce a local error, and rely on __gconv_transliterate to
produce the error if all transliteration options are exhausted.
This fixes transliteration of “½” to ASCII, which cannot use the
“ 12 ” alternative. Eventually, the “ 1/2 ” alternative is chosen,
but the error sticks. Therefore, iconv exited with status 1 before
this change.
Adjust iconv/tst-iconv_prog.sh to test both C and en_US.UTF-8 locales.
This requires changing the way the ICONV template is defined, so that
run_program_env is evaluated multiple times.
Fixes commit 9a4b0eaf726f5404c6683d5c7c5e86f61c3f3fbc ("iconv: do not
report error exit with transliteration [BZ #32448]"),
commit 6cbf845fcdc76131d0e674cee454fe738b69c69d ("iconv: Preserve
iconv -c error exit on invalid inputs (bug 32046)"), and bug 34236.
Reviewed-by: Aurelien Jarno <aurelien@aurel32.net>
Conflicts:
iconv/Makefile
(adjust for slight context differences)
iconv/tst-iconv_prog.sh
(adjust for slight context differences)
diff -Nrup a/iconv/Makefile b/iconv/Makefile
--- a/iconv/Makefile 2026-07-10 15:33:08.645711776 -0400
+++ b/iconv/Makefile 2026-07-10 15:35:00.893038141 -0400
@@ -130,7 +130,8 @@ $(objpfx)test-iconvconfig.out: $(objpfx)
rm -f $$tmp) > $@; \
$(evaluate-test)
-$(objpfx)tst-iconv_prog.out: tst-iconv_prog.sh $(objpfx)iconv_prog
+$(objpfx)tst-iconv_prog.out: tst-iconv_prog.sh $(objpfx)iconv_prog \
+ $(gen-locales)
$(BASH) $< $(common-objdir) '$(test-wrapper-env)' \
'$(run-program-env)' > $@; \
$(evaluate-test)
diff --git a/iconv/loop.c b/iconv/loop.c
index 27dea1f5d6..81841d0d2d 100644
--- a/iconv/loop.c
+++ b/iconv/loop.c
@@ -144,8 +144,10 @@
if (irreversible == NULL) \
{ \
/* This means we are in call from __gconv_transliterate. In this \
- case we are not doing any error recovery ourselves. */ \
- result = __gconv_mark_illegal_input (step_data); \
+ case we are not doing any error recovery ourselves. Do not create \
+ a persistent error state. If __gconv_transliterate exhausts all \
+ alternatives, it will call __gconv_mark_illegal_input itself. */ \
+ result = __GCONV_ILLEGAL_INPUT; \
break; \
} \
\
diff -Nrup a/iconv/tst-iconv_prog.sh b/iconv/tst-iconv_prog.sh
--- a/iconv/tst-iconv_prog.sh 2026-07-15 11:10:33.321344932 -0400
+++ b/iconv/tst-iconv_prog.sh 2026-07-15 11:16:10.328740778 -0400
@@ -27,10 +27,10 @@ LIBPATH=$codir:$codir/iconvdata
# How the start the iconv(1) program. $from is not defined/expanded yet.
ICONV='
+$test_wrapper_env $run_program_env
$codir/elf/ld.so --library-path $LIBPATH --inhibit-rpath ${from}.so
$codir/iconv/iconv_prog
'
-ICONV="$test_wrapper_env $run_program_env $ICONV"
# List of known hangs;
# Gathered by running an exhaustive 2 byte input search against glibc-2.28
@@ -216,6 +216,7 @@ testarray=(
"\x00\x00;;INVALID;UTF-8;1"
"\x00\x00;;UTF-8;INVALID;1"
"\xc3\xa9;;UTF-8;ASCII//TRANSLIT;0"
+"X\xc2\xbdY;;UTF-8;ASCII//TRANSLIT;0"
)
# Requires $twobyte input, $c flag, $from, and $to to be set; sets $ret
@@ -275,12 +276,21 @@ check_errtest_result ()
fi
}
-for testcommand in "${testarray[@]}"; do
- twobyte="$(echo "$testcommand" | cut -d";" -f 1)"
- c="$(echo "$testcommand" | cut -d";" -f 2)"
- from="$(echo "$testcommand" | cut -d";" -f 3)"
- to="$(echo "$testcommand" | cut -d";" -f 4)"
- eret="$(echo "$testcommand" | cut -d";" -f 5)"
- execute_test
- check_errtest_result
-done
+run_test_array ()
+{
+ for testcommand in "${testarray[@]}"; do
+ twobyte="$(echo "$testcommand" | cut -d";" -f 1)"
+ c="$(echo "$testcommand" | cut -d";" -f 2)"
+ from="$(echo "$testcommand" | cut -d";" -f 3)"
+ to="$(echo "$testcommand" | cut -d";" -f 4)"
+ eret="$(echo "$testcommand" | cut -d";" -f 5)"
+ execute_test
+ check_errtest_result
+ done
+}
+
+echo "info: testing C locale"
+run_test_array
+echo "info: testing en_US.UTF-8 locale"
+run_program_env="$run_program_env LC_ALL=en_US.UTF-8"
+run_test_array

View File

@ -0,0 +1,47 @@
commit 360f352c9a6da545d798ef3015e73ca114f0d230
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Jun 19 18:22:20 2026 +0200
resolv: Declare __p_class_syms, __p_type_syms for internal use
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/include/resolv.h b/include/resolv.h
index 4dbbac3800..d5ad9994b9 100644
--- a/include/resolv.h
+++ b/include/resolv.h
@@ -70,6 +70,11 @@ libc_hidden_proto (__libc_res_nameinquery)
extern __typeof (__res_queriesmatch) __libc_res_queriesmatch;
libc_hidden_proto (__libc_res_queriesmatch)
+extern const struct res_sym __p_class_syms[];
+libresolv_hidden_proto (__p_class_syms)
+extern const struct res_sym __p_type_syms[];
+libresolv_hidden_proto (__p_type_syms)
+
/* Variant of res_hnok which operates on binary (but uncompressed) names. */
bool __res_binary_hnok (const unsigned char *dn) attribute_hidden;
diff --git a/resolv/res_debug.c b/resolv/res_debug.c
index 73af0c72fe..6bf9962916 100644
--- a/resolv/res_debug.c
+++ b/resolv/res_debug.c
@@ -390,8 +390,6 @@ p_fqname(const u_char *cp, const u_char *msg, FILE *file) {
* that C_ANY is a qclass but not a class. (You can ask for records of class
* C_ANY, but you can't have any records of that class in the database.)
*/
-extern const struct res_sym __p_class_syms[];
-libresolv_hidden_proto (__p_class_syms)
const struct res_sym __p_class_syms[] = {
{C_IN, (char *) "IN"},
{C_CHAOS, (char *) "CHAOS"},
@@ -426,8 +424,6 @@ const struct res_sym __p_update_section_syms[] attribute_hidden = {
* Names of RR types and qtypes. The list is incomplete because its
* size is part of the ABI.
*/
-extern const struct res_sym __p_type_syms[];
-libresolv_hidden_proto (__p_type_syms)
const struct res_sym __p_type_syms[] = {
{ns_t_a, (char *) "A", (char *) "address"},
{ns_t_ns, (char *) "NS", (char *) "name server"},

View File

@ -0,0 +1,70 @@
commit f69b7f95e3694177546faec25d88bb266885c3b8
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Jun 19 18:22:20 2026 +0200
resolv: Fix ns_sprintrrf formatting of class, type values (bug 34289)
The p_class and p_type results could overwrite each other if both
were unknown. Format unknown values with CLASS and TYPE prefixes,
as in RFC 3597. Handle A6 separately because it cannot be added
to __p_type_syms for ABI reasons.
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/resolv/ns_print.c b/resolv/ns_print.c
index cef2212fd2..e75c39eaa8 100644
--- a/resolv/ns_print.c
+++ b/resolv/ns_print.c
@@ -78,6 +78,24 @@ ns_sprintrr(const ns_msg *handle, const ns_rr *rr,
}
libresolv_hidden_def (ns_sprintrr)
+/* Writes the class/type symbol NUMBER to *BUF, using the name from
+ *SYMS if possible. If NUMBER is not found in *SYMS, print the
+ number with PREFIX. */
+static int
+addsym (const struct res_sym *syms, int number, const char *prefix,
+ char **buf, size_t *buflen)
+{
+ for (; syms->name != NULL; syms++)
+ if (number == syms->number)
+ {
+ T (addstr (" ", 1, buf, buflen));
+ return addstr (syms->name, strlen (syms->name), buf, buflen);
+ }
+ char tmp[20];
+ int len = snprintf (tmp, sizeof (tmp), " %s%d", prefix, number);
+ return addstr (tmp, len, buf, buflen);
+}
+
/*%
* Convert the fields of an RR into presentation format.
*
@@ -128,11 +146,21 @@ ns_sprintrrf(const u_char *msg, size_t msglen,
/*
* TTL, Class, Type.
*/
- T(x = ns_format_ttl(ttl, buf, buflen));
- addlen(x, &buf, &buflen);
- len = SPRINTF((tmp, " %s %s", p_class(class), p_type(type)));
- T(addstr(tmp, len, &buf, &buflen));
- T(spaced = addtab(x + len, 16, spaced, &buf, &buflen));
+ {
+ char *start = buf;
+
+ T (x = ns_format_ttl (ttl, buf, buflen));
+ addlen (x, &buf, &buflen);
+ T (addsym (__p_class_syms, class, "CLASS", &buf, &buflen));
+ if (type == ns_t_a6)
+ /* A6 is not part of __p_type_syms, which is exported.
+ Adding A6 there would change its size. Handle it here. */
+ T (addstr (" A6", 3, &buf, &buflen));
+ else
+ T (addsym (__p_type_syms, type, "TYPE", &buf, &buflen));
+
+ T (spaced = addtab(buf - start, 16, spaced, &buf, &buflen));
+ }
/*
* RData.

View File

@ -0,0 +1,48 @@
commit d58415eb17d457a160af99f9e8ab164404ca151b
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Jun 19 18:22:20 2026 +0200
resolv: Improve formatting of unknown records in ns_sprintrrf
Do not add the "unknown RR type" comment. After adding the TYPE
prefix, the number is largely redundant.
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/resolv/ns_print.c b/resolv/ns_print.c
index e75c39eaa8..3d38876483 100644
--- a/resolv/ns_print.c
+++ b/resolv/ns_print.c
@@ -115,7 +115,6 @@ ns_sprintrrf(const u_char *msg, size_t msglen,
const char *comment;
char tmp[100];
- char errbuf[40];
int len, x;
/*
@@ -590,20 +589,18 @@ ns_sprintrrf(const u_char *msg, size_t msglen,
T(addstr(tmp, len, &buf, &buflen));
break;
}
-
default:
- snprintf (errbuf, sizeof (errbuf), "unknown RR type %d", type);
- comment = errbuf;
+ comment = "";
goto hexify;
}
return (buf - obuf);
formerr:
- comment = "RR format error";
+ comment = " ; RR format error";
hexify: {
int n, m;
char *p;
- len = SPRINTF((tmp, "\\# %u%s\t; %s", (unsigned)(edata - rdata),
+ len = SPRINTF((tmp, "\\# %u%s%s", (unsigned)(edata - rdata),
rdlen != 0U ? " (" : "", comment));
T(addstr(tmp, len, &buf, &buflen));
while (rdata < edata) {

View File

@ -0,0 +1,63 @@
commit cd0db208d56a2cecd528b8ae96df752ba5344d9a
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Jun 19 18:22:20 2026 +0200
resolv: Check for inet_ntop failure in ns_sprintrrf
This makes the output more consistent (either failure or complete
output) and helps with systematic testing with varying buffer
sizes.
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/resolv/ns_print.c b/resolv/ns_print.c
index 3d38876483..e58df5f35a 100644
--- a/resolv/ns_print.c
+++ b/resolv/ns_print.c
@@ -167,8 +167,9 @@ ns_sprintrrf(const u_char *msg, size_t msglen,
switch (type) {
case ns_t_a:
if (rdlen != (size_t)NS_INADDRSZ)
- goto formerr;
- (void) inet_ntop(AF_INET, rdata, buf, buflen);
+ goto formerr;
+ if (inet_ntop (AF_INET, rdata, buf, buflen) == NULL)
+ return -1;
addlen(strlen(buf), &buf, &buflen);
break;
@@ -334,9 +335,10 @@ ns_sprintrrf(const u_char *msg, size_t msglen,
}
case ns_t_aaaa:
- if (rdlen != (size_t)NS_IN6ADDRSZ)
- goto formerr;
- (void) inet_ntop(AF_INET6, rdata, buf, buflen);
+ if (rdlen != (size_t)NS_IN6ADDRSZ)
+ goto formerr;
+ if (inet_ntop (AF_INET6, rdata, buf, buflen) == NULL)
+ return -1;
addlen(strlen(buf), &buf, &buflen);
break;
@@ -427,7 +429,8 @@ ns_sprintrrf(const u_char *msg, size_t msglen,
goto formerr;
/* Address. */
- (void) inet_ntop(AF_INET, rdata, buf, buflen);
+ if (inet_ntop (AF_INET, rdata, buf, buflen) == NULL)
+ return -1;
addlen(strlen(buf), &buf, &buflen);
rdata += NS_INADDRSZ;
@@ -569,7 +572,8 @@ ns_sprintrrf(const u_char *msg, size_t msglen,
if (rdata + pbyte >= edata) goto formerr;
memset(&a, 0, sizeof(a));
memcpy(&a.s6_addr[pbyte], rdata, sizeof(a) - pbyte);
- (void) inet_ntop(AF_INET6, &a, buf, buflen);
+ if (inet_ntop (AF_INET6, &a, buf, buflen) == NULL)
+ return -1;
addlen(strlen(buf), &buf, &buflen);
rdata += sizeof(a) - pbyte;
}

View File

@ -0,0 +1,130 @@
commit ca44a6609c29a683b03575fa035c6d17aa591e72
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Jun 19 18:22:20 2026 +0200
resolv: More types as unknown in ns_sprintrrf (CVE-2026-5435)
Specifically, CERT, TKEY, TSIG, OPT. This removes the buggy
implementations of TSIG, fixing bug 34033, and partially
fixing bug 34069.
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Conflicts:
resolv/ns_print.c
Account for typo fix in 7f0d9e61f40c
diff --git a/resolv/ns_print.c b/resolv/ns_print.c
index e58df5f35a..ab68bf2cb7 100644
--- a/resolv/ns_print.c
+++ b/resolv/ns_print.c
@@ -464,96 +464,6 @@ ns_sprintrrf(const u_char *msg, size_t msglen,
break;
}
- case ns_t_cert: {
- u_int c_type, key_tag, alg;
- int n;
- unsigned int siz;
- char base64_cert[8192], tmp[40];
- const char *leader;
-
- c_type = ns_get16(rdata); rdata += NS_INT16SZ;
- key_tag = ns_get16(rdata); rdata += NS_INT16SZ;
- alg = (u_int) *rdata++;
-
- len = SPRINTF((tmp, "%d %d %d ", c_type, key_tag, alg));
- T(addstr(tmp, len, &buf, &buflen));
- siz = (edata-rdata)*4/3 + 4; /* "+4" accounts for trailing \0 */
- if (siz > sizeof(base64_cert) * 3/4) {
- const char *str = "record too long to print";
- T(addstr(str, strlen(str), &buf, &buflen));
- }
- else {
- len = b64_ntop(rdata, edata-rdata, base64_cert, siz);
-
- if (len < 0)
- goto formerr;
- else if (len > 15) {
- T(addstr(" (", 2, &buf, &buflen));
- leader = "\n\t\t";
- spaced = 0;
- }
- else
- leader = " ";
-
- for (n = 0; n < len; n += 48) {
- T(addstr(leader, strlen(leader),
- &buf, &buflen));
- T(addstr(base64_cert + n, MIN(len - n, 48),
- &buf, &buflen));
- }
- if (len > 15)
- T(addstr(" )", 2, &buf, &buflen));
- }
- break;
- }
-
- case ns_t_tkey: {
- /* KJD - need to complete this */
- u_long t;
- int mode, err, keysize;
-
- /* Algorithm name. */
- T(addname(msg, msglen, &rdata, origin, &buf, &buflen));
- T(addstr(" ", 1, &buf, &buflen));
-
- /* Inception. */
- t = ns_get32(rdata); rdata += NS_INT32SZ;
- len = SPRINTF((tmp, "%lu ", t));
- T(addstr(tmp, len, &buf, &buflen));
-
- /* Experation. */
- t = ns_get32(rdata); rdata += NS_INT32SZ;
- len = SPRINTF((tmp, "%lu ", t));
- T(addstr(tmp, len, &buf, &buflen));
-
- /* Mode , Error, Key Size. */
- /* Priority, Weight, Port. */
- mode = ns_get16(rdata); rdata += NS_INT16SZ;
- err = ns_get16(rdata); rdata += NS_INT16SZ;
- keysize = ns_get16(rdata); rdata += NS_INT16SZ;
- len = SPRINTF((tmp, "%u %u %u ", mode, err, keysize));
- T(addstr(tmp, len, &buf, &buflen));
-
- /* XXX need to dump key, print otherdata length & other data */
- break;
- }
-
- case ns_t_tsig: {
- /* BEW - need to complete this */
- int n;
-
- T(len = addname(msg, msglen, &rdata, origin, &buf, &buflen));
- T(addstr(" ", 1, &buf, &buflen));
- rdata += 8; /*%< time */
- n = ns_get16(rdata); rdata += INT16SZ;
- rdata += n; /*%< sig */
- n = ns_get16(rdata); rdata += INT16SZ; /*%< original id */
- sprintf(buf, "%d", ns_get16(rdata));
- rdata += INT16SZ;
- addlen(strlen(buf), &buf, &buflen);
- break;
- }
-
case ns_t_a6: {
struct in6_addr a;
int pbyte, pbit;
@@ -588,11 +498,6 @@ ns_sprintrrf(const u_char *msg, size_t msglen,
break;
}
- case ns_t_opt: {
- len = SPRINTF((tmp, "%u bytes", class));
- T(addstr(tmp, len, &buf, &buflen));
- break;
- }
default:
comment = "";
goto hexify;

View File

@ -0,0 +1,59 @@
commit a7b60d23bbb56eaef59f4962e4140062e552600a
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Jun 19 18:22:20 2026 +0200
resolv: Fix buffer overreads in ns_sprintrrf (CVE-2026-6238)
Check that the RDATA payload does not require more than RDATALEN
bytes while processing it. The fixes cover A6, LOC records.
(CERT, TKEY, TSIG were fixed before, by switching to the generic
formatter.)
The vulnerable LOC record handling was first introduced before
glibc 2.0, in commit ee188d555b8c32ad9704a7440cab400af967292f.
CERT, TSIG, TKEY handling came with commit
b43b13ac2544b11f35be301d1589b51a8473e32b, released with glibc 2.2.
A6 record handling was introduced in commit
91633816430e7ec5a19fe3ff510a7c4822a9557e ("* resolv/ns_print.c
(ns_sprintrrf): Handle ns_t_a6 and ns_t_opt."), which went into glibc
2.7.
This fixes bug 34069.
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/resolv/ns_print.c b/resolv/ns_print.c
index ab68bf2cb7..f9dd086804 100644
--- a/resolv/ns_print.c
+++ b/resolv/ns_print.c
@@ -345,7 +345,8 @@ ns_sprintrrf(const u_char *msg, size_t msglen,
case ns_t_loc: {
char t[255];
- /* XXX protocol format checking? */
+ if (rdlen != 16)
+ goto formerr;
(void) loc_ntoa(rdata, t);
T(addstr(t, strlen(t), &buf, &buflen));
break;
@@ -479,13 +480,14 @@ ns_sprintrrf(const u_char *msg, size_t msglen,
/* address suffix: provided only when prefix len != 128 */
if (pbit < 128) {
- if (rdata + pbyte >= edata) goto formerr;
+ unsigned int bytelen = sizeof(a) - pbyte;
+ if (edata - rdata < bytelen) goto formerr;
memset(&a, 0, sizeof(a));
- memcpy(&a.s6_addr[pbyte], rdata, sizeof(a) - pbyte);
+ memcpy(&a.s6_addr[pbyte], rdata, bytelen);
if (inet_ntop (AF_INET6, &a, buf, buflen) == NULL)
return -1;
addlen(strlen(buf), &buf, &buflen);
- rdata += sizeof(a) - pbyte;
+ rdata += bytelen;
}
/* prefix name: provided only when prefix len > 0 */

View File

@ -0,0 +1,371 @@
commit 4ba0b79b9596e5a4951cc9eaa1546a55e543e083
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Jun 19 18:22:20 2026 +0200
resolv: Add test case tst-ns_sprintrr (bug 34033, bug 34069)
This test case covers both input buffer overreads and output buffer
overflows. It should systematically cover these issues.
I used code auto-generation for updating the test expectations for
truncated RDATA in TXT, ISDN records, after writing the rest
of the test by hand.
Assisted-by: LLM
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/resolv/Makefile b/resolv/Makefile
index 68b3a4dbf3..02cc751732 100644
--- a/resolv/Makefile
+++ b/resolv/Makefile
@@ -108,6 +108,7 @@ tests += \
tst-ns_name \
tst-ns_name_compress \
tst-ns_name_pton \
+ tst-ns_sprintrr \
tst-res_hconf_reorder \
tst-res_hnok \
tst-resolv-aliases \
@@ -341,5 +342,6 @@ $(objpfx)tst-ns_name: $(objpfx)libresolv.so
$(objpfx)tst-ns_name.out: tst-ns_name.data
$(objpfx)tst-ns_name_compress: $(objpfx)libresolv.so
$(objpfx)tst-ns_name_pton: $(objpfx)libresolv.so
+$(objpfx)tst-ns_sprintrr: $(objpfx)libresolv.so
$(objpfx)tst-res_hnok: $(objpfx)libresolv.so
$(objpfx)tst-p_secstodate: $(objpfx)libresolv.so
diff --git a/resolv/tst-ns_sprintrr.c b/resolv/tst-ns_sprintrr.c
new file mode 100644
index 0000000000..34739b5924
--- /dev/null
+++ b/resolv/tst-ns_sprintrr.c
@@ -0,0 +1,329 @@
+/* Tests for the ns_sprintrr function.
+ Copyright (C) 2026 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <arpa/nameser.h>
+
+#include <alloc_buffer.h>
+#include <arpa/inet.h>
+#include <libc-diag.h>
+#include <stdbool.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/next_to_fault.h>
+
+#include <stdio.h>
+
+/* Regions that test_one_record uses for input and output. */
+static struct support_next_to_fault ntf_in;
+static struct support_next_to_fault ntf_out;
+
+/* This is used by test_one_record to construct the packet. */
+static const char packet_prefix[] =
+ /* DNS response with one question, one answer record. */
+ "AA\x81\x80\0\1\0\1\0\0\0\0"
+ /* Question: www.example.org/IN/ANY. */
+ "\3www\7example\3org\0\0\xff\0\1"
+ /* Response: compression reference. */
+ "\xc0\x0c";
+
+/* Use ns_sprintrr to format a DNS record (starting with
+ packet_prefix) of type RTYPE, with a record payload of RDATALEN
+ bytes starting at RDATA. Check successful formatting against
+ EXPECTED. Try various truncated input and output buffers to catch
+ overreads and buffer overflows, using ntf_in and ntf_out above. */
+static void
+test_one_record (uint16_t rtype, const char *rdata, size_t rdatalen,
+ const char *expected)
+{
+ struct rr_header
+ {
+ uint16_t typ;
+ uint16_t cls;
+ uint32_t ttl;
+ uint16_t rdatalen;
+ uint16_t pad;
+ } hdr =
+ {
+ .typ = htons (rtype),
+ .cls = htons (ns_c_in),
+ .ttl = htonl (86400), /* One day. */
+ .rdatalen = htons (rdatalen),
+ };
+ enum { hdrlen = offsetof (struct rr_header, pad) };
+ TEST_COMPARE (hdrlen, 10);
+
+ /* Construct the packet from packet_prefix, hdr, and rdata. */
+ unsigned char packet[512];
+ size_t packetlen;
+ {
+ struct alloc_buffer buf = alloc_buffer_create (packet, sizeof (packet));
+ alloc_buffer_copy_bytes (&buf, packet_prefix, sizeof (packet_prefix) - 1);
+ alloc_buffer_copy_bytes (&buf, &hdr, hdrlen);
+ alloc_buffer_copy_bytes (&buf, rdata, rdatalen);
+ packetlen = sizeof (packet) - alloc_buffer_size (&buf);
+ }
+
+ /* Parse the record. */
+ ns_msg msg;
+ TEST_COMPARE (ns_initparse (packet, packetlen, &msg), 0);
+ ns_rr rr;
+ TEST_COMPARE (ns_parserr (&msg, ns_s_an, 0, &rr), 0);
+
+ /* Try sizes up to this limit. Go a bit beyond the expected size to
+ check for errors. */
+ size_t max_result_size = strlen (expected) + 16;
+
+ bool success = false;
+ for (size_t result_size = 1; result_size <= max_result_size; ++result_size)
+ {
+ char *result_start = ntf_out.buffer + ntf_out.length - result_size;
+ memset (result_start, 'X', result_size);
+
+ /* ns_sprintrr was deprecated in 2.34. */
+ DIAG_PUSH_NEEDS_COMMENT;
+ DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wdeprecated-declarations");
+ int ret = ns_sprintrr (&msg, &rr, NULL, NULL, result_start, result_size);
+ DIAG_POP_NEEDS_COMMENT;
+
+ if (ret > 0)
+ {
+ TEST_COMPARE_STRING (result_start, expected);
+ TEST_COMPARE (ret, strlen (expected));
+ success = true;
+ }
+ else
+ {
+ TEST_VERIFY (!success);
+ TEST_COMPARE (ret, -1);
+ }
+ }
+ TEST_VERIFY (success);
+
+ /* Test with truncated RDATA. */
+ for (size_t rdata_size = 0; rdata_size <= rdatalen; ++rdata_size)
+ {
+ size_t truncated_packet_size = packetlen - rdatalen + rdata_size;
+ unsigned char *packet_start
+ = ((unsigned char *) ntf_in.buffer + ntf_in.length
+ - truncated_packet_size);
+ memcpy (packet_start, packet, truncated_packet_size);
+ /* Patch in the updated RDATA length field. */
+ uint16_t new_rdatalen = htons (rdata_size);
+ memcpy (packet_start + truncated_packet_size - rdata_size - 2,
+ &new_rdatalen, 2);
+
+ ns_msg msg;
+ TEST_COMPARE (ns_initparse (packet_start, truncated_packet_size, &msg),
+ 0);
+ ns_rr rr;
+ TEST_COMPARE (ns_parserr (&msg, ns_s_an, 0, &rr), 0);
+
+ size_t result_size = strlen (expected) + 1;
+ char *result_start = ntf_out.buffer + ntf_out.length - result_size;
+ memset (result_start, 'X', result_size);
+
+ /* ns_sprintrr was deprecated in 2.34. */
+ DIAG_PUSH_NEEDS_COMMENT;
+ DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wdeprecated-declarations");
+ int ret = ns_sprintrr (&msg, &rr, NULL, NULL, result_start, result_size);
+ DIAG_POP_NEEDS_COMMENT;
+
+ /* This flag indicates whether the output is syntactically
+ correct. In some cases, truncation may still yield a valid
+ payload. */
+ bool broken = rdata_size < rdatalen;
+ switch (rtype)
+ {
+ case ns_t_wks:
+ /* WKS records use all trailing bytes for the port bitmap. */
+ broken = rdata_size < 5;
+ break;
+ case ns_t_nsap:
+ /* Uses all bytes that are available. */
+ broken = false;
+ break;
+ case ns_t_txt:
+ /* Truncation produces a valid payload if it occurs right
+ after a complete string in the TXT payload. */
+ broken = false;
+ for (size_t pos = 0; pos < rdata_size; )
+ {
+ unsigned int slen = rdata[pos] & 0xff;
+ if (pos + 1 + slen > rdata_size)
+ {
+ broken = true;
+ break;
+ }
+ pos += 1 + slen;
+ }
+ break;
+ case ns_t_isdn:
+ /* The second field is optional. If it is present, it must
+ not be truncated. */
+ broken = rdata_size < 6 || (rdata_size > 6 && rdata_size < rdatalen);
+ break;
+ case ns_t_a6:
+ /* The first A6 subtest contains a trailing domain name,
+ which is ignored and not formatted. */
+ if (rdata_size > 0 && rdata[0] == 0)
+ broken = rdata_size < 17;
+ break;
+ case ns_t_cert:
+ case ns_t_tkey:
+ case ns_t_tsig:
+ /* Only generic printing, which does not validate anything. */
+ broken = false;
+ break;
+ }
+
+ if (broken)
+ {
+ if (strstr (result_start, "RR format error") != NULL)
+ /* No further checks if an error indicator has been added
+ to the output. */
+ ;
+ else
+ TEST_COMPARE (ret, -1);
+ }
+ else
+ TEST_VERIFY (ret > 0);
+ }
+}
+
+static int
+do_test (void)
+{
+ ntf_in = support_next_to_fault_allocate (512);
+ ntf_out = support_next_to_fault_allocate (256);
+
+#define T(rtype, rdata, expected) \
+ test_one_record (rtype, rdata, sizeof (rdata) - 1, expected)
+ T (ns_t_a, "\xc0\0\2\1", "www.example.org.\t1D IN A\t\t192.0.2.1");
+ T (ns_t_cname, "\4www1\4prod\xc0\x10",
+ "www.example.org.\t1D IN CNAME\twww1.prod.example.org.");
+ T (ns_t_hinfo, "\5first\6second",
+ "www.example.org.\t1D IN HINFO\t\"first\" \"second\"");
+ T (ns_t_isdn, "\5first\6second",
+ "www.example.org.\t1D IN ISDN\t\"first\" \"second\"");
+ /* Bug: Extra space at the end in the text representation of ISDN RRs. */
+ T (ns_t_isdn, "\5first", "www.example.org.\t1D IN ISDN\t\"first\" ");
+ T (ns_t_soa,
+ "\2ns\xc0\x10\12hostmaster\xc0\x10"
+ "\0\0\0\1\0\0\0\2\0\0\0\3\0\0\0\4\0\0\0\5",
+ "www.example.org.\t1D IN SOA\tns.example.org. hostmaster.example.org. (\n"
+ "\t\t\t\t\t1\t\t; serial\n"
+ "\t\t\t\t\t2S\t\t; refresh\n"
+ "\t\t\t\t\t3S\t\t; retry\n"
+ "\t\t\t\t\t4S\t\t; expiry\n"
+ "\t\t\t\t\t5S )\t\t; minimum\n");
+ T (ns_t_mx, "\0\xa\2mx\xc0\x10",
+ "www.example.org.\t1D IN MX\t10 mx.example.org.");
+ T (ns_t_px, "\0\xa\3px1\xc0\x10\3px2\xc0\x10",
+ "www.example.org.\t1D IN PX\t10 px1.example.org. px2.example.org.");
+ T (ns_t_x25, "\4X.25",
+ "www.example.org.\t1D IN X25\t\"X.25\"");
+ T (ns_t_txt, "\1A\2BC\3DEF",
+ "www.example.org.\t1D IN TXT\t\"A\" \"BC\" \"DEF\"");
+ T (ns_t_nsap, "",
+ "www.example.org.\t1D IN NSAP\t");
+ T (ns_t_nsap, "\1",
+ "www.example.org.\t1D IN NSAP\t01");
+ T (ns_t_nsap, "\1\2",
+ "www.example.org.\t1D IN NSAP\t01.02");
+ T (ns_t_nsap, "\1\2\3",
+ "www.example.org.\t1D IN NSAP\t01.0203");
+ T (ns_t_nsap, "\1\2\3\4",
+ "www.example.org.\t1D IN NSAP\t01.0203.04");
+ T (ns_t_nsap,
+ "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32"
+ "\33\34\35\36\37\40\41\42\43\44\45\46\47\50\51\52\53\54\55\56\57\60\61"
+ "\62\63\64\65\66\67\70\71\72\73\74\75\76\77\100\101\102\103\104\105\106"
+ "\107\110\111\112\113\114\115\116\117\120\121\122\123\124\125\126\127"
+ "\130\131\132\133\134\135\136\137\140\141\142\143\144\145\146\147\150"
+ "\151\152\153\154\155\156\157\160\161\162\163\164\165\166\167\170\171"
+ "\172\173\174\175\176\177\200\201\202\203\204\205\206\207\210\211\212"
+ "\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233"
+ "\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254"
+ "\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275"
+ "\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316"
+ "\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337"
+ "\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360"
+ "\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377",
+ "www.example.org.\t1D IN NSAP\t"
+ "01.0203.0405.0607.0809.0A0B.0C0D.0E0F.1011.1213.1415.1617.1819.1A1B"
+ ".1C1D.1E1F.2021.2223.2425.2627.2829.2A2B.2C2D.2E2F.3031.3233.3435.3637"
+ ".3839.3A3B.3C3D.3E3F.4041.4243.4445.4647.4849.4A4B.4C4D.4E4F.5051.5253"
+ ".5455.5657.5859.5A5B.5C5D.5E5F.6061.6263.6465.6667.6869.6A6B.6C6D.6E6F"
+ ".7071.7273.7475.7677.7879.7A7B.7C7D.7E7F.8081.8283.8485.8687.8889.8A8B"
+ ".8C8D.8E8F.9091.9293.9495.9697.9899.9A9B.9C9D.9E9F.A0A1.A2A3.A4A5.A6A7"
+ ".A8A9.AAAB.ACAD.AEAF.B0B1.B2B3.B4B5.B6B7.B8B9.BABB.BCBD.BEBF.C0C1.C2C3"
+ ".C4C5.C6C7.C8C9.CACB.CCCD.CECF.D0D1.D2D3.D4D5.D6D7.D8D9.DADB.DCDD.DEDF"
+ ".E0E1.E2E3.E4E5.E6E7.E8E9.EAEB.ECED.EEEF.F0F1.F2F3.F4F5.F6F7.F8F9.FAFB"
+ ".FCFD.FEFF");
+ T (ns_t_aaaa, "\x20\x01\x0d\xb8\0\0\0\0\0\0\0\0\0\0\x12\x34",
+ "www.example.org.\t1D IN AAAA\t2001:db8::1234");
+ /* Example from RFC 1876. The loc_ntoa format is different from the
+ official text representation. */
+ T (ns_t_loc,
+ "\000\063\026\023\211\027\055\320\160\276\025\360\000\230\215\040",
+ "www.example.org.\t1D IN LOC"
+ "\t42 21 54.000 N 71 06 18.000 W -24.00m 30.00m 10000.00m 10.00m");
+ T (ns_t_naptr,
+ "\0\1\0\2\5flags\7service\2.*\5naptr\xc0\x10",
+ "www.example.org.\t1D IN NAPTR\t1 2 \"flags\" \"service\" \".*\""
+ " naptr.example.org.");
+ T (ns_t_srv,
+ "\0\1\0\2\0\x50\4www1\xc0\x10",
+ "www.example.org.\t1D IN SRV\t1 2 80 www1.example.org.");
+ T (ns_t_rp, "\3rp1\xc0\x10\3rp2\xc0\x10",
+ "www.example.org.\t1D IN RP\trp1.example.org. rp2.example.org.");
+ T (ns_t_wks, "\xc0\0\2\1\6\0\0\0\0\0\0\0\0\0\0\200",
+ "www.example.org.\t1D IN WKS\t192.0.2.1 6 ( \n\t\t\t\t80 )");
+ T (ns_t_cert, "\0\1\x04\xd2\0blob",
+ "www.example.org.\t1D IN CERT\t\\# 9 (\n"
+ "\t00 01 04 d2 00 62 6c 6f 62 )\t\t\t; .....blob");
+ T (ns_t_tkey, "\4algo\0\0\0\0\1\0\0\0\2\0\3\0\4"
+ "\0\5\xa1\xa2\xa3\xa4\xa5\0\3\xb1\xb2\xb3",
+ "www.example.org.\t1D IN TYPE249\t\\# 30 (\n"
+ "\t04 61 6c 67 6f 00 00 00 00 01 00 00 00 02 00 03 ; .algo...........\n"
+ "\t00 04 00 05 a1 a2 a3 a4 a5 00 03 b1 b2 b3 )\t; ..............");
+ T (ns_t_tsig, "\4algo\0"
+ "\0\20\xdd\xcd\x64\x10\xe9\x21\x34\x1a\x8e\xe0\xa1\x9a\x30\xfc\x3b\xd1"
+ "\0\2\0\3\0\5other",
+ "www.example.org.\t1D IN TSIG\t\\# 35 (\n"
+ "\t04 61 6c 67 6f 00 00 10 dd cd 64 10 e9 21 34 1a ; .algo.....d..!4.\n"
+ "\t8e e0 a1 9a 30 fc 3b d1 00 02 00 03 00 05 6f 74 ; ....0.;.......ot\n"
+ "\t68 65 72 )\t\t\t\t\t; her");
+ T (ns_t_a6,
+ "\0\x20\x01\x0d\xb8\0\0\0\0\0\0\0\0\0\0\x12\x34\6prefix\xc0\x10",
+ "www.example.org.\t1D IN A6\t0 2001:db8::1234");
+ T (ns_t_a6,
+ "\0\x20\x01\x0d\xb8\0\0\0\0\0\0\0\0\0\0\x12\x35",
+ "www.example.org.\t1D IN A6\t0 2001:db8::1235");
+ T (ns_t_a6, "\200\6prefix\xc0\x10",
+ "www.example.org.\t1D IN A6\t128 prefix.example.org.");
+ T (ns_t_a6, "\x20\0\0\0\0\0\0\0\0\0\0\x12\x36\6prefix\xc0\x10",
+ "www.example.org.\t1D IN A6\t32 ::1236 prefix.example.org.");
+#undef T
+
+ support_next_to_fault_free (&ntf_in);
+ support_next_to_fault_free (&ntf_out);
+ return 0;
+}
+
+#include <support/test-driver.c>

View File

@ -0,0 +1,102 @@
commit ef3bfb5f910011f3780cb06aa47e730035f53285
Author: Rocket Ma <marocketbd@gmail.com>
Date: Fri May 1 20:39:07 2026 -0700
libio: Fix ungetwc operating on byte stream [BZ #33998]
* libio/wgenops.c: When _IO_wdefault_pbackfail attempts to push back one
character, it accidently compare the wchar to push back with the last
char from byte stream, instead of wide stream. Under specific coding,
attacker may exploit this to leak information. This commit fix bug
33998, or CVE-2026-5928.
Signed-off-by: Rocket Ma <marocketbd@gmail.com>
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
diff --git a/libio/Makefile b/libio/Makefile
index 93656466df..6e0627bb88 100644
--- a/libio/Makefile
+++ b/libio/Makefile
@@ -84,6 +84,7 @@ tests = \
bug-ungetwc1 \
bug-ungetwc2 \
bug-wfflush \
+ bug-wgenops-bz33998 \
bug-wmemstream1 \
bug-wsetpos \
test-fmemopen \
diff --git a/libio/bug-wgenops-bz33998.c b/libio/bug-wgenops-bz33998.c
new file mode 100644
index 0000000000..cc4067da99
--- /dev/null
+++ b/libio/bug-wgenops-bz33998.c
@@ -0,0 +1,54 @@
+/* Regression test for ungetwc operating on byte stream (BZ #33998)
+ Copyright (C) 2026 The GNU Toolchain Authors.
+ 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
+ <https://www.gnu.org/licenses/>. */
+
+#include "support/temp_file.h"
+#include "support/xstdio.h"
+#include "support/xunistd.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <support/check.h>
+
+static int
+do_test (void)
+{
+ char *filename;
+ int fd = create_temp_file ("tst-bz33998-", &filename);
+ TEST_VERIFY (fd != -1);
+ xwrite (fd, "A", sizeof ("A")); // write "A\0" by design
+ xclose (fd);
+
+ FILE *fp = xfopen (filename, "r+");
+ TEST_COMPARE (getwc (fp), L'A');
+ /* If the bug is fixed, then ungetwc should not touch byte stream.
+ If the bug is not fixed, ungetwc firstly match last read char, L'A',
+ failed, then the pbackfail branch, matching last read char in byte
+ stream, that is, '\0' (initialized when setup wide stream). */
+ char *old_read_ptr = fp->_IO_read_ptr;
+ TEST_COMPARE (ungetwc (L'\0', fp), L'\0');
+ TEST_VERIFY (fp->_IO_read_ptr == old_read_ptr);
+
+ xfclose (fp);
+ free (filename);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/libio/wgenops.c b/libio/wgenops.c
index 6829477e0c..5f36bc49a1 100644
--- a/libio/wgenops.c
+++ b/libio/wgenops.c
@@ -110,8 +110,8 @@ _IO_wdefault_pbackfail (FILE *fp, wint_t c)
{
if (fp->_wide_data->_IO_read_ptr > fp->_wide_data->_IO_read_base
&& !_IO_in_backup (fp)
- && (wint_t) fp->_IO_read_ptr[-1] == c)
- --fp->_IO_read_ptr;
+ && (wint_t) fp->_wide_data->_IO_read_ptr[-1] == c)
+ --fp->_wide_data->_IO_read_ptr;
else
{
/* Need to handle a filebuf in write mode (switch to read mode). FIXME!*/

View File

@ -0,0 +1,56 @@
commit a0f9bfc3a5cc10920787d70d0653720a8fa013f3
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Mon Nov 6 17:25:48 2023 -0300
elf: Remove any_debug from dl_main_state
Its usage can be implied by the GLRO(dl_debug_mask).
Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
Conflicts:
elf/dl-main.h
(fixup context)
diff --git a/elf/dl-main.h b/elf/dl-main.h
index d3820e0063143878..35728d47d463c3fb 100644
--- a/elf/dl-main.h
+++ b/elf/dl-main.h
@@ -94,9 +94,6 @@ struct dl_main_state
enum rtld_mode mode;
- /* True if any of the debugging options is enabled. */
- bool any_debug;
-
/* True if information about versions has to be printed. */
bool version_info;
};
diff --git a/elf/rtld.c b/elf/rtld.c
index b788857924e5a388..629ad7f8f4f75353 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -305,7 +305,6 @@ dl_main_state_init (struct dl_main_state *state)
state->glibc_hwcaps_prepend = NULL;
state->glibc_hwcaps_mask = NULL;
state->mode = rtld_mode_normal;
- state->any_debug = false;
state->version_info = false;
}
@@ -2707,7 +2706,6 @@ process_dl_debug (struct dl_main_state *state, const char *dl_debug)
&& memcmp (dl_debug, debopts[cnt].name, len) == 0)
{
GLRO(dl_debug_mask) |= debopts[cnt].mask;
- state->any_debug = true;
break;
}
@@ -2954,7 +2952,7 @@ process_envvars (struct dl_main_state *state)
/* If we have to run the dynamic linker in debugging mode and the
LD_DEBUG_OUTPUT environment variable is given, we write the debug
messages to this file. */
- else if (state->any_debug && debug_output != NULL)
+ else if (GLRO(dl_debug_mask) != 0 && debug_output != NULL)
{
const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NOFOLLOW;
size_t name_len = strlen (debug_output);

View File

@ -0,0 +1,374 @@
commit 332f8e62afef53492dd8285490bcf7aeef18c80a
Author: Frédéric Bérat <fberat@redhat.com>
Date: Fri Sep 5 16:14:38 2025 +0200
tls: Add debug logging for TLS and TCB management
Introduce the `DL_DEBUG_TLS` debug mask to enable detailed logging for
Thread-Local Storage (TLS) and Thread Control Block (TCB) management.
This change integrates a new `tls` option into the `LD_DEBUG`
environment variable, allowing developers to trace:
- TCB allocation, deallocation, and reuse events in `dl-tls.c`,
`nptl/allocatestack.c`, and `nptl/nptl-stack.c`.
- Thread startup events, including the TID and TCB address, in
`nptl/pthread_create.c`.
A new test, `tst-dl-debug-tid`, has been added to validate the
functionality of this new debug logging, ensuring that relevant messages
are correctly generated for both main and worker threads.
This enhances the debugging capabilities for diagnosing issues related
to TLS allocation and thread lifecycle within the dynamic linker.
Reviewed-by: DJ Delorie <dj@redhat.com>
Conflicts:
sysdeps/generic/ldsodefs.h
(PRELINK is still there downstream)
diff --git a/elf/dl-tls.c b/elf/dl-tls.c
index 1b1bc4292eb24747..20d6036b891f653c 100644
--- a/elf/dl-tls.c
+++ b/elf/dl-tls.c
@@ -503,6 +503,8 @@ _dl_allocate_tls_storage (void)
result = allocate_dtv (result);
if (result == NULL)
free (allocated);
+ else if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf ("TCB allocated: 0x%lx\n", (unsigned long int) result);
_dl_tls_allocate_end ();
return result;
@@ -691,6 +693,10 @@ rtld_hidden_def (_dl_allocate_tls)
void
_dl_deallocate_tls (void *tcb, bool dealloc_tcb)
{
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf ("TCB deallocating: 0x%lx (dealloc_tcb=%d)\n",
+ (unsigned long int) tcb, dealloc_tcb);
+
dtv_t *dtv = GET_DTV (tcb);
/* We need to free the memory allocated for non-static TLS. */
diff --git a/elf/rtld.c b/elf/rtld.c
index 629ad7f8f4f75353..ff4c160d8fafd70d 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -2676,10 +2676,12 @@ process_dl_debug (struct dl_main_state *state, const char *dl_debug)
DL_DEBUG_VERSIONS | DL_DEBUG_IMPCALLS },
{ LEN_AND_STR ("scopes"), "display scope information",
DL_DEBUG_SCOPES },
+ { LEN_AND_STR ("tls"), "display TLS structures processing",
+ DL_DEBUG_TLS },
{ LEN_AND_STR ("all"), "all previous options combined",
DL_DEBUG_LIBS | DL_DEBUG_RELOC | DL_DEBUG_FILES | DL_DEBUG_SYMBOLS
| DL_DEBUG_BINDINGS | DL_DEBUG_VERSIONS | DL_DEBUG_IMPCALLS
- | DL_DEBUG_SCOPES },
+ | DL_DEBUG_SCOPES | DL_DEBUG_TLS },
{ LEN_AND_STR ("statistics"), "display relocation statistics",
DL_DEBUG_STATISTICS },
{ LEN_AND_STR ("unused"), "determined unused DSOs",
diff --git a/nptl/Makefile b/nptl/Makefile
index 80c7587f0086677b..7001ecd17b752dba 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -374,6 +374,7 @@ tests-container = tst-pthread-getattr
tests-internal := \
tst-barrier5 \
tst-cond22 \
+ tst-dl-debug-tid \
tst-mutex8 \
tst-mutex8-static \
tst-mutexpi8 \
@@ -562,6 +563,7 @@ xtests-static += tst-setuid1-static
ifeq ($(run-built-tests),yes)
tests-special += \
+ $(objpfx)tst-dl-debug-tid.out \
$(objpfx)tst-oddstacklimit.out \
$(objpfx)tst-stack3-mem.out \
# tests-special
@@ -685,6 +687,11 @@ tst-stackguard1-ARGS = --command "$(host-test-program-cmd) --child"
tst-stackguard1-static-ARGS = --command "$(objpfx)tst-stackguard1-static --child"
ifeq ($(run-built-tests),yes)
+$(objpfx)tst-dl-debug-tid.out: tst-dl-debug-tid.sh $(objpfx)tst-dl-debug-tid
+ $(SHELL) $< $(common-objpfx) '$(test-wrapper)' '$(rtld-prefix)' \
+ '$(test-wrapper-env)' '$(run-program-env)' \
+ $(objpfx)tst-dl-debug-tid > $@; $(evaluate-test)
+
$(objpfx)tst-oddstacklimit.out: $(objpfx)tst-oddstacklimit $(objpfx)tst-basic1
$(test-program-prefix) $< --command '$(host-test-program-cmd)' > $@; \
$(evaluate-test)
diff --git a/nptl/allocatestack.c b/nptl/allocatestack.c
index b0f40afd3f5801f6..9e0bb8f0eeff8e88 100644
--- a/nptl/allocatestack.c
+++ b/nptl/allocatestack.c
@@ -114,6 +114,10 @@ get_cached_stack (size_t *sizep, void **memp)
/* Release the lock early. */
lll_unlock (GL (dl_stack_cache_lock), LLL_PRIVATE);
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ GLRO (dl_debug_printf) ("TLS TCB reused from cache: 0x%lx\n",
+ (unsigned long int) result);
+
/* Report size and location of the stack to the caller. */
*sizep = result->stackblock_size;
*memp = result->stackblock;
@@ -291,6 +295,12 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
stack cache nor will the memory (except the TLS memory) be freed. */
pd->user_stack = true;
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ GLRO (dl_debug_printf) (
+ "TCB for user-supplied stack created: 0x%lx, stack=0x%lx, size=%lu\n",
+ (unsigned long int) pd, (unsigned long int) pd->stackblock,
+ (unsigned long int) pd->stackblock_size);
+
/* This is at least the second thread. */
pd->header.multiple_threads = 1;
#ifndef TLS_MULTIPLE_THREADS_IN_TCB
@@ -425,6 +435,10 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
/* Don't allow setxid until cloned. */
pd->setxid_futex = -1;
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ GLRO (dl_debug_printf) ("TCB for new stack allocated: 0x%lx\n",
+ (unsigned long int) pd);
+
/* Allocate the DTV for this thread. */
if (_dl_allocate_tls (TLS_TPADJ (pd)) == NULL)
{
diff --git a/nptl/nptl-stack.c b/nptl/nptl-stack.c
index 3f33a4c20b39a4aa..4ef6527ff7e8192b 100644
--- a/nptl/nptl-stack.c
+++ b/nptl/nptl-stack.c
@@ -75,6 +75,11 @@ __nptl_free_stacks (size_t limit)
/* Account for the freed memory. */
GL (dl_stack_cache_actsize) -= curr->stackblock_size;
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ GLRO (dl_debug_printf) (
+ "TCB cache full, deallocating: TID=%ld, TCB=0x%lx\n",
+ (long int) curr->tid, (unsigned long int) curr);
+
/* Free the memory associated with the ELF TLS. */
_dl_deallocate_tls (TLS_TPADJ (curr), false);
@@ -96,6 +101,12 @@ static inline void
__attribute ((always_inline))
queue_stack (struct pthread *stack)
{
+ /* The 'stack' parameter is a pointer to the TCB (struct pthread),
+ not just the stack. */
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ GLRO (dl_debug_printf) ("TCB deallocated into cache: TID=%ld, TCB=0x%lx\n",
+ (long int) stack->tid, (unsigned long int) stack);
+
/* We unconditionally add the stack to the list. The memory may
still be in use but it will not be reused until the kernel marks
the stack as not used anymore. */
@@ -123,8 +134,16 @@ __nptl_deallocate_stack (struct pthread *pd)
if (__glibc_likely (! pd->user_stack))
(void) queue_stack (pd);
else
- /* Free the memory associated with the ELF TLS. */
- _dl_deallocate_tls (TLS_TPADJ (pd), false);
+ {
+ /* User-provided stack. We must not free it. But we must free
+ the TLS memory. */
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ GLRO (dl_debug_printf) (
+ "TCB for user-supplied stack deallocated: TID=%ld, TCB=0x%lx\n",
+ (long int) pd->tid, (unsigned long int) pd);
+ /* Free the memory associated with the ELF TLS. */
+ _dl_deallocate_tls (TLS_TPADJ (pd), false);
+ }
lll_unlock (GL (dl_stack_cache_lock), LLL_PRIVATE);
}
diff --git a/nptl/pthread_create.c b/nptl/pthread_create.c
index 109c5e3dc78c9aa2..37de54eaa570cedb 100644
--- a/nptl/pthread_create.c
+++ b/nptl/pthread_create.c
@@ -362,6 +362,10 @@ start_thread (void *arg)
goto out;
}
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ GLRO (dl_debug_printf) ("Thread starting: TID=%ld, TCB=0x%lx\n",
+ (long int) pd->tid, (unsigned long int) pd);
+
/* Initialize resolver state pointer. */
__resp = &pd->res;
diff --git a/nptl/tst-dl-debug-tid.c b/nptl/tst-dl-debug-tid.c
new file mode 100644
index 0000000000000000..231fa43516b233b3
--- /dev/null
+++ b/nptl/tst-dl-debug-tid.c
@@ -0,0 +1,69 @@
+/* Test for thread ID logging in dynamic linker.
+ 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
+ <https://www.gnu.org/licenses/>. */
+
+/* This test checks that the dynamic linker correctly logs thread creation
+ and destruction. It creates a detached thread followed by a joinable
+ thread to exercise different code paths. A barrier is used to ensure
+ the detached thread has started before the joinable one is created,
+ making the test more deterministic. The tst-dl-debug-tid.sh shell script
+ wrapper then verifies the LD_DEBUG output. */
+
+#include <pthread.h>
+#include <support/xthread.h>
+#include <stdio.h>
+#include <unistd.h>
+
+static void *
+thread_function (void *arg)
+{
+ if (arg)
+ pthread_barrier_wait ((pthread_barrier_t *) arg);
+ return NULL;
+}
+
+static int
+do_test (void)
+{
+ pthread_t thread1;
+ pthread_attr_t attr;
+ pthread_barrier_t barrier;
+
+ pthread_barrier_init (&barrier, NULL, 2);
+
+ /* A detached thread.
+ * Deallocation is done by the thread itself upon exit. */
+ xpthread_attr_init (&attr);
+ xpthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
+ /* We don't need the thread handle for the detached thread. */
+ xpthread_create (&attr, thread_function, &barrier);
+ xpthread_attr_destroy (&attr);
+
+ /* Wait for the detached thread to be executed. */
+ pthread_barrier_wait (&barrier);
+ pthread_barrier_destroy (&barrier);
+
+ /* A joinable thread.
+ * Deallocation is done by the main thread in pthread_join. */
+ thread1 = xpthread_create (NULL, thread_function, NULL);
+
+ xpthread_join (thread1);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/nptl/tst-dl-debug-tid.sh b/nptl/tst-dl-debug-tid.sh
new file mode 100644
index 0000000000000000..93d27134a09eaca7
--- /dev/null
+++ b/nptl/tst-dl-debug-tid.sh
@@ -0,0 +1,72 @@
+#!/bin/sh
+# Test for thread ID logging in dynamic linker.
+# 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
+# <https://www.gnu.org/licenses/>.
+
+# This script runs the tst-dl-debug-tid test case and verifies its
+# LD_DEBUG output. It checks for thread creation/destruction messages
+# to ensure the dynamic linker's thread-aware logging is working.
+
+set -e
+
+# Arguments are from Makefile.
+common_objpfx="$1"
+test_wrapper="$2"
+rtld_prefix="$3"
+test_wrapper_env="$4"
+run_program_env="$5"
+test_program="$6"
+
+debug_output="${common_objpfx}elf/tst-dl-debug-tid.debug"
+rm -f "${debug_output}".*
+
+# Run the test program with LD_DEBUG=tls.
+eval "${test_wrapper_env}" LD_DEBUG=tls LD_DEBUG_OUTPUT="${debug_output}" \
+ "${test_wrapper}" "${rtld_prefix}" "${test_program}"
+
+debug_output=$(ls "${debug_output}".*)
+# Check for the "Thread starting" message.
+if ! grep -q 'Thread starting: TID=' "${debug_output}"; then
+ echo "error: 'Thread starting' message not found"
+ cat "${debug_output}"
+ exit 1
+fi
+
+# Check that we have a message where the PID (from prefix) is different
+# from the TID (in the message). This indicates a worker thread log.
+if ! grep 'Thread starting: TID=' "${debug_output}" | awk -F '[ \t:]+' '{
+ sub(/,/, "", $5);
+ sub(/TID=/, "", $5);
+ if ($1 != $5)
+ exit 0;
+ exit 1
+}'; then
+ echo "error: No 'Thread starting' message from a worker thread found"
+ cat "${debug_output}"
+ exit 1
+fi
+
+# We expect messages from thread creation and destruction.
+if ! grep -q 'TCB allocated\|TCB deallocating\|TCB reused\|TCB deallocated' \
+ "${debug_output}"; then
+ echo "error: Expected TCB allocation/deallocation message not found"
+ cat "${debug_output}"
+ exit 1
+fi
+
+cat "${debug_output}"
+rm -f "${debug_output}"
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index e5860916345487e7..5080db9381c31c4f 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -560,9 +560,10 @@ struct rtld_global_ro
#define DL_DEBUG_STATISTICS (1 << 7)
#define DL_DEBUG_UNUSED (1 << 8)
#define DL_DEBUG_SCOPES (1 << 9)
-/* These two are used only internally. */
+/* DL_DEBUG_HELP is only used internally. */
#define DL_DEBUG_HELP (1 << 10)
#define DL_DEBUG_PRELINK (1 << 11)
+#define DL_DEBUG_TLS (1 << 12)
/* OS version. */
EXTERN unsigned int _dl_osversion;

View File

@ -0,0 +1,107 @@
commit d4d472366ba69df7b14eba22a75f887b99855d70
Author: Frédéric Bérat <fberat@redhat.com>
Date: Thu Oct 2 19:15:29 2025 +0200
docs: Add dynamic linker environment variable docs
The Dynamic Linker chapter now includes a new section detailing
environment variables that influence its behavior.
This new section documents the `LD_DEBUG` environment variable,
explaining how to enable debugging output and listing its various
keywords like `libs`, `reloc`, `files`, `symbols`, `bindings`,
`versions`, `scopes`, `tls`, `all`, `statistics`, `unused`, and `help`.
It also documents `LD_DEBUG_OUTPUT`, which controls where the debug
output is written, allowing redirection to a file with the process ID
appended.
This provides users with essential information for controlling and
debugging the dynamic linker.
Reviewed-by: DJ Delorie <dj@redhat.com>
diff --git a/manual/dynlink.texi b/manual/dynlink.texi
index 2b35e5883c3e65c0..469340e1d4ae8a88 100644
--- a/manual/dynlink.texi
+++ b/manual/dynlink.texi
@@ -14,6 +14,8 @@ Dynamic linkers are sometimes called @dfn{dynamic loaders}.
@menu
* Dynamic Linker Invocation:: Explicit invocation of the dynamic linker.
+* Dynamic Linker Environment Variables:: Environment variables that control the
+ dynamic linker.
* Dynamic Linker Introspection:: Interfaces for querying mapping information.
@end menu
@@ -348,6 +350,70 @@ probed CPU are omitted. Nothing is printed if the system does not
support the XGETBV instruction.
@end table
+@node Dynamic Linker Environment Variables
+@section Dynamic Linker Environment Variables
+
+The behavior of the dynamic linker can be modified through various environment
+variables.
+
+@table @code
+@item LD_DEBUG
+@cindex @code{LD_DEBUG} environment variable
+The @env{LD_DEBUG} environment variable can be set to a comma-separated list
+of keywords to enable debugging output from the dynamic linker. Setting it to
+@code{help} will display a list of all available keywords. The output is
+written to standard output by default.
+
+@table @code
+@item libs
+Display library search paths.
+
+@item reloc
+Display relocation processing.
+
+@item files
+Display progress for input file processing.
+
+@item symbols
+Display symbol table processing.
+
+@item bindings
+Display information about symbol binding.
+
+@item versions
+Display version dependencies.
+
+@item scopes
+Display scope information.
+
+@item tls
+Display information about Thread-Local Storage (TLS) handling, including TCB
+allocation, deallocation, and reuse. This is useful for debugging issues
+related to thread creation and lifecycle.
+
+@item all
+All previous options combined.
+
+@item statistics
+Display relocation statistics.
+
+@item unused
+Determined unused DSOs.
+
+@item help
+Display a help message with all available options and exit.
+@end table
+
+@item LD_DEBUG_OUTPUT
+@cindex @code{LD_DEBUG_OUTPUT} environment variable
+If @env{LD_DEBUG} is set, the output is written to standard output by
+default. If @env{LD_DEBUG_OUTPUT} is set, the output is written to the
+file specified by its value, with the process ID appended. For example, if
+@env{LD_DEBUG_OUTPUT} is set to @file{/tmp/glibc.debug}, the output will be
+written to a file named @file{/tmp/glibc.debug.12345}, where @code{12345} is
+the process ID.
+@end table
+
@node Dynamic Linker Introspection
@section Dynamic Linker Introspection

View File

@ -0,0 +1,70 @@
commit 20092f2ef601aef57cc184cbacd7cab39bba5a25
Author: Yury Khrustalev <yury.khrustalev@arm.com>
Date: Mon Dec 1 10:09:14 2025 +0000
nptl: tests: Fix test-wrapper use in tst-dl-debug-tid.sh
Test wrapper script was used twice: once to run the test
command and second time within the text command which
seems unnecessary and results in false errors when running
this test.
Fixes 332f8e62afef53492dd8285490bcf7aeef18c80a
Reviewed-by: Frédéric Bérat <fberat@redhat.com>
diff --git a/nptl/Makefile b/nptl/Makefile
index 7001ecd17b752dba..597311bf07223c4f 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -688,8 +688,8 @@ tst-stackguard1-static-ARGS = --command "$(objpfx)tst-stackguard1-static --child
ifeq ($(run-built-tests),yes)
$(objpfx)tst-dl-debug-tid.out: tst-dl-debug-tid.sh $(objpfx)tst-dl-debug-tid
- $(SHELL) $< $(common-objpfx) '$(test-wrapper)' '$(rtld-prefix)' \
- '$(test-wrapper-env)' '$(run-program-env)' \
+ $(SHELL) $< $(common-objpfx) '$(test-wrapper-env)' '$(rtld-prefix)' \
+ '$(run-program-env)' \
$(objpfx)tst-dl-debug-tid > $@; $(evaluate-test)
$(objpfx)tst-oddstacklimit.out: $(objpfx)tst-oddstacklimit $(objpfx)tst-basic1
diff --git a/nptl/tst-dl-debug-tid.sh b/nptl/tst-dl-debug-tid.sh
index 93d27134a09eaca7..9ee31ac4f241f0b4 100644
--- a/nptl/tst-dl-debug-tid.sh
+++ b/nptl/tst-dl-debug-tid.sh
@@ -25,18 +25,17 @@ set -e
# Arguments are from Makefile.
common_objpfx="$1"
-test_wrapper="$2"
+test_wrapper_env="$2"
rtld_prefix="$3"
-test_wrapper_env="$4"
-run_program_env="$5"
-test_program="$6"
+run_program_env="$4"
+test_program="$5"
debug_output="${common_objpfx}elf/tst-dl-debug-tid.debug"
rm -f "${debug_output}".*
# Run the test program with LD_DEBUG=tls.
eval "${test_wrapper_env}" LD_DEBUG=tls LD_DEBUG_OUTPUT="${debug_output}" \
- "${test_wrapper}" "${rtld_prefix}" "${test_program}"
+ "${rtld_prefix}" "${test_program}"
debug_output=$(ls "${debug_output}".*)
# Check for the "Thread starting" message.
@@ -49,9 +48,9 @@ fi
# Check that we have a message where the PID (from prefix) is different
# from the TID (in the message). This indicates a worker thread log.
if ! grep 'Thread starting: TID=' "${debug_output}" | awk -F '[ \t:]+' '{
- sub(/,/, "", $5);
- sub(/TID=/, "", $5);
- if ($1 != $5)
+ sub(/,/, "", $4);
+ sub(/TID=/, "", $4);
+ if ($1 != $4)
exit 0;
exit 1
}'; then

View File

@ -0,0 +1,681 @@
commit 1e47dbcce4d5cf2ce71377e729014e454a3e15ae
Author: Frédéric Bérat <fberat@redhat.com>
Date: Fri Dec 12 16:19:43 2025 +0100
elf(tls): Add debug logging for TLS operations
This commit introduces extensive debug logging for thread-local storage
(TLS) operations within the dynamic linker. When `LD_DEBUG=tls` is
enabled, messages are printed for:
- TLS module assignment and release.
- DTV (Dynamic Thread Vector) resizing events.
- TLS block allocations and deallocations.
- `__tls_get_addr` slow path events (DTV updates, lazy allocations, and
static TLS usage).
The log format is standardized to use a "tls: " prefix and identifies
modules using the "modid %lu" convention. To aid in debugging
multithreaded applications, thread-specific logs include the Thread
Control Block (TCB) address to identify the context of the operation.
A new test module `tst-tls-debug-mod.c` and a corresponding shell script
`tst-tls-debug-recursive.sh` have been added. Additionally, the existing
`tst-dl-debug-tid` NPTL test has been updated to verify these TLS debug
messages in a multithreaded context.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Conflicts:
elf/Makefile
(fixup context)
elf/dl-tls.c
(missing 5e249192cac7354af02a7347a0d8c984e0c88ed3 downstream)
sysdeps/x86_64/dl-tls.c
(missing 5e249192cac7354af02a7347a0d8c984e0c88ed3 downstream)
diff --git a/elf/Makefile b/elf/Makefile
index bd12a20435538fdd..2431e7032c44beb8 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -1252,6 +1252,7 @@ $(objpfx)tst-glibcelf.out: tst-glibcelf.py elf.h $(..)/scripts/glibcelf.py \
ifeq ($(run-built-tests),yes)
tests-special += $(objpfx)tst-tls-allocation-failure-static-patched.out
+tests-special += $(objpfx)tst-tls-debug-recursive.out
endif
# The test requires shared _and_ PIE because the executable
@@ -3052,3 +3053,15 @@ LDFLAGS-tst-version-hash-zero-linkmod.so = \
$(objpfx)tst-version-hash-zero-refmod.so: \
$(objpfx)tst-version-hash-zero-linkmod.so
tst-version-hash-zero-refmod.so-no-z-defs = yes
+
+ifeq ($(run-built-tests),yes)
+$(objpfx)tst-tls-debug-recursive.out: tst-tls-debug-recursive.sh \
+ $(objpfx)tst-recursive-tls \
+ $(objpfx)tst-recursive-tlsmallocmod.so \
+ $(patsubst %,$(objpfx)tst-recursive-tlsmod%.so, \
+ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)
+ $(SHELL) $< $(common-objpfx) '$(test-wrapper-env)' \
+ '$(rtld-prefix)' '$(run_program_env)' \
+ $(objpfx)tst-recursive-tls > $@; \
+ $(evaluate-test)
+endif
diff --git a/elf/dl-close.c b/elf/dl-close.c
index 4514c53d2db261c1..8020b0b182d66f4f 100644
--- a/elf/dl-close.c
+++ b/elf/dl-close.c
@@ -79,6 +79,11 @@ remove_slotinfo (size_t idx, struct dtv_slotinfo_list *listp, size_t disp,
if (__glibc_likely (old_map != NULL))
{
/* Mark the entry as unused. These can be read concurrently. */
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf (
+ "tls: release modid %lu from %s [%ld]\n",
+ (unsigned long int) idx, DSO_FILENAME (old_map->l_name),
+ (long int) old_map->l_ns);
atomic_store_relaxed (&listp->slotinfo[idx - disp].gen,
GL(dl_tls_generation) + 1);
atomic_store_relaxed (&listp->slotinfo[idx - disp].map, NULL);
diff --git a/elf/dl-tls.c b/elf/dl-tls.c
index 20d6036b891f653c..f22d828a86539057 100644
--- a/elf/dl-tls.c
+++ b/elf/dl-tls.c
@@ -215,6 +215,12 @@ _dl_assign_tls_modid (struct link_map *l)
}
l->l_tls_modid = result;
+
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf ("tls: assign modid %lu to %s [%ld]\n",
+ (unsigned long int) result,
+ DSO_FILENAME (l->l_name),
+ (long int) l->l_ns);
}
@@ -504,7 +510,7 @@ _dl_allocate_tls_storage (void)
if (result == NULL)
free (allocated);
else if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
- _dl_debug_printf ("TCB allocated: 0x%lx\n", (unsigned long int) result);
+ _dl_debug_printf ("tls: allocate TCB 0x%lx\n", (unsigned long int) result);
_dl_tls_allocate_end ();
return result;
@@ -517,13 +523,18 @@ extern dtv_t _dl_static_dtv[];
#endif
static dtv_t *
-_dl_resize_dtv (dtv_t *dtv, size_t max_modid)
+_dl_resize_dtv (dtv_t *dtv, size_t max_modid, void *tcb)
{
/* Resize the dtv. */
dtv_t *newp;
size_t newsize = max_modid + DTV_SURPLUS;
size_t oldsize = dtv[-1].counter;
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf ("tls: DTV resized for TCB 0x%lx: oldsize=%lu, newsize=%lu\n",
+ (unsigned long int) tcb,
+ (unsigned long int) oldsize, (unsigned long int) newsize);
+
_dl_tls_allocate_begin ();
if (dtv == GL(dl_initial_dtv))
{
@@ -592,7 +603,7 @@ _dl_allocate_tls_init (void *result, bool main_thread)
if (dtv[-1].counter < GL(dl_tls_max_dtv_idx))
{
/* Resize the dtv. */
- dtv = _dl_resize_dtv (dtv, GL(dl_tls_max_dtv_idx));
+ dtv = _dl_resize_dtv (dtv, GL(dl_tls_max_dtv_idx), result);
/* Install this new dtv in the thread data structures. */
INSTALL_DTV (result, &dtv[-1]);
@@ -683,9 +694,14 @@ rtld_hidden_def (_dl_allocate_tls_init)
void *
_dl_allocate_tls (void *mem)
{
- return _dl_allocate_tls_init (mem == NULL
- ? _dl_allocate_tls_storage ()
- : allocate_dtv (mem), false);
+ void *result = _dl_allocate_tls_init (mem == NULL
+ ? _dl_allocate_tls_storage ()
+ : allocate_dtv (mem), false);
+ if (__glibc_unlikely (result != NULL
+ && (GLRO (dl_debug_mask) & DL_DEBUG_TLS)))
+ _dl_debug_printf ("tls: TLS initialized for TCB 0x%lx\n",
+ (unsigned long int) result);
+ return result;
}
rtld_hidden_def (_dl_allocate_tls)
@@ -694,14 +710,22 @@ void
_dl_deallocate_tls (void *tcb, bool dealloc_tcb)
{
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
- _dl_debug_printf ("TCB deallocating: 0x%lx (dealloc_tcb=%d)\n",
+ _dl_debug_printf ("tls: deallocate TCB 0x%lx (dealloc_tcb=%d)\n",
(unsigned long int) tcb, dealloc_tcb);
dtv_t *dtv = GET_DTV (tcb);
/* We need to free the memory allocated for non-static TLS. */
for (size_t cnt = 0; cnt < dtv[-1].counter; ++cnt)
- free (dtv[1 + cnt].pointer.to_free);
+ {
+ if (dtv[1 + cnt].pointer.to_free != NULL
+ && __glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf (
+ "tls: deallocate block 0x%lx for modid %lu; TCB=0x%lx\n",
+ (unsigned long int) dtv[1 + cnt].pointer.to_free,
+ (unsigned long int) (1 + cnt), (unsigned long int) tcb);
+ free (dtv[1 + cnt].pointer.to_free);
+ }
/* The array starts with dtv[-1]. */
if (dtv != GL(dl_initial_dtv))
@@ -773,6 +797,12 @@ allocate_and_init (struct link_map *map)
(map->l_tls_align, map->l_tls_blocksize);
if (result.val == NULL)
oom ();
+ else if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf ("tls: allocate block 0x%lx for modid %lu; size=%lu, TCB=0x%lx\n",
+ (unsigned long int) result.to_free,
+ (unsigned long int) map->l_tls_modid,
+ (unsigned long int) map->l_tls_blocksize,
+ (unsigned long int) THREAD_SELF);
/* Initialize the memory. */
memset (__mempcpy (result.val, map->l_tls_initimage,
@@ -874,7 +904,7 @@ _dl_update_slotinfo (unsigned long int req_modid, size_t new_gen)
continue;
/* Resizing the dtv aborts on failure: bug 16134. */
- dtv = _dl_resize_dtv (dtv, max_modid);
+ dtv = _dl_resize_dtv (dtv, max_modid, THREAD_SELF);
assert (modid <= dtv[-1].counter);
@@ -895,6 +925,12 @@ _dl_update_slotinfo (unsigned long int req_modid, size_t new_gen)
least some dynamic TLS usage by interposed mallocs. */
if (dtv[modid].pointer.to_free != NULL)
{
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf (
+ "tls: DTV update for TCB 0x%lx: modid %lu deallocated block 0x%lx\n",
+ (unsigned long int) THREAD_SELF,
+ (unsigned long int) modid,
+ (unsigned long int) dtv[modid].pointer.to_free);
_dl_tls_allocate_begin ();
free (dtv[modid].pointer.to_free);
_dl_tls_allocate_end ();
@@ -975,6 +1011,11 @@ tls_get_addr_tail (GET_ADDR_ARGS, dtv_t *dtv, struct link_map *the_map)
dtv[GET_ADDR_MODULE].pointer.to_free = NULL;
dtv[GET_ADDR_MODULE].pointer.val = p;
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf ("tls: modid %lu using static TLS; TCB=0x%lx\n",
+ (unsigned long int) GET_ADDR_MODULE,
+ (unsigned long int) THREAD_SELF);
+
return (char *) p + GET_ADDR_OFFSET;
}
else
@@ -1030,18 +1071,28 @@ __tls_get_addr (GET_ADDR_ARGS)
{
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. */
- ;
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf ("tls: modid %lu reentrant usage; TCB=0x%lx\n",
+ (unsigned long int) GET_ADDR_MODULE,
+ (unsigned long int) THREAD_SELF);
+ }
else
{
/* Update DTV up to the global generation, see CONCURRENCY NOTES
in _dl_update_slotinfo. */
gen = atomic_load_acquire (&GL(dl_tls_generation));
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf (
+ "tls: modid %lu update DTV to generation %lu; TCB=0x%lx\n",
+ (unsigned long int) GET_ADDR_MODULE, (unsigned long int) gen,
+ (unsigned long int) THREAD_SELF);
return update_get_addr (GET_ADDR_PARAM, gen);
}
}
@@ -1049,7 +1100,13 @@ __tls_get_addr (GET_ADDR_ARGS)
void *p = dtv[GET_ADDR_MODULE].pointer.val;
if (__glibc_unlikely (p == TLS_DTV_UNALLOCATED))
- return tls_get_addr_tail (GET_ADDR_PARAM, dtv, NULL);
+ {
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf ("tls: modid %lu lazy allocation; TCB=0x%lx\n",
+ (unsigned long int) GET_ADDR_MODULE,
+ (unsigned long int) THREAD_SELF);
+ return tls_get_addr_tail (GET_ADDR_PARAM, dtv, NULL);
+ }
return (char *) p + GET_ADDR_OFFSET;
}
@@ -1119,6 +1176,10 @@ _dl_tls_initial_modid_limit_setup (void)
break;
}
_dl_tls_initial_modid_limit = idx;
+
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf ("tls: initial modid limit set to %lu\n",
+ (unsigned long int) idx);
}
diff --git a/elf/rtld.c b/elf/rtld.c
index ff4c160d8fafd70d..3221bc8520996097 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -1257,6 +1257,11 @@ rtld_setup_main_map (struct link_map *main_map)
/* This image gets the ID one. */
GL(dl_tls_max_dtv_idx) = main_map->l_tls_modid = 1;
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf ("tls: assign modid %lu to %s [%ld]\n",
+ (unsigned long int) main_map->l_tls_modid,
+ DSO_FILENAME (main_map->l_name),
+ (long int) main_map->l_ns);
}
break;
diff --git a/elf/tst-recursive-tlsmodN.c b/elf/tst-recursive-tlsmodN.c
index bb7592aee6ed347e..921980605cf32a38 100644
--- a/elf/tst-recursive-tlsmodN.c
+++ b/elf/tst-recursive-tlsmodN.c
@@ -19,10 +19,10 @@
/* Compiled with VAR and FUNC set via -D. FUNC requires some
relocation against TLS variable VAR. */
-__thread int VAR;
+__thread char VAR[32768];
int
FUNC (void)
{
- return VAR;
+ return VAR[0];
}
diff --git a/elf/tst-tls-debug-recursive.sh b/elf/tst-tls-debug-recursive.sh
new file mode 100755
index 0000000000000000..083e716f7216ffd5
--- /dev/null
+++ b/elf/tst-tls-debug-recursive.sh
@@ -0,0 +1,83 @@
+#!/bin/sh
+# Test for TLS logging in dynamic linker.
+# Copyright (C) 2026 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
+# <https://www.gnu.org/licenses/>.
+
+# This script runs the tst-tls-debug-recursive test case and verifies its
+# LD_DEBUG=tls output. It checks for various TLS-related messages
+# to ensure the dynamic linker's TLS logging is working correctly.
+
+set -e
+common_objpfx="$1"
+test_wrapper_env="$2"
+rtld_prefix="$3"
+run_program_env="$4"
+test_program="$5"
+
+debug_output="${common_objpfx}elf/tst-tls-debug-recursive.debug"
+rm -f "${debug_output}".*
+
+# Run the test program with LD_DEBUG=tls.
+eval "${test_wrapper_env}" LD_DEBUG=tls LD_DEBUG_OUTPUT="${debug_output}" \
+ "${rtld_prefix}" "${test_program}"
+
+debug_output=$(ls "${debug_output}".*)
+
+fail=0
+
+# Check for expected messages
+if ! grep -q 'tls: DTV resized for TCB 0x.*: oldsize' "${debug_output}"; then
+ echo "FAIL: DTV resized message not found"
+ fail=1
+fi
+
+if ! grep -q 'tls: DTV update for TCB 0x.*: modid .* deallocated block' "${debug_output}"; then
+ echo "FAIL: module deallocated during DTV update message not found"
+ fail=1
+fi
+
+if ! grep -q 'tls: assign modid .* to' "${debug_output}"; then
+ echo "FAIL: module assigned message not found"
+ fail=1
+fi
+
+if ! grep -q 'tls: allocate block .* for modid .* size=.*, TCB=0x' "${debug_output}"; then
+ echo "FAIL: module allocated message not found"
+ fail=1
+fi
+
+if ! grep -q 'tls: modid .* update DTV to generation .* TCB=0x' "${debug_output}"; then
+ echo "FAIL: update DTV message not found"
+ fail=1
+fi
+
+if ! grep -q 'tls: initial modid limit set to' "${debug_output}"; then
+ echo "FAIL: initial modid limit message not found"
+ fail=1
+fi
+
+if [ $fail -ne 0 ]; then
+ echo "Test FAILED"
+ cat "${debug_output}"
+ rm -f "${debug_output}"
+ exit 1
+fi
+
+echo "Test PASSED"
+cat "${debug_output}"
+rm -f "${debug_output}"
+exit 0
diff --git a/nptl/Makefile b/nptl/Makefile
index 597311bf07223c4f..958de6b0002f63aa 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -274,6 +274,7 @@ CFLAGS-tst-thread-exit-clobber.o = -std=gnu++11
LDLIBS-tst-thread-exit-clobber = -lstdc++
CFLAGS-tst-minstack-throw.o = -std=gnu++11
LDLIBS-tst-minstack-throw = -lstdc++
+LDLIBS-tst-dl-debug-tid = $(libdl)
tests = \
tst-attr2 \
@@ -486,6 +487,7 @@ modules-names = \
tst-compat-forwarder-mod \
tst-execstack-mod \
tst-stack4mod \
+ tst-tls-debug-mod \
tst-tls3mod \
tst-tls5mod \
tst-tls5moda \
@@ -687,7 +689,8 @@ tst-stackguard1-ARGS = --command "$(host-test-program-cmd) --child"
tst-stackguard1-static-ARGS = --command "$(objpfx)tst-stackguard1-static --child"
ifeq ($(run-built-tests),yes)
-$(objpfx)tst-dl-debug-tid.out: tst-dl-debug-tid.sh $(objpfx)tst-dl-debug-tid
+$(objpfx)tst-dl-debug-tid.out: tst-dl-debug-tid.sh $(objpfx)tst-dl-debug-tid \
+ $(objpfx)tst-tls-debug-mod.so
$(SHELL) $< $(common-objpfx) '$(test-wrapper-env)' '$(rtld-prefix)' \
'$(run-program-env)' \
$(objpfx)tst-dl-debug-tid > $@; $(evaluate-test)
diff --git a/nptl/allocatestack.c b/nptl/allocatestack.c
index 9e0bb8f0eeff8e88..6c6094e929f927cc 100644
--- a/nptl/allocatestack.c
+++ b/nptl/allocatestack.c
@@ -115,7 +115,7 @@ get_cached_stack (size_t *sizep, void **memp)
lll_unlock (GL (dl_stack_cache_lock), LLL_PRIVATE);
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
- GLRO (dl_debug_printf) ("TLS TCB reused from cache: 0x%lx\n",
+ GLRO (dl_debug_printf) ("tls: TCB reused from cache: 0x%lx\n",
(unsigned long int) result);
/* Report size and location of the stack to the caller. */
@@ -297,9 +297,9 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
GLRO (dl_debug_printf) (
- "TCB for user-supplied stack created: 0x%lx, stack=0x%lx, size=%lu\n",
- (unsigned long int) pd, (unsigned long int) pd->stackblock,
- (unsigned long int) pd->stackblock_size);
+ "tls: TCB created (user-supplied stack); stack=0x%lx, size=%lu, TCB=0x%lx\n",
+ (unsigned long int) pd->stackblock,
+ (unsigned long int) pd->stackblock_size, (unsigned long int) pd);
/* This is at least the second thread. */
pd->header.multiple_threads = 1;
@@ -436,7 +436,7 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
pd->setxid_futex = -1;
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
- GLRO (dl_debug_printf) ("TCB for new stack allocated: 0x%lx\n",
+ GLRO (dl_debug_printf) ("tls: TCB allocated (new stack): 0x%lx\n",
(unsigned long int) pd);
/* Allocate the DTV for this thread. */
diff --git a/nptl/nptl-stack.c b/nptl/nptl-stack.c
index 4ef6527ff7e8192b..d2d0729035a19dfe 100644
--- a/nptl/nptl-stack.c
+++ b/nptl/nptl-stack.c
@@ -77,7 +77,7 @@ __nptl_free_stacks (size_t limit)
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
GLRO (dl_debug_printf) (
- "TCB cache full, deallocating: TID=%ld, TCB=0x%lx\n",
+ "tls: TCB deallocating from full cache; TID=%ld, TCB=0x%lx\n",
(long int) curr->tid, (unsigned long int) curr);
/* Free the memory associated with the ELF TLS. */
@@ -104,7 +104,7 @@ queue_stack (struct pthread *stack)
/* The 'stack' parameter is a pointer to the TCB (struct pthread),
not just the stack. */
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
- GLRO (dl_debug_printf) ("TCB deallocated into cache: TID=%ld, TCB=0x%lx\n",
+ GLRO (dl_debug_printf) ("tls: TCB deallocated into cache; TID=%ld, TCB=0x%lx\n",
(long int) stack->tid, (unsigned long int) stack);
/* We unconditionally add the stack to the list. The memory may
@@ -139,7 +139,7 @@ __nptl_deallocate_stack (struct pthread *pd)
the TLS memory. */
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
GLRO (dl_debug_printf) (
- "TCB for user-supplied stack deallocated: TID=%ld, TCB=0x%lx\n",
+ "tls: TCB deallocated (user-supplied stack); TID=%ld, TCB=0x%lx\n",
(long int) pd->tid, (unsigned long int) pd);
/* Free the memory associated with the ELF TLS. */
_dl_deallocate_tls (TLS_TPADJ (pd), false);
diff --git a/nptl/pthread_create.c b/nptl/pthread_create.c
index 37de54eaa570cedb..c69b4108f60f647d 100644
--- a/nptl/pthread_create.c
+++ b/nptl/pthread_create.c
@@ -363,7 +363,7 @@ start_thread (void *arg)
}
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
- GLRO (dl_debug_printf) ("Thread starting: TID=%ld, TCB=0x%lx\n",
+ GLRO (dl_debug_printf) ("tls: thread starting; TID=%ld, TCB=0x%lx\n",
(long int) pd->tid, (unsigned long int) pd);
/* Initialize resolver state pointer. */
diff --git a/nptl/tst-dl-debug-tid.c b/nptl/tst-dl-debug-tid.c
index 231fa43516b233b3..f3b443f9786a9a99 100644
--- a/nptl/tst-dl-debug-tid.c
+++ b/nptl/tst-dl-debug-tid.c
@@ -27,12 +27,25 @@
#include <support/xthread.h>
#include <stdio.h>
#include <unistd.h>
+#include <dlfcn.h>
+#include <support/xdlfcn.h>
+#include <support/check.h>
static void *
thread_function (void *arg)
{
if (arg)
pthread_barrier_wait ((pthread_barrier_t *) arg);
+
+ /* Load a module with TLS to verify allocation/deallocation logs. */
+ void *h = xdlopen ("tst-tls-debug-mod.so", RTLD_NOW);
+
+ /* Call a function that accesses TLS. */
+ int (*fp) (void) = (int (*) (void)) xdlsym (h, "in_dso");
+ TEST_COMPARE (fp (), 0);
+
+ xdlclose (h);
+
return NULL;
}
diff --git a/nptl/tst-dl-debug-tid.sh b/nptl/tst-dl-debug-tid.sh
index 9ee31ac4f241f0b4..8708ac9f2ea25c9e 100644
--- a/nptl/tst-dl-debug-tid.sh
+++ b/nptl/tst-dl-debug-tid.sh
@@ -39,7 +39,7 @@ eval "${test_wrapper_env}" LD_DEBUG=tls LD_DEBUG_OUTPUT="${debug_output}" \
debug_output=$(ls "${debug_output}".*)
# Check for the "Thread starting" message.
-if ! grep -q 'Thread starting: TID=' "${debug_output}"; then
+if ! grep -q 'tls: thread starting; TID=.*, TCB=0x' "${debug_output}"; then
echo "error: 'Thread starting' message not found"
cat "${debug_output}"
exit 1
@@ -47,10 +47,10 @@ fi
# Check that we have a message where the PID (from prefix) is different
# from the TID (in the message). This indicates a worker thread log.
-if ! grep 'Thread starting: TID=' "${debug_output}" | awk -F '[ \t:]+' '{
- sub(/,/, "", $4);
- sub(/TID=/, "", $4);
- if ($1 != $4)
+if ! grep 'tls: thread starting; TID=.*, TCB=0x' "${debug_output}" | awk -F '[ \t:]+' '{
+ sub(/TID=/, "", $5);
+ sub(/,/, "", $5);
+ if ($1 != $5)
exit 0;
exit 1
}'; then
@@ -60,12 +60,33 @@ if ! grep 'Thread starting: TID=' "${debug_output}" | awk -F '[ \t:]+' '{
fi
# We expect messages from thread creation and destruction.
-if ! grep -q 'TCB allocated\|TCB deallocating\|TCB reused\|TCB deallocated' \
+if ! grep -q 'tls: allocate TCB 0x\|tls: deallocate TCB 0x\|tls: TCB reused from cache\|tls: TCB deallocated' \
"${debug_output}"; then
echo "error: Expected TCB allocation/deallocation message not found"
cat "${debug_output}"
exit 1
fi
+# Check for TLS module ID assignment.
+if ! grep -q 'tls: assign modid .* to' "${debug_output}"; then
+ echo "error: Expected 'modid ... assigned to' message not found"
+ cat "${debug_output}"
+ exit 1
+fi
+
+# Check for TLS block allocation.
+if ! grep -q 'tls: allocate block .* for modid .* size=.*, TCB=0x' "${debug_output}"; then
+ echo "error: Expected 'modid ... allocated' message not found"
+ cat "${debug_output}"
+ exit 1
+fi
+
+# TLS block deallocation might be skipped due to DTV surplus.
+if grep -q 'tls: deallocate block .* for modid .* TCB=0x' "${debug_output}"; then
+ echo "INFO: module deallocated message found"
+else
+ echo "INFO: module deallocated message not found (may be due to DTV surplus)"
+fi
+
cat "${debug_output}"
rm -f "${debug_output}"
diff --git a/nptl/tst-tls-debug-mod.c b/nptl/tst-tls-debug-mod.c
new file mode 100644
index 0000000000000000..0308c8a3e2d76fe1
--- /dev/null
+++ b/nptl/tst-tls-debug-mod.c
@@ -0,0 +1,26 @@
+/* Test for TLS logging in dynamic linker.
+ Copyright (C) 2026 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
+ <https://www.gnu.org/licenses/>. */
+
+__thread char tls_var[32768] __attribute__ ((tls_model ("global-dynamic")));
+
+int
+in_dso (void)
+{
+ tls_var[0] = 42;
+ return tls_var[0] - 42;
+}
diff --git a/sysdeps/x86_64/dl-tls.c b/sysdeps/x86_64/dl-tls.c
index 7abfb18413b1d06c..2bd336876a6613c8 100644
--- a/sysdeps/x86_64/dl-tls.c
+++ b/sysdeps/x86_64/dl-tls.c
@@ -41,11 +41,36 @@ __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);
+ if (_dl_tls_allocate_active ()
+ && GET_ADDR_MODULE < _dl_tls_initial_modid_limit)
+ {
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf (
+ "tls: modid %lu reentrant usage; TCB=0x%lx\n",
+ (unsigned long int) GET_ADDR_MODULE,
+ (unsigned long int) THREAD_SELF);
+ }
+ else
+ {
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf (
+ "tls: modid %lu update DTV to generation %lu; TCB=0x%lx\n",
+ (unsigned long int) GET_ADDR_MODULE, (unsigned long int) gen,
+ (unsigned long int) THREAD_SELF);
+ return update_get_addr (GET_ADDR_PARAM, gen);
+ }
+ }
+
+ if (__glibc_unlikely (dtv[GET_ADDR_MODULE].pointer.val == TLS_DTV_UNALLOCATED))
+ {
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf ("tls: modid %lu lazy allocation; TCB=0x%lx\n",
+ (unsigned long int) GET_ADDR_MODULE,
+ (unsigned long int) THREAD_SELF);
+ }
return tls_get_addr_tail (GET_ADDR_PARAM, dtv, NULL);
}

View File

@ -0,0 +1,177 @@
commit 9181dc6eb6084da95d8c14d9defe96189fd0360d
Author: Frédéric Bérat <berat.fred@gmail.com>
Date: Tue Jan 27 23:07:17 2026 +0100
feat(rtld): Allow LD_DEBUG category exclusion
Adds support for excluding specific categories from `LD_DEBUG` output.
The `LD_DEBUG` environment variable now accepts category names prefixed
with a dash (`-`) to disable their debugging output. This allows users
to enable broad categories (e.g., `all`) while suppressing verbose or
irrelevant information from specific sub-categories (e.g., `-tls`).
The `process_dl_debug` function in `rtld.c` has been updated to parse
these exclusion options and unset the corresponding bits in
`GLRO(dl_debug_mask)`. The `LD_DEBUG=help` output has also been updated
to document this new functionality. A new test `tst-dl-debug-exclude.sh`
is added to verify the correct behavior of category exclusion.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/elf/Makefile b/elf/Makefile
index 2431e7032c44beb8..ec87811c61565511 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -1253,6 +1253,7 @@ $(objpfx)tst-glibcelf.out: tst-glibcelf.py elf.h $(..)/scripts/glibcelf.py \
ifeq ($(run-built-tests),yes)
tests-special += $(objpfx)tst-tls-allocation-failure-static-patched.out
tests-special += $(objpfx)tst-tls-debug-recursive.out
+tests-special += $(objpfx)tst-dl-debug-exclude.out
endif
# The test requires shared _and_ PIE because the executable
@@ -3064,4 +3065,14 @@ $(objpfx)tst-tls-debug-recursive.out: tst-tls-debug-recursive.sh \
'$(rtld-prefix)' '$(run_program_env)' \
$(objpfx)tst-recursive-tls > $@; \
$(evaluate-test)
+
+$(objpfx)tst-dl-debug-exclude.out: tst-dl-debug-exclude.sh \
+ $(objpfx)tst-recursive-tls \
+ $(objpfx)tst-recursive-tlsmallocmod.so \
+ $(patsubst %,$(objpfx)tst-recursive-tlsmod%.so, \
+ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)
+ $(SHELL) $< $(common-objpfx) '$(test-wrapper-env)' \
+ '$(rtld-prefix)' '$(run_program_env)' \
+ $(objpfx)tst-recursive-tls > $@; \
+ $(evaluate-test)
endif
diff --git a/elf/rtld.c b/elf/rtld.c
index 3221bc8520996097..a4290890d6bca263 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -2708,11 +2708,18 @@ process_dl_debug (struct dl_main_state *state, const char *dl_debug)
&& dl_debug[len] != ',' && dl_debug[len] != ':')
++len;
+ bool exclude = *dl_debug == '-';
+ const char *name = exclude ? dl_debug + 1 : dl_debug;
+ size_t name_len = exclude ? len - 1 : len;
+
for (cnt = 0; cnt < ndebopts; ++cnt)
- if (debopts[cnt].len == len
- && memcmp (dl_debug, debopts[cnt].name, len) == 0)
+ if (debopts[cnt].len == name_len
+ && memcmp (name, debopts[cnt].name, name_len) == 0)
{
- GLRO(dl_debug_mask) |= debopts[cnt].mask;
+ if (exclude)
+ GLRO(dl_debug_mask) &= ~debopts[cnt].mask;
+ else
+ GLRO(dl_debug_mask) |= debopts[cnt].mask;
break;
}
@@ -2754,7 +2761,8 @@ Valid options for the LD_DEBUG environment variable are:\n\n");
_dl_printf ("\n\
To direct the debugging output into a file instead of standard output\n\
-a filename can be specified using the LD_DEBUG_OUTPUT environment variable.\n");
+a filename can be specified using the LD_DEBUG_OUTPUT environment variable.\n\
+Categories can be excluded by prefixing them with a dash (-).\n");
_exit (0);
}
}
diff --git a/elf/tst-dl-debug-exclude.sh b/elf/tst-dl-debug-exclude.sh
new file mode 100644
index 0000000000000000..9837ffcea8e07933
--- /dev/null
+++ b/elf/tst-dl-debug-exclude.sh
@@ -0,0 +1,87 @@
+#!/bin/sh
+# Test for LD_DEBUG category exclusion.
+# Copyright (C) 2026 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
+# <https://www.gnu.org/licenses/>.
+
+# This script verifies the LD_DEBUG category exclusion functionality.
+# It checks that:
+# 1. Categories can be excluded using the '-' prefix.
+# 2. Options are processed sequentially, meaning the last specified
+# option for a category (enable or exclude) takes precedence.
+
+set -e
+
+common_objpfx="$1"
+test_wrapper_env="$2"
+rtld_prefix="$3"
+run_program_env="$4"
+test_program="$5"
+
+debug_output="${common_objpfx}elf/tst-dl-debug-exclude.debug"
+rm -f "${debug_output}".*
+
+# Run the test program with LD_DEBUG=all,-tls.
+# We expect general logs but no TLS logs.
+eval "${test_wrapper_env}" LD_DEBUG=all,-tls LD_DEBUG_OUTPUT="${debug_output}" \
+ "${rtld_prefix}" "${test_program}"
+
+fail=0
+
+# 1. Check that general logs are present (e.g., file loading)
+if ! grep -q 'file=' "${debug_output}".*; then
+ echo "FAIL: 'file=' message not found (LD_DEBUG=all failed)"
+ fail=1
+fi
+
+# 2. Check that TLS logs are NOT present
+if grep -q 'tls: ' "${debug_output}".*; then
+ echo "FAIL: TLS message found (exclusion of -tls failed)"
+ fail=1
+fi
+
+rm -f "${debug_output}".*
+# 3. Check for LD_DEBUG=all,-tls,tls (ordering verification)
+# We expect TLS logs to BE present
+eval "${test_wrapper_env}" LD_DEBUG=all,-tls,tls LD_DEBUG_OUTPUT="${debug_output}" \
+ "${rtld_prefix}" "${test_program}"
+
+if ! grep -q 'tls: ' "${debug_output}".*; then
+ echo "FAIL: TLS message not found (ordering -tls,tls failed)"
+ fail=1
+fi
+
+rm -f "${debug_output}".*
+# 4. Check for LD_DEBUG=tls,-tls
+# We expect TLS logs to NOT be present
+eval "${test_wrapper_env}" LD_DEBUG=tls,-tls LD_DEBUG_OUTPUT="${debug_output}" \
+ "${rtld_prefix}" "${test_program}"
+
+if grep -q 'tls: ' "${debug_output}".*; then
+ echo "FAIL: TLS message found (ordering tls,-tls failed)"
+ fail=1
+fi
+
+if [ $fail -ne 0 ]; then
+ echo "Test FAILED"
+ cat "${debug_output}".*
+ rm -f "${debug_output}".*
+ exit 1
+fi
+
+echo "Test PASSED"
+rm -f "${debug_output}".*
+exit 0

View File

@ -0,0 +1,288 @@
commit 39ca997ab378990d5ac1aadbaa52aaf1db6d526f
Author: Andreas Schwab <schwab@suse.de>
Date: Mon Aug 5 10:55:51 2024 +0200
Fix name space violation in fortify wrappers (bug 32052)
Rename the identifier sz to __sz everywhere.
Fixes: a643f60c53 ("Make sure that the fortified function conditionals are constant")
Conflicts:
libio/bits/stdio2.h
(29951991f5db1001c059f8ed7afa2c359cdc7f44 missing downstream)
socket/bits/socket2.h
(4289b00d4393f490515527864cf09093f4f8c2c4 missing downstream)
stdlib/bits/stdlib.h
(d39a893ed6de8e63ffbfbcc4b7176a2fa852f8a8 missing downstream)
wcsmbs/bits/wchar2.h
(68444c045077368446eced143510419c901e31b1 missing downstream)
diff --git a/libio/bits/stdio2.h b/libio/bits/stdio2.h
index 4570f86a4496c1ee..5d1f3574f9f3fa9e 100644
--- a/libio/bits/stdio2.h
+++ b/libio/bits/stdio2.h
@@ -208,12 +208,12 @@ extern char *__REDIRECT (__fgets_chk_warn,
__fortify_function __wur __fortified_attr_access (__write_only__, 1, 2) char *
fgets (char *__restrict __s, int __n, FILE *__restrict __stream)
{
- size_t sz = __glibc_objsize (__s);
- if (__glibc_safe_or_unknown_len (__n, sizeof (char), sz))
+ size_t __sz = __glibc_objsize (__s);
+ if (__glibc_safe_or_unknown_len (__n, sizeof (char), __sz))
return __fgets_alias (__s, __n, __stream);
- if (__glibc_unsafe_len (__n, sizeof (char), sz))
- return __fgets_chk_warn (__s, sz, __n, __stream);
- return __fgets_chk (__s, sz, __n, __stream);
+ if (__glibc_unsafe_len (__n, sizeof (char), __sz))
+ return __fgets_chk_warn (__s, __sz, __n, __stream);
+ return __fgets_chk (__s, __sz, __n, __stream);
}
extern size_t __REDIRECT (__fread_alias,
@@ -232,12 +232,12 @@ __fortify_function __wur size_t
fread (void *__restrict __ptr, size_t __size, size_t __n,
FILE *__restrict __stream)
{
- size_t sz = __glibc_objsize0 (__ptr);
- if (__glibc_safe_or_unknown_len (__n, __size, sz))
+ size_t __sz = __glibc_objsize0 (__ptr);
+ if (__glibc_safe_or_unknown_len (__n, __size, __sz))
return __fread_alias (__ptr, __size, __n, __stream);
- if (__glibc_unsafe_len (__n, __size, sz))
- return __fread_chk_warn (__ptr, sz, __size, __n, __stream);
- return __fread_chk (__ptr, sz, __size, __n, __stream);
+ if (__glibc_unsafe_len (__n, __size, __sz))
+ return __fread_chk_warn (__ptr, __sz, __size, __n, __stream);
+ return __fread_chk (__ptr, __sz, __size, __n, __stream);
}
#ifdef __USE_GNU
@@ -254,12 +254,12 @@ extern char *__REDIRECT (__fgets_unlocked_chk_warn,
__fortify_function __wur __fortified_attr_access (__write_only__, 1, 2) char *
fgets_unlocked (char *__restrict __s, int __n, FILE *__restrict __stream)
{
- size_t sz = __glibc_objsize (__s);
- if (__glibc_safe_or_unknown_len (__n, sizeof (char), sz))
+ size_t __sz = __glibc_objsize (__s);
+ if (__glibc_safe_or_unknown_len (__n, sizeof (char), __sz))
return __fgets_unlocked_alias (__s, __n, __stream);
- if (__glibc_unsafe_len (__n, sizeof (char), sz))
- return __fgets_unlocked_chk_warn (__s, sz, __n, __stream);
- return __fgets_unlocked_chk (__s, sz, __n, __stream);
+ if (__glibc_unsafe_len (__n, sizeof (char), __sz))
+ return __fgets_unlocked_chk_warn (__s, __sz, __n, __stream);
+ return __fgets_unlocked_chk (__s, __sz, __n, __stream);
}
#endif
@@ -281,8 +281,8 @@ __fortify_function __wur size_t
fread_unlocked (void *__restrict __ptr, size_t __size, size_t __n,
FILE *__restrict __stream)
{
- size_t sz = __glibc_objsize0 (__ptr);
- if (__glibc_safe_or_unknown_len (__n, __size, sz))
+ size_t __sz = __glibc_objsize0 (__ptr);
+ if (__glibc_safe_or_unknown_len (__n, __size, __sz))
{
# ifdef __USE_EXTERN_INLINES
if (__builtin_constant_p (__size)
@@ -307,9 +307,9 @@ fread_unlocked (void *__restrict __ptr, size_t __size, size_t __n,
# endif
return __fread_unlocked_alias (__ptr, __size, __n, __stream);
}
- if (__glibc_unsafe_len (__n, __size, sz))
- return __fread_unlocked_chk_warn (__ptr, sz, __size, __n, __stream);
- return __fread_unlocked_chk (__ptr, sz, __size, __n, __stream);
+ if (__glibc_unsafe_len (__n, __size, __sz))
+ return __fread_unlocked_chk_warn (__ptr, __sz, __size, __n, __stream);
+ return __fread_unlocked_chk (__ptr, __sz, __size, __n, __stream);
}
#endif
diff --git a/socket/bits/socket2.h b/socket/bits/socket2.h
index b28cde55f3fd9c16..e278ebf37b03f121 100644
--- a/socket/bits/socket2.h
+++ b/socket/bits/socket2.h
@@ -33,12 +33,12 @@ extern ssize_t __REDIRECT (__recv_chk_warn,
__fortify_function ssize_t
recv (int __fd, void *__buf, size_t __n, int __flags)
{
- size_t sz = __glibc_objsize0 (__buf);
- if (__glibc_safe_or_unknown_len (__n, sizeof (char), sz))
+ size_t __sz = __glibc_objsize0 (__buf);
+ if (__glibc_safe_or_unknown_len (__n, sizeof (char), __sz))
return __recv_alias (__fd, __buf, __n, __flags);
- if (__glibc_unsafe_len (__n, sizeof (char), sz))
- return __recv_chk_warn (__fd, __buf, __n, sz, __flags);
- return __recv_chk (__fd, __buf, __n, sz, __flags);
+ if (__glibc_unsafe_len (__n, sizeof (char), __sz))
+ return __recv_chk_warn (__fd, __buf, __n, __sz, __flags);
+ return __recv_chk (__fd, __buf, __n, __sz, __flags);
}
extern ssize_t __recvfrom_chk (int __fd, void *__restrict __buf, size_t __n,
@@ -61,11 +61,11 @@ __fortify_function ssize_t
recvfrom (int __fd, void *__restrict __buf, size_t __n, int __flags,
__SOCKADDR_ARG __addr, socklen_t *__restrict __addr_len)
{
- size_t sz = __glibc_objsize0 (__buf);
- if (__glibc_safe_or_unknown_len (__n, sizeof (char), sz))
+ size_t __sz = __glibc_objsize0 (__buf);
+ if (__glibc_safe_or_unknown_len (__n, sizeof (char), __sz))
return __recvfrom_alias (__fd, __buf, __n, __flags, __addr, __addr_len);
- if (__glibc_unsafe_len (__n, sizeof (char), sz))
- return __recvfrom_chk_warn (__fd, __buf, __n, sz, __flags, __addr,
+ if (__glibc_unsafe_len (__n, sizeof (char), __sz))
+ return __recvfrom_chk_warn (__fd, __buf, __n, __sz, __flags, __addr,
__addr_len);
- return __recvfrom_chk (__fd, __buf, __n, sz, __flags, __addr, __addr_len);
+ return __recvfrom_chk (__fd, __buf, __n, __sz, __flags, __addr, __addr_len);
}
diff --git a/stdlib/bits/stdlib.h b/stdlib/bits/stdlib.h
index ac9badc81f3990c5..658a08b80f3a3141 100644
--- a/stdlib/bits/stdlib.h
+++ b/stdlib/bits/stdlib.h
@@ -36,16 +36,16 @@ extern char *__REDIRECT_NTH (__realpath_chk_warn,
__fortify_function __wur char *
__NTH (realpath (const char *__restrict __name, char *__restrict __resolved))
{
- size_t sz = __glibc_objsize (__resolved);
+ size_t __sz = __glibc_objsize (__resolved);
- if (sz == (size_t) -1)
+ if (__sz == (size_t) -1)
return __realpath_alias (__name, __resolved);
#if defined _LIBC_LIMITS_H_ && defined PATH_MAX
- if (__glibc_unsafe_len (PATH_MAX, sizeof (char), sz))
- return __realpath_chk_warn (__name, __resolved, sz);
+ if (__glibc_unsafe_len (PATH_MAX, sizeof (char), __sz))
+ return __realpath_chk_warn (__name, __resolved, __sz);
#endif
- return __realpath_chk (__name, __resolved, sz);
+ return __realpath_chk (__name, __resolved, __sz);
}
diff --git a/wcsmbs/bits/wchar2.h b/wcsmbs/bits/wchar2.h
index 50151b424d85a032..ee43dcb6a9170564 100644
--- a/wcsmbs/bits/wchar2.h
+++ b/wcsmbs/bits/wchar2.h
@@ -107,9 +107,9 @@ extern wchar_t *__REDIRECT_NTH (__wcscpy_alias,
__fortify_function wchar_t *
__NTH (wcscpy (wchar_t *__restrict __dest, const wchar_t *__restrict __src))
{
- size_t sz = __glibc_objsize (__dest);
- if (sz != (size_t) -1)
- return __wcscpy_chk (__dest, __src, sz / sizeof (wchar_t));
+ size_t __sz = __glibc_objsize (__dest);
+ if (__sz != (size_t) -1)
+ return __wcscpy_chk (__dest, __src, __sz / sizeof (wchar_t));
return __wcscpy_alias (__dest, __src);
}
@@ -121,9 +121,9 @@ extern wchar_t *__REDIRECT_NTH (__wcpcpy_alias,
__fortify_function wchar_t *
__NTH (wcpcpy (wchar_t *__restrict __dest, const wchar_t *__restrict __src))
{
- size_t sz = __glibc_objsize (__dest);
- if (sz != (size_t) -1)
- return __wcpcpy_chk (__dest, __src, sz / sizeof (wchar_t));
+ size_t __sz = __glibc_objsize (__dest);
+ if (__sz != (size_t) -1)
+ return __wcpcpy_chk (__dest, __src, __sz / sizeof (wchar_t));
return __wcpcpy_alias (__dest, __src);
}
@@ -177,9 +177,9 @@ extern wchar_t *__REDIRECT_NTH (__wcscat_alias,
__fortify_function wchar_t *
__NTH (wcscat (wchar_t *__restrict __dest, const wchar_t *__restrict __src))
{
- size_t sz = __glibc_objsize (__dest);
- if (sz != (size_t) -1)
- return __wcscat_chk (__dest, __src, sz / sizeof (wchar_t));
+ size_t __sz = __glibc_objsize (__dest);
+ if (__sz != (size_t) -1)
+ return __wcscat_chk (__dest, __src, __sz / sizeof (wchar_t));
return __wcscat_alias (__dest, __src);
}
@@ -193,9 +193,9 @@ __fortify_function wchar_t *
__NTH (wcsncat (wchar_t *__restrict __dest, const wchar_t *__restrict __src,
size_t __n))
{
- size_t sz = __glibc_objsize (__dest);
- if (sz != (size_t) -1)
- return __wcsncat_chk (__dest, __src, __n, sz / sizeof (wchar_t));
+ size_t __sz = __glibc_objsize (__dest);
+ if (__sz != (size_t) -1)
+ return __wcsncat_chk (__dest, __src, __n, __sz / sizeof (wchar_t));
return __wcsncat_alias (__dest, __src, __n);
}
@@ -211,10 +211,10 @@ __fortify_function int
__NTH (swprintf (wchar_t *__restrict __s, size_t __n,
const wchar_t *__restrict __fmt, ...))
{
- size_t sz = __glibc_objsize (__s);
- if (sz != (size_t) -1 || __USE_FORTIFY_LEVEL > 1)
+ size_t __sz = __glibc_objsize (__s);
+ if (__sz != (size_t) -1 || __USE_FORTIFY_LEVEL > 1)
return __swprintf_chk (__s, __n, __USE_FORTIFY_LEVEL - 1,
- sz / sizeof (wchar_t), __fmt, __va_arg_pack ());
+ __sz / sizeof (wchar_t), __fmt, __va_arg_pack ());
return __swprintf_alias (__s, __n, __fmt, __va_arg_pack ());
}
#elif !defined __cplusplus
@@ -236,10 +236,10 @@ __fortify_function int
__NTH (vswprintf (wchar_t *__restrict __s, size_t __n,
const wchar_t *__restrict __fmt, __gnuc_va_list __ap))
{
- size_t sz = __glibc_objsize (__s);
- if (sz != (size_t) -1 || __USE_FORTIFY_LEVEL > 1)
+ size_t __sz = __glibc_objsize (__s);
+ if (__sz != (size_t) -1 || __USE_FORTIFY_LEVEL > 1)
return __vswprintf_chk (__s, __n, __USE_FORTIFY_LEVEL - 1,
- sz / sizeof (wchar_t), __fmt, __ap);
+ __sz / sizeof (wchar_t), __fmt, __ap);
return __vswprintf_alias (__s, __n, __fmt, __ap);
}
@@ -293,12 +293,12 @@ extern wchar_t *__REDIRECT (__fgetws_chk_warn,
__fortify_function __wur wchar_t *
fgetws (wchar_t *__restrict __s, int __n, __FILE *__restrict __stream)
{
- size_t sz = __glibc_objsize (__s);
- if (__glibc_safe_or_unknown_len (__n, sizeof (wchar_t), sz))
+ size_t __sz = __glibc_objsize (__s);
+ if (__glibc_safe_or_unknown_len (__n, sizeof (wchar_t), __sz))
return __fgetws_alias (__s, __n, __stream);
- if (__glibc_unsafe_len (__n, sizeof (wchar_t), sz))
- return __fgetws_chk_warn (__s, sz / sizeof (wchar_t), __n, __stream);
- return __fgetws_chk (__s, sz / sizeof (wchar_t), __n, __stream);
+ if (__glibc_unsafe_len (__n, sizeof (wchar_t), __sz))
+ return __fgetws_chk_warn (__s, __sz / sizeof (wchar_t), __n, __stream);
+ return __fgetws_chk (__s, __sz / sizeof (wchar_t), __n, __stream);
}
#ifdef __USE_GNU
@@ -316,13 +316,13 @@ extern wchar_t *__REDIRECT (__fgetws_unlocked_chk_warn,
__fortify_function __wur wchar_t *
fgetws_unlocked (wchar_t *__restrict __s, int __n, __FILE *__restrict __stream)
{
- size_t sz = __glibc_objsize (__s);
- if (__glibc_safe_or_unknown_len (__n, sizeof (wchar_t), sz))
+ size_t __sz = __glibc_objsize (__s);
+ if (__glibc_safe_or_unknown_len (__n, sizeof (wchar_t), __sz))
return __fgetws_unlocked_alias (__s, __n, __stream);
- if (__glibc_unsafe_len (__n, sizeof (wchar_t), sz))
- return __fgetws_unlocked_chk_warn (__s, sz / sizeof (wchar_t), __n,
+ if (__glibc_unsafe_len (__n, sizeof (wchar_t), __sz))
+ return __fgetws_unlocked_chk_warn (__s, __sz / sizeof (wchar_t), __n,
__stream);
- return __fgetws_unlocked_chk (__s, sz / sizeof (wchar_t), __n, __stream);
+ return __fgetws_unlocked_chk (__s, __sz / sizeof (wchar_t), __n, __stream);
}
#endif

View File

@ -0,0 +1,111 @@
commit b52619f2e8bbae57d79c95538346198c4a9f24a6
Author: Arjun Shankar <arjun@redhat.com>
Date: Mon Jan 26 13:49:37 2026 +0100
dlfcn: Add dlinfo request type RTLD_DI_ORIGIN_PATH (bug #24298)
The existing dlinfo request type RTLD_DI_ORIGIN used for querying the
value of the '$ORIGIN' dynamic string token is prone to buffer
overflows.
This commit adds a new request type named RTLD_DI_ORIGIN_PATH that
returns a pointer to the dynamic string token (i.e. the 'l_origin' field
in the link map) instead. The dlinfo manual is updated with the new
request type, and the description of RTLD_DI_ORIGIN is updated to
recommend RTLD_DI_ORIGIN_PATH instead.
A test for the new request type is also added to tst-dlinfo.
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
diff --git a/dlfcn/dlfcn.h b/dlfcn/dlfcn.h
index 8168a71dbfe90e75..b80c3e3d9f240e80 100644
--- a/dlfcn/dlfcn.h
+++ b/dlfcn/dlfcn.h
@@ -169,7 +169,12 @@ enum
the number of program headers in the array. */
RTLD_DI_PHDR = 11,
- RTLD_DI_MAX = 11
+ /* Treat ARG as `const char **' and at that location, store the address
+ of the directory name used to expand $ORIGIN in this shared object's
+ dependency file names. */
+ RTLD_DI_ORIGIN_PATH = 12,
+
+ RTLD_DI_MAX = 12
};
diff --git a/dlfcn/dlinfo.c b/dlfcn/dlinfo.c
index 1842925fb7c594dd..28681a865a11e102 100644
--- a/dlfcn/dlinfo.c
+++ b/dlfcn/dlinfo.c
@@ -67,6 +67,13 @@ dlinfo_doit (void *argsblock)
strcpy (args->arg, l->l_origin);
break;
+ case RTLD_DI_ORIGIN_PATH:
+ if (l->l_origin != (char *) -1)
+ *(const char **) args->arg = l->l_origin;
+ else
+ *(const char **) args->arg = NULL;
+ break;
+
case RTLD_DI_TLS_MODID:
*(size_t *) args->arg = 0;
*(size_t *) args->arg = l->l_tls_modid;
diff --git a/dlfcn/tst-dlinfo.c b/dlfcn/tst-dlinfo.c
index d47eebc013a556b4..0765be8e649251b7 100644
--- a/dlfcn/tst-dlinfo.c
+++ b/dlfcn/tst-dlinfo.c
@@ -20,6 +20,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
+#include <support/check.h>
#define TEST_FUNCTION do_test ()
@@ -56,6 +57,13 @@ do_test (void)
printf ("origin: %s\n", origin);
}
+ const char *origin_path;
+ TRY (RTLD_DI_ORIGIN_PATH, &origin_path)
+ {
+ TEST_COMPARE_STRING (origin, origin_path);
+ printf ("origin_path: %s\n", origin_path);
+ }
+
Dl_serinfo counts;
TRY (RTLD_DI_SERINFOSIZE, &counts)
{
diff --git a/manual/dynlink.texi b/manual/dynlink.texi
index f97f13444f1c5946..2b35e5883c3e65c0 100644
--- a/manual/dynlink.texi
+++ b/manual/dynlink.texi
@@ -482,13 +482,23 @@ The namespace identifier of @var{handle} is written to
@code{*@var{arg}}. The @var{arg} argument must be the address of an
object of type @code{Lmid_t}.
+@item RTLD_DI_ORIGIN_PATH
+The @code{$ORIGIN} dynamic string token pointer for @var{handle} is written
+to @code{*@var{arg}}. The @var{arg} argument must be the address of a
+@code{const char *}. If the @code{$ORIGIN} could not be determined, the
+function still returns 0 but writes a null pointer to @code{*@var{arg}}.
+The returned string is only valid as long as the corresponding handle is
+valid. Accessing it after @code{dlclose} has been called for the
+corresponding @var{handle} is undefined behavior.
+
@item RTLD_DI_ORIGIN
The value of the @code{$ORIGIN} dynamic string token for @var{handle} is
written to the character array starting at @var{arg} as a
null-terminated string.
This request type should not be used because it is prone to buffer
-overflows.
+overflows. Instead, @code{RTLD_DI_ORIGIN_PATH} described above should be
+used.
@item RTLD_DI_SERINFO
@itemx RTLD_DI_SERINFOSIZE

View File

@ -1,2 +1,2 @@
eae38d8010e461f8f0d4a9ec27080eb89bc2e6fe
92cd2bbb684588d562a37ffea13a37cccebe428a
v1

View File

@ -1,3 +1,402 @@
commit 92cd2bbb684588d562a37ffea13a37cccebe428a
Author: DJ Delorie <dj@redhat.com>
AuthorDate: Wed Jul 29 13:35:13 2026 -0400
Commit: DJ Delorie <dj@redhat.com>
CommitDate: Wed Jul 29 14:23:26 2026 -0400
Stop shipping glibc32-debuginfo (RHEL-219516)
By stripping the symbol table from the glibc32 shared objects,
we disable the creation of debuginfo packages, which we do not
need nor want to ship.
Resolves: RHEL-219516
:100644 100644 509fa59 c7c189a M glibc.spec
commit 107c909712e52b9e6bd3792ac930d9436eb0ba8f
Author: Bruno Goncalves <bgoncalv@redhat.com>
AuthorDate: Fri Jun 19 14:28:54 2026 +0200
Commit: Bruno Goncalves <bgoncalv@redhat.com>
CommitDate: Tue Jul 28 09:55:38 2026 +0200
CI Gating: drop kernel-qe gating
RPM-Changelog: -
RPM-Skip-Release: yes
:100644 100644 9300333 2df044a M gating.yaml
commit 4927d93cd55390c899eae233b64b508e599fc678
Author: Patsy Griffin <patsy@redhat.com>
AuthorDate: Fri Jul 10 17:09:17 2026 -0400
Commit: pfrankli <pfrankli@redhat.com>
CommitDate: Wed Jul 22 16:46:27 2026 +0000
iconv: Suppress intermediate errors with //TRANSLIT (RHEL-178279)
Resolves: RHEL-178279
:000000 100644 0000000 ded65f1 A glibc-RHEL-178279.patch
commit 16fb6ccc05ad3ba571a4adf64058fa7bc30e455f
Author: Lenka Špačková <lspackova@redhat.com>
AuthorDate: Wed Jul 15 15:47:43 2026 +0200
Commit: Lenka Špačková <lspackova@redhat.com>
CommitDate: Wed Jul 15 15:47:43 2026 +0200
Regression/bz2115831-glibc-missing-gnu-debuglink-section-in: adjust relevancy for RHEL 9
Fixed in RHEL 9.8: RHEL-27851
RPM-Changelog: -
RPM-Skip-Release: yes
:100644 100644 36de426 b9c1fe0 M tests/Regression/bz2115831-glibc-missing-gnu-debuglink-section-in/main.fmf
commit 1d22b956dda1e7fdc090165ae3bc8d882cd4e516
Author: DJ Delorie <dj@redhat.com>
AuthorDate: Thu Jun 25 23:10:08 2026 -0400
Commit: Frédéric Bérat <fberat@redhat.com>
CommitDate: Tue Jul 7 12:54:03 2026 +0000
CVE-2026-5435 Out-of-bounds write via TSIG record processing
Resolves: RHEL-180336
Resolves: RHEL-185614
:000000 100644 0000000 9894622 A glibc-RHEL-180336-1.patch
:000000 100644 0000000 2f90da8 A glibc-RHEL-180336-2.patch
:000000 100644 0000000 cc495bc A glibc-RHEL-180336-3.patch
:000000 100644 0000000 acef669 A glibc-RHEL-180336-4.patch
:000000 100644 0000000 a019f27 A glibc-RHEL-180336-5.patch
:000000 100644 0000000 1222ad9 A glibc-RHEL-180336-6.patch
:000000 100644 0000000 3250e7b A glibc-RHEL-180336-7.patch
commit 2acec79102345198aa8dfd4b8a89f75f11205a0b
Author: Patsy Griffin <patsy@redhat.com>
AuthorDate: Fri Jun 26 11:52:03 2026 -0400
Commit: Patsy Griffin <patsy@redhat.com>
CommitDate: Fri Jun 26 11:52:03 2026 -0400
CVE-2026-5928: Fix ungetwc operating on byte stream (RHEL-180340)
Resolves: RHEL-180340
:000000 100644 0000000 0771d0a A glibc-RHEL-180340.patch
commit ec99e297ec21a794af038ae0b8ad9eae6a5b7a54
Author: Patsy Griffin <patsy@redhat.com>
AuthorDate: Mon Jun 22 06:53:56 2026 -0400
Commit: pfrankli <pfrankli@redhat.com>
CommitDate: Mon Jun 22 13:49:01 2026 +0000
CVE-2026-5450: Fix buffer overflow in scanf (RHEL-172710)
Resolves: RHEL-172710
:000000 100644 0000000 1bd6427 A glibc-RHEL-172710-1.patch
:000000 100644 0000000 a334a8c A glibc-RHEL-172710-2.patch
:000000 100644 0000000 6bfe8c5 A glibc-RHEL-172710-3.patch
:000000 100644 0000000 12a03cd A glibc-RHEL-172710-4.patch
commit 396dbae5399bc8ee97a617cc9ad4e1d95d26faf1
Author: Sergey Kolosov <skolosov@redhat.com>
AuthorDate: Fri Jun 19 14:54:09 2026 +0200
Commit: Sergey Kolosov <skolosov@redhat.com>
CommitDate: Fri Jun 19 14:54:09 2026 +0200
CI Gating: Add fail-fast test plan jobs as required rules
RPM-Changelog: -
RPM-Skip-Release: yes
:100644 100644 ba7f921 9300333 M gating.yaml
commit beeb5b354b9f5fd8745cc034694041b2ca91e31d
Author: Frédéric Bérat <fberat@redhat.com>
AuthorDate: Thu Jun 4 11:49:13 2026 +0200
Commit: Frédéric Bérat <fberat@redhat.com>
CommitDate: Tue Jun 9 11:29:39 2026 +0200
Fix gconv module reference counter overflow in swscanf and swprintf families
Resolves: RHEL-145156
:000000 100644 0000000 d591eea A glibc-RHEL-145156-1.patch
:000000 100644 0000000 01b7d9c A glibc-RHEL-145156-2.patch
:000000 100644 0000000 78b117e A glibc-RHEL-145156-3.patch
:000000 100644 0000000 62dec63 A glibc-RHEL-145156-4.patch
:000000 100644 0000000 41d91fd A glibc-RHEL-145156-5.patch
commit 381936891b4e421cd9a03b682bcb5a66039fd230
Author: Patsy Griffin <patsy@redhat.com>
AuthorDate: Mon May 4 22:48:56 2026 -0400
Commit: Florian Weimer <fweimer@redhat.com>
CommitDate: Sun May 10 17:40:44 2026 +0000
Fix incorrect setting of CXX variable in spec file (RHEL-148252)
Resolves: RHEL-148252
:100644 100644 54e9fb1 509fa59 M glibc.spec
commit c86a21c28e56e47fa7ff5507191d9f3146363574
Author: Frédéric Bérat <fberat@redhat.com>
AuthorDate: Wed May 6 10:07:53 2026 +0200
Commit: Florian Weimer <fweimer@redhat.com>
CommitDate: Sun May 10 05:29:05 2026 +0000
Fix __nss_get_default_domain logic to restore netgroup user enumeration (RHEL-168096)
Resolves: RHEL-168096
:000000 100644 0000000 cd80ed7 A glibc-RHEL-168096.patch
commit a35ccbc1615d3cc93ac7427a3c3b3d2f4da64e55
Author: Arjun Shankar <arjun@redhat.com>
AuthorDate: Mon May 4 13:48:25 2026 +0200
Commit: Florian Weimer <fweimer@redhat.com>
CommitDate: Sat May 9 08:46:30 2026 +0000
resolv: Correctly count records and check hostname validity (RHEL-168851, RHEL-168852)
Resolves: RHEL-168851
Resolves: RHEL-168852
:000000 100644 0000000 9e41b3f A glibc-RHEL-168851.patch
:000000 100644 0000000 159e4d5 A glibc-RHEL-168852.patch
commit 437183f942920f2ae5a825d4a710edd5b0def2d1
Author: Frédéric Bérat <fberat@redhat.com>
AuthorDate: Thu Apr 30 15:34:53 2026 +0200
Commit: Florian Weimer <fweimer@redhat.com>
CommitDate: Wed May 6 08:18:22 2026 +0000
CVE-2026-4046: Fix assertion failure in IBM1390 and IBM1399 iconv modules (RHEL-162901)
Resolves: RHEL-162901
:000000 100644 0000000 39624a7 A glibc-RHEL-162901-1.patch
:000000 100644 0000000 b31887c A glibc-RHEL-162901-2.patch
:000000 100644 0000000 9294f79 A glibc-RHEL-162901-3.patch
commit 7fbc71a39a05131ddc4e61bd013acc2e34c62ca5
Author: Martin Coufal <mcoufal@redhat.com>
AuthorDate: Wed Apr 29 13:22:27 2026 +0000
Commit: Martin Coufal <mcoufal@redhat.com>
CommitDate: Thu Apr 30 10:13:36 2026 +0200
gating.yaml: mark power9 and power10 specific jobs as required
RPM-Changelog: -
RPM-Skip-Release: yes
:100644 100644 a1e2d55 ba7f921 M gating.yaml
commit 6876da6c3cd740735f9227fa71494e3e72c91cdf
Author: Frédéric Bérat <fberat@redhat.com>
AuthorDate: Tue Mar 24 15:29:18 2026 +0100
Commit: Frédéric Bérat <fberat@redhat.com>
CommitDate: Thu Mar 26 16:10:21 2026 +0000
Fix namespace violation in fortify wrappers (bug 32052) (RHEL-52966)
Resolves: RHEL-52966
:000000 100644 0000000 52a65c1 A glibc-RHEL-52966.patch
commit d19518e588c4477fcde9374ec70350a1bc382c0b
Author: Frédéric Bérat <fberat@redhat.com>
AuthorDate: Tue Mar 24 14:18:52 2026 +0100
Commit: Frédéric Bérat <fberat@redhat.com>
CommitDate: Wed Mar 25 20:42:54 2026 +0000
Fix null pointer dereference and assertion failures in __nss_database_get (bug 28940) (RHEL-150269)
Resolves: RHEL-150269
:000000 100644 0000000 0eda8a7 A glibc-RHEL-150269-1.patch
:000000 100644 0000000 3925565 A glibc-RHEL-150269-2.patch
:000000 100644 0000000 7c99f3d A glibc-RHEL-150269-3.patch
:000000 100644 0000000 f811d42 A glibc-RHEL-150269-4.patch
commit 0e8e4ecae9fa126d2cea194d09042c554bdc420c
Author: Frédéric Bérat <fberat@redhat.com>
AuthorDate: Tue Mar 24 11:01:32 2026 +0100
Commit: Frédéric Bérat <fberat@redhat.com>
CommitDate: Wed Mar 25 12:30:41 2026 +0000
Add LD_DEBUG=tls support for tracking TLS and TCB events (RHEL-49785)
Resolves: RHEL-49785
:000000 100644 0000000 b9a8d92 A glibc-RHEL-49785-1.patch
:000000 100644 0000000 312faf7 A glibc-RHEL-49785-2.patch
:000000 100644 0000000 3969165 A glibc-RHEL-49785-3.patch
:000000 100644 0000000 6ee9ef6 A glibc-RHEL-49785-4.patch
:000000 100644 0000000 be0b0eb A glibc-RHEL-49785-5.patch
:000000 100644 0000000 e109ac5 A glibc-RHEL-49785-6.patch
commit d58e05d599e91bf2d7597da217208b2ef4c92fc1
Author: Florian Weimer <fweimer@redhat.com>
AuthorDate: Mon Mar 23 16:53:51 2026 +0100
Commit: Florian Weimer <fweimer@redhat.com>
CommitDate: Mon Mar 23 16:53:51 2026 +0100
Avoid duplicate DNS query if search list contains '.'
Resolves: RHEL-153056
:000000 100644 0000000 bf93cf7 A glibc-RHEL-153056-1.patch
:000000 100644 0000000 df43536 A glibc-RHEL-153056-2.patch
:000000 100644 0000000 ed5efd8 A glibc-RHEL-153056-3.patch
commit 6a5da8faa95148af5ace04cd63a9e11686b61d12
Author: Arjun Shankar <arjun@redhat.com>
AuthorDate: Tue Mar 17 12:39:23 2026 +0100
Commit: Arjun Shankar <arjun@redhat.com>
CommitDate: Tue Mar 17 12:39:23 2026 +0100
dlfcn: Add dlinfo request type RTLD_DI_ORIGIN_PATH (RHEL-54450)
Resolves: RHEL-54450
:000000 100644 0000000 ab7c187 A glibc-RHEL-54450.patch
commit 7bff59bb7c59d40d81878bbe411782c7e393b538
Author: Sergey Kolosov <skolosov@redhat.com>
AuthorDate: Mon Mar 2 20:38:08 2026 +0100
Commit: Sergey Kolosov <skolosov@redhat.com>
CommitDate: Mon Mar 9 08:59:24 2026 +0000
CI Gating: update gating.yaml for taskrpm deprecation
Replace the deprecated baseos-ci.brew-build.tier1.functional rule with
fast-line and slow-line rules for tier0, tier1 and testsuite tests.
Related: RHEL-126175
RPM-Changelog: -
RPM-Skip-Release: yes
:100644 100644 07eeedb a1e2d55 M gating.yaml
commit e2834b00283401e1a1cb8c6a1768b2c2fdaa7836
Author: Sergey Kolosov <skolosov@redhat.com>
AuthorDate: Tue Feb 24 09:51:00 2026 +0100
Commit: Sergey Kolosov <skolosov@redhat.com>
CommitDate: Mon Mar 2 19:10:44 2026 +0100
CI Tests: remove conflicting glibc32 to fix prepare stage
RPM-Changelog: -
RPM-Skip-Release: yes
:100644 100644 7249575 7749a2d M plans/ci.fmf
commit ca2c60812dc5d34b77f94382c7340dbdf0ca2748
Author: Sergey Kolosov <skolosov@redhat.com>
AuthorDate: Mon Feb 23 21:36:01 2026 +0100
Commit: Sergey Kolosov <skolosov@redhat.com>
CommitDate: Mon Mar 2 19:10:44 2026 +0100
CI Tests: add Regression/bz1882466-RHEL8-2-LD-PRELOAD-of-some-lib-that-has
This test was originally missing during the import from Fedora test
plan.
RPM-Changelog: -
RPM-Skip-Release: yes
:000000 100644 0000000 2852f58 A tests/Regression/bz1882466-RHEL8-2-LD-PRELOAD-of-some-lib-that-has/Makefile
:000000 100644 0000000 020631c A tests/Regression/bz1882466-RHEL8-2-LD-PRELOAD-of-some-lib-that-has/PURPOSE
:000000 100644 0000000 45d1371 A tests/Regression/bz1882466-RHEL8-2-LD-PRELOAD-of-some-lib-that-has/main.fmf
:000000 100755 0000000 d36bb2d A tests/Regression/bz1882466-RHEL8-2-LD-PRELOAD-of-some-lib-that-has/runtest.sh
:000000 100644 0000000 b8a08ec A tests/Regression/bz1882466-RHEL8-2-LD-PRELOAD-of-some-lib-that-has/testlib.cc
commit 1d7a0617ff0c43317425ee2a5b7379c7e2ef73ac
Author: Patsy Griffin <patsy@redhat.com>
AuthorDate: Mon Feb 23 18:22:41 2026 -0500
Commit: Frédéric Bérat <fberat@redhat.com>
CommitDate: Thu Feb 26 10:03:06 2026 +0000
Remove default value for LD_PROFILE_OUTPUT
Resolves: RHEL-142196
:000000 100644 0000000 fa52d55 A glibc-RHEL-142196-1.patch
:000000 100644 0000000 f12aeb8 A glibc-RHEL-142196-2.patch
:000000 100644 0000000 28567d2 A glibc-RHEL-142196-3.patch
:000000 100644 0000000 e1a8ab7 A glibc-RHEL-142196-4.patch
:000000 100644 0000000 84b027a A glibc-RHEL-142196-5.patch
:000000 100644 0000000 68cdcff A glibc-RHEL-142196-6.patch
:000000 100644 0000000 7997560 A glibc-RHEL-142196-7.patch
:000000 100644 0000000 a999fc8 A glibc-RHEL-142196-8.patch
commit 8a9ddda3347d613915845ebc322ed2c38145b070
Author: Frédéric Bérat <fberat@redhat.com>
AuthorDate: Tue Feb 24 15:23:26 2026 +0100
Commit: Frédéric Bérat <fberat@redhat.com>
CommitDate: Tue Feb 24 15:23:26 2026 +0100
Fix race condition in io/tst-stat symlink test (RHEL-142209)
Resolves: RHEL-142209
:000000 100644 0000000 0b0cc99 A glibc-RHEL-142209.patch
commit a5df4f06f796a4c1f78aa9c8e06f4ca823431072
Author: Frédéric Bérat <fberat@redhat.com>
AuthorDate: Mon Feb 23 14:17:12 2026 +0100
Commit: Frédéric Bérat <fberat@redhat.com>
CommitDate: Mon Feb 23 14:17:12 2026 +0100
Restore iconv tool verbosity for unrecognized encoding names (RHEL-1018)
Resolves: RHEL-1018
:000000 100644 0000000 ffe3c50 A glibc-RHEL-1018.patch
commit 3b96603253e3faf6588dee6d8b20043566c6d5e4
Author: Patsy Griffin <patsy@redhat.com>
AuthorDate: Fri Feb 6 16:08:23 2026 -0500
Commit: Florian Weimer <fweimer@redhat.com>
CommitDate: Mon Feb 16 07:43:17 2026 +0000
Optimize trylock for high cache contention workloads.
Resolves: RHEL-141072
:000000 100644 0000000 496a62f A glibc-RHEL-141072.patch
commit c7f278779ca2b29a5524121252ab11f051cfb8bb
Author: Patsy Griffin <patsy@redhat.com>
AuthorDate: Tue Feb 10 18:53:05 2026 -0500
Commit: Florian Weimer <fweimer@redhat.com>
CommitDate: Fri Feb 13 15:27:36 2026 +0000
Update locales for Croatia to use EUR as currency symbol
Resolves: RHEL-140105
:000000 100644 0000000 1b763ae A glibc-RHEL-140105.patch
commit 426ad63b6259338de1b2083c57dab64d16b06bb0
Author: Sergey Kolosov <skolosov@redhat.com>
AuthorDate: Thu Feb 12 20:37:12 2026 +0100
Commit: Florian Weimer <fweimer@redhat.com>
CommitDate: Fri Feb 13 15:21:49 2026 +0000
CI Tests: enable Regression/bz2115831-glibc-missing-gnu-debuglink-section-in
Re-enable this test as RHEL-27851 has been fixed
Related: RHEL-27851
RPM-Changelog: -
RPM-Skip-Release: yes
:100644 100644 4a3341c 36de426 M tests/Regression/bz2115831-glibc-missing-gnu-debuglink-section-in/main.fmf
commit eae38d8010e461f8f0d4a9ec27080eb89bc2e6fe
Author: Florian Weimer <fweimer@redhat.com>
AuthorDate: Thu Feb 12 15:17:05 2026 +0100

View File

@ -1844,7 +1844,7 @@ build build-%{target}-32 \
# Default set of compiler options.
build build-%{target} \
CC="gcc $glibc_flags_cc $glibc_flags_cc_main" \
CXX="gcc $glibc_flags_cc $glibc_flags_cc_main" \
CXX="g++ $glibc_flags_cc $glibc_flags_cc_main" \
CFLAGS="$glibc_flags_cflags" \
%{?glibc_rtld_early_cflags:--with-rtld-early-cflags=%glibc_rtld_early_cflags} \
%ifarch x86_64
@ -1902,6 +1902,15 @@ rm -rf etc var usr/bin usr/lib/gconv usr/libexec usr/sbin usr/share
rm -f lib/libnss_db* lib/libnss_hesiod* lib/libnsl* usr/lib/libnsl* usr/lib/libnss*
rm usr/lib/libc_malloc_debug.so
strip -g usr/lib/*.o
for f in usr/lib/*.so; do
case "$f" in
# libc.so is a text file, not an object
usr/lib/libc.so ) ;;
* ) strip $f ;;
esac
done
strip usr/lib/*.so.?
strip usr/lib/audit/*.so
popd
%endif