public inbox for libc-alpha@sourceware.org
 help / color / mirror / Atom feed
* [PATCH v3 00/19] Improve loader environment variable handling
@ 2023-11-06 20:25 Adhemerval Zanella
  2023-11-06 20:25 ` [PATCH v3 01/19] elf: Remove /etc/suid-debug support Adhemerval Zanella
                   ` (19 more replies)
  0 siblings, 20 replies; 40+ messages in thread
From: Adhemerval Zanella @ 2023-11-06 20:25 UTC (permalink / raw)
  To: libc-alpha, Siddhesh Poyarekar

The recent CVE-2023-4911 fix [1] and tunable change to SXID_ERASE
discussion [2] brought some issues with the current environment handling
by the loader. Besides the bugs in tuning parsing, some other questions
are:

  * What should be the security boundaries for tunable and other tuning
    environment variables?

  * Should tunables be filtered out or be disabled altogether in setuid
    binaries [3]?

  * How should ld.so handle security-sensitive tunable (like malloc
    options)?

  * How to handle ill-formatted tunable definition [4]?

  * Is tunable copy/parsing (through tunable_strdup) required [5]?

  * Which other environment variables the loader should ignore and/or
    filter in security-sensitive context.

On this patchset, I followed the idea laid out in the discussion on
whether to apply SXID_ERASE to all tunables [6]:

  1. ignore any tunable on AT_SECURE binaries (as some Linux distributions
      do already [7]);

  2. Add malloc tunables along with GLIBC_TUNABLES to unsecvars;

  3. Do not parse ill-formatted GLIBC_TUNABLES strings;

  4. Remove the requirement of duplicating the GLIBC_TUNABLES string for
     parsing.

  5. Ignore most of the environment variables on security-sensitive
     mode (AT_SECURE/setuid/setgid).

Patch #1 removes '/etc/suid-debug', which has not been working since
malloc debugging supported moved to libc_malloc_debug.so. It is one
thing less that might change AT_SECURE binaries' behavior
due to environment configurations.

Patch #2 removed tunables parsing and applying for setuid/setgid
binaries (similar to Alt Linux patch).

Patch #3 and #4 add all malloc tunable and GLIBC_TUNABLES to unsecvars
and improve tst-env-setuid.c to test all possible environment variables.

Patch #5 and #6 improved the GLIBC_TUNABLES handling to avoid handling
ill-formatted inputs.

Patch #7 makes _dl_debug_vdprintf usable before self-relocation so patch
#8 can add a loader warning that ill-formatted GLIBC_TUNABLES inputs are
ignored (it also fixes the issue where the GLIBC_TUNABLE allocation
failure will trigger a SEGFAULT on some architecture for PIE).

Patch #9, #10, and #11 remove the tunable_strdup and make the
GLIBC_TUNABLE parsing in place (no more possible allocation failure).
The parsing now tracks the tunable start and its size. The
dl-tunable-parse.h adds helper functions to help to parse, like an
strcmp that also checks for size and an iterator for suboptions that are
comma-separated (used on hwcap parsing by x86, powerpc, and s390x).

Patch #12, #13, #14, #16, and #18 make loader ignore all but just
LD_PRELOAD and LD_AUDIT for setuid binaries.  For both options, loader
ensures that pathnames containing slashes are ignored and shared
libraries are loaded only from the standard search directories and only
if they have set-user-ID mode bit enabled.

[1] https://sourceware.org/pipermail/libc-alpha/2023-October/151921.html
[2] https://sourceware.org/pipermail/libc-alpha/2023-October/151936.html
[3] https://www.openwall.com/lists/oss-security/2023/10/03/3
[4] https://sourceware.org/pipermail/libc-alpha/2023-October/151927.html
[5] https://sourceware.org/pipermail/libc-alpha/2023-October/151959.html
[6] https://sourceware.org/pipermail/libc-alpha/2023-October/152011.html
[7] https://git.altlinux.org/gears/g/glibc.git?p=glibc.git;a=commitdiff;h=5d1686416ab766f3dd0780ab730650c4c0f76ca9

Changes from v2:
* Extend tst-tunables with tunables aliases tests.
* Use warning instead of an error to indicate invalid tunables.
* Fixed tunable_initialize for string aliases.
Changes from v1:
* Ignore most of the environment variables on security-sensitive mode.
* Extend tests.

Adhemerval Zanella (19):
  elf: Remove /etc/suid-debug support
  elf: Add GLIBC_TUNABLES to unsecvars
  elf: Ignore GLIBC_TUNABLES for setuid/setgid binaries
  elf: Add all malloc tunable to unsecvars
  elf: Do not process invalid tunable format
  elf: Do not parse ill-formatted strings
  elf: Fix _dl_debug_vdprintf to work before self-relocation
  elf: Emit warning if tunable is ill-formatted
  x86: Use dl-symbol-redir-ifunc.h on cpu-tunables
  s390: Use dl-symbol-redir-ifunc.h on cpu-tunables
  elf: Do not duplicate the GLIBC_TUNABLES string
  elf: Ignore LD_PROFILE for setuid binaries
  elf: Remove LD_PROFILE for static binaries
  elf: Ignore loader debug env vars for setuid
  elf: Remove any_debug from dl_main_state
  elf: Ignore LD_LIBRARY_PATH and debug env var for setuid for static
  elf: Add comments on how LD_AUDIT and LD_PRELOAD handle
    __libc_enable_secure
  elf: Ignore LD_BIND_NOW and LD_BIND_NOT for setuid binaries
  elf: Refactor process_envvars

 elf/Makefile                                  |  26 +-
 elf/dl-load.c                                 |  10 +-
 elf/dl-main.h                                 |   3 -
 elf/dl-printf.c                               |  16 +-
 elf/dl-runtime.c                              |  12 +-
 elf/dl-support.c                              |  41 +--
 elf/dl-tunable-types.h                        |  10 -
 elf/dl-tunables.c                             | 219 ++++--------
 elf/dl-tunables.h                             |   6 +-
 elf/dl-tunables.list                          |  17 -
 elf/{dl-profstub.c => libc-dl-profstub.c}     |   0
 elf/rtld.c                                    | 122 +++++--
 elf/tst-env-setuid-static.c                   |   1 +
 elf/tst-env-setuid-tunables.c                 |  59 ++--
 elf/tst-env-setuid.c                          | 140 +++++---
 elf/tst-tunables.c                            | 321 ++++++++++++++++++
 include/dlfcn.h                               |   5 +
 manual/README.tunables                        |   9 -
 manual/memory.texi                            |   4 +-
 manual/tunables.texi                          |   4 +-
 scripts/gen-tunables.awk                      |  18 +-
 stdio-common/Makefile                         |   5 +
 stdio-common/_itoa.c                          |   5 +
 sysdeps/aarch64/dl-machine.h                  |   4 +-
 sysdeps/aarch64/dl-trampoline.S               |   2 +-
 sysdeps/alpha/dl-machine.h                    |   6 +-
 sysdeps/alpha/dl-trampoline.S                 |   4 +
 sysdeps/arm/dl-machine.h                      |   4 +-
 sysdeps/arm/dl-trampoline.S                   |   2 +-
 sysdeps/generic/dl-tunables-parse.h           | 129 +++++++
 sysdeps/generic/unsecvars.h                   |  10 +
 sysdeps/hppa/dl-machine.h                     |  36 +-
 sysdeps/hppa/dl-trampoline.S                  |   2 +
 sysdeps/i386/dl-machine.h                     |   2 +
 sysdeps/i386/dl-trampoline.S                  |   2 +-
 .../i686/multiarch/dl-symbol-redir-ifunc.h    |   5 +
 sysdeps/ia64/dl-machine.h                     |  10 +-
 sysdeps/ia64/dl-trampoline.S                  |   2 +-
 sysdeps/loongarch/dl-machine.h                |   6 +-
 sysdeps/loongarch/dl-trampoline.h             |   2 +
 sysdeps/m68k/dl-machine.h                     |   4 +-
 sysdeps/m68k/dl-trampoline.S                  |   2 +
 sysdeps/powerpc/powerpc32/dl-machine.c        |   2 +-
 sysdeps/powerpc/powerpc32/dl-machine.h        |  10 +-
 sysdeps/powerpc/powerpc32/dl-trampoline.S     |   2 +-
 sysdeps/powerpc/powerpc64/dl-machine.h        |  20 +-
 sysdeps/powerpc/powerpc64/dl-trampoline.S     |   2 +-
 sysdeps/s390/cpu-features.c                   | 169 ++++-----
 .../s390/multiarch/dl-symbol-redir-ifunc.h    |   2 +
 sysdeps/s390/s390-32/dl-machine.h             |   8 +-
 sysdeps/s390/s390-32/dl-trampoline.h          |   2 +-
 sysdeps/s390/s390-64/dl-machine.h             |   8 +-
 sysdeps/s390/s390-64/dl-trampoline.h          |   2 +-
 sysdeps/sh/dl-machine.h                       |   2 +
 sysdeps/sh/dl-trampoline.S                    |   2 +
 sysdeps/sparc/sparc32/dl-machine.h            |   4 +-
 sysdeps/sparc/sparc32/dl-trampoline.S         |   2 +
 sysdeps/sparc/sparc64/dl-machine.h            |   4 +-
 sysdeps/sparc/sparc64/dl-trampoline.S         |   2 +
 .../unix/sysv/linux/aarch64/cpu-features.c    |  38 ++-
 .../sysv/linux/i386/dl-writev.h}              |  18 +-
 .../unix/sysv/linux/powerpc/cpu-features.c    |  45 +--
 .../sysv/linux/powerpc/tst-hwcap-tunables.c   |   6 +-
 sysdeps/x86/Makefile                          |   4 +-
 sysdeps/x86/cpu-tunables.c                    | 135 +++-----
 sysdeps/x86/tst-hwcap-tunables.c              | 148 ++++++++
 sysdeps/x86_64/64/dl-tunables.list            |   1 -
 sysdeps/x86_64/dl-machine.h                   |   2 +
 sysdeps/x86_64/dl-trampoline.S                |  64 ++--
 .../x86_64/multiarch/dl-symbol-redir-ifunc.h  |  15 +
 70 files changed, 1281 insertions(+), 725 deletions(-)
 rename elf/{dl-profstub.c => libc-dl-profstub.c} (100%)
 create mode 100644 elf/tst-env-setuid-static.c
 create mode 100644 elf/tst-tunables.c
 create mode 100644 sysdeps/generic/dl-tunables-parse.h
 rename sysdeps/{x86_64/memcmp-isa-default-impl.h => unix/sysv/linux/i386/dl-writev.h} (62%)
 create mode 100644 sysdeps/x86/tst-hwcap-tunables.c

-- 
2.34.1


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v3 01/19] elf: Remove /etc/suid-debug support
  2023-11-06 20:25 [PATCH v3 00/19] Improve loader environment variable handling Adhemerval Zanella
@ 2023-11-06 20:25 ` Adhemerval Zanella
  2023-11-06 20:25 ` [PATCH v3 02/19] elf: Add GLIBC_TUNABLES to unsecvars Adhemerval Zanella
                   ` (18 subsequent siblings)
  19 siblings, 0 replies; 40+ messages in thread
From: Adhemerval Zanella @ 2023-11-06 20:25 UTC (permalink / raw)
  To: libc-alpha, Siddhesh Poyarekar

Since malloc debug support moved to a different library
(libc_malloc_debug.so), the glibc.malloc.check requires preloading the
debug library to enable it.  It means that suid-debug support has not
been working since 2.34.

To restore its support, it would require to add additional information
and parsing to where to find libc_malloc_debug.so.

It is one thing less that might change AT_SECURE binaries' behavior
due to environment configurations.

Checked on x86_64-linux-gnu.
Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
---
 elf/dl-tunables.c    | 16 ----------------
 elf/rtld.c           |  3 +--
 manual/memory.texi   |  4 +---
 manual/tunables.texi |  4 +---
 4 files changed, 3 insertions(+), 24 deletions(-)

diff --git a/elf/dl-tunables.c b/elf/dl-tunables.c
index cae67efa0a..24252af22c 100644
--- a/elf/dl-tunables.c
+++ b/elf/dl-tunables.c
@@ -252,20 +252,6 @@ parse_tunables (char *tunestr, char *valstring)
     tunestr[off] = '\0';
 }
 
-/* Enable the glibc.malloc.check tunable in SETUID/SETGID programs only when
-   the system administrator has created the /etc/suid-debug file.  This is a
-   special case where we want to conditionally enable/disable a tunable even
-   for setuid binaries.  We use the special version of access() to avoid
-   setting ERRNO, which is a TLS variable since TLS has not yet been set
-   up.  */
-static __always_inline void
-maybe_enable_malloc_check (void)
-{
-  tunable_id_t id = TUNABLE_ENUM_NAME (glibc, malloc, check);
-  if (__libc_enable_secure && __access_noerrno ("/etc/suid-debug", F_OK) == 0)
-    tunable_list[id].security_level = TUNABLE_SECLEVEL_NONE;
-}
-
 /* Initialize the tunables list from the environment.  For now we only use the
    ENV_ALIAS to find values.  Later we will also use the tunable names to find
    values.  */
@@ -277,8 +263,6 @@ __tunables_init (char **envp)
   size_t len = 0;
   char **prev_envp = envp;
 
-  maybe_enable_malloc_check ();
-
   while ((envp = get_next_env (envp, &envname, &len, &envval,
 			       &prev_envp)) != NULL)
     {
diff --git a/elf/rtld.c b/elf/rtld.c
index 5107d16fe3..51b6d9f326 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -2670,8 +2670,7 @@ process_envvars (struct dl_main_state *state)
 	}
       while (*nextp != '\0');
 
-      if (__access ("/etc/suid-debug", F_OK) != 0)
-	GLRO(dl_debug_mask) = 0;
+      GLRO(dl_debug_mask) = 0;
 
       if (state->mode != rtld_mode_normal)
 	_exit (5);
diff --git a/manual/memory.texi b/manual/memory.texi
index 5781a64f35..258fdbd3a0 100644
--- a/manual/memory.texi
+++ b/manual/memory.texi
@@ -1379,9 +1379,7 @@ There is one problem with @code{MALLOC_CHECK_}: in SUID or SGID binaries
 it could possibly be exploited since diverging from the normal programs
 behavior it now writes something to the standard error descriptor.
 Therefore the use of @code{MALLOC_CHECK_} is disabled by default for
-SUID and SGID binaries.  It can be enabled again by the system
-administrator by adding a file @file{/etc/suid-debug} (the content is
-not important it could be empty).
+SUID and SGID binaries.
 
 So, what's the difference between using @code{MALLOC_CHECK_} and linking
 with @samp{-lmcheck}?  @code{MALLOC_CHECK_} is orthogonal with respect to
diff --git a/manual/tunables.texi b/manual/tunables.texi
index 776fd93fd9..347b5698b5 100644
--- a/manual/tunables.texi
+++ b/manual/tunables.texi
@@ -136,9 +136,7 @@ termination of the process.
 Like @env{MALLOC_CHECK_}, @code{glibc.malloc.check} has a problem in that it
 diverges from normal program behavior by writing to @code{stderr}, which could
 by exploited in SUID and SGID binaries.  Therefore, @code{glibc.malloc.check}
-is disabled by default for SUID and SGID binaries.  This can be enabled again
-by the system administrator by adding a file @file{/etc/suid-debug}; the
-content of the file could be anything or even empty.
+is disabled by default for SUID and SGID binaries.
 @end deftp
 
 @deftp Tunable glibc.malloc.top_pad
-- 
2.34.1


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v3 02/19] elf: Add GLIBC_TUNABLES to unsecvars
  2023-11-06 20:25 [PATCH v3 00/19] Improve loader environment variable handling Adhemerval Zanella
  2023-11-06 20:25 ` [PATCH v3 01/19] elf: Remove /etc/suid-debug support Adhemerval Zanella
@ 2023-11-06 20:25 ` Adhemerval Zanella
  2023-11-06 20:25 ` [PATCH v3 03/19] elf: Ignore GLIBC_TUNABLES for setuid/setgid binaries Adhemerval Zanella
                   ` (17 subsequent siblings)
  19 siblings, 0 replies; 40+ messages in thread
From: Adhemerval Zanella @ 2023-11-06 20:25 UTC (permalink / raw)
  To: libc-alpha, Siddhesh Poyarekar; +Cc: Florian Weimer

setuid/setgid process now ignores any glibc tunables, and filters out
all environment variables that might changes its behavior. This patch
also adds GLIBC_TUNABLES, so any spawned process by setuid/setgid
processes should set tunable explicitly.

Checked on x86_64-linux-gnu.

Reviewed-by: Florian Weimer <fweimer@redhat.com>
Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
---
 elf/tst-env-setuid-tunables.c | 32 ++++----------------------------
 sysdeps/generic/unsecvars.h   |  1 +
 2 files changed, 5 insertions(+), 28 deletions(-)

diff --git a/elf/tst-env-setuid-tunables.c b/elf/tst-env-setuid-tunables.c
index f0b92c97e7..2603007b7b 100644
--- a/elf/tst-env-setuid-tunables.c
+++ b/elf/tst-env-setuid-tunables.c
@@ -60,45 +60,21 @@ const char *teststrings[] =
   "glibc.not_valid.check=2",
 };
 
-const char *resultstrings[] =
-{
-  "glibc.malloc.mmap_threshold=4096",
-  "glibc.malloc.mmap_threshold=4096",
-  "glibc.malloc.mmap_threshold=4096",
-  "glibc.malloc.perturb=0x800",
-  "glibc.malloc.perturb=0x800:glibc.malloc.mmap_threshold=4096",
-  "glibc.malloc.perturb=0x800:glibc.malloc.mmap_threshold=4096",
-  "glibc.malloc.mmap_threshold=4096",
-  "glibc.malloc.mmap_threshold=4096",
-  "glibc.malloc.mmap_threshold=glibc.malloc.mmap_threshold=4096",
-  "",
-  "",
-  "",
-  "",
-  "",
-  "",
-  "",
-};
-
 static int
 test_child (int off)
 {
   const char *val = getenv ("GLIBC_TUNABLES");
+  int ret = 1;
 
   printf ("    [%d] GLIBC_TUNABLES is %s\n", off, val);
   fflush (stdout);
-  if (val != NULL && strcmp (val, resultstrings[off]) == 0)
-    return 0;
-
   if (val != NULL)
-    printf ("    [%d] Unexpected GLIBC_TUNABLES VALUE %s, expected %s\n",
-	    off, val, resultstrings[off]);
+    printf ("    [%d] Unexpected GLIBC_TUNABLES VALUE %s\n", off, val);
   else
-    printf ("    [%d] GLIBC_TUNABLES environment variable absent\n", off);
-
+    ret = 0;
   fflush (stdout);
 
-  return 1;
+  return ret;
 }
 
 static int
diff --git a/sysdeps/generic/unsecvars.h b/sysdeps/generic/unsecvars.h
index 8278c50a84..81397fb90b 100644
--- a/sysdeps/generic/unsecvars.h
+++ b/sysdeps/generic/unsecvars.h
@@ -4,6 +4,7 @@
 #define UNSECURE_ENVVARS \
   "GCONV_PATH\0"							      \
   "GETCONF_DIR\0"							      \
+  "GLIBC_TUNABLES\0"							      \
   "HOSTALIASES\0"							      \
   "LD_AUDIT\0"								      \
   "LD_DEBUG\0"								      \
-- 
2.34.1


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v3 03/19] elf: Ignore GLIBC_TUNABLES for setuid/setgid binaries
  2023-11-06 20:25 [PATCH v3 00/19] Improve loader environment variable handling Adhemerval Zanella
  2023-11-06 20:25 ` [PATCH v3 01/19] elf: Remove /etc/suid-debug support Adhemerval Zanella
  2023-11-06 20:25 ` [PATCH v3 02/19] elf: Add GLIBC_TUNABLES to unsecvars Adhemerval Zanella
@ 2023-11-06 20:25 ` Adhemerval Zanella
  2023-11-06 20:25 ` [PATCH v3 04/19] elf: Add all malloc tunable to unsecvars Adhemerval Zanella
                   ` (16 subsequent siblings)
  19 siblings, 0 replies; 40+ messages in thread
From: Adhemerval Zanella @ 2023-11-06 20:25 UTC (permalink / raw)
  To: libc-alpha, Siddhesh Poyarekar; +Cc: DJ Delorie

The tunable privilege levels were a retrofit to try and keep the malloc
tunable environment variables' behavior unchanged across security
boundaries.  However, CVE-2023-4911 shows how tricky can be
tunable parsing in a security-sensitive environment.

Not only parsing, but the malloc tunable essentially changes some
semantics on setuid/setgid processes.  Although it is not a direct
security issue, allowing users to change setuid/setgid semantics is not
a good security practice, and requires extra code and analysis to check
if each tunable is safe to use on all security boundaries.

It also means that security opt-in features, like aarch64 MTE, would
need to be explicit enabled by an administrator with a wrapper script
or with a possible future system-wide tunable setting.

Co-authored-by: Siddhesh Poyarekar  <siddhesh@sourceware.org>
Reviewed-by: DJ Delorie <dj@redhat.com>
---
 elf/Makefile                       |   5 +-
 elf/dl-tunable-types.h             |  10 --
 elf/dl-tunables.c                  | 127 ++++-----------
 elf/dl-tunables.list               |  17 --
 elf/tst-env-setuid-tunables.c      |  29 +++-
 elf/tst-tunables.c                 | 244 +++++++++++++++++++++++++++++
 manual/README.tunables             |   9 --
 scripts/gen-tunables.awk           |  18 +--
 sysdeps/x86_64/64/dl-tunables.list |   1 -
 9 files changed, 299 insertions(+), 161 deletions(-)
 create mode 100644 elf/tst-tunables.c

diff --git a/elf/Makefile b/elf/Makefile
index b46dee1c16..5f24a00a92 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -263,7 +263,6 @@ tests-static-normal := \
   tst-dl-iter-static \
   tst-dst-static \
   tst-env-setuid \
-  tst-env-setuid-tunables \
   tst-getauxval-static \
   tst-linkall-static \
   tst-single_threaded-pthread-static \
@@ -276,10 +275,12 @@ tests-static-normal := \
 tests-static-internal := \
   tst-dl-printf-static \
   tst-dl_find_object-static \
+  tst-env-setuid-tunables \
   tst-ptrguard1-static \
   tst-stackguard1-static \
   tst-tls1-static \
   tst-tls1-static-non-pie \
+  tst-tunables \
   # tests-static-internal
 
 CRT-tst-tls1-static-non-pie := $(csu-objpfx)crt1.o
@@ -2658,6 +2659,8 @@ $(objpfx)tst-glibc-hwcaps-mask.out: \
 # tst-glibc-hwcaps-cache.
 $(objpfx)tst-glibc-hwcaps-cache.out: $(objpfx)tst-glibc-hwcaps
 
+tst-tunables-ARGS = -- $(host-test-program-cmd)
+
 $(objpfx)list-tunables.out: tst-rtld-list-tunables.sh $(objpfx)ld.so
 	$(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
 	    '$(run_program_env)' > $(objpfx)/tst-rtld-list-tunables.out
diff --git a/elf/dl-tunable-types.h b/elf/dl-tunable-types.h
index c88332657e..62d6d9e629 100644
--- a/elf/dl-tunable-types.h
+++ b/elf/dl-tunable-types.h
@@ -64,16 +64,6 @@ struct _tunable
   tunable_val_t val;			/* The value.  */
   bool initialized;			/* Flag to indicate that the tunable is
 					   initialized.  */
-  tunable_seclevel_t security_level;	/* Specify the security level for the
-					   tunable with respect to AT_SECURE
-					   programs.  See description of
-					   tunable_seclevel_t to see a
-					   description of the values.
-
-					   Note that even if the tunable is
-					   read, it may not get used by the
-					   target module if the value is
-					   considered unsafe.  */
   /* Compatibility elements.  */
   const char env_alias[TUNABLE_ALIAS_MAX]; /* The compatibility environment
 					   variable name.  */
diff --git a/elf/dl-tunables.c b/elf/dl-tunables.c
index 24252af22c..f7dca8f7c1 100644
--- a/elf/dl-tunables.c
+++ b/elf/dl-tunables.c
@@ -154,50 +154,51 @@ __tunable_set_val (tunable_id_t id, tunable_val_t *valp, tunable_num_t *minp,
   do_tunable_update_val (cur, valp, minp, maxp);
 }
 
-/* Parse the tunable string TUNESTR and adjust it to drop any tunables that may
-   be unsafe for AT_SECURE processes so that it can be used as the new
-   environment variable value for GLIBC_TUNABLES.  VALSTRING is the original
-   environment variable string which we use to make NULL terminated values so
-   that we don't have to allocate memory again for it.  */
+/* Parse the tunable string VALSTRING.  VALSTRING is a duplicated value,
+   where delimiters ':' are replaced with '\0', so string tunables are null
+   terminated.  */
 static void
-parse_tunables (char *tunestr, char *valstring)
+parse_tunables (char *valstring)
 {
-  if (tunestr == NULL || *tunestr == '\0')
+  if (valstring == NULL || *valstring == '\0')
     return;
 
-  char *p = tunestr;
-  size_t off = 0;
+  char *p = valstring;
+  bool done = false;
 
-  while (true)
+  while (!done)
     {
       char *name = p;
-      size_t len = 0;
 
       /* First, find where the name ends.  */
-      while (p[len] != '=' && p[len] != ':' && p[len] != '\0')
-	len++;
+      while (*p != '=' && *p != ':' && *p != '\0')
+	p++;
 
       /* If we reach the end of the string before getting a valid name-value
 	 pair, bail out.  */
-      if (p[len] == '\0')
+      if (*p == '\0')
 	break;
 
       /* We did not find a valid name-value pair before encountering the
 	 colon.  */
-      if (p[len]== ':')
+      if (*p == ':')
 	{
-	  p += len + 1;
+	  p++;
 	  continue;
 	}
 
-      p += len + 1;
+      /* Skip the '='.  */
+      p++;
 
-      /* Take the value from the valstring since we need to NULL terminate it.  */
-      char *value = &valstring[p - tunestr];
-      len = 0;
+      const char *value = p;
 
-      while (p[len] != ':' && p[len] != '\0')
-	len++;
+      while (*p != ':' && *p != '\0')
+	p++;
+
+      if (*p == '\0')
+	done = true;
+      else
+	*p++ = '\0';
 
       /* Add the tunable if it exists.  */
       for (size_t i = 0; i < sizeof (tunable_list) / sizeof (tunable_t); i++)
@@ -206,50 +207,11 @@ parse_tunables (char *tunestr, char *valstring)
 
 	  if (tunable_is_name (cur->name, name))
 	    {
-	      /* If we are in a secure context (AT_SECURE) then ignore the
-		 tunable unless it is explicitly marked as secure.  Tunable
-		 values take precedence over their envvar aliases.  We write
-		 the tunables that are not SXID_ERASE back to TUNESTR, thus
-		 dropping all SXID_ERASE tunables and any invalid or
-		 unrecognized tunables.  */
-	      if (__libc_enable_secure)
-		{
-		  if (cur->security_level != TUNABLE_SECLEVEL_SXID_ERASE)
-		    {
-		      if (off > 0)
-			tunestr[off++] = ':';
-
-		      const char *n = cur->name;
-
-		      while (*n != '\0')
-			tunestr[off++] = *n++;
-
-		      tunestr[off++] = '=';
-
-		      for (size_t j = 0; j < len; j++)
-			tunestr[off++] = value[j];
-		    }
-
-		  if (cur->security_level != TUNABLE_SECLEVEL_NONE)
-		    break;
-		}
-
-	      value[len] = '\0';
 	      tunable_initialize (cur, value);
 	      break;
 	    }
 	}
-
-      /* We reached the end while processing the tunable string.  */
-      if (p[len] == '\0')
-	break;
-
-      p += len + 1;
     }
-
-  /* Terminate tunestr before we leave.  */
-  if (__libc_enable_secure)
-    tunestr[off] = '\0';
 }
 
 /* Initialize the tunables list from the environment.  For now we only use the
@@ -263,16 +225,16 @@ __tunables_init (char **envp)
   size_t len = 0;
   char **prev_envp = envp;
 
+  /* Ignore tunables for AT_SECURE programs.  */
+  if (__libc_enable_secure)
+    return;
+
   while ((envp = get_next_env (envp, &envname, &len, &envval,
 			       &prev_envp)) != NULL)
     {
       if (tunable_is_name ("GLIBC_TUNABLES", envname))
 	{
-	  char *new_env = tunables_strdup (envname);
-	  if (new_env != NULL)
-	    parse_tunables (new_env + len + 1, envval);
-	  /* Put in the updated envval.  */
-	  *prev_envp = new_env;
+	  parse_tunables (tunables_strdup (envval));
 	  continue;
 	}
 
@@ -290,39 +252,6 @@ __tunables_init (char **envp)
 	  /* We have a match.  Initialize and move on to the next line.  */
 	  if (tunable_is_name (name, envname))
 	    {
-	      /* For AT_SECURE binaries, we need to check the security settings of
-		 the tunable and decide whether we read the value and also whether
-		 we erase the value so that child processes don't inherit them in
-		 the environment.  */
-	      if (__libc_enable_secure)
-		{
-		  if (cur->security_level == TUNABLE_SECLEVEL_SXID_ERASE)
-		    {
-		      /* Erase the environment variable.  */
-		      char **ep = prev_envp;
-
-		      while (*ep != NULL)
-			{
-			  if (tunable_is_name (name, *ep))
-			    {
-			      char **dp = ep;
-
-			      do
-				dp[0] = dp[1];
-			      while (*dp++);
-			    }
-			  else
-			    ++ep;
-			}
-		      /* Reset the iterator so that we read the environment again
-			 from the point we erased.  */
-		      envp = prev_envp;
-		    }
-
-		  if (cur->security_level != TUNABLE_SECLEVEL_NONE)
-		    continue;
-		}
-
 	      tunable_initialize (cur, envval);
 	      break;
 	    }
diff --git a/elf/dl-tunables.list b/elf/dl-tunables.list
index 695ba7192e..fe26c76e1a 100644
--- a/elf/dl-tunables.list
+++ b/elf/dl-tunables.list
@@ -21,14 +21,6 @@
 # minval: Optional minimum acceptable value
 # maxval: Optional maximum acceptable value
 # env_alias: An alias environment variable
-# security_level: Specify security level of the tunable for AT_SECURE binaries.
-# 		  Valid values are:
-#
-# 	     SXID_ERASE: (default) Do not read and do not pass on to
-# 	     child processes.
-# 	     SXID_IGNORE: Do not read, but retain for non-AT_SECURE
-# 	     subprocesses.
-# 	     NONE: Read all the time.
 
 glibc {
   malloc {
@@ -41,7 +33,6 @@ glibc {
     top_pad {
       type: SIZE_T
       env_alias: MALLOC_TOP_PAD_
-      security_level: SXID_IGNORE
       default: 131072
     }
     perturb {
@@ -49,35 +40,29 @@ glibc {
       minval: 0
       maxval: 0xff
       env_alias: MALLOC_PERTURB_
-      security_level: SXID_IGNORE
     }
     mmap_threshold {
       type: SIZE_T
       env_alias: MALLOC_MMAP_THRESHOLD_
-      security_level: SXID_IGNORE
     }
     trim_threshold {
       type: SIZE_T
       env_alias: MALLOC_TRIM_THRESHOLD_
-      security_level: SXID_IGNORE
     }
     mmap_max {
       type: INT_32
       env_alias: MALLOC_MMAP_MAX_
-      security_level: SXID_IGNORE
       minval: 0
     }
     arena_max {
       type: SIZE_T
       env_alias: MALLOC_ARENA_MAX
       minval: 1
-      security_level: SXID_IGNORE
     }
     arena_test {
       type: SIZE_T
       env_alias: MALLOC_ARENA_TEST
       minval: 1
-      security_level: SXID_IGNORE
     }
     tcache_max {
       type: SIZE_T
@@ -91,7 +76,6 @@ glibc {
     mxfast {
       type: SIZE_T
       minval: 0
-      security_level: SXID_IGNORE
     }
     hugetlb {
       type: SIZE_T
@@ -158,7 +142,6 @@ glibc {
       type: INT_32
       minval: 0
       maxval: 255
-      security_level: SXID_IGNORE
     }
   }
 
diff --git a/elf/tst-env-setuid-tunables.c b/elf/tst-env-setuid-tunables.c
index 2603007b7b..ca02dbba58 100644
--- a/elf/tst-env-setuid-tunables.c
+++ b/elf/tst-env-setuid-tunables.c
@@ -15,14 +15,10 @@
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
-/* Verify that tunables correctly filter out unsafe tunables like
-   glibc.malloc.check and glibc.malloc.mmap_threshold but also retain
-   glibc.malloc.mmap_threshold in an unprivileged child.  */
-
-#define _LIBC 1
-#include "config.h"
-#undef _LIBC
+/* Verify that GLIBC_TUNABLES is kept unchanged but no tunable is actually
+   enabled for AT_SECURE processes.  */
 
+#include <dl-tunables.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <stdlib.h>
@@ -40,7 +36,7 @@
 #include <support/test-driver.h>
 #include <support/capture_subprocess.h>
 
-const char *teststrings[] =
+static const char *teststrings[] =
 {
   "glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096",
   "glibc.malloc.check=2:glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096",
@@ -74,6 +70,23 @@ test_child (int off)
     ret = 0;
   fflush (stdout);
 
+  /* Also check if the set tunables are effectively unchanged.  */
+  int32_t check = TUNABLE_GET_FULL (glibc, malloc, check, int32_t, NULL);
+  size_t mmap_threshold = TUNABLE_GET_FULL (glibc, malloc, mmap_threshold,
+					    size_t, NULL);
+  int32_t perturb = TUNABLE_GET_FULL (glibc, malloc, perturb, int32_t, NULL);
+
+  printf ("    [%d] glibc.malloc.check=%d\n", off, check);
+  fflush (stdout);
+  printf ("    [%d] glibc.malloc.mmap_threshold=%zu\n", off, mmap_threshold);
+  fflush (stdout);
+  printf ("    [%d] glibc.malloc.perturb=%d\n", off, perturb);
+  fflush (stdout);
+
+  ret |= check != 0;
+  ret |= mmap_threshold != 0;
+  ret |= perturb != 0;
+
   return ret;
 }
 
diff --git a/elf/tst-tunables.c b/elf/tst-tunables.c
new file mode 100644
index 0000000000..d874b73b68
--- /dev/null
+++ b/elf/tst-tunables.c
@@ -0,0 +1,244 @@
+/* Check GLIBC_TUNABLES parsing.
+   Copyright (C) 2023 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 <dl-tunables.h>
+#include <getopt.h>
+#include <intprops.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <support/capture_subprocess.h>
+#include <support/check.h>
+
+static int restart;
+#define CMDLINE_OPTIONS \
+  { "restart", no_argument, &restart, 1 },
+
+static const struct test_t
+{
+  const char *env;
+  int32_t expected_malloc_check;
+  size_t expected_mmap_threshold;
+  int32_t expected_perturb;
+} tests[] =
+{
+  /* Expected tunable format.  */
+  {
+    "glibc.malloc.check=2",
+    2,
+    0,
+    0,
+  },
+  {
+    "glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096",
+    2,
+    4096,
+    0,
+  },
+  /* Empty tunable are ignored.  */
+  {
+    "glibc.malloc.check=2::glibc.malloc.mmap_threshold=4096",
+    2,
+    4096,
+    0,
+  },
+  /* As well empty values.  */
+  {
+    "glibc.malloc.check=:glibc.malloc.mmap_threshold=4096",
+    0,
+    4096,
+    0,
+  },
+  /* Tunable are processed from left to right, so last one is the one set.  */
+  {
+    "glibc.malloc.check=1:glibc.malloc.check=2",
+    2,
+    0,
+    0,
+  },
+  {
+    "glibc.malloc.check=1:glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096",
+    2,
+    4096,
+    0,
+  },
+  {
+    "glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096:glibc.malloc.check=1",
+    1,
+    4096,
+    0,
+  },
+  /* 0x800 is larger than tunable maxval (0xff), so the tunable is unchanged.  */
+  {
+    "glibc.malloc.perturb=0x800",
+    0,
+    0,
+    0,
+  },
+  {
+    "glibc.malloc.perturb=0x55",
+    0,
+    0,
+    0x55,
+  },
+  /* Out of range values are just ignored.  */
+  {
+    "glibc.malloc.perturb=0x800:glibc.malloc.mmap_threshold=4096",
+    0,
+    4096,
+    0,
+  },
+  /* Invalid keys are ignored.  */
+  {
+    ":glibc.malloc.garbage=2:glibc.malloc.check=1",
+    1,
+    0,
+    0,
+  },
+  {
+    "glibc.malloc.perturb=0x800:not_valid.malloc.check=2:glibc.malloc.mmap_threshold=4096",
+    0,
+    4096,
+    0,
+  },
+  {
+    "glibc.not_valid.check=2:glibc.malloc.mmap_threshold=4096",
+    0,
+    4096,
+    0,
+  },
+  {
+    "not_valid.malloc.check=2:glibc.malloc.mmap_threshold=4096",
+    0,
+    4096,
+    0,
+  },
+  /* Invalid subkeys are ignored.  */
+  {
+    "glibc.malloc.garbage=2:glibc.maoc.mmap_threshold=4096:glibc.malloc.check=2",
+    2,
+    0,
+    0,
+  },
+  {
+    "glibc.malloc.check=4:glibc.malloc.garbage=2:glibc.maoc.mmap_threshold=4096",
+    0,
+    0,
+    0,
+  },
+  {
+    "not_valid.malloc.check=2",
+    0,
+    0,
+    0,
+  },
+  {
+    "glibc.not_valid.check=2",
+    0,
+    0,
+    0,
+  },
+  /* An ill-formatted tunable in the for key=key=value will considere the
+     value as 'key=value' (which can not be parsed as an integer).  */
+  {
+    "glibc.malloc.mmap_threshold=glibc.malloc.mmap_threshold=4096",
+    0,
+    0,
+    0,
+  },
+  /* The ill-formatted tunable is also skipped.  */
+  {
+    "glibc.malloc.mmap_threshold=glibc.malloc.mmap_threshold=4096:glibc.malloc.check=2",
+    2,
+    0,
+    0,
+  },
+  /* For an integer tunable, parse will stop on non number character.  */
+  {
+    "glibc.malloc.check=2=2",
+    2,
+    0,
+    0,
+  },
+  {
+    "glibc.malloc.check=2=2:glibc.malloc.mmap_threshold=4096",
+    2,
+    4096,
+    0,
+  }
+};
+
+static int
+handle_restart (int i)
+{
+  TEST_COMPARE (tests[i].expected_malloc_check,
+		TUNABLE_GET_FULL (glibc, malloc, check, int32_t, NULL));
+  TEST_COMPARE (tests[i].expected_mmap_threshold,
+		TUNABLE_GET_FULL (glibc, malloc, mmap_threshold, size_t, NULL));
+  TEST_COMPARE (tests[i].expected_perturb,
+		TUNABLE_GET_FULL (glibc, malloc, perturb, int32_t, NULL));
+  return 0;
+}
+
+static int
+do_test (int argc, char *argv[])
+{
+  /* We must have either:
+     - One our fource parameters left if called initially:
+       + path to ld.so         optional
+       + "--library-path"      optional
+       + the library path      optional
+       + the application name
+       + the test to check  */
+
+  TEST_VERIFY_EXIT (argc == 2 || argc == 5);
+
+  if (restart)
+    return handle_restart (atoi (argv[1]));
+
+  char nteststr[INT_BUFSIZE_BOUND (int)];
+
+  char *spargv[10];
+  {
+    int i = 0;
+    for (; i < argc - 1; i++)
+      spargv[i] = argv[i + 1];
+    spargv[i++] = (char *) "--direct";
+    spargv[i++] = (char *) "--restart";
+    spargv[i++] = nteststr;
+    spargv[i] = NULL;
+  }
+
+  for (int i = 0; i < array_length (tests); i++)
+    {
+      snprintf (nteststr, sizeof nteststr, "%d", i);
+
+      printf ("[%d] Spawned test for %s\n", i, tests[i].env);
+      setenv ("GLIBC_TUNABLES", tests[i].env, 1);
+      struct support_capture_subprocess result
+	= support_capture_subprogram (spargv[0], spargv);
+      support_capture_subprocess_check (&result, "tst-tunables", 0,
+					sc_allow_stderr);
+      support_capture_subprocess_free (&result);
+    }
+
+  return 0;
+}
+
+#define TEST_FUNCTION_ARGV do_test
+#include <support/test-driver.c>
diff --git a/manual/README.tunables b/manual/README.tunables
index 605ddd78cd..72ae00dc02 100644
--- a/manual/README.tunables
+++ b/manual/README.tunables
@@ -59,15 +59,6 @@ The list of allowed attributes are:
 
 - env_alias:		An alias environment variable
 
-- security_level:	Specify security level of the tunable for AT_SECURE
-			binaries.  Valid values are:
-
-			SXID_ERASE: (default) Do not read and do not pass on to
-			child processes.
-			SXID_IGNORE: Do not read, but retain for non-AT_SECURE
-			child processes.
-			NONE: Read all the time.
-
 2. Use TUNABLE_GET/TUNABLE_SET/TUNABLE_SET_WITH_BOUNDS to get and set tunables.
 
 3. OPTIONAL: If tunables in a namespace are being used multiple times within a
diff --git a/scripts/gen-tunables.awk b/scripts/gen-tunables.awk
index d6de100df0..1e9d6b534e 100644
--- a/scripts/gen-tunables.awk
+++ b/scripts/gen-tunables.awk
@@ -61,9 +61,6 @@ $1 == "}" {
     if (!env_alias[top_ns,ns,tunable]) {
       env_alias[top_ns,ns,tunable] = "{0}"
     }
-    if (!security_level[top_ns,ns,tunable]) {
-      security_level[top_ns,ns,tunable] = "SXID_ERASE"
-    }
     len = length(top_ns"."ns"."tunable)
     if (len > max_name_len)
       max_name_len = len
@@ -118,17 +115,6 @@ $1 == "}" {
     if (len > max_alias_len)
       max_alias_len = len
   }
-  else if (attr == "security_level") {
-    if (val == "SXID_ERASE" || val == "SXID_IGNORE" || val == "NONE") {
-      security_level[top_ns,ns,tunable] = val
-    }
-    else {
-      printf("Line %d: Invalid value (%s) for security_level: %s, ", NR, val,
-	     $0)
-      print("Allowed values are 'SXID_ERASE', 'SXID_IGNORE', or 'NONE'")
-      exit 1
-    }
-  }
   else if (attr == "default") {
     if (types[top_ns,ns,tunable] == "STRING") {
       default_val[top_ns,ns,tunable] = sprintf(".strval = \"%s\"", val);
@@ -177,9 +163,9 @@ END {
     n = indices[2];
     m = indices[3];
     printf ("  {TUNABLE_NAME_S(%s, %s, %s)", t, n, m)
-    printf (", {TUNABLE_TYPE_%s, %s, %s}, {%s}, false, TUNABLE_SECLEVEL_%s, %s},\n",
+    printf (", {TUNABLE_TYPE_%s, %s, %s}, {%s}, false, %s},\n",
 	    types[t,n,m], minvals[t,n,m], maxvals[t,n,m],
-	    default_val[t,n,m], security_level[t,n,m], env_alias[t,n,m]);
+	    default_val[t,n,m], env_alias[t,n,m]);
   }
   print "};"
   print "#endif"
diff --git a/sysdeps/x86_64/64/dl-tunables.list b/sysdeps/x86_64/64/dl-tunables.list
index 0aab52e662..54a216a461 100644
--- a/sysdeps/x86_64/64/dl-tunables.list
+++ b/sysdeps/x86_64/64/dl-tunables.list
@@ -23,7 +23,6 @@ glibc {
       minval: 0
       maxval: 1
       env_alias: LD_PREFER_MAP_32BIT_EXEC
-      security_level: SXID_IGNORE
     }
   }
 }
-- 
2.34.1


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v3 04/19] elf: Add all malloc tunable to unsecvars
  2023-11-06 20:25 [PATCH v3 00/19] Improve loader environment variable handling Adhemerval Zanella
                   ` (2 preceding siblings ...)
  2023-11-06 20:25 ` [PATCH v3 03/19] elf: Ignore GLIBC_TUNABLES for setuid/setgid binaries Adhemerval Zanella
@ 2023-11-06 20:25 ` Adhemerval Zanella
  2023-11-06 20:25 ` [PATCH v3 05/19] elf: Do not process invalid tunable format Adhemerval Zanella
                   ` (15 subsequent siblings)
  19 siblings, 0 replies; 40+ messages in thread
From: Adhemerval Zanella @ 2023-11-06 20:25 UTC (permalink / raw)
  To: libc-alpha, Siddhesh Poyarekar; +Cc: DJ Delorie

Some environment variables allow alteration of allocator behavior
across setuid boundaries, where a setuid program may ignore the
tunable, but its non-setuid child can read it and adjust the memory
allocator behavior accordingly.

Most library behavior tunings is limited to the current process and does
not bleed in scope; so it is unclear how pratical this misfeature is.
If behavior change across privilege boundaries is desirable, it would be
better done with a wrapper program around the non-setuid child that sets
these envvars, instead of using the setuid process as the messenger.

The patch as fixes tst-env-setuid, where it fail if any unsecvars is
set.  It also adds a dynamic test, although it requires
--enable-hardcoded-path-in-tests so kernel correctly sets the setuid
bit (using the loader command directly would require to set the
setuid bit on the loader itself, which is not a usual deployment).

Co-authored-by: Siddhesh Poyarekar <siddhesh@sourceware.org>

Checked on x86_64-linux-gnu.
Reviewed-by: DJ Delorie <dj@redhat.com>
---
 elf/Makefile                |   8 +--
 elf/tst-env-setuid-static.c |   1 +
 elf/tst-env-setuid.c        | 128 +++++++++++++++++++++---------------
 sysdeps/generic/unsecvars.h |   7 ++
 4 files changed, 86 insertions(+), 58 deletions(-)
 create mode 100644 elf/tst-env-setuid-static.c

diff --git a/elf/Makefile b/elf/Makefile
index 5f24a00a92..52981c19d0 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -262,7 +262,7 @@ tests-static-normal := \
   tst-array5-static \
   tst-dl-iter-static \
   tst-dst-static \
-  tst-env-setuid \
+  tst-env-setuid-static \
   tst-getauxval-static \
   tst-linkall-static \
   tst-single_threaded-pthread-static \
@@ -305,6 +305,7 @@ tests := \
   tst-array5 \
   tst-auxv \
   tst-dl-hash \
+  tst-env-setuid \
   tst-leaks1 \
   tst-stringtable \
   tst-tls9 \
@@ -2429,9 +2430,6 @@ $(objpfx)tst-nodelete-dlclose: $(objpfx)tst-nodelete-dlclose-dso.so
 $(objpfx)tst-nodelete-dlclose.out: $(objpfx)tst-nodelete-dlclose-dso.so \
 				   $(objpfx)tst-nodelete-dlclose-plugin.so
 
-tst-env-setuid-ENV = MALLOC_CHECK_=2 MALLOC_MMAP_THRESHOLD_=4096 \
-		     LD_HWCAP_MASK=0x1
-
 $(objpfx)tst-debug1.out: $(objpfx)tst-debug1mod1.so
 
 $(objpfx)tst-debug1mod1.so: $(objpfx)testobj1.so
@@ -2983,3 +2981,5 @@ LDFLAGS-tst-dlclose-lazy-mod1.so = -Wl,-z,lazy,--no-as-needed
 $(objpfx)tst-dlclose-lazy-mod1.so: $(objpfx)tst-dlclose-lazy-mod2.so
 $(objpfx)tst-dlclose-lazy.out: \
   $(objpfx)tst-dlclose-lazy-mod1.so $(objpfx)tst-dlclose-lazy-mod2.so
+
+tst-env-setuid-ARGS = -- $(host-test-program-cmd)
diff --git a/elf/tst-env-setuid-static.c b/elf/tst-env-setuid-static.c
new file mode 100644
index 0000000000..0d88ae88b9
--- /dev/null
+++ b/elf/tst-env-setuid-static.c
@@ -0,0 +1 @@
+#include "tst-env-setuid.c"
diff --git a/elf/tst-env-setuid.c b/elf/tst-env-setuid.c
index 032ab44be2..ba295a6a14 100644
--- a/elf/tst-env-setuid.c
+++ b/elf/tst-env-setuid.c
@@ -15,18 +15,14 @@
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
-/* Verify that tunables correctly filter out unsafe environment variables like
-   MALLOC_CHECK_ and MALLOC_MMAP_THRESHOLD_ but also retain
-   MALLOC_MMAP_THRESHOLD_ in an unprivileged child.  */
+/* Verify that correctly filter out unsafe environment variables defined
+   in unsecvars.h.  */
 
-#include <errno.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <stdint.h>
+#include <array_length.h>
+#include <gnu/lib-names.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
 #include <unistd.h>
 
 #include <support/check.h>
@@ -36,61 +32,72 @@
 
 static char SETGID_CHILD[] = "setgid-child";
 
-#ifndef test_child
-static int
-test_child (void)
-{
-  if (getenv ("MALLOC_CHECK_") != NULL)
-    {
-      printf ("MALLOC_CHECK_ is still set\n");
-      return 1;
-    }
-
-  if (getenv ("MALLOC_MMAP_THRESHOLD_") == NULL)
-    {
-      printf ("MALLOC_MMAP_THRESHOLD_ lost\n");
-      return 1;
-    }
+#define FILTERED_VALUE   "some-filtered-value"
+#define UNFILTERED_VALUE "some-unfiltered-value"
 
-  if (getenv ("LD_HWCAP_MASK") != NULL)
-    {
-      printf ("LD_HWCAP_MASK still set\n");
-      return 1;
-    }
+struct envvar_t
+{
+  const char *env;
+  const char *value;
+};
 
-  return 0;
-}
-#endif
+/* That is not an extensible list of all filtered out environment
+   variables.  */
+static const struct envvar_t filtered_envvars[] =
+{
+  { "GLIBC_TUNABLES",          FILTERED_VALUE },
+  { "LD_AUDIT",                FILTERED_VALUE },
+  { "LD_HWCAP_MASK",           FILTERED_VALUE },
+  { "LD_LIBRARY_PATH",         FILTERED_VALUE },
+  { "LD_PRELOAD",              FILTERED_VALUE },
+  { "LD_PROFILE",              FILTERED_VALUE },
+  { "MALLOC_ARENA_MAX",        FILTERED_VALUE },
+  { "MALLOC_PERTURB_",         FILTERED_VALUE },
+  { "MALLOC_TRACE",            FILTERED_VALUE },
+  { "MALLOC_TRIM_THRESHOLD_",  FILTERED_VALUE },
+  { "RES_OPTIONS",             FILTERED_VALUE },
+};
+
+static const struct envvar_t unfiltered_envvars[] =
+{
+  { "LD_BIND_NOW",             "0" },
+  { "LD_BIND_NOT",             "1" },
+  /* Non longer supported option.  */
+  { "LD_ASSUME_KERNEL",        UNFILTERED_VALUE },
+};
 
-#ifndef test_parent
 static int
-test_parent (void)
+test_child (void)
 {
-  if (getenv ("MALLOC_CHECK_") == NULL)
-    {
-      printf ("MALLOC_CHECK_ lost\n");
-      return 1;
-    }
+  int ret = 0;
 
-  if (getenv ("MALLOC_MMAP_THRESHOLD_") == NULL)
+  for (const struct envvar_t *e = filtered_envvars;
+       e != array_end (filtered_envvars);
+       e++)
     {
-      printf ("MALLOC_MMAP_THRESHOLD_ lost\n");
-      return 1;
+      const char *env = getenv (e->env);
+      ret |= env != NULL;
     }
 
-  if (getenv ("LD_HWCAP_MASK") == NULL)
+  for (const struct envvar_t *e = unfiltered_envvars;
+       e != array_end (unfiltered_envvars);
+       e++)
     {
-      printf ("LD_HWCAP_MASK lost\n");
-      return 1;
+      const char *env = getenv (e->env);
+      ret |= !(env != NULL && strcmp (env, e->value) == 0);
     }
 
-  return 0;
+  return ret;
 }
-#endif
 
 static int
 do_test (int argc, char **argv)
 {
+  /* For dynamic loader, the test requires --enable-hardcoded-path-in-tests so
+     the kernel sets the AT_SECURE on process initialization.  */
+  if (argc >= 2 && strstr (argv[1], LD_SO) != 0)
+    FAIL_UNSUPPORTED ("dynamic test requires --enable-hardcoded-path-in-tests");
+
   /* Setgid child process.  */
   if (argc == 2 && strcmp (argv[1], SETGID_CHILD) == 0)
     {
@@ -104,20 +111,33 @@ do_test (int argc, char **argv)
       if (ret != 0)
 	exit (1);
 
-      exit (EXIT_SUCCESS);
+      /* Special return code to make sure that the child executed all the way
+	 through.  */
+      exit (42);
     }
   else
     {
-      if (test_parent () != 0)
-	exit (1);
+      for (const struct envvar_t *e = filtered_envvars;
+	   e != array_end (filtered_envvars);
+	   e++)
+	setenv (e->env, e->value, 1);
+
+      for (const struct envvar_t *e = unfiltered_envvars;
+	   e != array_end (unfiltered_envvars);
+	   e++)
+	setenv (e->env, e->value, 1);
 
       int status = support_capture_subprogram_self_sgid (SETGID_CHILD);
 
       if (WEXITSTATUS (status) == EXIT_UNSUPPORTED)
-	return EXIT_UNSUPPORTED;
-
-      if (!WIFEXITED (status))
-	FAIL_EXIT1 ("Unexpected exit status %d from child process\n", status);
+	exit (EXIT_UNSUPPORTED);
+
+      if (WEXITSTATUS (status) != 42)
+	{
+	  printf ("    child failed with status %d\n",
+		  WEXITSTATUS (status));
+	  support_record_failure ();
+	}
 
       return 0;
     }
diff --git a/sysdeps/generic/unsecvars.h b/sysdeps/generic/unsecvars.h
index 81397fb90b..f7ebed60e5 100644
--- a/sysdeps/generic/unsecvars.h
+++ b/sysdeps/generic/unsecvars.h
@@ -18,7 +18,14 @@
   "LD_SHOW_AUXV\0"							      \
   "LOCALDOMAIN\0"							      \
   "LOCPATH\0"								      \
+  "MALLOC_ARENA_MAX\0"							      \
+  "MALLOC_ARENA_TEST\0"							      \
+  "MALLOC_MMAP_MAX_\0"							      \
+  "MALLOC_MMAP_THRESHOLD_\0"						      \
+  "MALLOC_PERTURB_\0"							      \
+  "MALLOC_TOP_PAD_\0"							      \
   "MALLOC_TRACE\0"							      \
+  "MALLOC_TRIM_THRESHOLD_\0"						      \
   "NIS_PATH\0"								      \
   "NLSPATH\0"								      \
   "RESOLV_HOST_CONF\0"							      \
-- 
2.34.1


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v3 05/19] elf: Do not process invalid tunable format
  2023-11-06 20:25 [PATCH v3 00/19] Improve loader environment variable handling Adhemerval Zanella
                   ` (3 preceding siblings ...)
  2023-11-06 20:25 ` [PATCH v3 04/19] elf: Add all malloc tunable to unsecvars Adhemerval Zanella
@ 2023-11-06 20:25 ` Adhemerval Zanella
  2023-11-06 20:25 ` [PATCH v3 06/19] elf: Do not parse ill-formatted strings Adhemerval Zanella
                   ` (14 subsequent siblings)
  19 siblings, 0 replies; 40+ messages in thread
From: Adhemerval Zanella @ 2023-11-06 20:25 UTC (permalink / raw)
  To: libc-alpha, Siddhesh Poyarekar

Tunable definitions with more than one '=' on are parsed and enabled,
and any subsequent '=' are ignored.  It means that tunables in the form
'tunable=tunable=value' or 'tunable=value=value' are handled as
'tunable=value'.  These inputs are likely user input errors, which
should not be accepted.

Checked on x86_64-linux-gnu.
Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
---
 elf/dl-tunables.c  |  6 ++++--
 elf/tst-tunables.c | 22 +++++++++++++++++-----
 2 files changed, 21 insertions(+), 7 deletions(-)

diff --git a/elf/dl-tunables.c b/elf/dl-tunables.c
index f7dca8f7c1..082a76d9c4 100644
--- a/elf/dl-tunables.c
+++ b/elf/dl-tunables.c
@@ -192,10 +192,12 @@ parse_tunables (char *valstring)
 
       const char *value = p;
 
-      while (*p != ':' && *p != '\0')
+      while (*p != '=' && *p != ':' && *p != '\0')
 	p++;
 
-      if (*p == '\0')
+      if (*p == '=')
+	break;
+      else if (*p == '\0')
 	done = true;
       else
 	*p++ = '\0';
diff --git a/elf/tst-tunables.c b/elf/tst-tunables.c
index d874b73b68..7fe9907e05 100644
--- a/elf/tst-tunables.c
+++ b/elf/tst-tunables.c
@@ -161,24 +161,36 @@ static const struct test_t
     0,
     0,
   },
-  /* The ill-formatted tunable is also skipped.  */
+  /* If there is a ill-formatted key=value, everything after is also ignored.  */
   {
     "glibc.malloc.mmap_threshold=glibc.malloc.mmap_threshold=4096:glibc.malloc.check=2",
-    2,
+    0,
     0,
     0,
   },
-  /* For an integer tunable, parse will stop on non number character.  */
   {
     "glibc.malloc.check=2=2",
-    2,
+    0,
     0,
     0,
   },
   {
     "glibc.malloc.check=2=2:glibc.malloc.mmap_threshold=4096",
+    0,
+    0,
+    0,
+  },
+  {
+    "glibc.malloc.check=2=2:glibc.malloc.check=2",
+    0,
+    0,
+    0,
+  },
+  /* Valid tunables set before ill-formatted ones are set.  */
+  {
+    "glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096=4096",
     2,
-    4096,
+    0,
     0,
   }
 };
-- 
2.34.1


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v3 06/19] elf: Do not parse ill-formatted strings
  2023-11-06 20:25 [PATCH v3 00/19] Improve loader environment variable handling Adhemerval Zanella
                   ` (4 preceding siblings ...)
  2023-11-06 20:25 ` [PATCH v3 05/19] elf: Do not process invalid tunable format Adhemerval Zanella
@ 2023-11-06 20:25 ` Adhemerval Zanella
  2023-11-20 21:48   ` Siddhesh Poyarekar
  2023-11-06 20:25 ` [PATCH v3 07/19] elf: Fix _dl_debug_vdprintf to work before self-relocation Adhemerval Zanella
                   ` (13 subsequent siblings)
  19 siblings, 1 reply; 40+ messages in thread
From: Adhemerval Zanella @ 2023-11-06 20:25 UTC (permalink / raw)
  To: libc-alpha, Siddhesh Poyarekar

Instead of ignoring ill-formatted tunable strings, first, check all the
tunable definitions are correct and then set each tunable value. It
means that partially invalid strings, like "key1=value1:key2=key2=value'
or 'key1=value':key2=value2=value2' do not enable 'key1=value1'. It
avoids possible user-defined errors in tunable definitions.

Checked on x86_64-linux-gnu.
---
 elf/dl-tunables.c  | 48 ++++++++++++++++++++++++++++++++++------------
 elf/tst-tunables.c | 13 +++++++++----
 2 files changed, 45 insertions(+), 16 deletions(-)

diff --git a/elf/dl-tunables.c b/elf/dl-tunables.c
index 082a76d9c4..e1198869d6 100644
--- a/elf/dl-tunables.c
+++ b/elf/dl-tunables.c
@@ -154,17 +154,29 @@ __tunable_set_val (tunable_id_t id, tunable_val_t *valp, tunable_num_t *minp,
   do_tunable_update_val (cur, valp, minp, maxp);
 }
 
-/* Parse the tunable string VALSTRING.  VALSTRING is a duplicated value,
-   where delimiters ':' are replaced with '\0', so string tunables are null
-   terminated.  */
-static void
-parse_tunables (char *valstring)
+struct tunable_toset_t
+{
+  tunable_t *t;
+  const char *value;
+};
+
+enum { tunables_list_size = array_length (tunable_list) };
+
+/* Parse the tunable string VALSTRING and set TUNABLES with the found tunables
+   and their respectibles values.  VALSTRING is a duplicated values,  where
+   delimiters ':' are replaced with '\0', so string tunables are null
+   terminated.
+   Return the number of tunables found (including 0 if the string is empty)
+   or -1 if for a ill-formatted definition.  */
+static int
+parse_tunables_string (char *valstring, struct tunable_toset_t *tunables)
 {
   if (valstring == NULL || *valstring == '\0')
-    return;
+    return 0;
 
   char *p = valstring;
   bool done = false;
+  int ntunables = 0;
 
   while (!done)
     {
@@ -177,7 +189,7 @@ parse_tunables (char *valstring)
       /* If we reach the end of the string before getting a valid name-value
 	 pair, bail out.  */
       if (*p == '\0')
-	break;
+	return -1;
 
       /* We did not find a valid name-value pair before encountering the
 	 colon.  */
@@ -190,30 +202,42 @@ parse_tunables (char *valstring)
       /* Skip the '='.  */
       p++;
 
-      const char *value = p;
+      char *value = p;
 
       while (*p != '=' && *p != ':' && *p != '\0')
 	p++;
 
       if (*p == '=')
-	break;
+	return -1;
       else if (*p == '\0')
 	done = true;
       else
 	*p++ = '\0';
 
       /* Add the tunable if it exists.  */
-      for (size_t i = 0; i < sizeof (tunable_list) / sizeof (tunable_t); i++)
+      for (size_t i = 0; i < tunables_list_size; i++)
 	{
 	  tunable_t *cur = &tunable_list[i];
 
 	  if (tunable_is_name (cur->name, name))
 	    {
-	      tunable_initialize (cur, value);
+	      tunables[ntunables++] = (struct tunable_toset_t) { cur, value };
 	      break;
 	    }
 	}
     }
+
+  return ntunables;
+}
+
+static void
+parse_tunables (char *valstring)
+{
+  struct tunable_toset_t tunables[tunables_list_size];
+  int ntunables = parse_tunables_string (valstring, tunables);
+
+  for (int i = 0; i < ntunables; i++)
+    tunable_initialize (tunables[i].t, tunables[i].value);
 }
 
 /* Initialize the tunables list from the environment.  For now we only use the
@@ -240,7 +264,7 @@ __tunables_init (char **envp)
 	  continue;
 	}
 
-      for (int i = 0; i < sizeof (tunable_list) / sizeof (tunable_t); i++)
+      for (int i = 0; i < tunables_list_size; i++)
 	{
 	  tunable_t *cur = &tunable_list[i];
 
diff --git a/elf/tst-tunables.c b/elf/tst-tunables.c
index 7fe9907e05..e1ad44f27c 100644
--- a/elf/tst-tunables.c
+++ b/elf/tst-tunables.c
@@ -161,7 +161,7 @@ static const struct test_t
     0,
     0,
   },
-  /* If there is a ill-formatted key=value, everything after is also ignored.  */
+  /* Ill-formatted tunables string is not parsed.  */
   {
     "glibc.malloc.mmap_threshold=glibc.malloc.mmap_threshold=4096:glibc.malloc.check=2",
     0,
@@ -186,13 +186,18 @@ static const struct test_t
     0,
     0,
   },
-  /* Valid tunables set before ill-formatted ones are set.  */
   {
     "glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096=4096",
-    2,
     0,
     0,
-  }
+    0,
+  },
+  {
+    "glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096=4096",
+    0,
+    0,
+    0,
+  },
 };
 
 static int
-- 
2.34.1


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v3 07/19] elf: Fix _dl_debug_vdprintf to work before self-relocation
  2023-11-06 20:25 [PATCH v3 00/19] Improve loader environment variable handling Adhemerval Zanella
                   ` (5 preceding siblings ...)
  2023-11-06 20:25 ` [PATCH v3 06/19] elf: Do not parse ill-formatted strings Adhemerval Zanella
@ 2023-11-06 20:25 ` Adhemerval Zanella
  2023-11-06 20:25 ` [PATCH v3 08/19] elf: Emit warning if tunable is ill-formatted Adhemerval Zanella
                   ` (12 subsequent siblings)
  19 siblings, 0 replies; 40+ messages in thread
From: Adhemerval Zanella @ 2023-11-06 20:25 UTC (permalink / raw)
  To: libc-alpha, Siddhesh Poyarekar

The strlen might trigger and invalid GOT entry if it used before
the process is self-relocated (for instance on dl-tunables if any
error occurs).

For i386, _dl_writev with PIE requires to use the old 'int $0x80'
syscall mode because the calling the TLS register (gs) is not yet
initialized.

Checked on x86_64-linux-gnu.
Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
---
 elf/dl-printf.c                          | 16 ++++++++++++++--
 stdio-common/Makefile                    |  5 +++++
 stdio-common/_itoa.c                     |  5 +++++
 sysdeps/unix/sysv/linux/i386/dl-writev.h | 24 ++++++++++++++++++++++++
 4 files changed, 48 insertions(+), 2 deletions(-)
 create mode 100644 sysdeps/unix/sysv/linux/i386/dl-writev.h

diff --git a/elf/dl-printf.c b/elf/dl-printf.c
index 6efb4c019a..5e93208535 100644
--- a/elf/dl-printf.c
+++ b/elf/dl-printf.c
@@ -17,6 +17,10 @@
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
+#include <string.h>
+#if BUILD_PIE_DEFAULT
+# pragma GCC visibility push(hidden)
+#endif
 #include <_itoa.h>
 #include <assert.h>
 #include <dl-writev.h>
@@ -25,11 +29,19 @@
 #include <stdarg.h>
 #include <stdint.h>
 #include <stdlib.h>
-#include <string.h>
 #include <sys/uio.h>
 #include <unistd.h>
 #include <intprops.h>
 
+/* The function might be called before the process is self-relocated.  */
+static size_t
+_dl_debug_strlen (const char *s)
+{
+  const char *p = s;
+  for (; *s != '\0'; s++);
+  return s - p;
+}
+
 /* Bare-bones printf implementation.  This function only knows about
    the formats and flags needed and can handle only up to 64 stripes in
    the output.  */
@@ -193,7 +205,7 @@ _dl_debug_vdprintf (int fd, int tag_p, const char *fmt, va_list arg)
 	    case 's':
 	      /* Get the string argument.  */
 	      iov[niov].iov_base = va_arg (arg, char *);
-	      iov[niov].iov_len = strlen (iov[niov].iov_base);
+	      iov[niov].iov_len = _dl_debug_strlen (iov[niov].iov_base);
 	      if (prec != -1)
 		iov[niov].iov_len = MIN ((size_t) prec, iov[niov].iov_len);
 	      ++niov;
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index bacb795fed..e88a9cea29 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -460,6 +460,11 @@ CFLAGS-isoc23_scanf.c += -fexceptions
 
 CFLAGS-dprintf.c += $(config-cflags-wno-ignored-attributes)
 
+# Called during static library initialization, so turn stack-protection
+# off for non-shared builds.
+CFLAGS-_itoa.o = $(no-stack-protector)
+CFLAGS-_itoa.op = $(no-stack-protector)
+
 # scanf18.c and scanf19.c test a deprecated extension which is no
 # longer visible under most conformance levels; see the source files
 # for more detail.
diff --git a/stdio-common/_itoa.c b/stdio-common/_itoa.c
index 3037b0f529..48f2903ecb 100644
--- a/stdio-common/_itoa.c
+++ b/stdio-common/_itoa.c
@@ -16,6 +16,11 @@
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
+/* Mark symbols hidden in static PIE for early self relocation to work.
+   Note: string.h may have ifuncs which cannot be hidden on i686.  */
+#if BUILD_PIE_DEFAULT
+# pragma GCC visibility push(hidden)
+#endif
 #include <gmp-mparam.h>
 #include <gmp.h>
 #include <limits.h>
diff --git a/sysdeps/unix/sysv/linux/i386/dl-writev.h b/sysdeps/unix/sysv/linux/i386/dl-writev.h
new file mode 100644
index 0000000000..624d0e46b0
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/i386/dl-writev.h
@@ -0,0 +1,24 @@
+/* Message-writing for the dynamic linker.  Linux/i386 version.
+   Copyright (C) 2013-2023 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/>.  */
+
+#if BUILD_PIE_DEFAULT
+/* Can't use "call *%gs:SYSINFO_OFFSET" during startup in static PIE.  */
+# define I386_USE_SYSENTER 0
+#endif
+
+#include <sysdeps/unix/sysv/linux/dl-writev.h>
-- 
2.34.1


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v3 08/19] elf: Emit warning if tunable is ill-formatted
  2023-11-06 20:25 [PATCH v3 00/19] Improve loader environment variable handling Adhemerval Zanella
                   ` (6 preceding siblings ...)
  2023-11-06 20:25 ` [PATCH v3 07/19] elf: Fix _dl_debug_vdprintf to work before self-relocation Adhemerval Zanella
@ 2023-11-06 20:25 ` Adhemerval Zanella
  2023-11-20 21:50   ` Siddhesh Poyarekar
  2023-11-06 20:25 ` [PATCH v3 09/19] x86: Use dl-symbol-redir-ifunc.h on cpu-tunables Adhemerval Zanella
                   ` (11 subsequent siblings)
  19 siblings, 1 reply; 40+ messages in thread
From: Adhemerval Zanella @ 2023-11-06 20:25 UTC (permalink / raw)
  To: libc-alpha, Siddhesh Poyarekar

So caller knows that the tunable will be ignored.

Checked on x86_64-linux-gnu.
---
 elf/dl-tunables.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/elf/dl-tunables.c b/elf/dl-tunables.c
index e1198869d6..6e3e6a2cf8 100644
--- a/elf/dl-tunables.c
+++ b/elf/dl-tunables.c
@@ -235,6 +235,12 @@ parse_tunables (char *valstring)
 {
   struct tunable_toset_t tunables[tunables_list_size];
   int ntunables = parse_tunables_string (valstring, tunables);
+  if (ntunables == -1)
+    {
+      _dl_error_printf (
+        "WARNING: ld.so: invalid GLIBC_TUNABLES `%s': ignored.\n", valstring);
+      return;
+    }
 
   for (int i = 0; i < ntunables; i++)
     tunable_initialize (tunables[i].t, tunables[i].value);
-- 
2.34.1


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v3 09/19] x86: Use dl-symbol-redir-ifunc.h on cpu-tunables
  2023-11-06 20:25 [PATCH v3 00/19] Improve loader environment variable handling Adhemerval Zanella
                   ` (7 preceding siblings ...)
  2023-11-06 20:25 ` [PATCH v3 08/19] elf: Emit warning if tunable is ill-formatted Adhemerval Zanella
@ 2023-11-06 20:25 ` Adhemerval Zanella
  2023-11-06 20:25 ` [PATCH v3 10/19] s390: " Adhemerval Zanella
                   ` (10 subsequent siblings)
  19 siblings, 0 replies; 40+ messages in thread
From: Adhemerval Zanella @ 2023-11-06 20:25 UTC (permalink / raw)
  To: libc-alpha, Siddhesh Poyarekar; +Cc: Noah Goldstein

The dl-symbol-redir-ifunc.h redirects compiler-generated libcalls to
arch-specific memory implementations to avoid ifunc calls where it is not
yet possible. The memcmp-isa-default-impl.h aims to fix the same issue
by calling the specific memset implementation directly.

Using the memcmp symbol directly allows the compiler to inline the memset
calls (especially because _dl_tunable_set_hwcaps uses constants values),
generating better code.

Checked on x86_64-linux-gnu.

Reviewed-by: Noah Goldstein <goldstein.w.n@gmail.com>
Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
---
 .../i686/multiarch/dl-symbol-redir-ifunc.h    |  5 +++
 sysdeps/x86/cpu-tunables.c                    | 39 ++++++-------------
 sysdeps/x86_64/memcmp-isa-default-impl.h      | 28 -------------
 .../x86_64/multiarch/dl-symbol-redir-ifunc.h  | 15 +++++++
 4 files changed, 32 insertions(+), 55 deletions(-)
 delete mode 100644 sysdeps/x86_64/memcmp-isa-default-impl.h

diff --git a/sysdeps/i386/i686/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/i386/i686/multiarch/dl-symbol-redir-ifunc.h
index dee69d19db..220c586bd2 100644
--- a/sysdeps/i386/i686/multiarch/dl-symbol-redir-ifunc.h
+++ b/sysdeps/i386/i686/multiarch/dl-symbol-redir-ifunc.h
@@ -19,6 +19,11 @@
 #ifndef _DL_IFUNC_GENERIC_H
 #define _DL_IFUNC_GENERIC_H
 
+#ifndef SHARED
+
 asm ("memset = __memset_ia32");
+asm ("memcmp = __memcmp_ia32");
+
+#endif /* SHARED */
 
 #endif
diff --git a/sysdeps/x86/cpu-tunables.c b/sysdeps/x86/cpu-tunables.c
index 0d4f328585..5697885226 100644
--- a/sysdeps/x86/cpu-tunables.c
+++ b/sysdeps/x86/cpu-tunables.c
@@ -24,24 +24,11 @@
 #include <string.h>
 #include <cpu-features.h>
 #include <ldsodefs.h>
-
-/* We can't use IFUNC memcmp nor strlen in init_cpu_features from libc.a
-   since IFUNC must be set up by init_cpu_features.  */
-#if defined USE_MULTIARCH && !defined SHARED
-# ifdef __x86_64__
-/* DEFAULT_MEMCMP by sysdeps/x86_64/memcmp-isa-default-impl.h.  */
-#  include <sysdeps/x86_64/memcmp-isa-default-impl.h>
-# else
-#  define DEFAULT_MEMCMP	__memcmp_ia32
-# endif
-extern __typeof (memcmp) DEFAULT_MEMCMP;
-#else
-# define DEFAULT_MEMCMP	memcmp
-#endif
+#include <dl-symbol-redir-ifunc.h>
 
 #define CHECK_GLIBC_IFUNC_CPU_OFF(f, cpu_features, name, len)		\
   _Static_assert (sizeof (#name) - 1 == len, #name " != " #len);	\
-  if (!DEFAULT_MEMCMP (f, #name, len))					\
+  if (memcmp (f, #name, len) == 0)					\
     {									\
       CPU_FEATURE_UNSET (cpu_features, name)				\
       break;								\
@@ -51,7 +38,7 @@ extern __typeof (memcmp) DEFAULT_MEMCMP;
    which isn't available.  */
 #define CHECK_GLIBC_IFUNC_PREFERRED_OFF(f, cpu_features, name, len)	\
   _Static_assert (sizeof (#name) - 1 == len, #name " != " #len);	\
-  if (!DEFAULT_MEMCMP (f, #name, len))					\
+  if (memcmp (f, #name, len) == 0)					\
     {									\
       cpu_features->preferred[index_arch_##name]			\
 	&= ~bit_arch_##name;						\
@@ -62,7 +49,7 @@ extern __typeof (memcmp) DEFAULT_MEMCMP;
 #define CHECK_GLIBC_IFUNC_PREFERRED_BOTH(f, cpu_features, name,	\
 					  disable, len)			\
   _Static_assert (sizeof (#name) - 1 == len, #name " != " #len);	\
-  if (!DEFAULT_MEMCMP (f, #name, len))					\
+  if (memcmp (f, #name, len) == 0)					\
     {									\
       if (disable)							\
 	cpu_features->preferred[index_arch_##name] &= ~bit_arch_##name;	\
@@ -76,7 +63,7 @@ extern __typeof (memcmp) DEFAULT_MEMCMP;
 #define CHECK_GLIBC_IFUNC_PREFERRED_NEED_BOTH(f, cpu_features, name,	\
 					       need, disable, len)	\
   _Static_assert (sizeof (#name) - 1 == len, #name " != " #len);	\
-  if (!DEFAULT_MEMCMP (f, #name, len))					\
+  if (memcmp (f, #name, len) == 0)					\
     {									\
       if (disable)							\
 	cpu_features->preferred[index_arch_##name] &= ~bit_arch_##name;	\
@@ -177,7 +164,7 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
 	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, POPCNT, 6);
 	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, SSE4_1, 6);
 	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, SSE4_2, 6);
-	      if (!DEFAULT_MEMCMP (n, "XSAVEC", 6))
+	      if (memcmp (n, "XSAVEC", 6) == 0)
 		{
 		  /* Update xsave_state_size to XSAVE state size.  */
 		  cpu_features->xsave_state_size
@@ -290,12 +277,11 @@ attribute_hidden
 void
 TUNABLE_CALLBACK (set_x86_ibt) (tunable_val_t *valp)
 {
-  if (DEFAULT_MEMCMP (valp->strval, "on", sizeof ("on")) == 0)
+  if (memcmp (valp->strval, "on", sizeof ("on")) == 0)
     GL(dl_x86_feature_control).ibt = cet_always_on;
-  else if (DEFAULT_MEMCMP (valp->strval, "off", sizeof ("off")) == 0)
+  else if (memcmp (valp->strval, "off", sizeof ("off")) == 0)
     GL(dl_x86_feature_control).ibt = cet_always_off;
-  else if (DEFAULT_MEMCMP (valp->strval, "permissive",
-			   sizeof ("permissive")) == 0)
+  else if (memcmp (valp->strval, "permissive", sizeof ("permissive")) == 0)
     GL(dl_x86_feature_control).ibt = cet_permissive;
 }
 
@@ -303,12 +289,11 @@ attribute_hidden
 void
 TUNABLE_CALLBACK (set_x86_shstk) (tunable_val_t *valp)
 {
-  if (DEFAULT_MEMCMP (valp->strval, "on", sizeof ("on")) == 0)
+  if (memcmp (valp->strval, "on", sizeof ("on")) == 0)
     GL(dl_x86_feature_control).shstk = cet_always_on;
-  else if (DEFAULT_MEMCMP (valp->strval, "off", sizeof ("off")) == 0)
+  else if (memcmp (valp->strval, "off", sizeof ("off")) == 0)
     GL(dl_x86_feature_control).shstk = cet_always_off;
-  else if (DEFAULT_MEMCMP (valp->strval, "permissive",
-			   sizeof ("permissive")) == 0)
+  else if (memcmp (valp->strval, "permissive", sizeof ("permissive")) == 0)
     GL(dl_x86_feature_control).shstk = cet_permissive;
 }
 #endif
diff --git a/sysdeps/x86_64/memcmp-isa-default-impl.h b/sysdeps/x86_64/memcmp-isa-default-impl.h
deleted file mode 100644
index 0962e83c3d..0000000000
--- a/sysdeps/x86_64/memcmp-isa-default-impl.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/* Set default memcmp impl based on ISA level.
-   Copyright (C) 2022-2023 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 <isa-level.h>
-#if MINIMUM_X86_ISA_LEVEL == 1 || MINIMUM_X86_ISA_LEVEL == 2
-# define DEFAULT_MEMCMP	__memcmp_sse2
-#elif MINIMUM_X86_ISA_LEVEL == 3
-# define DEFAULT_MEMCMP	__memcmp_avx2_movbe
-#elif MINIMUM_X86_ISA_LEVEL == 4
-# define DEFAULT_MEMCMP	__memcmp_evex_movbe
-#else
-# error "Unknown default memcmp implementation"
-#endif
diff --git a/sysdeps/x86_64/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/x86_64/multiarch/dl-symbol-redir-ifunc.h
index 3fe73ca1c3..c7d8961bb6 100644
--- a/sysdeps/x86_64/multiarch/dl-symbol-redir-ifunc.h
+++ b/sysdeps/x86_64/multiarch/dl-symbol-redir-ifunc.h
@@ -19,6 +19,8 @@
 #ifndef _DL_IFUNC_GENERIC_H
 #define _DL_IFUNC_GENERIC_H
 
+#ifndef SHARED
+
 #include <isa-level.h>
 
 #if MINIMUM_X86_ISA_LEVEL >= 4
@@ -31,4 +33,17 @@
 
 asm ("memset = " HAVE_MEMSET_IFUNC_GENERIC);
 
+
+#if MINIMUM_X86_ISA_LEVEL >= 4
+# define HAVE_MEMCMP_IFUNC_GENERIC "__memcmp_evex_movbe"
+#elif MINIMUM_X86_ISA_LEVEL == 3
+# define HAVE_MEMCMP_IFUNC_GENERIC "__memcmp_avx2_movbe"
+#else
+# define HAVE_MEMCMP_IFUNC_GENERIC "__memcmp_sse2"
+#endif
+
+asm ("memcmp = " HAVE_MEMCMP_IFUNC_GENERIC);
+
+#endif /* SHARED */
+
 #endif
-- 
2.34.1


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v3 10/19] s390: Use dl-symbol-redir-ifunc.h on cpu-tunables
  2023-11-06 20:25 [PATCH v3 00/19] Improve loader environment variable handling Adhemerval Zanella
                   ` (8 preceding siblings ...)
  2023-11-06 20:25 ` [PATCH v3 09/19] x86: Use dl-symbol-redir-ifunc.h on cpu-tunables Adhemerval Zanella
@ 2023-11-06 20:25 ` Adhemerval Zanella
  2023-11-06 20:25 ` [PATCH v3 11/19] elf: Do not duplicate the GLIBC_TUNABLES string Adhemerval Zanella
                   ` (9 subsequent siblings)
  19 siblings, 0 replies; 40+ messages in thread
From: Adhemerval Zanella @ 2023-11-06 20:25 UTC (permalink / raw)
  To: libc-alpha, Siddhesh Poyarekar

Using the memcmp symbol directly allows the compile to inline the
memcmp calls (especially because _dl_tunable_set_hwcaps uses constants
values), generating better code.

Checked with tst-tunables on s390x-linux-gnu (qemu system).
Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
---
 sysdeps/s390/cpu-features.c                   | 30 +++++++++----------
 .../s390/multiarch/dl-symbol-redir-ifunc.h    |  2 ++
 2 files changed, 17 insertions(+), 15 deletions(-)

diff --git a/sysdeps/s390/cpu-features.c b/sysdeps/s390/cpu-features.c
index 39f8c23a60..55449ba07f 100644
--- a/sysdeps/s390/cpu-features.c
+++ b/sysdeps/s390/cpu-features.c
@@ -21,7 +21,7 @@
 #include <elf/dl-tunables.h>
 #include <ifunc-memcmp.h>
 #include <string.h>
-extern __typeof (memcmp) MEMCMP_DEFAULT;
+#include <dl-symbol-redir-ifunc.h>
 
 #define S390_COPY_CPU_FEATURES(SRC_PTR, DEST_PTR)	\
   (DEST_PTR)->hwcap = (SRC_PTR)->hwcap;			\
@@ -89,9 +89,9 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
       if ((*feature == 'z' || *feature == 'a'))
 	{
 	  if ((feature_len == 5 && *feature == 'z'
-	       && MEMCMP_DEFAULT (feature, "zEC12", 5) == 0)
+	       && memcmp (feature, "zEC12", 5) == 0)
 	      || (feature_len == 6 && *feature == 'a'
-		  && MEMCMP_DEFAULT (feature, "arch10", 6) == 0))
+		  && memcmp (feature, "arch10", 6) == 0))
 	    {
 	      reset_features = true;
 	      disable = true;
@@ -100,9 +100,9 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
 	      stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
 	    }
 	  else if ((feature_len == 3 && *feature == 'z'
-		    && MEMCMP_DEFAULT (feature, "z13", 3) == 0)
+		    && memcmp (feature, "z13", 3) == 0)
 		   || (feature_len == 6 && *feature == 'a'
-		       && MEMCMP_DEFAULT (feature, "arch11", 6) == 0))
+		       && memcmp (feature, "arch11", 6) == 0))
 	    {
 	      reset_features = true;
 	      disable = true;
@@ -110,9 +110,9 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
 	      stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
 	    }
 	  else if ((feature_len == 3 && *feature == 'z'
-		    && MEMCMP_DEFAULT (feature, "z14", 3) == 0)
+		    && memcmp (feature, "z14", 3) == 0)
 		   || (feature_len == 6 && *feature == 'a'
-		       && MEMCMP_DEFAULT (feature, "arch12", 6) == 0))
+		       && memcmp (feature, "arch12", 6) == 0))
 	    {
 	      reset_features = true;
 	      disable = true;
@@ -120,11 +120,11 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
 	      stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
 	    }
 	  else if ((feature_len == 3 && *feature == 'z'
-		    && (MEMCMP_DEFAULT (feature, "z15", 3) == 0
-			|| MEMCMP_DEFAULT (feature, "z16", 3) == 0))
+		    && (memcmp (feature, "z15", 3) == 0
+			|| memcmp (feature, "z16", 3) == 0))
 		   || (feature_len == 6
-		       && (MEMCMP_DEFAULT (feature, "arch13", 6) == 0
-			   || MEMCMP_DEFAULT (feature, "arch14", 6) == 0)))
+		       && (memcmp (feature, "arch13", 6) == 0
+			   || memcmp (feature, "arch14", 6) == 0)))
 	    {
 	      /* For z15 or newer we don't have to disable something,
 		 but we have to reset to the original values.  */
@@ -134,14 +134,14 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
       else if (*feature == 'H')
 	{
 	  if (feature_len == 15
-	      && MEMCMP_DEFAULT (feature, "HWCAP_S390_VXRS", 15) == 0)
+	      && memcmp (feature, "HWCAP_S390_VXRS", 15) == 0)
 	    {
 	      hwcap_mask = HWCAP_S390_VXRS;
 	      if (disable)
 		hwcap_mask |= HWCAP_S390_VXRS_EXT | HWCAP_S390_VXRS_EXT2;
 	    }
 	  else if (feature_len == 19
-		   && MEMCMP_DEFAULT (feature, "HWCAP_S390_VXRS_EXT", 19) == 0)
+		   && memcmp (feature, "HWCAP_S390_VXRS_EXT", 19) == 0)
 	    {
 	      hwcap_mask = HWCAP_S390_VXRS_EXT;
 	      if (disable)
@@ -150,7 +150,7 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
 		hwcap_mask |= HWCAP_S390_VXRS;
 	    }
 	  else if (feature_len == 20
-		   && MEMCMP_DEFAULT (feature, "HWCAP_S390_VXRS_EXT2", 20) == 0)
+		   && memcmp (feature, "HWCAP_S390_VXRS_EXT2", 20) == 0)
 	    {
 	      hwcap_mask = HWCAP_S390_VXRS_EXT2;
 	      if (!disable)
@@ -160,7 +160,7 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
       else if (*feature == 'S')
 	{
 	  if (feature_len == 10
-	      && MEMCMP_DEFAULT (feature, "STFLE_MIE3", 10) == 0)
+	      && memcmp (feature, "STFLE_MIE3", 10) == 0)
 	    {
 	      stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
 	    }
diff --git a/sysdeps/s390/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/s390/multiarch/dl-symbol-redir-ifunc.h
index aa084fdcde..0f58897c48 100644
--- a/sysdeps/s390/multiarch/dl-symbol-redir-ifunc.h
+++ b/sysdeps/s390/multiarch/dl-symbol-redir-ifunc.h
@@ -20,10 +20,12 @@
 #define _DL_IFUNC_GENERIC_H
 
 #include <ifunc-memset.h>
+#include <ifunc-memcmp.h>
 
 #define IFUNC_SYMBOL_STR1(s)	#s
 #define IFUNC_SYMBOL_STR(s)	IFUNC_SYMBOL_STR1(s)
 
 asm ("memset = " IFUNC_SYMBOL_STR(MEMSET_DEFAULT));
+asm ("memcmp = " IFUNC_SYMBOL_STR(MEMCMP_DEFAULT));
 
 #endif
-- 
2.34.1


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v3 11/19] elf: Do not duplicate the GLIBC_TUNABLES string
  2023-11-06 20:25 [PATCH v3 00/19] Improve loader environment variable handling Adhemerval Zanella
                   ` (9 preceding siblings ...)
  2023-11-06 20:25 ` [PATCH v3 10/19] s390: " Adhemerval Zanella
@ 2023-11-06 20:25 ` Adhemerval Zanella
  2023-11-20 22:44   ` Siddhesh Poyarekar
  2023-11-06 20:25 ` [PATCH v3 12/19] elf: Ignore LD_PROFILE for setuid binaries Adhemerval Zanella
                   ` (8 subsequent siblings)
  19 siblings, 1 reply; 40+ messages in thread
From: Adhemerval Zanella @ 2023-11-06 20:25 UTC (permalink / raw)
  To: libc-alpha, Siddhesh Poyarekar

The tunable parsing duplicates the tunable environment variable so it
null-terminates each one since it simplifies the later parsing. It has
the drawback of adding another point of failure (__minimal_malloc
failing), and the memory copy requires tuning the compiler to avoid mem
operations calls.

The parsing now tracks the tunable start and its size. The
dl-tunable-parse.h adds helper functions to help parsing, like a strcmp
that also checks for size and an iterator for suboptions that are
comma-separated (used on hwcap parsing by x86, powerpc, and s390x).

Since the environment variable is allocated on the stack by the kernel,
it is safe to keep the references to the suboptions for later parsing
of string tunables (as done by set_hwcaps by multiple architectures).

Checked on x86_64-linux-gnu, powerpc64le-linux-gnu, and
aarch64-linux-gnu.
---
 elf/dl-tunables.c                             |  66 +++----
 elf/dl-tunables.h                             |   6 +-
 elf/tst-tunables.c                            |  66 ++++++-
 sysdeps/generic/dl-tunables-parse.h           | 129 ++++++++++++++
 sysdeps/s390/cpu-features.c                   | 167 +++++++-----------
 .../unix/sysv/linux/aarch64/cpu-features.c    |  38 ++--
 .../unix/sysv/linux/powerpc/cpu-features.c    |  45 ++---
 .../sysv/linux/powerpc/tst-hwcap-tunables.c   |   6 +-
 sysdeps/x86/Makefile                          |   4 +-
 sysdeps/x86/cpu-tunables.c                    | 118 +++++--------
 sysdeps/x86/tst-hwcap-tunables.c              | 148 ++++++++++++++++
 11 files changed, 517 insertions(+), 276 deletions(-)
 create mode 100644 sysdeps/generic/dl-tunables-parse.h
 create mode 100644 sysdeps/x86/tst-hwcap-tunables.c

diff --git a/elf/dl-tunables.c b/elf/dl-tunables.c
index 6e3e6a2cf8..f406521735 100644
--- a/elf/dl-tunables.c
+++ b/elf/dl-tunables.c
@@ -36,28 +36,6 @@
 #define TUNABLES_INTERNAL 1
 #include "dl-tunables.h"
 
-#include <not-errno.h>
-
-static char *
-tunables_strdup (const char *in)
-{
-  size_t i = 0;
-
-  while (in[i++] != '\0');
-  char *out = __minimal_malloc (i + 1);
-
-  /* For most of the tunables code, we ignore user errors.  However,
-     this is a system error - and running out of memory at program
-     startup should be reported, so we do.  */
-  if (out == NULL)
-    _dl_fatal_printf ("failed to allocate memory to process tunables\n");
-
-  while (i-- > 0)
-    out[i] = in[i];
-
-  return out;
-}
-
 static char **
 get_next_env (char **envp, char **name, size_t *namelen, char **val,
 	      char ***prev_envp)
@@ -134,14 +112,14 @@ do_tunable_update_val (tunable_t *cur, const tunable_val_t *valp,
 /* Validate range of the input value and initialize the tunable CUR if it looks
    good.  */
 static void
-tunable_initialize (tunable_t *cur, const char *strval)
+tunable_initialize (tunable_t *cur, const char *strval, size_t len)
 {
-  tunable_val_t val;
+  tunable_val_t val = { 0 };
 
   if (cur->type.type_code != TUNABLE_TYPE_STRING)
     val.numval = (tunable_num_t) _dl_strtoul (strval, NULL);
   else
-    val.strval = strval;
+    val.strval = (struct tunable_str_t) { strval, len };
   do_tunable_update_val (cur, &val, NULL, NULL);
 }
 
@@ -158,29 +136,29 @@ struct tunable_toset_t
 {
   tunable_t *t;
   const char *value;
+  size_t len;
 };
 
 enum { tunables_list_size = array_length (tunable_list) };
 
 /* Parse the tunable string VALSTRING and set TUNABLES with the found tunables
-   and their respectibles values.  VALSTRING is a duplicated values,  where
-   delimiters ':' are replaced with '\0', so string tunables are null
-   terminated.
+   and their respectibles values.  The VALSTRING is parsed in place, with the
+   tunable start and size recorded in TUNABLES.
    Return the number of tunables found (including 0 if the string is empty)
    or -1 if for a ill-formatted definition.  */
 static int
-parse_tunables_string (char *valstring, struct tunable_toset_t *tunables)
+parse_tunables_string (const char *valstring, struct tunable_toset_t *tunables)
 {
   if (valstring == NULL || *valstring == '\0')
     return 0;
 
-  char *p = valstring;
+  const char *p = valstring;
   bool done = false;
   int ntunables = 0;
 
   while (!done)
     {
-      char *name = p;
+      const char *name = p;
 
       /* First, find where the name ends.  */
       while (*p != '=' && *p != ':' && *p != '\0')
@@ -202,7 +180,7 @@ parse_tunables_string (char *valstring, struct tunable_toset_t *tunables)
       /* Skip the '='.  */
       p++;
 
-      char *value = p;
+      const char *value = p;
 
       while (*p != '=' && *p != ':' && *p != '\0')
 	p++;
@@ -211,8 +189,6 @@ parse_tunables_string (char *valstring, struct tunable_toset_t *tunables)
 	return -1;
       else if (*p == '\0')
 	done = true;
-      else
-	*p++ = '\0';
 
       /* Add the tunable if it exists.  */
       for (size_t i = 0; i < tunables_list_size; i++)
@@ -221,7 +197,8 @@ parse_tunables_string (char *valstring, struct tunable_toset_t *tunables)
 
 	  if (tunable_is_name (cur->name, name))
 	    {
-	      tunables[ntunables++] = (struct tunable_toset_t) { cur, value };
+	      tunables[ntunables++] =
+		(struct tunable_toset_t) { cur, value, p - value };
 	      break;
 	    }
 	}
@@ -231,7 +208,7 @@ parse_tunables_string (char *valstring, struct tunable_toset_t *tunables)
 }
 
 static void
-parse_tunables (char *valstring)
+parse_tunables (const char *valstring)
 {
   struct tunable_toset_t tunables[tunables_list_size];
   int ntunables = parse_tunables_string (valstring, tunables);
@@ -243,7 +220,7 @@ parse_tunables (char *valstring)
     }
 
   for (int i = 0; i < ntunables; i++)
-    tunable_initialize (tunables[i].t, tunables[i].value);
+    tunable_initialize (tunables[i].t, tunables[i].value, tunables[i].len);
 }
 
 /* Initialize the tunables list from the environment.  For now we only use the
@@ -264,9 +241,12 @@ __tunables_init (char **envp)
   while ((envp = get_next_env (envp, &envname, &len, &envval,
 			       &prev_envp)) != NULL)
     {
+      /* The environment variable is allocated on the stack by the kernel, so
+	 it is safe to keep the references to the suboptions for later parsing
+	 of string tunables.  */
       if (tunable_is_name ("GLIBC_TUNABLES", envname))
 	{
-	  parse_tunables (tunables_strdup (envval));
+	  parse_tunables (envval);
 	  continue;
 	}
 
@@ -284,7 +264,7 @@ __tunables_init (char **envp)
 	  /* We have a match.  Initialize and move on to the next line.  */
 	  if (tunable_is_name (name, envname))
 	    {
-	      tunable_initialize (cur, envval);
+	      tunable_initialize (cur, envval, len);
 	      break;
 	    }
 	}
@@ -298,7 +278,7 @@ __tunables_print (void)
     {
       const tunable_t *cur = &tunable_list[i];
       if (cur->type.type_code == TUNABLE_TYPE_STRING
-	  && cur->val.strval == NULL)
+	  && cur->val.strval.str == NULL)
 	_dl_printf ("%s:\n", cur->name);
       else
 	{
@@ -324,7 +304,9 @@ __tunables_print (void)
 			  (size_t) cur->type.max);
 	      break;
 	    case TUNABLE_TYPE_STRING:
-	      _dl_printf ("%s\n", cur->val.strval);
+	      _dl_printf ("%.*s\n",
+			  (int) cur->val.strval.len,
+			  cur->val.strval.str);
 	      break;
 	    default:
 	      __builtin_unreachable ();
@@ -359,7 +341,7 @@ __tunable_get_val (tunable_id_t id, void *valp, tunable_callback_t callback)
 	}
     case TUNABLE_TYPE_STRING:
 	{
-	  *((const char **)valp) = cur->val.strval;
+	  *((struct tunable_str_t **) valp) = &cur->val.strval;
 	  break;
 	}
     default:
diff --git a/elf/dl-tunables.h b/elf/dl-tunables.h
index 45c191e021..0e777d7d37 100644
--- a/elf/dl-tunables.h
+++ b/elf/dl-tunables.h
@@ -30,7 +30,11 @@ typedef intmax_t tunable_num_t;
 typedef union
 {
   tunable_num_t numval;
-  const char *strval;
+  struct tunable_str_t
+  {
+    const char *str;
+    size_t len;
+  } strval;
 } tunable_val_t;
 
 typedef void (*tunable_callback_t) (tunable_val_t *);
diff --git a/elf/tst-tunables.c b/elf/tst-tunables.c
index e1ad44f27c..188345b070 100644
--- a/elf/tst-tunables.c
+++ b/elf/tst-tunables.c
@@ -31,7 +31,8 @@ static int restart;
 
 static const struct test_t
 {
-  const char *env;
+  const char *name;
+  const char *value;
   int32_t expected_malloc_check;
   size_t expected_mmap_threshold;
   int32_t expected_perturb;
@@ -39,12 +40,14 @@ static const struct test_t
 {
   /* Expected tunable format.  */
   {
+    "GLIBC_TUNABLES",
     "glibc.malloc.check=2",
     2,
     0,
     0,
   },
   {
+    "GLIBC_TUNABLES",
     "glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096",
     2,
     4096,
@@ -52,6 +55,7 @@ static const struct test_t
   },
   /* Empty tunable are ignored.  */
   {
+    "GLIBC_TUNABLES",
     "glibc.malloc.check=2::glibc.malloc.mmap_threshold=4096",
     2,
     4096,
@@ -59,6 +63,7 @@ static const struct test_t
   },
   /* As well empty values.  */
   {
+    "GLIBC_TUNABLES",
     "glibc.malloc.check=:glibc.malloc.mmap_threshold=4096",
     0,
     4096,
@@ -66,18 +71,21 @@ static const struct test_t
   },
   /* Tunable are processed from left to right, so last one is the one set.  */
   {
+    "GLIBC_TUNABLES",
     "glibc.malloc.check=1:glibc.malloc.check=2",
     2,
     0,
     0,
   },
   {
+    "GLIBC_TUNABLES",
     "glibc.malloc.check=1:glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096",
     2,
     4096,
     0,
   },
   {
+    "GLIBC_TUNABLES",
     "glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096:glibc.malloc.check=1",
     1,
     4096,
@@ -85,12 +93,14 @@ static const struct test_t
   },
   /* 0x800 is larger than tunable maxval (0xff), so the tunable is unchanged.  */
   {
+    "GLIBC_TUNABLES",
     "glibc.malloc.perturb=0x800",
     0,
     0,
     0,
   },
   {
+    "GLIBC_TUNABLES",
     "glibc.malloc.perturb=0x55",
     0,
     0,
@@ -98,6 +108,7 @@ static const struct test_t
   },
   /* Out of range values are just ignored.  */
   {
+    "GLIBC_TUNABLES",
     "glibc.malloc.perturb=0x800:glibc.malloc.mmap_threshold=4096",
     0,
     4096,
@@ -105,24 +116,28 @@ static const struct test_t
   },
   /* Invalid keys are ignored.  */
   {
+    "GLIBC_TUNABLES",
     ":glibc.malloc.garbage=2:glibc.malloc.check=1",
     1,
     0,
     0,
   },
   {
+    "GLIBC_TUNABLES",
     "glibc.malloc.perturb=0x800:not_valid.malloc.check=2:glibc.malloc.mmap_threshold=4096",
     0,
     4096,
     0,
   },
   {
+    "GLIBC_TUNABLES",
     "glibc.not_valid.check=2:glibc.malloc.mmap_threshold=4096",
     0,
     4096,
     0,
   },
   {
+    "GLIBC_TUNABLES",
     "not_valid.malloc.check=2:glibc.malloc.mmap_threshold=4096",
     0,
     4096,
@@ -130,24 +145,28 @@ static const struct test_t
   },
   /* Invalid subkeys are ignored.  */
   {
+    "GLIBC_TUNABLES",
     "glibc.malloc.garbage=2:glibc.maoc.mmap_threshold=4096:glibc.malloc.check=2",
     2,
     0,
     0,
   },
   {
+    "GLIBC_TUNABLES",
     "glibc.malloc.check=4:glibc.malloc.garbage=2:glibc.maoc.mmap_threshold=4096",
     0,
     0,
     0,
   },
   {
+    "GLIBC_TUNABLES",
     "not_valid.malloc.check=2",
     0,
     0,
     0,
   },
   {
+    "GLIBC_TUNABLES",
     "glibc.not_valid.check=2",
     0,
     0,
@@ -156,6 +175,7 @@ static const struct test_t
   /* An ill-formatted tunable in the for key=key=value will considere the
      value as 'key=value' (which can not be parsed as an integer).  */
   {
+    "GLIBC_TUNABLES",
     "glibc.malloc.mmap_threshold=glibc.malloc.mmap_threshold=4096",
     0,
     0,
@@ -163,41 +183,77 @@ static const struct test_t
   },
   /* Ill-formatted tunables string is not parsed.  */
   {
+    "GLIBC_TUNABLES",
     "glibc.malloc.mmap_threshold=glibc.malloc.mmap_threshold=4096:glibc.malloc.check=2",
     0,
     0,
     0,
   },
   {
+    "GLIBC_TUNABLES",
     "glibc.malloc.check=2=2",
     0,
     0,
     0,
   },
   {
+    "GLIBC_TUNABLES",
     "glibc.malloc.check=2=2:glibc.malloc.mmap_threshold=4096",
     0,
     0,
     0,
   },
   {
+    "GLIBC_TUNABLES",
     "glibc.malloc.check=2=2:glibc.malloc.check=2",
     0,
     0,
     0,
   },
   {
+    "GLIBC_TUNABLES",
     "glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096=4096",
     0,
     0,
     0,
   },
   {
+    "GLIBC_TUNABLES",
     "glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096=4096",
     0,
     0,
     0,
   },
+  /* Also check some tunable aliases.  */
+  {
+    "MALLOC_CHECK_",
+    "2",
+    2,
+    0,
+    0,
+  },
+  {
+    "MALLOC_MMAP_THRESHOLD_",
+    "4096",
+    0,
+    4096,
+    0,
+  },
+  {
+    "MALLOC_PERTURB_",
+    "0x55",
+    0,
+    0,
+    0x55,
+  },
+  /* 0x800 is larger than tunable maxval (0xff), so the tunable is unchanged.  */
+  {
+    "MALLOC_PERTURB_",
+    "0x800",
+    0,
+    0,
+    0,
+  },
 };
 
 static int
@@ -245,13 +301,17 @@ do_test (int argc, char *argv[])
     {
       snprintf (nteststr, sizeof nteststr, "%d", i);
 
-      printf ("[%d] Spawned test for %s\n", i, tests[i].env);
-      setenv ("GLIBC_TUNABLES", tests[i].env, 1);
+      printf ("[%d] Spawned test for %s=%s\n",
+	      i,
+	      tests[i].name,
+	      tests[i].value);
+      setenv (tests[i].name, tests[i].value, 1);
       struct support_capture_subprocess result
 	= support_capture_subprogram (spargv[0], spargv);
       support_capture_subprocess_check (&result, "tst-tunables", 0,
 					sc_allow_stderr);
       support_capture_subprocess_free (&result);
+      unsetenv (tests[i].name);
     }
 
   return 0;
diff --git a/sysdeps/generic/dl-tunables-parse.h b/sysdeps/generic/dl-tunables-parse.h
new file mode 100644
index 0000000000..5b399f852d
--- /dev/null
+++ b/sysdeps/generic/dl-tunables-parse.h
@@ -0,0 +1,129 @@
+/* Helper functions to handle tunable strings.
+   Copyright (C) 2023 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/>.  */
+
+#ifndef _DL_TUNABLES_PARSE_H
+#define _DL_TUNABLES_PARSE_H 1
+
+#include <string.h>
+
+/* Compare the contents of STRVAL with STR of size LEN.  The STR might not
+   be null-terminated.   */
+static __always_inline bool
+tunable_strcmp (const struct tunable_str_t *strval, const char *str,
+		size_t len)
+{
+  return strval->len == len && memcmp (strval->str, str, len) == 0;
+}
+#define tunable_strcmp_cte(__tunable, __str) \
+ tunable_strcmp (&__tunable->strval, __str, sizeof (__str) - 1)
+
+/*
+   Helper functions to iterate over a tunable string composed by multiple
+   suboptions separated by commaxi; this is a common pattern for CPU.  Each
+   suboptions is return in the form of { address, size } (no null terminated).
+   For instance:
+
+     struct tunable_str_comma_t ts;
+     tunable_str_comma_init (&ts, valp);
+
+     struct tunable_str_t t;
+     while (tunable_str_comma_next (&ts, &t))
+      {
+	_dl_printf ("[%s] %.*s (%d)\n",
+		    __func__,
+		    (int) tstr.len,
+		    tstr.str,
+		    (int) tstr.len);
+
+        if (tunable_str_comma_strcmp (&t, opt, opt1_len))
+	  {
+	    [...]
+	  }
+	else if (tunable_str_comma_strcmp_cte (&t, "opt2"))
+	  {
+	    [...]
+	  }
+      }
+*/
+
+struct tunable_str_comma_state_t
+{
+  const char *p;
+  size_t plen;
+  size_t maxplen;
+};
+
+struct tunable_str_comma_t
+{
+  const char *str;
+  size_t len;
+  bool disable;
+};
+
+static inline void
+tunable_str_comma_init (struct tunable_str_comma_state_t *state,
+			tunable_val_t *valp)
+{
+  state->p = valp->strval.str;
+  state->plen = 0;
+  state->maxplen = valp->strval.len;
+}
+
+static inline bool
+tunable_str_comma_next (struct tunable_str_comma_state_t *state,
+			struct tunable_str_comma_t *str)
+{
+  if (*state->p == '\0' || state->plen >= state->maxplen)
+    return false;
+
+  const char *c;
+  for (c = state->p; *c != ','; c++, state->plen++)
+    if (*c == '\0' || state->plen == state->maxplen)
+      break;
+
+  str->str = state->p;
+  str->len = c - state->p;
+
+  if (str->len > 0)
+    {
+      str->disable = *str->str == '-';
+      if (str->disable)
+	{
+	  str->str = str->str + 1;
+	  str->len = str->len - 1;
+	}
+    }
+
+  state->p = c + 1;
+  state->plen++;
+
+  return true;
+}
+
+/* Compare the contents of T with STR of size LEN.  The STR might not be
+   null-terminated.   */
+static __always_inline bool
+tunable_str_comma_strcmp (const struct tunable_str_comma_t *t, const char *str,
+			  size_t len)
+{
+  return t->len == len && memcmp (t->str, str, len) == 0;
+}
+#define tunable_str_comma_strcmp_cte(__t, __str) \
+  tunable_str_comma_strcmp (__t, __str, sizeof (__str) - 1)
+
+#endif
diff --git a/sysdeps/s390/cpu-features.c b/sysdeps/s390/cpu-features.c
index 55449ba07f..8b1466fa7b 100644
--- a/sysdeps/s390/cpu-features.c
+++ b/sysdeps/s390/cpu-features.c
@@ -22,6 +22,7 @@
 #include <ifunc-memcmp.h>
 #include <string.h>
 #include <dl-symbol-redir-ifunc.h>
+#include <dl-tunables-parse.h>
 
 #define S390_COPY_CPU_FEATURES(SRC_PTR, DEST_PTR)	\
   (DEST_PTR)->hwcap = (SRC_PTR)->hwcap;			\
@@ -51,33 +52,14 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
   struct cpu_features cpu_features_curr;
   S390_COPY_CPU_FEATURES (cpu_features, &cpu_features_curr);
 
-  const char *token = valp->strval;
-  do
+  struct tunable_str_comma_state_t ts;
+  tunable_str_comma_init (&ts, valp);
+
+  struct tunable_str_comma_t t;
+  while (tunable_str_comma_next (&ts, &t))
     {
-      const char *token_end, *feature;
-      bool disable;
-      size_t token_len;
-      size_t feature_len;
-
-      /* Find token separator or end of string.  */
-      for (token_end = token; *token_end != ','; token_end++)
-	if (*token_end == '\0')
-	  break;
-
-      /* Determine feature.  */
-      token_len = token_end - token;
-      if (*token == '-')
-	{
-	  disable = true;
-	  feature = token + 1;
-	  feature_len = token_len - 1;
-	}
-      else
-	{
-	  disable = false;
-	  feature = token;
-	  feature_len = token_len;
-	}
+      if (t.len == 0)
+	continue;
 
       /* Handle only the features here which are really used in the
 	 IFUNC-resolvers.  All others are ignored as the values are only used
@@ -85,85 +67,65 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
       bool reset_features = false;
       unsigned long int hwcap_mask = 0UL;
       unsigned long long stfle_bits0_mask = 0ULL;
+      bool disable = t.disable;
 
-      if ((*feature == 'z' || *feature == 'a'))
+      if (tunable_str_comma_strcmp_cte (&t, "zEC12")
+	  || tunable_str_comma_strcmp_cte (&t, "arch10"))
+	{
+	  reset_features = true;
+	  disable = true;
+	  hwcap_mask = HWCAP_S390_VXRS | HWCAP_S390_VXRS_EXT
+	    | HWCAP_S390_VXRS_EXT2;
+	  stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
+	}
+      else if (tunable_str_comma_strcmp_cte (&t, "z13")
+	       || tunable_str_comma_strcmp_cte (&t, "arch11"))
+	{
+	  reset_features = true;
+	  disable = true;
+	  hwcap_mask = HWCAP_S390_VXRS_EXT | HWCAP_S390_VXRS_EXT2;
+	  stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
+	}
+      else if (tunable_str_comma_strcmp_cte (&t, "z14")
+	       || tunable_str_comma_strcmp_cte (&t, "arch12"))
+	{
+	  reset_features = true;
+	  disable = true;
+	  hwcap_mask = HWCAP_S390_VXRS_EXT2;
+	  stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
+	}
+      else if (tunable_str_comma_strcmp_cte (&t, "z15")
+	       || tunable_str_comma_strcmp_cte (&t, "z16")
+	       || tunable_str_comma_strcmp_cte (&t, "arch13")
+	       || tunable_str_comma_strcmp_cte (&t, "arch14"))
 	{
-	  if ((feature_len == 5 && *feature == 'z'
-	       && memcmp (feature, "zEC12", 5) == 0)
-	      || (feature_len == 6 && *feature == 'a'
-		  && memcmp (feature, "arch10", 6) == 0))
-	    {
-	      reset_features = true;
-	      disable = true;
-	      hwcap_mask = HWCAP_S390_VXRS | HWCAP_S390_VXRS_EXT
-		| HWCAP_S390_VXRS_EXT2;
-	      stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
-	    }
-	  else if ((feature_len == 3 && *feature == 'z'
-		    && memcmp (feature, "z13", 3) == 0)
-		   || (feature_len == 6 && *feature == 'a'
-		       && memcmp (feature, "arch11", 6) == 0))
-	    {
-	      reset_features = true;
-	      disable = true;
-	      hwcap_mask = HWCAP_S390_VXRS_EXT | HWCAP_S390_VXRS_EXT2;
-	      stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
-	    }
-	  else if ((feature_len == 3 && *feature == 'z'
-		    && memcmp (feature, "z14", 3) == 0)
-		   || (feature_len == 6 && *feature == 'a'
-		       && memcmp (feature, "arch12", 6) == 0))
-	    {
-	      reset_features = true;
-	      disable = true;
-	      hwcap_mask = HWCAP_S390_VXRS_EXT2;
-	      stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
-	    }
-	  else if ((feature_len == 3 && *feature == 'z'
-		    && (memcmp (feature, "z15", 3) == 0
-			|| memcmp (feature, "z16", 3) == 0))
-		   || (feature_len == 6
-		       && (memcmp (feature, "arch13", 6) == 0
-			   || memcmp (feature, "arch14", 6) == 0)))
-	    {
-	      /* For z15 or newer we don't have to disable something,
-		 but we have to reset to the original values.  */
-	      reset_features = true;
-	    }
+	  /* For z15 or newer we don't have to disable something, but we have
+	     to reset to the original values.  */
+	  reset_features = true;
 	}
-      else if (*feature == 'H')
+      else if (tunable_str_comma_strcmp_cte (&t, "HWCAP_S390_VXRS"))
 	{
-	  if (feature_len == 15
-	      && memcmp (feature, "HWCAP_S390_VXRS", 15) == 0)
-	    {
-	      hwcap_mask = HWCAP_S390_VXRS;
-	      if (disable)
-		hwcap_mask |= HWCAP_S390_VXRS_EXT | HWCAP_S390_VXRS_EXT2;
-	    }
-	  else if (feature_len == 19
-		   && memcmp (feature, "HWCAP_S390_VXRS_EXT", 19) == 0)
-	    {
-	      hwcap_mask = HWCAP_S390_VXRS_EXT;
-	      if (disable)
-		hwcap_mask |= HWCAP_S390_VXRS_EXT2;
-	      else
-		hwcap_mask |= HWCAP_S390_VXRS;
-	    }
-	  else if (feature_len == 20
-		   && memcmp (feature, "HWCAP_S390_VXRS_EXT2", 20) == 0)
-	    {
-	      hwcap_mask = HWCAP_S390_VXRS_EXT2;
-	      if (!disable)
-		hwcap_mask |= HWCAP_S390_VXRS | HWCAP_S390_VXRS_EXT;
-	    }
+	  hwcap_mask = HWCAP_S390_VXRS;
+	  if (t.disable)
+	    hwcap_mask |= HWCAP_S390_VXRS_EXT | HWCAP_S390_VXRS_EXT2;
 	}
-      else if (*feature == 'S')
+      else if (tunable_str_comma_strcmp_cte (&t, "HWCAP_S390_VXRS_EXT"))
 	{
-	  if (feature_len == 10
-	      && memcmp (feature, "STFLE_MIE3", 10) == 0)
-	    {
-	      stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
-	    }
+	  hwcap_mask = HWCAP_S390_VXRS_EXT;
+	  if (t.disable)
+	    hwcap_mask |= HWCAP_S390_VXRS_EXT2;
+	  else
+	    hwcap_mask |= HWCAP_S390_VXRS;
+	}
+      else if (tunable_str_comma_strcmp_cte (&t, "HWCAP_S390_VXRS_EXT2"))
+	{
+	  hwcap_mask = HWCAP_S390_VXRS_EXT2;
+	  if (!t.disable)
+	    hwcap_mask |= HWCAP_S390_VXRS | HWCAP_S390_VXRS_EXT;
+	}
+      else if (tunable_str_comma_strcmp_cte (&t, "STFLE_MIE3"))
+	{
+	  stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
 	}
 
       /* Perform the actions determined above.  */
@@ -187,14 +149,7 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
 	  else
 	    cpu_features_curr.stfle_bits[0] |= stfle_bits0_mask;
 	}
-
-      /* Jump over current token ... */
-      token += token_len;
-
-      /* ... and skip token separator for next round.  */
-      if (*token == ',') token++;
     }
-  while (*token != '\0');
 
   /* Copy back the features after checking that no unsupported features were
      enabled by user.  */
diff --git a/sysdeps/unix/sysv/linux/aarch64/cpu-features.c b/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
index 233d5b2407..9b76cb89c7 100644
--- a/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
+++ b/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
@@ -16,10 +16,12 @@
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
+#include <array_length.h>
 #include <cpu-features.h>
 #include <sys/auxv.h>
 #include <elf/dl-hwcaps.h>
 #include <sys/prctl.h>
+#include <dl-tunables-parse.h>
 
 #define DCZID_DZP_MASK (1 << 4)
 #define DCZID_BS_MASK (0xf)
@@ -33,28 +35,32 @@
 struct cpu_list
 {
   const char *name;
+  size_t len;
   uint64_t midr;
 };
 
-static struct cpu_list cpu_list[] = {
-      {"falkor",	 0x510FC000},
-      {"thunderxt88",	 0x430F0A10},
-      {"thunderx2t99",   0x431F0AF0},
-      {"thunderx2t99p1", 0x420F5160},
-      {"phecda",	 0x680F0000},
-      {"ares",		 0x411FD0C0},
-      {"emag",		 0x503F0001},
-      {"kunpeng920", 	 0x481FD010},
-      {"a64fx",		 0x460F0010},
-      {"generic", 	 0x0}
+static const struct cpu_list cpu_list[] =
+{
+#define CPU_LIST_ENTRY(__str, __num) { __str, sizeof (__str) - 1, __num }
+  CPU_LIST_ENTRY ("falkor",         0x510FC000),
+  CPU_LIST_ENTRY ("thunderxt88",    0x430F0A10),
+  CPU_LIST_ENTRY ("thunderx2t99",   0x431F0AF0),
+  CPU_LIST_ENTRY ("thunderx2t99p1", 0x420F5160),
+  CPU_LIST_ENTRY ("phecda",         0x680F0000),
+  CPU_LIST_ENTRY ("ares",           0x411FD0C0),
+  CPU_LIST_ENTRY ("emag",           0x503F0001),
+  CPU_LIST_ENTRY ("kunpeng920",     0x481FD010),
+  CPU_LIST_ENTRY ("a64fx",          0x460F0010),
+  CPU_LIST_ENTRY ("generic",        0x0),
 };
 
 static uint64_t
-get_midr_from_mcpu (const char *mcpu)
+get_midr_from_mcpu (const struct tunable_str_t *mcpu)
 {
-  for (int i = 0; i < sizeof (cpu_list) / sizeof (struct cpu_list); i++)
-    if (strcmp (mcpu, cpu_list[i].name) == 0)
+  for (int i = 0; i < array_length (cpu_list); i++) {
+    if (tunable_strcmp (mcpu, cpu_list[i].name, cpu_list[i].len))
       return cpu_list[i].midr;
+  }
 
   return UINT64_MAX;
 }
@@ -65,7 +71,9 @@ init_cpu_features (struct cpu_features *cpu_features)
   register uint64_t midr = UINT64_MAX;
 
   /* Get the tunable override.  */
-  const char *mcpu = TUNABLE_GET (glibc, cpu, name, const char *, NULL);
+  const struct tunable_str_t *mcpu = TUNABLE_GET (glibc, cpu, name,
+						  struct tunable_str_t *,
+						  NULL);
   if (mcpu != NULL)
     midr = get_midr_from_mcpu (mcpu);
 
diff --git a/sysdeps/unix/sysv/linux/powerpc/cpu-features.c b/sysdeps/unix/sysv/linux/powerpc/cpu-features.c
index 7c6e20e702..390b3fd11a 100644
--- a/sysdeps/unix/sysv/linux/powerpc/cpu-features.c
+++ b/sysdeps/unix/sysv/linux/powerpc/cpu-features.c
@@ -20,6 +20,7 @@
 #include <stdint.h>
 #include <cpu-features.h>
 #include <elf/dl-tunables.h>
+#include <dl-tunables-parse.h>
 #include <unistd.h>
 #include <string.h>
 
@@ -43,41 +44,26 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
   struct cpu_features *cpu_features = &GLRO(dl_powerpc_cpu_features);
   unsigned long int tcbv_hwcap = cpu_features->hwcap;
   unsigned long int tcbv_hwcap2 = cpu_features->hwcap2;
-  const char *token = valp->strval;
-  do
+
+  struct tunable_str_comma_state_t ts;
+  tunable_str_comma_init (&ts, valp);
+
+  struct tunable_str_comma_t t;
+  while (tunable_str_comma_next (&ts, &t))
     {
-      const char *token_end, *feature;
-      bool disable;
-      size_t token_len, i, feature_len, offset = 0;
-      /* Find token separator or end of string.  */
-      for (token_end = token; *token_end != ','; token_end++)
-	if (*token_end == '\0')
-	  break;
+      if (t.len == 0)
+	continue;
 
-      /* Determine feature.  */
-      token_len = token_end - token;
-      if (*token == '-')
-	{
-	  disable = true;
-	  feature = token + 1;
-	  feature_len = token_len - 1;
-	}
-      else
-	{
-	  disable = false;
-	  feature = token;
-	  feature_len = token_len;
-	}
-      for (i = 0; i < array_length (hwcap_tunables); ++i)
+      size_t offset = 0;
+      for (int i = 0; i < array_length (hwcap_tunables); ++i)
 	{
 	  const char *hwcap_name = hwcap_names + offset;
 	  size_t hwcap_name_len = strlen (hwcap_name);
 	  /* Check the tunable name on the supported list.  */
-	  if (hwcap_name_len == feature_len
-	      && memcmp (feature, hwcap_name, feature_len) == 0)
+	  if (tunable_str_comma_strcmp (&t, hwcap_name, hwcap_name_len))
 	    {
 	      /* Update the hwcap and hwcap2 bits.  */
-	      if (disable)
+	      if (t.disable)
 		{
 		  /* Id is 1 for hwcap2 tunable.  */
 		  if (hwcap_tunables[i].id)
@@ -98,12 +84,7 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
 	    }
 	  offset += hwcap_name_len + 1;
 	}
-	token += token_len;
-	/* ... and skip token separator for next round.  */
-	if (*token == ',')
-	  token++;
     }
-  while (*token != '\0');
 }
 
 static inline void
diff --git a/sysdeps/unix/sysv/linux/powerpc/tst-hwcap-tunables.c b/sysdeps/unix/sysv/linux/powerpc/tst-hwcap-tunables.c
index 2631016a3a..049164f841 100644
--- a/sysdeps/unix/sysv/linux/powerpc/tst-hwcap-tunables.c
+++ b/sysdeps/unix/sysv/linux/powerpc/tst-hwcap-tunables.c
@@ -110,7 +110,11 @@ do_test (int argc, char *argv[])
 	run_test ("-arch_2_06", "__memcpy_power7");
       if (hwcap & PPC_FEATURE_ARCH_2_05)
 	run_test ("-arch_2_06,-arch_2_05","__memcpy_power6");
-      run_test ("-arch_2_06,-arch_2_05,-power5+,-power5,-power4", "__memcpy_power4");
+      run_test ("-arch_2_06,-arch_2_05,-power5+,-power5,-power4",
+		"__memcpy_power4");
+      /* Also run with valid, but empty settings.  */
+      run_test (",-,-arch_2_06,-arch_2_05,-power5+,-power5,,-power4,-",
+		"__memcpy_power4");
     }
   else
     {
diff --git a/sysdeps/x86/Makefile b/sysdeps/x86/Makefile
index 917c26f116..a64e5f002a 100644
--- a/sysdeps/x86/Makefile
+++ b/sysdeps/x86/Makefile
@@ -12,7 +12,8 @@ CFLAGS-get-cpuid-feature-leaf.o += $(no-stack-protector)
 
 tests += tst-get-cpu-features tst-get-cpu-features-static \
 	 tst-cpu-features-cpuinfo tst-cpu-features-cpuinfo-static \
-	 tst-cpu-features-supports tst-cpu-features-supports-static
+	 tst-cpu-features-supports tst-cpu-features-supports-static \
+	 tst-hwcap-tunables
 tests-static += tst-get-cpu-features-static \
 		tst-cpu-features-cpuinfo-static \
 		tst-cpu-features-supports-static
@@ -65,6 +66,7 @@ $(objpfx)tst-isa-level-1.out: $(objpfx)tst-isa-level-mod-1-baseline.so \
 endif
 tst-ifunc-isa-2-ENV = GLIBC_TUNABLES=glibc.cpu.hwcaps=-SSE4_2,-AVX,-AVX2,-AVX512F
 tst-ifunc-isa-2-static-ENV = $(tst-ifunc-isa-2-ENV)
+tst-hwcap-tunables-ARGS = -- $(host-test-program-cmd)
 endif
 
 ifeq ($(subdir),math)
diff --git a/sysdeps/x86/cpu-tunables.c b/sysdeps/x86/cpu-tunables.c
index 5697885226..0608209768 100644
--- a/sysdeps/x86/cpu-tunables.c
+++ b/sysdeps/x86/cpu-tunables.c
@@ -24,11 +24,12 @@
 #include <string.h>
 #include <cpu-features.h>
 #include <ldsodefs.h>
+#include <dl-tunables-parse.h>
 #include <dl-symbol-redir-ifunc.h>
 
 #define CHECK_GLIBC_IFUNC_CPU_OFF(f, cpu_features, name, len)		\
   _Static_assert (sizeof (#name) - 1 == len, #name " != " #len);	\
-  if (memcmp (f, #name, len) == 0)					\
+  if (tunable_str_comma_strcmp_cte (&f, #name))				\
     {									\
       CPU_FEATURE_UNSET (cpu_features, name)				\
       break;								\
@@ -38,7 +39,7 @@
    which isn't available.  */
 #define CHECK_GLIBC_IFUNC_PREFERRED_OFF(f, cpu_features, name, len)	\
   _Static_assert (sizeof (#name) - 1 == len, #name " != " #len);	\
-  if (memcmp (f, #name, len) == 0)					\
+  if (tunable_str_comma_strcmp_cte (&f, #name) == 0)			\
     {									\
       cpu_features->preferred[index_arch_##name]			\
 	&= ~bit_arch_##name;						\
@@ -46,12 +47,11 @@
     }
 
 /* Enable/disable a preferred feature NAME.  */
-#define CHECK_GLIBC_IFUNC_PREFERRED_BOTH(f, cpu_features, name,	\
-					  disable, len)			\
+#define CHECK_GLIBC_IFUNC_PREFERRED_BOTH(f, cpu_features, name,	len)	\
   _Static_assert (sizeof (#name) - 1 == len, #name " != " #len);	\
-  if (memcmp (f, #name, len) == 0)					\
+  if (tunable_str_comma_strcmp_cte (&f, #name))				\
     {									\
-      if (disable)							\
+      if (f.disable)							\
 	cpu_features->preferred[index_arch_##name] &= ~bit_arch_##name;	\
       else								\
 	cpu_features->preferred[index_arch_##name] |= bit_arch_##name;	\
@@ -61,11 +61,11 @@
 /* Enable/disable a preferred feature NAME.  Enable a preferred feature
    only if the feature NEED is usable.  */
 #define CHECK_GLIBC_IFUNC_PREFERRED_NEED_BOTH(f, cpu_features, name,	\
-					       need, disable, len)	\
+					      need, len)		\
   _Static_assert (sizeof (#name) - 1 == len, #name " != " #len);	\
-  if (memcmp (f, #name, len) == 0)					\
+  if (tunable_str_comma_strcmp_cte (&f, #name))				\
     {									\
-      if (disable)							\
+      if (f.disable)							\
 	cpu_features->preferred[index_arch_##name] &= ~bit_arch_##name;	\
       else if (CPU_FEATURE_USABLE_P (cpu_features, need))		\
 	cpu_features->preferred[index_arch_##name] |= bit_arch_##name;	\
@@ -93,38 +93,20 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
      NOTE: the IFUNC selection may change over time.  Please check all
      multiarch implementations when experimenting.  */
 
-  const char *p = valp->strval, *c;
   struct cpu_features *cpu_features = &GLRO(dl_x86_cpu_features);
-  size_t len;
 
-  do
-    {
-      const char *n;
-      bool disable;
-      size_t nl;
-
-      for (c = p; *c != ','; c++)
-	if (*c == '\0')
-	  break;
+  struct tunable_str_comma_state_t ts;
+  tunable_str_comma_init (&ts, valp);
 
-      len = c - p;
-      disable = *p == '-';
-      if (disable)
-	{
-	  n = p + 1;
-	  nl = len - 1;
-	}
-      else
-	{
-	  n = p;
-	  nl = len;
-	}
-      switch (nl)
+  struct tunable_str_comma_t n;
+  while (tunable_str_comma_next (&ts, &n))
+    {
+      switch (n.len)
 	{
 	default:
 	  break;
 	case 3:
-	  if (disable)
+	  if (n.disable)
 	    {
 	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, AVX, 3);
 	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, CX8, 3);
@@ -135,7 +117,7 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
 	    }
 	  break;
 	case 4:
-	  if (disable)
+	  if (n.disable)
 	    {
 	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, AVX2, 4);
 	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, BMI1, 4);
@@ -149,7 +131,7 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
 	    }
 	  break;
 	case 5:
-	  if (disable)
+	  if (n.disable)
 	    {
 	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, LZCNT, 5);
 	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, MOVBE, 5);
@@ -159,12 +141,12 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
 	    }
 	  break;
 	case 6:
-	  if (disable)
+	  if (n.disable)
 	    {
 	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, POPCNT, 6);
 	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, SSE4_1, 6);
 	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, SSE4_2, 6);
-	      if (memcmp (n, "XSAVEC", 6) == 0)
+	      if (memcmp (n.str, "XSAVEC", 6) == 0)
 		{
 		  /* Update xsave_state_size to XSAVE state size.  */
 		  cpu_features->xsave_state_size
@@ -174,14 +156,14 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
 	    }
 	  break;
 	case 7:
-	  if (disable)
+	  if (n.disable)
 	    {
 	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, AVX512F, 7);
 	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, OSXSAVE, 7);
 	    }
 	  break;
 	case 8:
-	  if (disable)
+	  if (n.disable)
 	    {
 	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, AVX512CD, 8);
 	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, AVX512BW, 8);
@@ -190,86 +172,72 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
 	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, AVX512PF, 8);
 	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, AVX512VL, 8);
 	    }
-	  CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features, Slow_BSF,
-					    disable, 8);
+	  CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features, Slow_BSF, 8);
 	  break;
 	case 11:
 	    {
-	      CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features,
-						Prefer_ERMS,
-						disable, 11);
-	      CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features,
-						Prefer_FSRM,
-						disable, 11);
+	      CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features, Prefer_ERMS,
+						11);
+	      CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features, Prefer_FSRM,
+						11);
 	      CHECK_GLIBC_IFUNC_PREFERRED_NEED_BOTH (n, cpu_features,
 						     Slow_SSE4_2,
 						     SSE4_2,
-						     disable, 11);
+						     11);
 	    }
 	  break;
 	case 15:
 	    {
 	      CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features,
-						Fast_Rep_String,
-						disable, 15);
+						Fast_Rep_String, 15);
 	    }
 	  break;
 	case 16:
 	    {
 	      CHECK_GLIBC_IFUNC_PREFERRED_NEED_BOTH
-		(n, cpu_features, Prefer_No_AVX512, AVX512F,
-		 disable, 16);
+		(n, cpu_features, Prefer_No_AVX512, AVX512F, 16);
 	    }
 	  break;
 	case 18:
 	    {
 	      CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features,
-						Fast_Copy_Backward,
-						disable, 18);
+						Fast_Copy_Backward, 18);
 	    }
 	  break;
 	case 19:
 	    {
 	      CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features,
-						Fast_Unaligned_Load,
-						disable, 19);
+						Fast_Unaligned_Load, 19);
 	      CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features,
-						Fast_Unaligned_Copy,
-						disable, 19);
+						Fast_Unaligned_Copy, 19);
 	    }
 	  break;
 	case 20:
 	    {
 	      CHECK_GLIBC_IFUNC_PREFERRED_NEED_BOTH
-		(n, cpu_features, Prefer_No_VZEROUPPER, AVX, disable,
-		 20);
+		(n, cpu_features, Prefer_No_VZEROUPPER, AVX, 20);
 	    }
 	  break;
 	case 23:
 	    {
 	      CHECK_GLIBC_IFUNC_PREFERRED_NEED_BOTH
-		(n, cpu_features, AVX_Fast_Unaligned_Load, AVX,
-		 disable, 23);
+		(n, cpu_features, AVX_Fast_Unaligned_Load, AVX, 23);
 	    }
 	  break;
 	case 24:
 	    {
 	      CHECK_GLIBC_IFUNC_PREFERRED_NEED_BOTH
-		(n, cpu_features, MathVec_Prefer_No_AVX512, AVX512F,
-		 disable, 24);
+		(n, cpu_features, MathVec_Prefer_No_AVX512, AVX512F, 24);
 	    }
 	  break;
 	case 26:
 	    {
 	      CHECK_GLIBC_IFUNC_PREFERRED_NEED_BOTH
-		(n, cpu_features, Prefer_PMINUB_for_stringop, SSE2,
-		 disable, 26);
+		(n, cpu_features, Prefer_PMINUB_for_stringop, SSE2, 26);
 	    }
 	  break;
 	}
-      p += len + 1;
     }
-  while (*c != '\0');
 }
 
 #if CET_ENABLED
@@ -277,11 +245,11 @@ attribute_hidden
 void
 TUNABLE_CALLBACK (set_x86_ibt) (tunable_val_t *valp)
 {
-  if (memcmp (valp->strval, "on", sizeof ("on")) == 0)
+  if (tunable_strcmp_cte (valp, "on"))
     GL(dl_x86_feature_control).ibt = cet_always_on;
-  else if (memcmp (valp->strval, "off", sizeof ("off")) == 0)
+  else if (tunable_strcmp_cte (valp, "off"))
     GL(dl_x86_feature_control).ibt = cet_always_off;
-  else if (memcmp (valp->strval, "permissive", sizeof ("permissive")) == 0)
+  else if (tunable_strcmp_cte (valp, "permissive"))
     GL(dl_x86_feature_control).ibt = cet_permissive;
 }
 
@@ -289,11 +257,11 @@ attribute_hidden
 void
 TUNABLE_CALLBACK (set_x86_shstk) (tunable_val_t *valp)
 {
-  if (memcmp (valp->strval, "on", sizeof ("on")) == 0)
+  if (tunable_strcmp_cte (valp, "on"))
     GL(dl_x86_feature_control).shstk = cet_always_on;
-  else if (memcmp (valp->strval, "off", sizeof ("off")) == 0)
+  else if (tunable_strcmp_cte (valp, "off"))
     GL(dl_x86_feature_control).shstk = cet_always_off;
-  else if (memcmp (valp->strval, "permissive", sizeof ("permissive")) == 0)
+  else if (tunable_strcmp_cte (valp, "permissive"))
     GL(dl_x86_feature_control).shstk = cet_permissive;
 }
 #endif
diff --git a/sysdeps/x86/tst-hwcap-tunables.c b/sysdeps/x86/tst-hwcap-tunables.c
new file mode 100644
index 0000000000..0d0a45fa93
--- /dev/null
+++ b/sysdeps/x86/tst-hwcap-tunables.c
@@ -0,0 +1,148 @@
+/* Tests for powerpc GLIBC_TUNABLES=glibc.cpu.hwcaps filter.
+   Copyright (C) 2023 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
+   <http://www.gnu.org/licenses/>.  */
+
+#include <array_length.h>
+#include <getopt.h>
+#include <ifunc-impl-list.h>
+#include <spawn.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <intprops.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/xunistd.h>
+#include <support/capture_subprocess.h>
+
+/* Nonzero if the program gets called via `exec'.  */
+#define CMDLINE_OPTIONS \
+  { "restart", no_argument, &restart, 1 },
+static int restart;
+
+/* Disable everything.  */
+static const char *test_1[] =
+{
+  "__memcpy_avx512_no_vzeroupper",
+  "__memcpy_avx512_unaligned",
+  "__memcpy_avx512_unaligned_erms",
+  "__memcpy_evex_unaligned",
+  "__memcpy_evex_unaligned_erms",
+  "__memcpy_avx_unaligned",
+  "__memcpy_avx_unaligned_erms",
+  "__memcpy_avx_unaligned_rtm",
+  "__memcpy_avx_unaligned_erms_rtm",
+  "__memcpy_ssse3",
+};
+
+static const struct test_t
+{
+  const char *env;
+  const char *const *funcs;
+  size_t nfuncs;
+} tests[] =
+{
+  {
+    /* Disable everything.  */
+    "-Prefer_ERMS,-Prefer_FSRM,-AVX,-AVX2,-AVX_Usable,-AVX2_Usable,"
+    "-AVX512F_Usable,-SSE4_1,-SSE4_2,-SSSE3,-Fast_Unaligned_Load,-ERMS,"
+    "-AVX_Fast_Unaligned_Load",
+    test_1,
+    array_length (test_1)
+  },
+  {
+    /* Same as before, but with some empty suboptions.  */
+    ",-,-Prefer_ERMS,-Prefer_FSRM,-AVX,-AVX2,-AVX_Usable,-AVX2_Usable,"
+    "-AVX512F_Usable,-SSE4_1,-SSE4_2,,-SSSE3,-Fast_Unaligned_Load,,-,-ERMS,"
+    "-AVX_Fast_Unaligned_Load,-,",
+    test_1,
+    array_length (test_1)
+  }
+};
+
+/* Called on process re-execution.  */
+_Noreturn static void
+handle_restart (int ntest)
+{
+  struct libc_ifunc_impl impls[32];
+  int cnt = __libc_ifunc_impl_list ("memcpy", impls, array_length (impls));
+  if (cnt == 0)
+    _exit (EXIT_SUCCESS);
+  TEST_VERIFY_EXIT (cnt >= 1);
+  for (int i = 0; i < cnt; i++)
+    {
+      for (int f = 0; f < tests[ntest].nfuncs; f++)
+	{
+	  if (strcmp (impls[i].name, tests[ntest].funcs[f]) == 0)
+	    TEST_COMPARE (impls[i].usable, false);
+	}
+    }
+
+  _exit (EXIT_SUCCESS);
+}
+
+static int
+do_test (int argc, char *argv[])
+{
+  /* We must have either:
+     - One our fource parameters left if called initially:
+       + path to ld.so         optional
+       + "--library-path"      optional
+       + the library path      optional
+       + the application name
+       + the test to check  */
+
+  TEST_VERIFY_EXIT (argc == 2 || argc == 5);
+
+  if (restart)
+    handle_restart (atoi (argv[1]));
+
+  char nteststr[INT_BUFSIZE_BOUND (int)];
+
+  char *spargv[10];
+  {
+    int i = 0;
+    for (; i < argc - 1; i++)
+      spargv[i] = argv[i + 1];
+    spargv[i++] = (char *) "--direct";
+    spargv[i++] = (char *) "--restart";
+    spargv[i++] = nteststr;
+    spargv[i] = NULL;
+  }
+
+  for (int i = 0; i < array_length (tests); i++)
+    {
+      snprintf (nteststr, sizeof nteststr, "%d", i);
+
+      printf ("[%d] Spawned test for %s\n", i, tests[i].env);
+      char *tunable = xasprintf ("glibc.cpu.hwcaps=%s", tests[i].env);
+      setenv ("GLIBC_TUNABLES", tunable, 1);
+
+      struct support_capture_subprocess result
+	= support_capture_subprogram (spargv[0], spargv);
+      support_capture_subprocess_check (&result, "tst-tunables", 0,
+					sc_allow_stderr);
+      support_capture_subprocess_free (&result);
+
+      free (tunable);
+    }
+
+  return 0;
+}
+
+#define TEST_FUNCTION_ARGV do_test
+#include <support/test-driver.c>
-- 
2.34.1


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v3 12/19] elf: Ignore LD_PROFILE for setuid binaries
  2023-11-06 20:25 [PATCH v3 00/19] Improve loader environment variable handling Adhemerval Zanella
                   ` (10 preceding siblings ...)
  2023-11-06 20:25 ` [PATCH v3 11/19] elf: Do not duplicate the GLIBC_TUNABLES string Adhemerval Zanella
@ 2023-11-06 20:25 ` Adhemerval Zanella
  2023-11-20 22:47   ` Siddhesh Poyarekar
  2023-11-06 20:25 ` [PATCH v3 13/19] elf: Remove LD_PROFILE for static binaries Adhemerval Zanella
                   ` (7 subsequent siblings)
  19 siblings, 1 reply; 40+ messages in thread
From: Adhemerval Zanella @ 2023-11-06 20:25 UTC (permalink / raw)
  To: libc-alpha, Siddhesh Poyarekar

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
---
 elf/Makefile         |  3 +++
 elf/rtld.c           |  8 +++-----
 elf/tst-env-setuid.c | 12 +++++++++++-
 3 files changed, 17 insertions(+), 6 deletions(-)

diff --git a/elf/Makefile b/elf/Makefile
index 52981c19d0..08896bb895 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -2983,3 +2983,6 @@ $(objpfx)tst-dlclose-lazy.out: \
   $(objpfx)tst-dlclose-lazy-mod1.so $(objpfx)tst-dlclose-lazy-mod2.so
 
 tst-env-setuid-ARGS = -- $(host-test-program-cmd)
+
+# Reuse a module with a SONAME, to specific as the LD_PROFILE.
+$(objpfx)tst-env-setuid: $(objpfx)tst-sonamemove-runmod2.so
diff --git a/elf/rtld.c b/elf/rtld.c
index 51b6d9f326..a09cf2a9df 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -361,6 +361,7 @@ struct rtld_global_ro _rtld_global_ro attribute_relro =
     ._dl_fpu_control = _FPU_DEFAULT,
     ._dl_pagesize = EXEC_PAGESIZE,
     ._dl_inhibit_cache = 0,
+    ._dl_profile_output = "/var/tmp",
 
     /* Function pointers.  */
     ._dl_debug_printf = _dl_debug_printf,
@@ -2534,10 +2535,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;
@@ -2586,7 +2583,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;
 
diff --git a/elf/tst-env-setuid.c b/elf/tst-env-setuid.c
index ba295a6a14..76b8e1fb45 100644
--- a/elf/tst-env-setuid.c
+++ b/elf/tst-env-setuid.c
@@ -34,6 +34,9 @@ static char SETGID_CHILD[] = "setgid-child";
 
 #define FILTERED_VALUE   "some-filtered-value"
 #define UNFILTERED_VALUE "some-unfiltered-value"
+/* It assumes no other programs is being profile with a library with same
+   SONAME using the default folder.  */
+#define PROFILE_LIB      "tst-sonamemove-runmod2.so"
 
 struct envvar_t
 {
@@ -50,7 +53,7 @@ static const struct envvar_t filtered_envvars[] =
   { "LD_HWCAP_MASK",           FILTERED_VALUE },
   { "LD_LIBRARY_PATH",         FILTERED_VALUE },
   { "LD_PRELOAD",              FILTERED_VALUE },
-  { "LD_PROFILE",              FILTERED_VALUE },
+  { "LD_PROFILE",              "tst-sonamemove-runmod2.so" },
   { "MALLOC_ARENA_MAX",        FILTERED_VALUE },
   { "MALLOC_PERTURB_",         FILTERED_VALUE },
   { "MALLOC_TRACE",            FILTERED_VALUE },
@@ -87,6 +90,13 @@ test_child (void)
       ret |= !(env != NULL && strcmp (env, e->value) == 0);
     }
 
+  /* Also check if no profile file was created.  */
+  {
+    char *profilepath = xasprintf ("/var/tmp/%s.profile", PROFILE_LIB);
+    ret |= !access (profilepath, R_OK);
+    free (profilepath);
+  }
+
   return ret;
 }
 
-- 
2.34.1


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v3 13/19] elf: Remove LD_PROFILE for static binaries
  2023-11-06 20:25 [PATCH v3 00/19] Improve loader environment variable handling Adhemerval Zanella
                   ` (11 preceding siblings ...)
  2023-11-06 20:25 ` [PATCH v3 12/19] elf: Ignore LD_PROFILE for setuid binaries Adhemerval Zanella
@ 2023-11-06 20:25 ` Adhemerval Zanella
  2023-11-20 22:55   ` Siddhesh Poyarekar
  2023-11-06 20:25 ` [PATCH v3 14/19] elf: Ignore loader debug env vars for setuid Adhemerval Zanella
                   ` (6 subsequent siblings)
  19 siblings, 1 reply; 40+ messages in thread
From: Adhemerval Zanella @ 2023-11-06 20:25 UTC (permalink / raw)
  To: libc-alpha, Siddhesh Poyarekar

The _dl_non_dynamic_init does not parse LD_PROFILE, which does not
enable profile for dlopen objects.  Since dlopen is deprecated for
static objects, it is better to remove the support.

It also allows to trim down libc.a of profile support.

Checked on x86_64-linux-gnu.
---
 elf/Makefile                              | 10 ++--
 elf/dl-load.c                             | 10 ++--
 elf/dl-runtime.c                          | 12 ++---
 elf/dl-support.c                          |  9 ----
 elf/{dl-profstub.c => libc-dl-profstub.c} |  0
 include/dlfcn.h                           |  5 ++
 sysdeps/aarch64/dl-machine.h              |  4 +-
 sysdeps/aarch64/dl-trampoline.S           |  2 +-
 sysdeps/alpha/dl-machine.h                |  6 ++-
 sysdeps/alpha/dl-trampoline.S             |  4 ++
 sysdeps/arm/dl-machine.h                  |  4 +-
 sysdeps/arm/dl-trampoline.S               |  2 +-
 sysdeps/hppa/dl-machine.h                 | 36 +++++++------
 sysdeps/hppa/dl-trampoline.S              |  2 +
 sysdeps/i386/dl-machine.h                 |  2 +
 sysdeps/i386/dl-trampoline.S              |  2 +-
 sysdeps/ia64/dl-machine.h                 | 10 ++--
 sysdeps/ia64/dl-trampoline.S              |  2 +-
 sysdeps/loongarch/dl-machine.h            |  6 ++-
 sysdeps/loongarch/dl-trampoline.h         |  2 +
 sysdeps/m68k/dl-machine.h                 |  4 +-
 sysdeps/m68k/dl-trampoline.S              |  2 +
 sysdeps/powerpc/powerpc32/dl-machine.c    |  2 +-
 sysdeps/powerpc/powerpc32/dl-machine.h    | 10 ++--
 sysdeps/powerpc/powerpc32/dl-trampoline.S |  2 +-
 sysdeps/powerpc/powerpc64/dl-machine.h    | 20 ++++---
 sysdeps/powerpc/powerpc64/dl-trampoline.S |  2 +-
 sysdeps/s390/s390-32/dl-machine.h         |  8 +--
 sysdeps/s390/s390-32/dl-trampoline.h      |  2 +-
 sysdeps/s390/s390-64/dl-machine.h         |  8 +--
 sysdeps/s390/s390-64/dl-trampoline.h      |  2 +-
 sysdeps/sh/dl-machine.h                   |  2 +
 sysdeps/sh/dl-trampoline.S                |  2 +
 sysdeps/sparc/sparc32/dl-machine.h        |  4 +-
 sysdeps/sparc/sparc32/dl-trampoline.S     |  2 +
 sysdeps/sparc/sparc64/dl-machine.h        |  4 +-
 sysdeps/sparc/sparc64/dl-trampoline.S     |  2 +
 sysdeps/x86_64/dl-machine.h               |  2 +
 sysdeps/x86_64/dl-trampoline.S            | 64 ++++++++++++-----------
 39 files changed, 165 insertions(+), 109 deletions(-)
 rename elf/{dl-profstub.c => libc-dl-profstub.c} (100%)

diff --git a/elf/Makefile b/elf/Makefile
index 08896bb895..d17ff5424e 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -37,12 +37,12 @@ routines = \
   dl-iteratephdr \
   dl-libc \
   dl-origin \
-  dl-profstub \
   dl-reloc-static-pie \
   dl-support \
   dl-sym \
   dl-sysdep \
   enbl-secure \
+  libc-dl-profstub  \
   libc-dl_find_object \
   libc_early_init \
   rtld_static_init \
@@ -72,7 +72,6 @@ dl-routines = \
   dl-open \
   dl-origin \
   dl-printf \
-  dl-profile \
   dl-reloc \
   dl-runtime \
   dl-scope \
@@ -117,7 +116,11 @@ elide-routines.os = \
   # elide-routines.os
 
 # These object files are only included in the dynamically-linked libc.
-shared-only-routines = libc-dl_find_object
+shared-only-routines = \
+  libc-dl-profile \
+  libc-dl-profstub \
+  libc-dl_find_object \
+  # shared-only-routines
 
 # ld.so uses those routines, plus some special stuff for being the program
 # interpreter and operating independent of libc.
@@ -135,6 +138,7 @@ rtld-routines = \
   dl-libc_freeres \
   dl-minimal \
   dl-mutex \
+  dl-profile \
   dl-sysdep \
   dl-usage \
   rtld \
diff --git a/elf/dl-load.c b/elf/dl-load.c
index 2923b1141d..7356a4fe48 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -1443,11 +1443,6 @@ cannot enable executable stack as shared object requires");
      name by which the DSO is actually known.  Add that as well.  */
   if (__glibc_unlikely (origname != NULL))
     add_name_to_object (l, origname);
-#else
-  /* Audit modules only exist when linking is dynamic so ORIGNAME
-     cannot be non-NULL.  */
-  assert (origname == NULL);
-#endif
 
   /* When we profile the SONAME might be needed for something else but
      loading.  Add it right away.  */
@@ -1455,6 +1450,11 @@ cannot enable executable stack as shared object requires");
       && l->l_info[DT_SONAME] != NULL)
     add_name_to_object (l, ((const char *) D_PTR (l, l_info[DT_STRTAB])
 			    + l->l_info[DT_SONAME]->d_un.d_val));
+#else
+  /* Audit modules only exist when linking is dynamic so ORIGNAME
+     cannot be non-NULL.  */
+  assert (origname == NULL);
+#endif
 
   /* If we have newly loaded libc.so, update the namespace
      description.  */
diff --git a/elf/dl-runtime.c b/elf/dl-runtime.c
index 32a8bfcf74..fe7deda32a 100644
--- a/elf/dl-runtime.c
+++ b/elf/dl-runtime.c
@@ -162,14 +162,14 @@ _dl_fixup (
   return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);
 }
 
-#ifndef PROF
+#if !defined PROF && defined SHARED
 DL_FIXUP_VALUE_TYPE
 __attribute ((noinline))
 DL_ARCH_FIXUP_ATTRIBUTE
 _dl_profile_fixup (
-#ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS
+# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS
 		   ELF_MACHINE_RUNTIME_FIXUP_ARGS,
-#endif
+# endif
 		   struct link_map *l, ElfW(Word) reloc_arg,
 		   ElfW(Addr) retaddr, void *regs, long int *framesizep)
 {
@@ -309,14 +309,12 @@ _dl_profile_fixup (
       /* And now perhaps the relocation addend.  */
       value = elf_machine_plt_value (l, reloc, value);
 
-#ifdef SHARED
       /* Auditing checkpoint: we have a new binding.  Provide the
 	 auditing libraries the possibility to change the value and
 	 tell us whether further auditing is wanted.  */
       if (defsym != NULL && GLRO(dl_naudit) > 0)
 	_dl_audit_symbind (l, reloc_result, reloc, defsym, &value, result,
 			   true);
-#endif
 
       /* Store the result for later runs.  */
       if (__glibc_likely (! GLRO(dl_bind_not)))
@@ -335,11 +333,9 @@ _dl_profile_fixup (
   long int framesize = -1;
 
 
-#ifdef SHARED
   /* Auditing checkpoint: report the PLT entering and allow the
      auditors to change the value.  */
   _dl_audit_pltenter (l, reloc_result, &value, regs, &framesize);
-#endif
 
   /* Store the frame size information.  */
   *framesizep = framesize;
@@ -349,4 +345,4 @@ _dl_profile_fixup (
   return value;
 }
 
-#endif /* PROF */
+#endif /* !defined PROF && defined SHARED */
diff --git a/elf/dl-support.c b/elf/dl-support.c
index 44a54dea07..31a608df87 100644
--- a/elf/dl-support.c
+++ b/elf/dl-support.c
@@ -60,10 +60,6 @@ int _dl_dynamic_weak;
 /* If nonzero print warnings about problematic situations.  */
 int _dl_verbose;
 
-/* We never do profiling.  */
-const char *_dl_profile;
-const char *_dl_profile_output;
-
 /* Names of shared object for which the RUNPATHs and RPATHs should be
    ignored.  */
 const char *_dl_inhibit_rpath;
@@ -301,11 +297,6 @@ _dl_non_dynamic_init (void)
 
   _dl_dynamic_weak = *(getenv ("LD_DYNAMIC_WEAK") ?: "") == '\0';
 
-  _dl_profile_output = getenv ("LD_PROFILE_OUTPUT");
-  if (_dl_profile_output == NULL || _dl_profile_output[0] == '\0')
-    _dl_profile_output
-      = &"/var/tmp\0/var/profile"[__libc_enable_secure ? 9 : 0];
-
   if (__libc_enable_secure)
     {
       static const char unsecure_envvars[] =
diff --git a/elf/dl-profstub.c b/elf/libc-dl-profstub.c
similarity index 100%
rename from elf/dl-profstub.c
rename to elf/libc-dl-profstub.c
diff --git a/include/dlfcn.h b/include/dlfcn.h
index ae25f05303..a44420fa37 100644
--- a/include/dlfcn.h
+++ b/include/dlfcn.h
@@ -135,5 +135,10 @@ extern int __dladdr1 (const void *address, Dl_info *info,
 extern int __dlinfo (void *handle, int request, void *arg);
 extern char *__dlerror (void);
 
+#ifndef SHARED
+# undef DL_CALL_FCT
+# define DL_CALL_FCT(fctp, args) ((fctp) args)
+#endif
+
 #endif
 #endif
diff --git a/sysdeps/aarch64/dl-machine.h b/sysdeps/aarch64/dl-machine.h
index 4170b9269f..a56eb96a79 100644
--- a/sysdeps/aarch64/dl-machine.h
+++ b/sysdeps/aarch64/dl-machine.h
@@ -68,7 +68,6 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
     {
       ElfW(Addr) *got;
       extern void _dl_runtime_resolve (ElfW(Word));
-      extern void _dl_runtime_profile (ElfW(Word));
 
       got = (ElfW(Addr) *) D_PTR (l, l_info[DT_PLTGOT]);
       if (got[1])
@@ -83,6 +82,8 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 	 to intercept the calls to collect information.  In this case we
 	 don't store the address in the GOT so that all future calls also
 	 end in this function.  */
+#ifdef SHARED
+      extern void _dl_runtime_profile (ElfW(Word));
       if ( profile)
 	{
 	   got[2] = (ElfW(Addr)) &_dl_runtime_profile;
@@ -94,6 +95,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 	    GL(dl_profile_map) = l;
 	}
       else
+#endif
 	{
 	  /* This function will get called to fix up the GOT entry
 	     indicated by the offset on the stack, and then jump to
diff --git a/sysdeps/aarch64/dl-trampoline.S b/sysdeps/aarch64/dl-trampoline.S
index a3474ba741..fc82151ba2 100644
--- a/sysdeps/aarch64/dl-trampoline.S
+++ b/sysdeps/aarch64/dl-trampoline.S
@@ -122,7 +122,7 @@ _dl_runtime_resolve:
 
 	cfi_endproc
 	.size _dl_runtime_resolve, .-_dl_runtime_resolve
-#ifndef PROF
+#if !defined PROF && defined SHARED
 	.globl _dl_runtime_profile
 	.type _dl_runtime_profile, #function
 	cfi_startproc
diff --git a/sysdeps/alpha/dl-machine.h b/sysdeps/alpha/dl-machine.h
index ed5389e3c5..7fe2afca93 100644
--- a/sysdeps/alpha/dl-machine.h
+++ b/sysdeps/alpha/dl-machine.h
@@ -75,9 +75,7 @@ elf_machine_runtime_setup (struct link_map *map, struct r_scope_elem *scope[],
 			   int lazy, int profile)
 {
   extern char _dl_runtime_resolve_new[] attribute_hidden;
-  extern char _dl_runtime_profile_new[] attribute_hidden;
   extern char _dl_runtime_resolve_old[] attribute_hidden;
-  extern char _dl_runtime_profile_old[] attribute_hidden;
 
   struct pltgot {
     char *resolve;
@@ -109,6 +107,9 @@ elf_machine_runtime_setup (struct link_map *map, struct r_scope_elem *scope[],
   else
     resolve = _dl_runtime_resolve_old;
 
+#ifdef SHARED
+  extern char _dl_runtime_profile_new[] attribute_hidden;
+  extern char _dl_runtime_profile_old[] attribute_hidden;
   if (__builtin_expect (profile, 0))
     {
       if (secureplt)
@@ -123,6 +124,7 @@ elf_machine_runtime_setup (struct link_map *map, struct r_scope_elem *scope[],
 	  GL(dl_profile_map) = map;
 	}
     }
+#endif
 
   pg->resolve = resolve;
   pg->link = map;
diff --git a/sysdeps/alpha/dl-trampoline.S b/sysdeps/alpha/dl-trampoline.S
index f8c3d33906..5dffa62587 100644
--- a/sysdeps/alpha/dl-trampoline.S
+++ b/sysdeps/alpha/dl-trampoline.S
@@ -89,6 +89,7 @@ _dl_runtime_resolve_new:
 	.globl	_dl_runtime_profile_new
 	.type	_dl_runtime_profile_new, @function
 
+#ifdef SHARED
 #undef FRAMESIZE
 #define FRAMESIZE	20*8
 
@@ -207,6 +208,7 @@ _dl_runtime_profile_new:
 
 	cfi_endproc
 	.size	_dl_runtime_profile_new, .-_dl_runtime_profile_new
+#endif /* SHARED */
 
 	.align	4
 	.globl	_dl_runtime_resolve_old
@@ -340,6 +342,7 @@ _dl_runtime_resolve_old:
 	.usepv	_dl_runtime_profile_old, no
 	.type	_dl_runtime_profile_old, @function
 
+#ifdef SHARED
 	/* We save the registers in a different order than desired by
 	   .mask/.fmask, so we have to use explicit cfi directives.  */
 	cfi_startproc
@@ -538,3 +541,4 @@ _dl_runtime_profile_old:
 
 	cfi_endproc
 	.size	_dl_runtime_profile_old, .-_dl_runtime_profile_old
+#endif /* SHARED */
diff --git a/sysdeps/arm/dl-machine.h b/sysdeps/arm/dl-machine.h
index d720c02c96..a68679e653 100644
--- a/sysdeps/arm/dl-machine.h
+++ b/sysdeps/arm/dl-machine.h
@@ -65,7 +65,6 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 {
   Elf32_Addr *got;
   extern void _dl_runtime_resolve (Elf32_Word);
-  extern void _dl_runtime_profile (Elf32_Word);
 
   if (l->l_info[DT_JMPREL] && lazy)
     {
@@ -88,6 +87,8 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 	 to intercept the calls to collect information.  In this case we
 	 don't store the address in the GOT so that all future calls also
 	 end in this function.  */
+#ifdef SHARED
+      extern void _dl_runtime_profile (Elf32_Word);
       if (profile)
 	{
 	  got[2] = (Elf32_Addr) &_dl_runtime_profile;
@@ -99,6 +100,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 	    GL(dl_profile_map) = l;
 	}
       else
+#endif
 	/* This function will get called to fix up the GOT entry indicated by
 	   the offset on the stack, and then jump to the resolved address.  */
 	got[2] = (Elf32_Addr) &_dl_runtime_resolve;
diff --git a/sysdeps/arm/dl-trampoline.S b/sysdeps/arm/dl-trampoline.S
index 23c2476917..2df5b7ee36 100644
--- a/sysdeps/arm/dl-trampoline.S
+++ b/sysdeps/arm/dl-trampoline.S
@@ -70,7 +70,7 @@ _dl_runtime_resolve:
 	cfi_endproc
 	.size _dl_runtime_resolve, .-_dl_runtime_resolve
 
-#ifndef PROF
+#if !defined PROF && defined SHARED
 	.globl _dl_runtime_profile
 	.type _dl_runtime_profile, #function
 	CFI_SECTIONS
diff --git a/sysdeps/hppa/dl-machine.h b/sysdeps/hppa/dl-machine.h
index 4e6e70b3c9..993593de5d 100644
--- a/sysdeps/hppa/dl-machine.h
+++ b/sysdeps/hppa/dl-machine.h
@@ -195,7 +195,6 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
   end_jmprel = jmprel + l->l_info[DT_PLTRELSZ]->d_un.d_val;
 
   extern void _dl_runtime_resolve (void);
-  extern void _dl_runtime_profile (void);
 
   /* Linking lazily */
   if (lazy)
@@ -235,22 +234,9 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 	      got[1] = (Elf32_Addr) l;
 
 	      /* This function will be called to perform the relocation. */
-	      if (__builtin_expect (!profile, 1))
-		{
-		  /* If a static application called us, then _dl_runtime_resolve is not
-		     a function descriptor, but the *real* address of the function... */
-		  if((unsigned long) &_dl_runtime_resolve & 3)
-		    {
-		      got[-2] = (Elf32_Addr) ((struct fdesc *)
-				  ((unsigned long) &_dl_runtime_resolve & ~3))->ip;
-		    }
-		  else
-		    {
-		      /* Static executable! */
-		      got[-2] = (Elf32_Addr) &_dl_runtime_resolve;
-		    }
-		}
-	      else
+#ifdef SHARED
+	      extern void _dl_runtime_profile (void);
+	      if (__glibc_unlikely (profile))
 		{
 		  if (GLRO(dl_profile) != NULL
 		      && _dl_name_match_p (GLRO(dl_profile), l))
@@ -272,6 +258,22 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 		      got[-2] = (Elf32_Addr) &_dl_runtime_profile;
 		    }
 		}
+	      else
+#endif
+		{
+		  /* If a static application called us, then _dl_runtime_resolve is not
+		     a function descriptor, but the *real* address of the function... */
+		  if((unsigned long) &_dl_runtime_resolve & 3)
+		    {
+		      got[-2] = (Elf32_Addr) ((struct fdesc *)
+				  ((unsigned long) &_dl_runtime_resolve & ~3))->ip;
+		    }
+		  else
+		    {
+		      /* Static executable! */
+		      got[-2] = (Elf32_Addr) &_dl_runtime_resolve;
+		    }
+		}
 	      /* Plunk in the gp of this function descriptor so we
 		 can make the call to _dl_runtime_xxxxxx */
 	      got[-1] = ltp;
diff --git a/sysdeps/hppa/dl-trampoline.S b/sysdeps/hppa/dl-trampoline.S
index 689c6e1a40..9e904df3d2 100644
--- a/sysdeps/hppa/dl-trampoline.S
+++ b/sysdeps/hppa/dl-trampoline.S
@@ -156,6 +156,7 @@ _dl_runtime_resolve:
 	cfi_endproc
 	.size   _dl_runtime_resolve, . - _dl_runtime_resolve
 
+#ifdef SHARED
         .text
         .global _dl_runtime_profile
         .type _dl_runtime_profile,@function
@@ -359,3 +360,4 @@ L(cont):
         .PROCEND
 	cfi_endproc
 	.size   _dl_runtime_profile, . - _dl_runtime_profile
+#endif
diff --git a/sysdeps/i386/dl-machine.h b/sysdeps/i386/dl-machine.h
index 18749f2ec2..07469e99b0 100644
--- a/sysdeps/i386/dl-machine.h
+++ b/sysdeps/i386/dl-machine.h
@@ -92,6 +92,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 	 to intercept the calls to collect information.  In this case we
 	 don't store the address in the GOT so that all future calls also
 	 end in this function.  */
+#ifdef SHARED
       if (__glibc_unlikely (profile))
 	{
 	  got[2] = (shstk_enabled
@@ -105,6 +106,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 	    GL(dl_profile_map) = l;
 	}
       else
+#endif
 	/* This function will get called to fix up the GOT entry indicated by
 	   the offset on the stack, and then jump to the resolved address.  */
 	got[2] = (shstk_enabled
diff --git a/sysdeps/i386/dl-trampoline.S b/sysdeps/i386/dl-trampoline.S
index 2d55f373b4..3604aabe87 100644
--- a/sysdeps/i386/dl-trampoline.S
+++ b/sysdeps/i386/dl-trampoline.S
@@ -70,7 +70,7 @@ _dl_runtime_resolve_shstk:
 	cfi_endproc
 	.size _dl_runtime_resolve_shstk, .-_dl_runtime_resolve_shstk
 
-#ifndef PROF
+#if !defined PROF && defined SHARED
 # The SHSTK compatible version.
 	.globl _dl_runtime_profile_shstk
 	.type _dl_runtime_profile_shstk, @function
diff --git a/sysdeps/ia64/dl-machine.h b/sysdeps/ia64/dl-machine.h
index e1da3dadcb..3ef6b0ef4b 100644
--- a/sysdeps/ia64/dl-machine.h
+++ b/sysdeps/ia64/dl-machine.h
@@ -121,9 +121,8 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
       reserve[0] = (Elf64_Addr) l;
 
       /* This function will be called to perform the relocation.  */
-      if (!profile)
-	doit = (Elf64_Addr) ELF_PTR_TO_FDESC (&_dl_runtime_resolve)->ip;
-      else
+#ifdef SHARED
+      if (__glibc_unlikely (profile))
 	{
 	  if (GLRO(dl_profile) != NULL
 	      && _dl_name_match_p (GLRO(dl_profile), l))
@@ -134,6 +133,11 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 	    }
 	  doit = (Elf64_Addr) ELF_PTR_TO_FDESC (&_dl_runtime_profile)->ip;
 	}
+      else
+#endif
+	{
+	  doit = (Elf64_Addr) ELF_PTR_TO_FDESC (&_dl_runtime_resolve)->ip;
+	}
 
       reserve[1] = doit;
       reserve[2] = gp;
diff --git a/sysdeps/ia64/dl-trampoline.S b/sysdeps/ia64/dl-trampoline.S
index 54b33c8c02..10d8432c8f 100644
--- a/sysdeps/ia64/dl-trampoline.S
+++ b/sysdeps/ia64/dl-trampoline.S
@@ -188,7 +188,7 @@ END(_dl_runtime_resolve)
 #define PLTENTER_FRAME_SIZE (4*8 + 8*8 + 8*16 + 2*8 + 16)
 #define PLTEXIT_FRAME_SIZE (PLTENTER_FRAME_SIZE + 4*8 + 8*16)
 
-#ifndef PROF
+#if !defined PROF && defined SHARED
 ENTRY(_dl_runtime_profile)
 	{ .mii
 	  .prologue
diff --git a/sysdeps/loongarch/dl-machine.h b/sysdeps/loongarch/dl-machine.h
index 57913cefaa..0d17fd21e3 100644
--- a/sysdeps/loongarch/dl-machine.h
+++ b/sysdeps/loongarch/dl-machine.h
@@ -287,15 +287,16 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 	 to intercept the calls to collect information.  In this case we
 	 don't store the address in the GOT so that all future calls also
 	 end in this function.  */
+#ifdef SHARED
       if (profile != 0)
 	{
-#if !defined __loongarch_soft_float
+# if !defined __loongarch_soft_float
 	  if (SUPPORT_LASX)
 	    gotplt[0] = (ElfW(Addr)) &_dl_runtime_profile_lasx;
 	  else if (SUPPORT_LSX)
 	    gotplt[0] = (ElfW(Addr)) &_dl_runtime_profile_lsx;
 	  else
-#endif
+# endif
 	    gotplt[0] = (ElfW(Addr)) &_dl_runtime_profile;
 
 	  if (GLRO(dl_profile) != NULL
@@ -305,6 +306,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 	    GL(dl_profile_map) = l;
 	}
       else
+#endif
 	{
 	  /* This function will get called to fix up the GOT entry
 	     indicated by the offset on the stack, and then jump to
diff --git a/sysdeps/loongarch/dl-trampoline.h b/sysdeps/loongarch/dl-trampoline.h
index e298439d39..1da70aeb23 100644
--- a/sysdeps/loongarch/dl-trampoline.h
+++ b/sysdeps/loongarch/dl-trampoline.h
@@ -126,6 +126,7 @@ ENTRY (_dl_runtime_resolve)
 	jirl	zero, t1, 0
 END (_dl_runtime_resolve)
 
+#ifdef SHARED
 #include "dl-link.h"
 
 ENTRY (_dl_runtime_profile)
@@ -367,3 +368,4 @@ ENTRY (_dl_runtime_profile)
 	jirl	zero, ra, 0
 
 END (_dl_runtime_profile)
+#endif /* SHARED */
diff --git a/sysdeps/m68k/dl-machine.h b/sysdeps/m68k/dl-machine.h
index 5ee586b27b..8d7e733e2a 100644
--- a/sysdeps/m68k/dl-machine.h
+++ b/sysdeps/m68k/dl-machine.h
@@ -75,7 +75,6 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 {
   Elf32_Addr *got;
   extern void _dl_runtime_resolve (Elf32_Word);
-  extern void _dl_runtime_profile (Elf32_Word);
 
   if (l->l_info[DT_JMPREL] && lazy)
     {
@@ -93,6 +92,8 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 	 to intercept the calls to collect information.  In this case we
 	 don't store the address in the GOT so that all future calls also
 	 end in this function.  */
+#ifdef SHARED
+      extern void _dl_runtime_profile (Elf32_Word);
       if (profile)
 	{
 	  got[2] = (Elf32_Addr) &_dl_runtime_profile;
@@ -106,6 +107,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 	    }
 	}
       else
+#endif
 	/* This function will get called to fix up the GOT entry indicated by
 	   the offset on the stack, and then jump to the resolved address.  */
 	got[2] = (Elf32_Addr) &_dl_runtime_resolve;
diff --git a/sysdeps/m68k/dl-trampoline.S b/sysdeps/m68k/dl-trampoline.S
index dba3741400..f1b4943868 100644
--- a/sysdeps/m68k/dl-trampoline.S
+++ b/sysdeps/m68k/dl-trampoline.S
@@ -60,6 +60,7 @@ _dl_runtime_resolve:
 	cfi_endproc
 	.size _dl_runtime_resolve, . - _dl_runtime_resolve
 
+#ifdef SHARED
 	.text
 	.globl _dl_runtime_profile
 	.type _dl_runtime_profile, @function
@@ -220,3 +221,4 @@ _dl_runtime_profile:
 	rts
 	cfi_endproc
 	.size _dl_runtime_profile, . - _dl_runtime_profile
+#endif /* SHARED */
diff --git a/sysdeps/powerpc/powerpc32/dl-machine.c b/sysdeps/powerpc/powerpc32/dl-machine.c
index ef84911ede..e6b603de94 100644
--- a/sysdeps/powerpc/powerpc32/dl-machine.c
+++ b/sysdeps/powerpc/powerpc32/dl-machine.c
@@ -226,7 +226,7 @@ __elf_machine_runtime_setup (struct link_map *map, int lazy, int profile)
 	  Elf32_Word dlrr;
 	  Elf32_Word offset;
 
-#ifndef PROF
+#if !defined PROF && defined SHARED
 	  dlrr = (Elf32_Word) (profile
 			       ? _dl_prof_resolve
 			       : _dl_runtime_resolve);
diff --git a/sysdeps/powerpc/powerpc32/dl-machine.h b/sysdeps/powerpc/powerpc32/dl-machine.h
index a4cad7583c..1ff46d5f8a 100644
--- a/sysdeps/powerpc/powerpc32/dl-machine.h
+++ b/sysdeps/powerpc/powerpc32/dl-machine.h
@@ -188,15 +188,19 @@ elf_machine_runtime_setup (struct link_map *map, struct r_scope_elem *scope[],
       extern void _dl_runtime_resolve (void);
       extern void _dl_prof_resolve (void);
 
-      if (__glibc_likely (!profile))
-	dlrr = _dl_runtime_resolve;
-      else
+#ifdef SHARED
+      if (__glibc_unlikely (profile))
 	{
 	  if (GLRO(dl_profile) != NULL
 	      &&_dl_name_match_p (GLRO(dl_profile), map))
 	    GL(dl_profile_map) = map;
 	  dlrr = _dl_prof_resolve;
 	}
+      else
+#endif
+	{
+	  dlrr = _dl_runtime_resolve;
+	}
       got = (Elf32_Addr *) map->l_info[DT_PPC(GOT)]->d_un.d_ptr;
       glink = got[1];
       got[1] = (Elf32_Addr) dlrr;
diff --git a/sysdeps/powerpc/powerpc32/dl-trampoline.S b/sysdeps/powerpc/powerpc32/dl-trampoline.S
index 93b1673ebb..be8de0e2dc 100644
--- a/sysdeps/powerpc/powerpc32/dl-trampoline.S
+++ b/sysdeps/powerpc/powerpc32/dl-trampoline.S
@@ -70,7 +70,7 @@ _dl_runtime_resolve:
 	cfi_endproc
 	.size	 _dl_runtime_resolve,.-_dl_runtime_resolve
 
-#ifndef PROF
+#if !defined PROF && defined SHARED
 	.align 2
 	.globl _dl_prof_resolve
 	.type _dl_prof_resolve,@function
diff --git a/sysdeps/powerpc/powerpc64/dl-machine.h b/sysdeps/powerpc/powerpc64/dl-machine.h
index 449208e86f..601c3cba9d 100644
--- a/sysdeps/powerpc/powerpc64/dl-machine.h
+++ b/sysdeps/powerpc/powerpc64/dl-machine.h
@@ -362,13 +362,19 @@ elf_machine_runtime_setup (struct link_map *map, struct r_scope_elem *scope[],
 	  Elf64_Word offset;
 	  Elf64_Addr dlrr;
 
-	  dlrr = (Elf64_Addr) (profile ? _dl_profile_resolve
-				       : _dl_runtime_resolve);
-	  if (profile && GLRO(dl_profile) != NULL
-	      && _dl_name_match_p (GLRO(dl_profile), map))
-	    /* This is the object we are looking for.  Say that we really
-	       want profiling and the timers are started.  */
-	    GL(dl_profile_map) = map;
+#ifdef SHARED
+	  if (__glibc_unlikely (profile))
+	    {
+	      dlrr = (Elf64_Addr) _dl_profile_resolve;
+	      if (profile && GLRO(dl_profile) != NULL
+		  && _dl_name_match_p (GLRO(dl_profile), map))
+		/* This is the object we are looking for.  Say that we really
+		   want profiling and the timers are started.  */
+		GL(dl_profile_map) = map;
+	    }
+	  else
+#endif
+	    dlrr = (Elf64_Addr) _dl_runtime_resolve;
 
 #if _CALL_ELF != 2
 	  /* We need to stuff the address/TOC of _dl_runtime_resolve
diff --git a/sysdeps/powerpc/powerpc64/dl-trampoline.S b/sysdeps/powerpc/powerpc64/dl-trampoline.S
index 1d04ec8109..b2fc2bb133 100644
--- a/sysdeps/powerpc/powerpc64/dl-trampoline.S
+++ b/sysdeps/powerpc/powerpc64/dl-trampoline.S
@@ -195,7 +195,7 @@ END(_dl_runtime_resolve)
    and r11 contains the link_map (from PLT0+16).  The link_map becomes
    parm1 (r3) and the index (r0) needs to be converted to an offset
    (index * 24) in parm2 (r4).  */
-#ifndef PROF
+#if !defined PROF && defined SHARED
 	.hidden _dl_profile_resolve
 ENTRY (_dl_profile_resolve, 4)
 /* Spill r30, r31 to preserve the link_map* and reloc_addr, in case we
diff --git a/sysdeps/s390/s390-32/dl-machine.h b/sysdeps/s390/s390-32/dl-machine.h
index 100a3e05f6..b8bf2796c7 100644
--- a/sysdeps/s390/s390-32/dl-machine.h
+++ b/sysdeps/s390/s390-32/dl-machine.h
@@ -124,16 +124,17 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 	 to intercept the calls to collect information.  In this case we
 	 don't store the address in the GOT so that all future calls also
 	 end in this function.  */
+#ifdef SHARED
       if (__glibc_unlikely (profile))
 	{
-#if defined HAVE_S390_VX_ASM_SUPPORT
+# if defined HAVE_S390_VX_ASM_SUPPORT
 	  if (GLRO(dl_hwcap) & HWCAP_S390_VX)
 	    got[2] = (Elf32_Addr) &_dl_runtime_profile_vx;
 	  else
 	    got[2] = (Elf32_Addr) &_dl_runtime_profile;
-#else
+# else
 	  got[2] = (Elf32_Addr) &_dl_runtime_profile;
-#endif
+# endif
 
 	  if (GLRO(dl_profile) != NULL
 	      && _dl_name_match_p (GLRO(dl_profile), l))
@@ -142,6 +143,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 	    GL(dl_profile_map) = l;
 	}
       else
+#endif
 	{
 	  /* This function will get called to fix up the GOT entry indicated by
 	     the offset on the stack, and then jump to the resolved address.  */
diff --git a/sysdeps/s390/s390-32/dl-trampoline.h b/sysdeps/s390/s390-32/dl-trampoline.h
index 78fdca9d53..8093ab08d3 100644
--- a/sysdeps/s390/s390-32/dl-trampoline.h
+++ b/sysdeps/s390/s390-32/dl-trampoline.h
@@ -148,7 +148,7 @@ _dl_runtime_resolve:
 #undef F0_OFF
 #undef F2_OFF
 
-#ifndef PROF
+#if !defined PROF && defined SHARED
 # define SIZEOF_STRUCT_LA_S390_32_REGS 168
 # define REGS_OFF -264
 # define R2_OFF -264
diff --git a/sysdeps/s390/s390-64/dl-machine.h b/sysdeps/s390/s390-64/dl-machine.h
index 9fabb09750..82259dad64 100644
--- a/sysdeps/s390/s390-64/dl-machine.h
+++ b/sysdeps/s390/s390-64/dl-machine.h
@@ -111,16 +111,17 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 	 to intercept the calls to collect information.	 In this case we
 	 don't store the address in the GOT so that all future calls also
 	 end in this function.	*/
+#ifdef SHARED
       if (__glibc_unlikely (profile))
 	{
-#if defined HAVE_S390_VX_ASM_SUPPORT
+# if defined HAVE_S390_VX_ASM_SUPPORT
 	  if (GLRO(dl_hwcap) & HWCAP_S390_VX)
 	    got[2] = (Elf64_Addr) &_dl_runtime_profile_vx;
 	  else
 	    got[2] = (Elf64_Addr) &_dl_runtime_profile;
-#else
+# else
 	  got[2] = (Elf64_Addr) &_dl_runtime_profile;
-#endif
+# endif
 
 	  if (GLRO(dl_profile) != NULL
 	      && _dl_name_match_p (GLRO(dl_profile), l))
@@ -129,6 +130,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 	    GL(dl_profile_map) = l;
 	}
       else
+#endif
 	{
 	  /* This function will get called to fix up the GOT entry indicated by
 	     the offset on the stack, and then jump to the resolved address.  */
diff --git a/sysdeps/s390/s390-64/dl-trampoline.h b/sysdeps/s390/s390-64/dl-trampoline.h
index 3a7cfc5f92..61188119e6 100644
--- a/sysdeps/s390/s390-64/dl-trampoline.h
+++ b/sysdeps/s390/s390-64/dl-trampoline.h
@@ -150,7 +150,7 @@ _dl_runtime_resolve:
 #undef F4_OFF
 #undef F6_OFF
 
-#ifndef PROF
+#if !defined PROF && defined SHARED
 # define SIZEOF_STRUCT_LA_S390_64_REGS 200
 # define REGS_OFF -360
 # define R2_OFF -360
diff --git a/sysdeps/sh/dl-machine.h b/sysdeps/sh/dl-machine.h
index 0e4eac42c3..e0480eae5a 100644
--- a/sysdeps/sh/dl-machine.h
+++ b/sysdeps/sh/dl-machine.h
@@ -101,6 +101,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 	 to intercept the calls to collect information.	 In this case we
 	 don't store the address in the GOT so that all future calls also
 	 end in this function.	*/
+#ifdef SHARED
       if (profile)
 	{
 	  got[2] = (Elf32_Addr) &_dl_runtime_profile;
@@ -110,6 +111,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 	    GL(dl_profile_map) = l;
 	}
       else
+#endif
 	/* This function will get called to fix up the GOT entry indicated by
 	   the offset on the stack, and then jump to the resolved address.  */
 	got[2] = (Elf32_Addr) &_dl_runtime_resolve;
diff --git a/sysdeps/sh/dl-trampoline.S b/sysdeps/sh/dl-trampoline.S
index 5d703341ed..ecaae34db4 100644
--- a/sysdeps/sh/dl-trampoline.S
+++ b/sysdeps/sh/dl-trampoline.S
@@ -142,6 +142,7 @@ _dl_runtime_resolve:
 	.size _dl_runtime_resolve, .-_dl_runtime_resolve
 
 
+#ifdef SHARED
 	.globl _dl_runtime_profile
 	.type _dl_runtime_profile,@function
 	cfi_startproc
@@ -428,3 +429,4 @@ _dl_runtime_profile:
 8:	.long _dl_audit_pltexit
 #endif
 	.size _dl_runtime_profile, .-_dl_runtime_profile
+#endif /* SHARED */
diff --git a/sysdeps/sparc/sparc32/dl-machine.h b/sysdeps/sparc/sparc32/dl-machine.h
index 9b57ae1a93..b10e541810 100644
--- a/sysdeps/sparc/sparc32/dl-machine.h
+++ b/sysdeps/sparc/sparc32/dl-machine.h
@@ -116,7 +116,8 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 	 bits of %g1 with an offset into the .rela.plt section and jump to
 	 the beginning of the PLT.  */
       plt = (Elf32_Addr *) D_PTR (l, l_info[DT_PLTGOT]);
-      if (__builtin_expect(profile, 0))
+#ifdef SHARED
+      if (__glibc_unlikely (profile))
 	{
 	  rfunc = (Elf32_Addr) &_dl_runtime_profile;
 
@@ -125,6 +126,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 	    GL(dl_profile_map) = l;
 	}
       else
+#endif
 	{
 	  rfunc = (Elf32_Addr) &_dl_runtime_resolve;
 	}
diff --git a/sysdeps/sparc/sparc32/dl-trampoline.S b/sysdeps/sparc/sparc32/dl-trampoline.S
index 08ff31b474..5e7d860ae4 100644
--- a/sysdeps/sparc/sparc32/dl-trampoline.S
+++ b/sysdeps/sparc/sparc32/dl-trampoline.S
@@ -47,6 +47,7 @@ _dl_runtime_resolve:
 
 	.size	_dl_runtime_resolve, .-_dl_runtime_resolve
 
+#ifdef SHARED
 	/* For the profiling cases we pass in our stack frame
 	 * as the base of the La_sparc32_regs, so it looks
 	 * like:
@@ -185,3 +186,4 @@ _dl_runtime_profile:
 	cfi_endproc
 
 	.size	_dl_runtime_profile, .-_dl_runtime_profile
+#endif
diff --git a/sysdeps/sparc/sparc64/dl-machine.h b/sysdeps/sparc/sparc64/dl-machine.h
index 2f04ac550e..98469e7604 100644
--- a/sysdeps/sparc/sparc64/dl-machine.h
+++ b/sysdeps/sparc/sparc64/dl-machine.h
@@ -136,7 +136,8 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
       Elf64_Addr res0_addr, res1_addr;
       unsigned int *plt = (void *) D_PTR (l, l_info[DT_PLTGOT]);
 
-      if (__builtin_expect(profile, 0))
+#ifdef SHARED
+      if (__glibc_unlikely (profile))
 	{
 	  res0_addr = (Elf64_Addr) &_dl_runtime_profile_0;
 	  res1_addr = (Elf64_Addr) &_dl_runtime_profile_1;
@@ -146,6 +147,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 	    GL(dl_profile_map) = l;
 	}
       else
+#endif
 	{
 	  res0_addr = (Elf64_Addr) &_dl_runtime_resolve_0;
 	  res1_addr = (Elf64_Addr) &_dl_runtime_resolve_1;
diff --git a/sysdeps/sparc/sparc64/dl-trampoline.S b/sysdeps/sparc/sparc64/dl-trampoline.S
index 444690a71e..82b42681dd 100644
--- a/sysdeps/sparc/sparc64/dl-trampoline.S
+++ b/sysdeps/sparc/sparc64/dl-trampoline.S
@@ -92,6 +92,7 @@ _dl_runtime_resolve_1:
 
 	.size	_dl_runtime_resolve_1, .-_dl_runtime_resolve_1
 
+#ifdef SHARED
 	/* For the profiling cases we pass in our stack frame
 	 * as the base of the La_sparc64_regs, so it looks
 	 * like:
@@ -323,3 +324,4 @@ _dl_runtime_profile_1:
 	cfi_endproc
 
 	.size	_dl_runtime_resolve_1, .-_dl_runtime_resolve_1
+#endif
diff --git a/sysdeps/x86_64/dl-machine.h b/sysdeps/x86_64/dl-machine.h
index 9ea2a70837..581a2f1a9e 100644
--- a/sysdeps/x86_64/dl-machine.h
+++ b/sysdeps/x86_64/dl-machine.h
@@ -89,6 +89,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 
       const struct cpu_features* cpu_features = __get_cpu_features ();
 
+#ifdef SHARED
       /* The got[2] entry contains the address of a function which gets
 	 called to get the address of a so far unresolved function and
 	 jump to it.  The profiling extension of the dynamic linker allows
@@ -111,6 +112,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
 	    GL(dl_profile_map) = l;
 	}
       else
+#endif
 	{
 	  /* This function will get called to fix up the GOT entry
 	     indicated by the offset on the stack, and then jump to
diff --git a/sysdeps/x86_64/dl-trampoline.S b/sysdeps/x86_64/dl-trampoline.S
index a6b9a1826b..3fd30d58fc 100644
--- a/sysdeps/x86_64/dl-trampoline.S
+++ b/sysdeps/x86_64/dl-trampoline.S
@@ -53,45 +53,49 @@
 
 #define RESTORE_AVX
 
-#define VEC_SIZE		64
-#define VMOVA			vmovdqa64
-#define VEC(i)			zmm##i
-#define _dl_runtime_profile	_dl_runtime_profile_avx512
-# define SECTION(p)		p##.evex512
-#include "dl-trampoline.h"
-#undef _dl_runtime_profile
-#undef VEC
-#undef VMOVA
-#undef VEC_SIZE
-#undef SECTION
-
-#if MINIMUM_X86_ISA_LEVEL <= AVX_X86_ISA_LEVEL
-# define VEC_SIZE		32
-# define VMOVA			vmovdqa
-# define VEC(i)			ymm##i
-# define SECTION(p)		p##.avx
-# define _dl_runtime_profile	_dl_runtime_profile_avx
+#ifdef SHARED
+# define VEC_SIZE		64
+# define VMOVA			vmovdqa64
+# define VEC(i)			zmm##i
+# define _dl_runtime_profile	_dl_runtime_profile_avx512
+#  define SECTION(p)		p##.evex512
 # include "dl-trampoline.h"
 # undef _dl_runtime_profile
 # undef VEC
 # undef VMOVA
 # undef VEC_SIZE
 # undef SECTION
-#endif
 
-#if MINIMUM_X86_ISA_LEVEL < AVX_X86_ISA_LEVEL
+# if MINIMUM_X86_ISA_LEVEL <= AVX_X86_ISA_LEVEL
+#  define VEC_SIZE		32
+#  define VMOVA			vmovdqa
+#  define VEC(i)			ymm##i
+#  define SECTION(p)		p##.avx
+#  define _dl_runtime_profile	_dl_runtime_profile_avx
+#  include "dl-trampoline.h"
+#  undef _dl_runtime_profile
+#  undef VEC
+#  undef VMOVA
+#  undef VEC_SIZE
+#  undef SECTION
+# endif
+
+# if MINIMUM_X86_ISA_LEVEL < AVX_X86_ISA_LEVEL
 /* movaps/movups is 1-byte shorter.  */
-# define VEC_SIZE		16
-# define VMOVA			movaps
-# define VEC(i)			xmm##i
-# define _dl_runtime_profile	_dl_runtime_profile_sse
-# undef RESTORE_AVX
-# include "dl-trampoline.h"
-# undef _dl_runtime_profile
-# undef VEC
-# undef VMOVA
-# undef VEC_SIZE
+#  define VEC_SIZE		16
+#  define VMOVA			movaps
+#  define VEC(i)			xmm##i
+#  define _dl_runtime_profile	_dl_runtime_profile_sse
+#  undef RESTORE_AVX
+#  include "dl-trampoline.h"
+#  undef _dl_runtime_profile
+#  undef VEC
+#  undef VMOVA
+#  undef VEC_SIZE
+# endif
+#endif /* SHARED */
 
+#if MINIMUM_X86_ISA_LEVEL < AVX_X86_ISA_LEVEL
 # define USE_FXSAVE
 # define STATE_SAVE_ALIGNMENT	16
 # define _dl_runtime_resolve	_dl_runtime_resolve_fxsave
-- 
2.34.1


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v3 14/19] elf: Ignore loader debug env vars for setuid
  2023-11-06 20:25 [PATCH v3 00/19] Improve loader environment variable handling Adhemerval Zanella
                   ` (12 preceding siblings ...)
  2023-11-06 20:25 ` [PATCH v3 13/19] elf: Remove LD_PROFILE for static binaries Adhemerval Zanella
@ 2023-11-06 20:25 ` Adhemerval Zanella
  2023-11-20 22:57   ` Siddhesh Poyarekar
  2023-11-06 20:25 ` [PATCH v3 15/19] elf: Remove any_debug from dl_main_state Adhemerval Zanella
                   ` (5 subsequent siblings)
  19 siblings, 1 reply; 40+ messages in thread
From: Adhemerval Zanella @ 2023-11-06 20:25 UTC (permalink / raw)
  To: libc-alpha, Siddhesh Poyarekar

Loader already ignores LD_DEBUG, LD_DEBUG_OUTPUT, and
LD_TRACE_LOADED_OBJECTS. Both LD_WARN and LD_VERBOSE are similar to
LD_DEBUG, in the sense they enable additional checks and debug
information, so it makes sense to disable them.

Checked on x86_64-linux-gnu.
---
 elf/rtld.c           | 22 ++++++++++++++--------
 elf/tst-env-setuid.c |  2 ++
 2 files changed, 16 insertions(+), 8 deletions(-)

diff --git a/elf/rtld.c b/elf/rtld.c
index a09cf2a9df..e7f90d37e7 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -2552,13 +2552,15 @@ process_envvars (struct dl_main_state *state)
 	{
 	case 4:
 	  /* Warning level, verbose or not.  */
-	  if (memcmp (envline, "WARN", 4) == 0)
+	  if (!__libc_enable_secure
+	      && memcmp (envline, "WARN", 4) == 0)
 	    GLRO(dl_verbose) = envline[5] != '\0';
 	  break;
 
 	case 5:
 	  /* Debugging of the dynamic linker?  */
-	  if (memcmp (envline, "DEBUG", 5) == 0)
+	  if (!__libc_enable_secure
+	      && memcmp (envline, "DEBUG", 5) == 0)
 	    {
 	      process_dl_debug (state, &envline[6]);
 	      break;
@@ -2569,7 +2571,8 @@ process_envvars (struct dl_main_state *state)
 
 	case 7:
 	  /* Print information about versions.  */
-	  if (memcmp (envline, "VERBOSE", 7) == 0)
+	  if (!__libc_enable_secure
+	      && memcmp (envline, "VERBOSE", 7) == 0)
 	    {
 	      state->version_info = envline[8] != '\0';
 	      break;
@@ -2625,7 +2628,8 @@ process_envvars (struct dl_main_state *state)
 	    }
 
 	  /* Where to place the profiling data file.  */
-	  if (memcmp (envline, "DEBUG_OUTPUT", 12) == 0)
+	  if (!__libc_enable_secure
+	      && memcmp (envline, "DEBUG_OUTPUT", 12) == 0)
 	    {
 	      debug_output = &envline[13];
 	      break;
@@ -2646,7 +2650,8 @@ process_envvars (struct dl_main_state *state)
 
 	case 20:
 	  /* The mode of the dynamic linker can be set.  */
-	  if (memcmp (envline, "TRACE_LOADED_OBJECTS", 20) == 0)
+	  if (!__libc_enable_secure
+	      && memcmp (envline, "TRACE_LOADED_OBJECTS", 20) == 0)
 	    {
 	      state->mode = rtld_mode_trace;
 	      state->mode_trace_program
@@ -2668,9 +2673,10 @@ process_envvars (struct dl_main_state *state)
 	}
       while (*nextp != '\0');
 
-      GLRO(dl_debug_mask) = 0;
-
-      if (state->mode != rtld_mode_normal)
+      if (GLRO(dl_debug_mask) != 0
+	  || GLRO(dl_verbose) != 0
+	  || state->mode != rtld_mode_normal
+	  || state->version_info)
 	_exit (5);
     }
   /* If we have to run the dynamic linker in debugging mode and the
diff --git a/elf/tst-env-setuid.c b/elf/tst-env-setuid.c
index 76b8e1fb45..dcf213a4cd 100644
--- a/elf/tst-env-setuid.c
+++ b/elf/tst-env-setuid.c
@@ -59,6 +59,8 @@ static const struct envvar_t filtered_envvars[] =
   { "MALLOC_TRACE",            FILTERED_VALUE },
   { "MALLOC_TRIM_THRESHOLD_",  FILTERED_VALUE },
   { "RES_OPTIONS",             FILTERED_VALUE },
+  { "LD_DEBUG",                "all" },
+  { "LD_DEBUG_OUTPUT",         "/tmp/some-file" },
 };
 
 static const struct envvar_t unfiltered_envvars[] =
-- 
2.34.1


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v3 15/19] elf: Remove any_debug from dl_main_state
  2023-11-06 20:25 [PATCH v3 00/19] Improve loader environment variable handling Adhemerval Zanella
                   ` (13 preceding siblings ...)
  2023-11-06 20:25 ` [PATCH v3 14/19] elf: Ignore loader debug env vars for setuid Adhemerval Zanella
@ 2023-11-06 20:25 ` Adhemerval Zanella
  2023-11-20 22:58   ` Siddhesh Poyarekar
  2023-11-06 20:25 ` [PATCH v3 16/19] elf: Ignore LD_LIBRARY_PATH and debug env var for setuid for static Adhemerval Zanella
                   ` (4 subsequent siblings)
  19 siblings, 1 reply; 40+ messages in thread
From: Adhemerval Zanella @ 2023-11-06 20:25 UTC (permalink / raw)
  To: libc-alpha, Siddhesh Poyarekar

Its usage can be implied by the GLRO(dl_debug_mask).
---
 elf/dl-main.h | 3 ---
 elf/rtld.c    | 4 +---
 2 files changed, 1 insertion(+), 6 deletions(-)

diff --git a/elf/dl-main.h b/elf/dl-main.h
index 92766d06b4..f876904cb6 100644
--- a/elf/dl-main.h
+++ b/elf/dl-main.h
@@ -97,9 +97,6 @@ struct dl_main_state
   /* True if program should be also printed for rtld_mode_trace.  */
   bool mode_trace_program;
 
-  /* 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 e7f90d37e7..638b019670 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -300,7 +300,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;
 }
 
@@ -2481,7 +2480,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;
 	      }
 
@@ -2682,7 +2680,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);
-- 
2.34.1


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v3 16/19] elf: Ignore LD_LIBRARY_PATH and debug env var for setuid for static
  2023-11-06 20:25 [PATCH v3 00/19] Improve loader environment variable handling Adhemerval Zanella
                   ` (14 preceding siblings ...)
  2023-11-06 20:25 ` [PATCH v3 15/19] elf: Remove any_debug from dl_main_state Adhemerval Zanella
@ 2023-11-06 20:25 ` Adhemerval Zanella
  2023-11-20 22:59   ` Siddhesh Poyarekar
  2023-11-06 20:25 ` [PATCH v3 17/19] elf: Add comments on how LD_AUDIT and LD_PRELOAD handle __libc_enable_secure Adhemerval Zanella
                   ` (3 subsequent siblings)
  19 siblings, 1 reply; 40+ messages in thread
From: Adhemerval Zanella @ 2023-11-06 20:25 UTC (permalink / raw)
  To: libc-alpha, Siddhesh Poyarekar

It mimics the ld.so behavior.

Checked on x86_64-linux-gnu.
---
 elf/dl-support.c | 32 ++++++++++++++++----------------
 1 file changed, 16 insertions(+), 16 deletions(-)

diff --git a/elf/dl-support.c b/elf/dl-support.c
index 31a608df87..837fa1c836 100644
--- a/elf/dl-support.c
+++ b/elf/dl-support.c
@@ -272,8 +272,6 @@ _dl_non_dynamic_init (void)
   _dl_main_map.l_phdr = GL(dl_phdr);
   _dl_main_map.l_phnum = GL(dl_phnum);
 
-  _dl_verbose = *(getenv ("LD_WARN") ?: "") == '\0' ? 0 : 1;
-
   /* Set up the data structures for the system-supplied DSO early,
      so they can influence _dl_init_paths.  */
   setup_vdso (NULL, NULL);
@@ -281,6 +279,22 @@ _dl_non_dynamic_init (void)
   /* With vDSO setup we can initialize the function pointers.  */
   setup_vdso_pointers ();
 
+  if (__libc_enable_secure)
+    {
+      static const char unsecure_envvars[] =
+	UNSECURE_ENVVARS
+	;
+      const char *cp = unsecure_envvars;
+
+      while (cp < unsecure_envvars + sizeof (unsecure_envvars))
+	{
+	  __unsetenv (cp);
+	  cp = strchr (cp, '\0') + 1;
+	}
+    }
+
+  _dl_verbose = *(getenv ("LD_WARN") ?: "") == '\0' ? 0 : 1;
+
   /* Initialize the data structures for the search paths for shared
      objects.  */
   _dl_init_paths (getenv ("LD_LIBRARY_PATH"), "LD_LIBRARY_PATH",
@@ -297,20 +311,6 @@ _dl_non_dynamic_init (void)
 
   _dl_dynamic_weak = *(getenv ("LD_DYNAMIC_WEAK") ?: "") == '\0';
 
-  if (__libc_enable_secure)
-    {
-      static const char unsecure_envvars[] =
-	UNSECURE_ENVVARS
-	;
-      const char *cp = unsecure_envvars;
-
-      while (cp < unsecure_envvars + sizeof (unsecure_envvars))
-	{
-	  __unsetenv (cp);
-	  cp = strchr (cp, '\0') + 1;
-	}
-    }
-
 #ifdef DL_PLATFORM_INIT
   DL_PLATFORM_INIT;
 #endif
-- 
2.34.1


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v3 17/19] elf: Add comments on how LD_AUDIT and LD_PRELOAD handle __libc_enable_secure
  2023-11-06 20:25 [PATCH v3 00/19] Improve loader environment variable handling Adhemerval Zanella
                   ` (15 preceding siblings ...)
  2023-11-06 20:25 ` [PATCH v3 16/19] elf: Ignore LD_LIBRARY_PATH and debug env var for setuid for static Adhemerval Zanella
@ 2023-11-06 20:25 ` Adhemerval Zanella
  2023-11-06 20:25 ` [PATCH v3 18/19] elf: Ignore LD_BIND_NOW and LD_BIND_NOT for setuid binaries Adhemerval Zanella
                   ` (2 subsequent siblings)
  19 siblings, 0 replies; 40+ messages in thread
From: Adhemerval Zanella @ 2023-11-06 20:25 UTC (permalink / raw)
  To: libc-alpha, Siddhesh Poyarekar

To make explicit why __libc_enable_secure is not checked.
Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
---
 elf/rtld.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/elf/rtld.c b/elf/rtld.c
index 638b019670..d1017ba9e9 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -2563,6 +2563,10 @@ process_envvars (struct dl_main_state *state)
 	      process_dl_debug (state, &envline[6]);
 	      break;
 	    }
+	  /* For __libc_enable_secure mode, audit pathnames containing slashes
+	     are ignored.  Also, shared audit objects are only loaded only from
+	     the standard search directories and only if they have set-user-ID
+	     mode bit enabled.  */
 	  if (memcmp (envline, "AUDIT", 5) == 0)
 	    audit_list_add_string (&state->audit_list, &envline[6]);
 	  break;
@@ -2576,7 +2580,10 @@ process_envvars (struct dl_main_state *state)
 	      break;
 	    }
 
-	  /* List of objects to be preloaded.  */
+	  /* For __libc_enable_secure mode, preload pathnames containing slashes
+	     are ignored.  Also, shared objects are only preloaded from the
+	     standard search directories and only if they have set-user-ID mode
+	     bit enabled.  */
 	  if (memcmp (envline, "PRELOAD", 7) == 0)
 	    {
 	      state->preloadlist = &envline[8];
-- 
2.34.1


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v3 18/19] elf: Ignore LD_BIND_NOW and LD_BIND_NOT for setuid binaries
  2023-11-06 20:25 [PATCH v3 00/19] Improve loader environment variable handling Adhemerval Zanella
                   ` (16 preceding siblings ...)
  2023-11-06 20:25 ` [PATCH v3 17/19] elf: Add comments on how LD_AUDIT and LD_PRELOAD handle __libc_enable_secure Adhemerval Zanella
@ 2023-11-06 20:25 ` Adhemerval Zanella
  2023-11-20 23:02   ` Siddhesh Poyarekar
  2023-11-06 20:25 ` [PATCH v3 19/19] elf: Refactor process_envvars Adhemerval Zanella
  2023-11-20 23:12 ` [PATCH v3 00/19] Improve loader environment variable handling Siddhesh Poyarekar
  19 siblings, 1 reply; 40+ messages in thread
From: Adhemerval Zanella @ 2023-11-06 20:25 UTC (permalink / raw)
  To: libc-alpha, Siddhesh Poyarekar

To avoid any environment variable to change setuid binaries
semantics.

Checked on x86_64-linux-gnu.
---
 elf/rtld.c                  | 8 ++++++--
 elf/tst-env-setuid.c        | 4 ++--
 sysdeps/generic/unsecvars.h | 2 ++
 3 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/elf/rtld.c b/elf/rtld.c
index d1017ba9e9..cfba30eba0 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -2598,12 +2598,14 @@ process_envvars (struct dl_main_state *state)
 
 	case 8:
 	  /* Do we bind early?  */
-	  if (memcmp (envline, "BIND_NOW", 8) == 0)
+	  if (!__libc_enable_secure
+	      && memcmp (envline, "BIND_NOW", 8) == 0)
 	    {
 	      GLRO(dl_lazy) = envline[9] == '\0';
 	      break;
 	    }
-	  if (memcmp (envline, "BIND_NOT", 8) == 0)
+	  if (! __libc_enable_secure
+	      && memcmp (envline, "BIND_NOT", 8) == 0)
 	    GLRO(dl_bind_not) = envline[9] != '\0';
 	  break;
 
@@ -2680,6 +2682,8 @@ process_envvars (struct dl_main_state *state)
 
       if (GLRO(dl_debug_mask) != 0
 	  || GLRO(dl_verbose) != 0
+	  || GLRO(dl_lazy) != 1
+	  || GLRO(dl_bind_not) != 0
 	  || state->mode != rtld_mode_normal
 	  || state->version_info)
 	_exit (5);
diff --git a/elf/tst-env-setuid.c b/elf/tst-env-setuid.c
index dcf213a4cd..eb9613d717 100644
--- a/elf/tst-env-setuid.c
+++ b/elf/tst-env-setuid.c
@@ -61,12 +61,12 @@ static const struct envvar_t filtered_envvars[] =
   { "RES_OPTIONS",             FILTERED_VALUE },
   { "LD_DEBUG",                "all" },
   { "LD_DEBUG_OUTPUT",         "/tmp/some-file" },
+  { "LD_BIND_NOW",             "0" },
+  { "LD_BIND_NOT",             "1" },
 };
 
 static const struct envvar_t unfiltered_envvars[] =
 {
-  { "LD_BIND_NOW",             "0" },
-  { "LD_BIND_NOT",             "1" },
   /* Non longer supported option.  */
   { "LD_ASSUME_KERNEL",        UNFILTERED_VALUE },
 };
diff --git a/sysdeps/generic/unsecvars.h b/sysdeps/generic/unsecvars.h
index f7ebed60e5..c71b70ed95 100644
--- a/sysdeps/generic/unsecvars.h
+++ b/sysdeps/generic/unsecvars.h
@@ -7,6 +7,8 @@
   "GLIBC_TUNABLES\0"							      \
   "HOSTALIASES\0"							      \
   "LD_AUDIT\0"								      \
+  "LD_BIND_NOT\0"							      \
+  "LD_BIND_NOW\0"							      \
   "LD_DEBUG\0"								      \
   "LD_DEBUG_OUTPUT\0"							      \
   "LD_DYNAMIC_WEAK\0"							      \
-- 
2.34.1


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v3 19/19] elf: Refactor process_envvars
  2023-11-06 20:25 [PATCH v3 00/19] Improve loader environment variable handling Adhemerval Zanella
                   ` (17 preceding siblings ...)
  2023-11-06 20:25 ` [PATCH v3 18/19] elf: Ignore LD_BIND_NOW and LD_BIND_NOT for setuid binaries Adhemerval Zanella
@ 2023-11-06 20:25 ` Adhemerval Zanella
  2023-11-20 23:09   ` Siddhesh Poyarekar
  2023-11-20 23:12 ` [PATCH v3 00/19] Improve loader environment variable handling Siddhesh Poyarekar
  19 siblings, 1 reply; 40+ messages in thread
From: Adhemerval Zanella @ 2023-11-06 20:25 UTC (permalink / raw)
  To: libc-alpha, Siddhesh Poyarekar

It splits between process_envvars_secure and process_envvars_default,
with the former used to process arguments for __libc_enable_secure.
It does not have any semantic change, just simplify the code so there
is no need to handle __libc_enable_secure on each len switch.

Checked on x86_64-linux-gnu and aarch64-linux-gnu.
---
 elf/rtld.c | 132 ++++++++++++++++++++++++++++++++++-------------------
 1 file changed, 84 insertions(+), 48 deletions(-)

diff --git a/elf/rtld.c b/elf/rtld.c
index cfba30eba0..95dcd32185 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -2527,7 +2527,67 @@ a filename can be specified using the LD_DEBUG_OUTPUT environment variable.\n");
 }
 \f
 static void
-process_envvars (struct dl_main_state *state)
+process_envvars_secure (struct dl_main_state *state)
+{
+  char **runp = _environ;
+  char *envline;
+
+  while ((envline = _dl_next_ld_env_entry (&runp)) != NULL)
+    {
+      size_t len = 0;
+
+      while (envline[len] != '\0' && envline[len] != '=')
+	++len;
+
+      if (envline[len] != '=')
+	/* This is a "LD_" variable at the end of the string without
+	   a '=' character.  Ignore it since otherwise we will access
+	   invalid memory below.  */
+	continue;
+
+      switch (len)
+	{
+	case 5:
+	  /* For __libc_enable_secure mode, audit pathnames containing slashes
+	     are ignored.  Also, shared audit objects are only loaded only from
+	     the standard search directories and only if they have set-user-ID
+	     mode bit enabled.  */
+	  if (memcmp (envline, "AUDIT", 5) == 0)
+	    audit_list_add_string (&state->audit_list, &envline[6]);
+	  break;
+
+	case 7:
+	  /* For __libc_enable_secure mode, preload pathnames containing slashes
+	     are ignored.  Also, shared objects are only preloaded from the
+	     standard search directories and only if they have set-user-ID mode
+	     bit enabled.  */
+	  if (memcmp (envline, "PRELOAD", 7) == 0)
+	    state->preloadlist = &envline[8];
+	  break;
+	}
+    }
+
+  /* Extra security for SUID binaries.  Remove all dangerous environment
+     variables.  */
+  const char *nextp = UNSECURE_ENVVARS;
+  do
+    {
+      unsetenv (nextp);
+      nextp = strchr (nextp, '\0') + 1;
+    }
+  while (*nextp != '\0');
+
+  if (GLRO(dl_debug_mask) != 0
+      || GLRO(dl_verbose) != 0
+      || GLRO(dl_lazy) != 1
+      || GLRO(dl_bind_not) != 0
+      || state->mode != rtld_mode_normal
+      || state->version_info)
+    _exit (5);
+}
+
+static void
+process_envvars_default (struct dl_main_state *state)
 {
   char **runp = _environ;
   char *envline;
@@ -2550,15 +2610,13 @@ process_envvars (struct dl_main_state *state)
 	{
 	case 4:
 	  /* Warning level, verbose or not.  */
-	  if (!__libc_enable_secure
-	      && memcmp (envline, "WARN", 4) == 0)
+	  if (memcmp (envline, "WARN", 4) == 0)
 	    GLRO(dl_verbose) = envline[5] != '\0';
 	  break;
 
 	case 5:
 	  /* Debugging of the dynamic linker?  */
-	  if (!__libc_enable_secure
-	      && memcmp (envline, "DEBUG", 5) == 0)
+	  if (memcmp (envline, "DEBUG", 5) == 0)
 	    {
 	      process_dl_debug (state, &envline[6]);
 	      break;
@@ -2573,8 +2631,7 @@ process_envvars (struct dl_main_state *state)
 
 	case 7:
 	  /* Print information about versions.  */
-	  if (!__libc_enable_secure
-	      && memcmp (envline, "VERBOSE", 7) == 0)
+	  if (memcmp (envline, "VERBOSE", 7) == 0)
 	    {
 	      state->version_info = envline[8] != '\0';
 	      break;
@@ -2591,43 +2648,37 @@ process_envvars (struct dl_main_state *state)
 	    }
 
 	  /* Which shared object shall be profiled.  */
-	  if (!__libc_enable_secure
-	      && memcmp (envline, "PROFILE", 7) == 0 && envline[8] != '\0')
+	  if (memcmp (envline, "PROFILE", 7) == 0 && envline[8] != '\0')
 	    GLRO(dl_profile) = &envline[8];
 	  break;
 
 	case 8:
 	  /* Do we bind early?  */
-	  if (!__libc_enable_secure
-	      && memcmp (envline, "BIND_NOW", 8) == 0)
+	  if (memcmp (envline, "BIND_NOW", 8) == 0)
 	    {
 	      GLRO(dl_lazy) = envline[9] == '\0';
 	      break;
 	    }
-	  if (! __libc_enable_secure
-	      && memcmp (envline, "BIND_NOT", 8) == 0)
+	  if (memcmp (envline, "BIND_NOT", 8) == 0)
 	    GLRO(dl_bind_not) = envline[9] != '\0';
 	  break;
 
 	case 9:
 	  /* Test whether we want to see the content of the auxiliary
 	     array passed up from the kernel.  */
-	  if (!__libc_enable_secure
-	      && memcmp (envline, "SHOW_AUXV", 9) == 0)
+	  if (memcmp (envline, "SHOW_AUXV", 9) == 0)
 	    _dl_show_auxv ();
 	  break;
 
 	case 11:
 	  /* Path where the binary is found.  */
-	  if (!__libc_enable_secure
-	      && memcmp (envline, "ORIGIN_PATH", 11) == 0)
+	  if (memcmp (envline, "ORIGIN_PATH", 11) == 0)
 	    GLRO(dl_origin_path) = &envline[12];
 	  break;
 
 	case 12:
 	  /* The library search path.  */
-	  if (!__libc_enable_secure
-	      && memcmp (envline, "LIBRARY_PATH", 12) == 0)
+	  if (memcmp (envline, "LIBRARY_PATH", 12) == 0)
 	    {
 	      state->library_path = &envline[13];
 	      state->library_path_source = "LD_LIBRARY_PATH";
@@ -2635,30 +2686,26 @@ process_envvars (struct dl_main_state *state)
 	    }
 
 	  /* Where to place the profiling data file.  */
-	  if (!__libc_enable_secure
-	      && memcmp (envline, "DEBUG_OUTPUT", 12) == 0)
+	  if (memcmp (envline, "DEBUG_OUTPUT", 12) == 0)
 	    {
 	      debug_output = &envline[13];
 	      break;
 	    }
 
-	  if (!__libc_enable_secure
-	      && memcmp (envline, "DYNAMIC_WEAK", 12) == 0)
+	  if (memcmp (envline, "DYNAMIC_WEAK", 12) == 0)
 	    GLRO(dl_dynamic_weak) = 1;
 	  break;
 
 	case 14:
 	  /* Where to place the profiling data file.  */
-	  if (!__libc_enable_secure
-	      && memcmp (envline, "PROFILE_OUTPUT", 14) == 0
+	  if (memcmp (envline, "PROFILE_OUTPUT", 14) == 0
 	      && envline[15] != '\0')
 	    GLRO(dl_profile_output) = &envline[15];
 	  break;
 
 	case 20:
 	  /* The mode of the dynamic linker can be set.  */
-	  if (!__libc_enable_secure
-	      && memcmp (envline, "TRACE_LOADED_OBJECTS", 20) == 0)
+	  if (memcmp (envline, "TRACE_LOADED_OBJECTS", 20) == 0)
 	    {
 	      state->mode = rtld_mode_trace;
 	      state->mode_trace_program
@@ -2668,30 +2715,10 @@ process_envvars (struct dl_main_state *state)
 	}
     }
 
-  /* Extra security for SUID binaries.  Remove all dangerous environment
-     variables.  */
-  if (__glibc_unlikely (__libc_enable_secure))
-    {
-      const char *nextp = UNSECURE_ENVVARS;
-      do
-	{
-	  unsetenv (nextp);
-	  nextp = strchr (nextp, '\0') + 1;
-	}
-      while (*nextp != '\0');
-
-      if (GLRO(dl_debug_mask) != 0
-	  || GLRO(dl_verbose) != 0
-	  || GLRO(dl_lazy) != 1
-	  || GLRO(dl_bind_not) != 0
-	  || state->mode != rtld_mode_normal
-	  || state->version_info)
-	_exit (5);
-    }
   /* 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 (GLRO(dl_debug_mask) != 0 && debug_output != NULL)
+  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);
@@ -2710,6 +2737,15 @@ process_envvars (struct dl_main_state *state)
     }
 }
 
+static void
+process_envvars (struct dl_main_state *state)
+{
+  if (__glibc_unlikely (__libc_enable_secure))
+    process_envvars_secure (state);
+  else
+    process_envvars_default (state);
+}
+
 #if HP_TIMING_INLINE
 static void
 print_statistics_item (const char *title, hp_timing_t time,
-- 
2.34.1


^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 06/19] elf: Do not parse ill-formatted strings
  2023-11-06 20:25 ` [PATCH v3 06/19] elf: Do not parse ill-formatted strings Adhemerval Zanella
@ 2023-11-20 21:48   ` Siddhesh Poyarekar
  0 siblings, 0 replies; 40+ messages in thread
From: Siddhesh Poyarekar @ 2023-11-20 21:48 UTC (permalink / raw)
  To: Adhemerval Zanella, libc-alpha

On 2023-11-06 15:25, Adhemerval Zanella wrote:
> Instead of ignoring ill-formatted tunable strings, first, check all the
> tunable definitions are correct and then set each tunable value. It
> means that partially invalid strings, like "key1=value1:key2=key2=value'
> or 'key1=value':key2=value2=value2' do not enable 'key1=value1'. It
> avoids possible user-defined errors in tunable definitions.
> 
> Checked on x86_64-linux-gnu.
> ---

Only tiny language nits below, LGTM otherwise.

Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>

>   elf/dl-tunables.c  | 48 ++++++++++++++++++++++++++++++++++------------
>   elf/tst-tunables.c | 13 +++++++++----
>   2 files changed, 45 insertions(+), 16 deletions(-)
> 
> diff --git a/elf/dl-tunables.c b/elf/dl-tunables.c
> index 082a76d9c4..e1198869d6 100644
> --- a/elf/dl-tunables.c
> +++ b/elf/dl-tunables.c
> @@ -154,17 +154,29 @@ __tunable_set_val (tunable_id_t id, tunable_val_t *valp, tunable_num_t *minp,
>     do_tunable_update_val (cur, valp, minp, maxp);
>   }
>   
> -/* Parse the tunable string VALSTRING.  VALSTRING is a duplicated value,
> -   where delimiters ':' are replaced with '\0', so string tunables are null
> -   terminated.  */
> -static void
> -parse_tunables (char *valstring)
> +struct tunable_toset_t
> +{
> +  tunable_t *t;
> +  const char *value;
> +};
> +
> +enum { tunables_list_size = array_length (tunable_list) };
> +
> +/* Parse the tunable string VALSTRING and set TUNABLES with the found tunables
> +   and their respectibles values.  VALSTRING is a duplicated values,  where

s/respectibles/respective/

s/duplicated values/duplicated string/

> +   delimiters ':' are replaced with '\0', so string tunables are null
> +   terminated.
> +   Return the number of tunables found (including 0 if the string is empty)
> +   or -1 if for a ill-formatted definition.  */

s/a ill-formatted/an ill-formatted/

> +static int
> +parse_tunables_string (char *valstring, struct tunable_toset_t *tunables)
>   {
>     if (valstring == NULL || *valstring == '\0')
> -    return;
> +    return 0;
>   
>     char *p = valstring;
>     bool done = false;
> +  int ntunables = 0;
>   
>     while (!done)
>       {
> @@ -177,7 +189,7 @@ parse_tunables (char *valstring)
>         /* If we reach the end of the string before getting a valid name-value
>   	 pair, bail out.  */
>         if (*p == '\0')
> -	break;
> +	return -1;
>   
>         /* We did not find a valid name-value pair before encountering the
>   	 colon.  */
> @@ -190,30 +202,42 @@ parse_tunables (char *valstring)
>         /* Skip the '='.  */
>         p++;
>   
> -      const char *value = p;
> +      char *value = p;
>   
>         while (*p != '=' && *p != ':' && *p != '\0')
>   	p++;
>   
>         if (*p == '=')
> -	break;
> +	return -1;
>         else if (*p == '\0')
>   	done = true;
>         else
>   	*p++ = '\0';
>   
>         /* Add the tunable if it exists.  */
> -      for (size_t i = 0; i < sizeof (tunable_list) / sizeof (tunable_t); i++)
> +      for (size_t i = 0; i < tunables_list_size; i++)
>   	{
>   	  tunable_t *cur = &tunable_list[i];
>   
>   	  if (tunable_is_name (cur->name, name))
>   	    {
> -	      tunable_initialize (cur, value);
> +	      tunables[ntunables++] = (struct tunable_toset_t) { cur, value };
>   	      break;
>   	    }
>   	}
>       }
> +
> +  return ntunables;
> +}
> +
> +static void
> +parse_tunables (char *valstring)
> +{
> +  struct tunable_toset_t tunables[tunables_list_size];
> +  int ntunables = parse_tunables_string (valstring, tunables);
> +
> +  for (int i = 0; i < ntunables; i++)
> +    tunable_initialize (tunables[i].t, tunables[i].value);
>   }
>   
>   /* Initialize the tunables list from the environment.  For now we only use the
> @@ -240,7 +264,7 @@ __tunables_init (char **envp)
>   	  continue;
>   	}
>   
> -      for (int i = 0; i < sizeof (tunable_list) / sizeof (tunable_t); i++)
> +      for (int i = 0; i < tunables_list_size; i++)
>   	{
>   	  tunable_t *cur = &tunable_list[i];
>   
> diff --git a/elf/tst-tunables.c b/elf/tst-tunables.c
> index 7fe9907e05..e1ad44f27c 100644
> --- a/elf/tst-tunables.c
> +++ b/elf/tst-tunables.c
> @@ -161,7 +161,7 @@ static const struct test_t
>       0,
>       0,
>     },
> -  /* If there is a ill-formatted key=value, everything after is also ignored.  */
> +  /* Ill-formatted tunables string is not parsed.  */
>     {
>       "glibc.malloc.mmap_threshold=glibc.malloc.mmap_threshold=4096:glibc.malloc.check=2",
>       0,
> @@ -186,13 +186,18 @@ static const struct test_t
>       0,
>       0,
>     },
> -  /* Valid tunables set before ill-formatted ones are set.  */
>     {
>       "glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096=4096",
> -    2,
>       0,
>       0,
> -  }
> +    0,
> +  },
> +  {
> +    "glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096=4096",
> +    0,
> +    0,
> +    0,
> +  },
>   };
>   
>   static int

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 08/19] elf: Emit warning if tunable is ill-formatted
  2023-11-06 20:25 ` [PATCH v3 08/19] elf: Emit warning if tunable is ill-formatted Adhemerval Zanella
@ 2023-11-20 21:50   ` Siddhesh Poyarekar
  0 siblings, 0 replies; 40+ messages in thread
From: Siddhesh Poyarekar @ 2023-11-20 21:50 UTC (permalink / raw)
  To: Adhemerval Zanella, libc-alpha



On 2023-11-06 15:25, Adhemerval Zanella wrote:
> So caller knows that the tunable will be ignored.
> 
> Checked on x86_64-linux-gnu.
> ---

Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>

>   elf/dl-tunables.c | 6 ++++++
>   1 file changed, 6 insertions(+)
> 
> diff --git a/elf/dl-tunables.c b/elf/dl-tunables.c
> index e1198869d6..6e3e6a2cf8 100644
> --- a/elf/dl-tunables.c
> +++ b/elf/dl-tunables.c
> @@ -235,6 +235,12 @@ parse_tunables (char *valstring)
>   {
>     struct tunable_toset_t tunables[tunables_list_size];
>     int ntunables = parse_tunables_string (valstring, tunables);
> +  if (ntunables == -1)
> +    {
> +      _dl_error_printf (
> +        "WARNING: ld.so: invalid GLIBC_TUNABLES `%s': ignored.\n", valstring);
> +      return;
> +    }
>   
>     for (int i = 0; i < ntunables; i++)
>       tunable_initialize (tunables[i].t, tunables[i].value);

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 11/19] elf: Do not duplicate the GLIBC_TUNABLES string
  2023-11-06 20:25 ` [PATCH v3 11/19] elf: Do not duplicate the GLIBC_TUNABLES string Adhemerval Zanella
@ 2023-11-20 22:44   ` Siddhesh Poyarekar
  2023-11-21 18:12     ` Adhemerval Zanella Netto
  0 siblings, 1 reply; 40+ messages in thread
From: Siddhesh Poyarekar @ 2023-11-20 22:44 UTC (permalink / raw)
  To: Adhemerval Zanella, libc-alpha

On 2023-11-06 15:25, Adhemerval Zanella wrote:
> The tunable parsing duplicates the tunable environment variable so it
> null-terminates each one since it simplifies the later parsing. It has
> the drawback of adding another point of failure (__minimal_malloc
> failing), and the memory copy requires tuning the compiler to avoid mem
> operations calls.
> 
> The parsing now tracks the tunable start and its size. The
> dl-tunable-parse.h adds helper functions to help parsing, like a strcmp
> that also checks for size and an iterator for suboptions that are
> comma-separated (used on hwcap parsing by x86, powerpc, and s390x).
> 
> Since the environment variable is allocated on the stack by the kernel,
> it is safe to keep the references to the suboptions for later parsing
> of string tunables (as done by set_hwcaps by multiple architectures).
> 
> Checked on x86_64-linux-gnu, powerpc64le-linux-gnu, and
> aarch64-linux-gnu.
> ---
>   elf/dl-tunables.c                             |  66 +++----
>   elf/dl-tunables.h                             |   6 +-
>   elf/tst-tunables.c                            |  66 ++++++-
>   sysdeps/generic/dl-tunables-parse.h           | 129 ++++++++++++++
>   sysdeps/s390/cpu-features.c                   | 167 +++++++-----------
>   .../unix/sysv/linux/aarch64/cpu-features.c    |  38 ++--
>   .../unix/sysv/linux/powerpc/cpu-features.c    |  45 ++---
>   .../sysv/linux/powerpc/tst-hwcap-tunables.c   |   6 +-
>   sysdeps/x86/Makefile                          |   4 +-
>   sysdeps/x86/cpu-tunables.c                    | 118 +++++--------
>   sysdeps/x86/tst-hwcap-tunables.c              | 148 ++++++++++++++++
>   11 files changed, 517 insertions(+), 276 deletions(-)
>   create mode 100644 sysdeps/generic/dl-tunables-parse.h
>   create mode 100644 sysdeps/x86/tst-hwcap-tunables.c
> 
> diff --git a/elf/dl-tunables.c b/elf/dl-tunables.c
> index 6e3e6a2cf8..f406521735 100644
> --- a/elf/dl-tunables.c
> +++ b/elf/dl-tunables.c
> @@ -36,28 +36,6 @@
>   #define TUNABLES_INTERNAL 1
>   #include "dl-tunables.h"
>   
> -#include <not-errno.h>
> -
> -static char *
> -tunables_strdup (const char *in)
> -{
> -  size_t i = 0;
> -
> -  while (in[i++] != '\0');
> -  char *out = __minimal_malloc (i + 1);
> -
> -  /* For most of the tunables code, we ignore user errors.  However,
> -     this is a system error - and running out of memory at program
> -     startup should be reported, so we do.  */
> -  if (out == NULL)
> -    _dl_fatal_printf ("failed to allocate memory to process tunables\n");
> -
> -  while (i-- > 0)
> -    out[i] = in[i];
> -
> -  return out;
> -}
> -
>   static char **
>   get_next_env (char **envp, char **name, size_t *namelen, char **val,
>   	      char ***prev_envp)
> @@ -134,14 +112,14 @@ do_tunable_update_val (tunable_t *cur, const tunable_val_t *valp,
>   /* Validate range of the input value and initialize the tunable CUR if it looks
>      good.  */
>   static void
> -tunable_initialize (tunable_t *cur, const char *strval)
> +tunable_initialize (tunable_t *cur, const char *strval, size_t len)
>   {
> -  tunable_val_t val;
> +  tunable_val_t val = { 0 };
>   
>     if (cur->type.type_code != TUNABLE_TYPE_STRING)
>       val.numval = (tunable_num_t) _dl_strtoul (strval, NULL);

There's an implicit assumption that strval is NULL terminated for 
numeric values.  Is that safe?  Maybe all you need to do here is copy 
strval into a static buffer of size 21 (basically size of the string 
representing -1ULL) and pass that to _dl_strtoul.

>     else
> -    val.strval = strval;
> +    val.strval = (struct tunable_str_t) { strval, len };
>     do_tunable_update_val (cur, &val, NULL, NULL);
>   }
>   
> @@ -158,29 +136,29 @@ struct tunable_toset_t
>   {
>     tunable_t *t;
>     const char *value;
> +  size_t len;
>   };
>   
>   enum { tunables_list_size = array_length (tunable_list) };
>   
>   /* Parse the tunable string VALSTRING and set TUNABLES with the found tunables
> -   and their respectibles values.  VALSTRING is a duplicated values,  where
> -   delimiters ':' are replaced with '\0', so string tunables are null
> -   terminated.
> +   and their respectibles values.  The VALSTRING is parsed in place, with the
> +   tunable start and size recorded in TUNABLES.
>      Return the number of tunables found (including 0 if the string is empty)
>      or -1 if for a ill-formatted definition.  */
>   static int
> -parse_tunables_string (char *valstring, struct tunable_toset_t *tunables)
> +parse_tunables_string (const char *valstring, struct tunable_toset_t *tunables)
>   {
>     if (valstring == NULL || *valstring == '\0')
>       return 0;
>   
> -  char *p = valstring;
> +  const char *p = valstring;
>     bool done = false;
>     int ntunables = 0;
>   
>     while (!done)
>       {
> -      char *name = p;
> +      const char *name = p;
>   
>         /* First, find where the name ends.  */
>         while (*p != '=' && *p != ':' && *p != '\0')
> @@ -202,7 +180,7 @@ parse_tunables_string (char *valstring, struct tunable_toset_t *tunables)
>         /* Skip the '='.  */
>         p++;
>   
> -      char *value = p;
> +      const char *value = p;
>   
>         while (*p != '=' && *p != ':' && *p != '\0')
>   	p++;
> @@ -211,8 +189,6 @@ parse_tunables_string (char *valstring, struct tunable_toset_t *tunables)
>   	return -1;
>         else if (*p == '\0')
>   	done = true;
> -      else
> -	*p++ = '\0';
>   
>         /* Add the tunable if it exists.  */
>         for (size_t i = 0; i < tunables_list_size; i++)
> @@ -221,7 +197,8 @@ parse_tunables_string (char *valstring, struct tunable_toset_t *tunables)
>   
>   	  if (tunable_is_name (cur->name, name))
>   	    {
> -	      tunables[ntunables++] = (struct tunable_toset_t) { cur, value };
> +	      tunables[ntunables++] =
> +		(struct tunable_toset_t) { cur, value, p - value };
>   	      break;
>   	    }
>   	}
> @@ -231,7 +208,7 @@ parse_tunables_string (char *valstring, struct tunable_toset_t *tunables)
>   }
>   
>   static void
> -parse_tunables (char *valstring)
> +parse_tunables (const char *valstring)
>   {
>     struct tunable_toset_t tunables[tunables_list_size];
>     int ntunables = parse_tunables_string (valstring, tunables);
> @@ -243,7 +220,7 @@ parse_tunables (char *valstring)
>       }
>   
>     for (int i = 0; i < ntunables; i++)
> -    tunable_initialize (tunables[i].t, tunables[i].value);
> +    tunable_initialize (tunables[i].t, tunables[i].value, tunables[i].len);
>   }
>   
>   /* Initialize the tunables list from the environment.  For now we only use the
> @@ -264,9 +241,12 @@ __tunables_init (char **envp)
>     while ((envp = get_next_env (envp, &envname, &len, &envval,
>   			       &prev_envp)) != NULL)
>       {
> +      /* The environment variable is allocated on the stack by the kernel, so
> +	 it is safe to keep the references to the suboptions for later parsing
> +	 of string tunables.  */
>         if (tunable_is_name ("GLIBC_TUNABLES", envname))
>   	{
> -	  parse_tunables (tunables_strdup (envval));
> +	  parse_tunables (envval);
>   	  continue;
>   	}
>   
> @@ -284,7 +264,7 @@ __tunables_init (char **envp)
>   	  /* We have a match.  Initialize and move on to the next line.  */
>   	  if (tunable_is_name (name, envname))
>   	    {
> -	      tunable_initialize (cur, envval);
> +	      tunable_initialize (cur, envval, len);
>   	      break;
>   	    }
>   	}
> @@ -298,7 +278,7 @@ __tunables_print (void)
>       {
>         const tunable_t *cur = &tunable_list[i];
>         if (cur->type.type_code == TUNABLE_TYPE_STRING
> -	  && cur->val.strval == NULL)
> +	  && cur->val.strval.str == NULL)
>   	_dl_printf ("%s:\n", cur->name);
>         else
>   	{
> @@ -324,7 +304,9 @@ __tunables_print (void)
>   			  (size_t) cur->type.max);
>   	      break;
>   	    case TUNABLE_TYPE_STRING:
> -	      _dl_printf ("%s\n", cur->val.strval);
> +	      _dl_printf ("%.*s\n",
> +			  (int) cur->val.strval.len,
> +			  cur->val.strval.str);
>   	      break;
>   	    default:
>   	      __builtin_unreachable ();
> @@ -359,7 +341,7 @@ __tunable_get_val (tunable_id_t id, void *valp, tunable_callback_t callback)
>   	}
>       case TUNABLE_TYPE_STRING:
>   	{
> -	  *((const char **)valp) = cur->val.strval;
> +	  *((struct tunable_str_t **) valp) = &cur->val.strval;
>   	  break;
>   	}
>       default:
> diff --git a/elf/dl-tunables.h b/elf/dl-tunables.h
> index 45c191e021..0e777d7d37 100644
> --- a/elf/dl-tunables.h
> +++ b/elf/dl-tunables.h
> @@ -30,7 +30,11 @@ typedef intmax_t tunable_num_t;
>   typedef union
>   {
>     tunable_num_t numval;
> -  const char *strval;
> +  struct tunable_str_t
> +  {
> +    const char *str;
> +    size_t len;
> +  } strval;
>   } tunable_val_t;
>   
>   typedef void (*tunable_callback_t) (tunable_val_t *);
> diff --git a/elf/tst-tunables.c b/elf/tst-tunables.c
> index e1ad44f27c..188345b070 100644
> --- a/elf/tst-tunables.c
> +++ b/elf/tst-tunables.c
> @@ -31,7 +31,8 @@ static int restart;
>   
>   static const struct test_t
>   {
> -  const char *env;
> +  const char *name;
> +  const char *value;
>     int32_t expected_malloc_check;
>     size_t expected_mmap_threshold;
>     int32_t expected_perturb;
> @@ -39,12 +40,14 @@ static const struct test_t
>   {
>     /* Expected tunable format.  */
>     {
> +    "GLIBC_TUNABLES",
>       "glibc.malloc.check=2",
>       2,
>       0,
>       0,
>     },
>     {
> +    "GLIBC_TUNABLES",
>       "glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096",
>       2,
>       4096,
> @@ -52,6 +55,7 @@ static const struct test_t
>     },
>     /* Empty tunable are ignored.  */
>     {
> +    "GLIBC_TUNABLES",
>       "glibc.malloc.check=2::glibc.malloc.mmap_threshold=4096",
>       2,
>       4096,
> @@ -59,6 +63,7 @@ static const struct test_t
>     },
>     /* As well empty values.  */
>     {
> +    "GLIBC_TUNABLES",
>       "glibc.malloc.check=:glibc.malloc.mmap_threshold=4096",
>       0,
>       4096,
> @@ -66,18 +71,21 @@ static const struct test_t
>     },
>     /* Tunable are processed from left to right, so last one is the one set.  */
>     {
> +    "GLIBC_TUNABLES",
>       "glibc.malloc.check=1:glibc.malloc.check=2",
>       2,
>       0,
>       0,
>     },
>     {
> +    "GLIBC_TUNABLES",
>       "glibc.malloc.check=1:glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096",
>       2,
>       4096,
>       0,
>     },
>     {
> +    "GLIBC_TUNABLES",
>       "glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096:glibc.malloc.check=1",
>       1,
>       4096,
> @@ -85,12 +93,14 @@ static const struct test_t
>     },
>     /* 0x800 is larger than tunable maxval (0xff), so the tunable is unchanged.  */
>     {
> +    "GLIBC_TUNABLES",
>       "glibc.malloc.perturb=0x800",
>       0,
>       0,
>       0,
>     },
>     {
> +    "GLIBC_TUNABLES",
>       "glibc.malloc.perturb=0x55",
>       0,
>       0,
> @@ -98,6 +108,7 @@ static const struct test_t
>     },
>     /* Out of range values are just ignored.  */
>     {
> +    "GLIBC_TUNABLES",
>       "glibc.malloc.perturb=0x800:glibc.malloc.mmap_threshold=4096",
>       0,
>       4096,
> @@ -105,24 +116,28 @@ static const struct test_t
>     },
>     /* Invalid keys are ignored.  */
>     {
> +    "GLIBC_TUNABLES",
>       ":glibc.malloc.garbage=2:glibc.malloc.check=1",
>       1,
>       0,
>       0,
>     },
>     {
> +    "GLIBC_TUNABLES",
>       "glibc.malloc.perturb=0x800:not_valid.malloc.check=2:glibc.malloc.mmap_threshold=4096",
>       0,
>       4096,
>       0,
>     },
>     {
> +    "GLIBC_TUNABLES",
>       "glibc.not_valid.check=2:glibc.malloc.mmap_threshold=4096",
>       0,
>       4096,
>       0,
>     },
>     {
> +    "GLIBC_TUNABLES",
>       "not_valid.malloc.check=2:glibc.malloc.mmap_threshold=4096",
>       0,
>       4096,
> @@ -130,24 +145,28 @@ static const struct test_t
>     },
>     /* Invalid subkeys are ignored.  */
>     {
> +    "GLIBC_TUNABLES",
>       "glibc.malloc.garbage=2:glibc.maoc.mmap_threshold=4096:glibc.malloc.check=2",
>       2,
>       0,
>       0,
>     },
>     {
> +    "GLIBC_TUNABLES",
>       "glibc.malloc.check=4:glibc.malloc.garbage=2:glibc.maoc.mmap_threshold=4096",
>       0,
>       0,
>       0,
>     },
>     {
> +    "GLIBC_TUNABLES",
>       "not_valid.malloc.check=2",
>       0,
>       0,
>       0,
>     },
>     {
> +    "GLIBC_TUNABLES",
>       "glibc.not_valid.check=2",
>       0,
>       0,
> @@ -156,6 +175,7 @@ static const struct test_t
>     /* An ill-formatted tunable in the for key=key=value will considere the
>        value as 'key=value' (which can not be parsed as an integer).  */
>     {
> +    "GLIBC_TUNABLES",
>       "glibc.malloc.mmap_threshold=glibc.malloc.mmap_threshold=4096",
>       0,
>       0,
> @@ -163,41 +183,77 @@ static const struct test_t
>     },
>     /* Ill-formatted tunables string is not parsed.  */
>     {
> +    "GLIBC_TUNABLES",
>       "glibc.malloc.mmap_threshold=glibc.malloc.mmap_threshold=4096:glibc.malloc.check=2",
>       0,
>       0,
>       0,
>     },
>     {
> +    "GLIBC_TUNABLES",
>       "glibc.malloc.check=2=2",
>       0,
>       0,
>       0,
>     },
>     {
> +    "GLIBC_TUNABLES",
>       "glibc.malloc.check=2=2:glibc.malloc.mmap_threshold=4096",
>       0,
>       0,
>       0,
>     },
>     {
> +    "GLIBC_TUNABLES",
>       "glibc.malloc.check=2=2:glibc.malloc.check=2",
>       0,
>       0,
>       0,
>     },
>     {
> +    "GLIBC_TUNABLES",
>       "glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096=4096",
>       0,
>       0,
>       0,
>     },
>     {
> +    "GLIBC_TUNABLES",
>       "glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096=4096",
>       0,
>       0,
>       0,
>     },
> +  /* Also check some tunable aliases.  */
> +  {
> +    "MALLOC_CHECK_",
> +    "2",
> +    2,
> +    0,
> +    0,
> +  },
> +  {
> +    "MALLOC_MMAP_THRESHOLD_",
> +    "4096",
> +    0,
> +    4096,
> +    0,
> +  },
> +  {
> +    "MALLOC_PERTURB_",
> +    "0x55",
> +    0,
> +    0,
> +    0x55,
> +  },
> +  /* 0x800 is larger than tunable maxval (0xff), so the tunable is unchanged.  */
> +  {
> +    "MALLOC_PERTURB_",
> +    "0x800",
> +    0,
> +    0,
> +    0,
> +  },
>   };
>   
>   static int
> @@ -245,13 +301,17 @@ do_test (int argc, char *argv[])
>       {
>         snprintf (nteststr, sizeof nteststr, "%d", i);
>   
> -      printf ("[%d] Spawned test for %s\n", i, tests[i].env);
> -      setenv ("GLIBC_TUNABLES", tests[i].env, 1);
> +      printf ("[%d] Spawned test for %s=%s\n",
> +	      i,
> +	      tests[i].name,
> +	      tests[i].value);
> +      setenv (tests[i].name, tests[i].value, 1);
>         struct support_capture_subprocess result
>   	= support_capture_subprogram (spargv[0], spargv);
>         support_capture_subprocess_check (&result, "tst-tunables", 0,
>   					sc_allow_stderr);
>         support_capture_subprocess_free (&result);
> +      unsetenv (tests[i].name);
>       }
>   
>     return 0;
> diff --git a/sysdeps/generic/dl-tunables-parse.h b/sysdeps/generic/dl-tunables-parse.h
> new file mode 100644
> index 0000000000..5b399f852d
> --- /dev/null
> +++ b/sysdeps/generic/dl-tunables-parse.h
> @@ -0,0 +1,129 @@
> +/* Helper functions to handle tunable strings.
> +   Copyright (C) 2023 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/>.  */
> +
> +#ifndef _DL_TUNABLES_PARSE_H
> +#define _DL_TUNABLES_PARSE_H 1
> +
> +#include <string.h>
> +
> +/* Compare the contents of STRVAL with STR of size LEN.  The STR might not
> +   be null-terminated.   */
> +static __always_inline bool
> +tunable_strcmp (const struct tunable_str_t *strval, const char *str,
> +		size_t len)
> +{
> +  return strval->len == len && memcmp (strval->str, str, len) == 0;
> +}
> +#define tunable_strcmp_cte(__tunable, __str) \
> + tunable_strcmp (&__tunable->strval, __str, sizeof (__str) - 1)
> +
> +/*
> +   Helper functions to iterate over a tunable string composed by multiple
> +   suboptions separated by commaxi; this is a common pattern for CPU.  Each
> +   suboptions is return in the form of { address, size } (no null terminated).
> +   For instance:
> +
> +     struct tunable_str_comma_t ts;
> +     tunable_str_comma_init (&ts, valp);
> +
> +     struct tunable_str_t t;
> +     while (tunable_str_comma_next (&ts, &t))
> +      {
> +	_dl_printf ("[%s] %.*s (%d)\n",
> +		    __func__,
> +		    (int) tstr.len,
> +		    tstr.str,
> +		    (int) tstr.len);
> +
> +        if (tunable_str_comma_strcmp (&t, opt, opt1_len))
> +	  {
> +	    [...]
> +	  }
> +	else if (tunable_str_comma_strcmp_cte (&t, "opt2"))
> +	  {
> +	    [...]
> +	  }
> +      }
> +*/
> +
> +struct tunable_str_comma_state_t
> +{
> +  const char *p;
> +  size_t plen;
> +  size_t maxplen;
> +};
> +
> +struct tunable_str_comma_t
> +{
> +  const char *str;
> +  size_t len;
> +  bool disable;
> +};
> +
> +static inline void
> +tunable_str_comma_init (struct tunable_str_comma_state_t *state,
> +			tunable_val_t *valp)
> +{

Maybe add an assertion here that valp->strval.str is not NULL since all 
functions assume that?  Or at least a comment somewhere.

> +  state->p = valp->strval.str;
> +  state->plen = 0;
> +  state->maxplen = valp->strval.len;
> +}
> +
> +static inline bool
> +tunable_str_comma_next (struct tunable_str_comma_state_t *state,
> +			struct tunable_str_comma_t *str)
> +{
> +  if (*state->p == '\0' || state->plen >= state->maxplen)
> +    return false;
> +
> +  const char *c;
> +  for (c = state->p; *c != ','; c++, state->plen++)
> +    if (*c == '\0' || state->plen == state->maxplen)
> +      break;
> +
> +  str->str = state->p;
> +  str->len = c - state->p;
> +
> +  if (str->len > 0)
> +    {
> +      str->disable = *str->str == '-';
> +      if (str->disable)
> +	{
> +	  str->str = str->str + 1;
> +	  str->len = str->len - 1;
> +	}
> +    }
> +
> +  state->p = c + 1;
> +  state->plen++;
> +
> +  return true;
> +}
> +
> +/* Compare the contents of T with STR of size LEN.  The STR might not be
> +   null-terminated.   */
> +static __always_inline bool
> +tunable_str_comma_strcmp (const struct tunable_str_comma_t *t, const char *str,
> +			  size_t len)
> +{
> +  return t->len == len && memcmp (t->str, str, len) == 0;
> +}
> +#define tunable_str_comma_strcmp_cte(__t, __str) \
> +  tunable_str_comma_strcmp (__t, __str, sizeof (__str) - 1)
> +
> +#endif
> diff --git a/sysdeps/s390/cpu-features.c b/sysdeps/s390/cpu-features.c
> index 55449ba07f..8b1466fa7b 100644
> --- a/sysdeps/s390/cpu-features.c
> +++ b/sysdeps/s390/cpu-features.c
> @@ -22,6 +22,7 @@
>   #include <ifunc-memcmp.h>
>   #include <string.h>
>   #include <dl-symbol-redir-ifunc.h>
> +#include <dl-tunables-parse.h>
>   
>   #define S390_COPY_CPU_FEATURES(SRC_PTR, DEST_PTR)	\
>     (DEST_PTR)->hwcap = (SRC_PTR)->hwcap;			\
> @@ -51,33 +52,14 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
>     struct cpu_features cpu_features_curr;
>     S390_COPY_CPU_FEATURES (cpu_features, &cpu_features_curr);
>   
> -  const char *token = valp->strval;
> -  do
> +  struct tunable_str_comma_state_t ts;
> +  tunable_str_comma_init (&ts, valp);
> +
> +  struct tunable_str_comma_t t;
> +  while (tunable_str_comma_next (&ts, &t))
>       {
> -      const char *token_end, *feature;
> -      bool disable;
> -      size_t token_len;
> -      size_t feature_len;
> -
> -      /* Find token separator or end of string.  */
> -      for (token_end = token; *token_end != ','; token_end++)
> -	if (*token_end == '\0')
> -	  break;
> -
> -      /* Determine feature.  */
> -      token_len = token_end - token;
> -      if (*token == '-')
> -	{
> -	  disable = true;
> -	  feature = token + 1;
> -	  feature_len = token_len - 1;
> -	}
> -      else
> -	{
> -	  disable = false;
> -	  feature = token;
> -	  feature_len = token_len;
> -	}
> +      if (t.len == 0)
> +	continue;
>   
>         /* Handle only the features here which are really used in the
>   	 IFUNC-resolvers.  All others are ignored as the values are only used
> @@ -85,85 +67,65 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
>         bool reset_features = false;
>         unsigned long int hwcap_mask = 0UL;
>         unsigned long long stfle_bits0_mask = 0ULL;
> +      bool disable = t.disable;
>   
> -      if ((*feature == 'z' || *feature == 'a'))
> +      if (tunable_str_comma_strcmp_cte (&t, "zEC12")
> +	  || tunable_str_comma_strcmp_cte (&t, "arch10"))
> +	{
> +	  reset_features = true;
> +	  disable = true;
> +	  hwcap_mask = HWCAP_S390_VXRS | HWCAP_S390_VXRS_EXT
> +	    | HWCAP_S390_VXRS_EXT2;
> +	  stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
> +	}
> +      else if (tunable_str_comma_strcmp_cte (&t, "z13")
> +	       || tunable_str_comma_strcmp_cte (&t, "arch11"))
> +	{
> +	  reset_features = true;
> +	  disable = true;
> +	  hwcap_mask = HWCAP_S390_VXRS_EXT | HWCAP_S390_VXRS_EXT2;
> +	  stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
> +	}
> +      else if (tunable_str_comma_strcmp_cte (&t, "z14")
> +	       || tunable_str_comma_strcmp_cte (&t, "arch12"))
> +	{
> +	  reset_features = true;
> +	  disable = true;
> +	  hwcap_mask = HWCAP_S390_VXRS_EXT2;
> +	  stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
> +	}
> +      else if (tunable_str_comma_strcmp_cte (&t, "z15")
> +	       || tunable_str_comma_strcmp_cte (&t, "z16")
> +	       || tunable_str_comma_strcmp_cte (&t, "arch13")
> +	       || tunable_str_comma_strcmp_cte (&t, "arch14"))
>   	{
> -	  if ((feature_len == 5 && *feature == 'z'
> -	       && memcmp (feature, "zEC12", 5) == 0)
> -	      || (feature_len == 6 && *feature == 'a'
> -		  && memcmp (feature, "arch10", 6) == 0))
> -	    {
> -	      reset_features = true;
> -	      disable = true;
> -	      hwcap_mask = HWCAP_S390_VXRS | HWCAP_S390_VXRS_EXT
> -		| HWCAP_S390_VXRS_EXT2;
> -	      stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
> -	    }
> -	  else if ((feature_len == 3 && *feature == 'z'
> -		    && memcmp (feature, "z13", 3) == 0)
> -		   || (feature_len == 6 && *feature == 'a'
> -		       && memcmp (feature, "arch11", 6) == 0))
> -	    {
> -	      reset_features = true;
> -	      disable = true;
> -	      hwcap_mask = HWCAP_S390_VXRS_EXT | HWCAP_S390_VXRS_EXT2;
> -	      stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
> -	    }
> -	  else if ((feature_len == 3 && *feature == 'z'
> -		    && memcmp (feature, "z14", 3) == 0)
> -		   || (feature_len == 6 && *feature == 'a'
> -		       && memcmp (feature, "arch12", 6) == 0))
> -	    {
> -	      reset_features = true;
> -	      disable = true;
> -	      hwcap_mask = HWCAP_S390_VXRS_EXT2;
> -	      stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
> -	    }
> -	  else if ((feature_len == 3 && *feature == 'z'
> -		    && (memcmp (feature, "z15", 3) == 0
> -			|| memcmp (feature, "z16", 3) == 0))
> -		   || (feature_len == 6
> -		       && (memcmp (feature, "arch13", 6) == 0
> -			   || memcmp (feature, "arch14", 6) == 0)))
> -	    {
> -	      /* For z15 or newer we don't have to disable something,
> -		 but we have to reset to the original values.  */
> -	      reset_features = true;
> -	    }
> +	  /* For z15 or newer we don't have to disable something, but we have
> +	     to reset to the original values.  */
> +	  reset_features = true;
>   	}
> -      else if (*feature == 'H')
> +      else if (tunable_str_comma_strcmp_cte (&t, "HWCAP_S390_VXRS"))
>   	{
> -	  if (feature_len == 15
> -	      && memcmp (feature, "HWCAP_S390_VXRS", 15) == 0)
> -	    {
> -	      hwcap_mask = HWCAP_S390_VXRS;
> -	      if (disable)
> -		hwcap_mask |= HWCAP_S390_VXRS_EXT | HWCAP_S390_VXRS_EXT2;
> -	    }
> -	  else if (feature_len == 19
> -		   && memcmp (feature, "HWCAP_S390_VXRS_EXT", 19) == 0)
> -	    {
> -	      hwcap_mask = HWCAP_S390_VXRS_EXT;
> -	      if (disable)
> -		hwcap_mask |= HWCAP_S390_VXRS_EXT2;
> -	      else
> -		hwcap_mask |= HWCAP_S390_VXRS;
> -	    }
> -	  else if (feature_len == 20
> -		   && memcmp (feature, "HWCAP_S390_VXRS_EXT2", 20) == 0)
> -	    {
> -	      hwcap_mask = HWCAP_S390_VXRS_EXT2;
> -	      if (!disable)
> -		hwcap_mask |= HWCAP_S390_VXRS | HWCAP_S390_VXRS_EXT;
> -	    }
> +	  hwcap_mask = HWCAP_S390_VXRS;
> +	  if (t.disable)
> +	    hwcap_mask |= HWCAP_S390_VXRS_EXT | HWCAP_S390_VXRS_EXT2;
>   	}
> -      else if (*feature == 'S')
> +      else if (tunable_str_comma_strcmp_cte (&t, "HWCAP_S390_VXRS_EXT"))
>   	{
> -	  if (feature_len == 10
> -	      && memcmp (feature, "STFLE_MIE3", 10) == 0)
> -	    {
> -	      stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
> -	    }
> +	  hwcap_mask = HWCAP_S390_VXRS_EXT;
> +	  if (t.disable)
> +	    hwcap_mask |= HWCAP_S390_VXRS_EXT2;
> +	  else
> +	    hwcap_mask |= HWCAP_S390_VXRS;
> +	}
> +      else if (tunable_str_comma_strcmp_cte (&t, "HWCAP_S390_VXRS_EXT2"))
> +	{
> +	  hwcap_mask = HWCAP_S390_VXRS_EXT2;
> +	  if (!t.disable)
> +	    hwcap_mask |= HWCAP_S390_VXRS | HWCAP_S390_VXRS_EXT;
> +	}
> +      else if (tunable_str_comma_strcmp_cte (&t, "STFLE_MIE3"))
> +	{

Redundant braces.

> +	  stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
>   	}
>   
>         /* Perform the actions determined above.  */
> @@ -187,14 +149,7 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
>   	  else
>   	    cpu_features_curr.stfle_bits[0] |= stfle_bits0_mask;
>   	}
> -
> -      /* Jump over current token ... */
> -      token += token_len;
> -
> -      /* ... and skip token separator for next round.  */
> -      if (*token == ',') token++;
>       }
> -  while (*token != '\0');
>   
>     /* Copy back the features after checking that no unsupported features were
>        enabled by user.  */
> diff --git a/sysdeps/unix/sysv/linux/aarch64/cpu-features.c b/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
> index 233d5b2407..9b76cb89c7 100644
> --- a/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
> +++ b/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
> @@ -16,10 +16,12 @@
>      License along with the GNU C Library; if not, see
>      <https://www.gnu.org/licenses/>.  */
>   
> +#include <array_length.h>
>   #include <cpu-features.h>
>   #include <sys/auxv.h>
>   #include <elf/dl-hwcaps.h>
>   #include <sys/prctl.h>
> +#include <dl-tunables-parse.h>
>   
>   #define DCZID_DZP_MASK (1 << 4)
>   #define DCZID_BS_MASK (0xf)
> @@ -33,28 +35,32 @@
>   struct cpu_list
>   {
>     const char *name;
> +  size_t len;
>     uint64_t midr;
>   };
>   
> -static struct cpu_list cpu_list[] = {
> -      {"falkor",	 0x510FC000},
> -      {"thunderxt88",	 0x430F0A10},
> -      {"thunderx2t99",   0x431F0AF0},
> -      {"thunderx2t99p1", 0x420F5160},
> -      {"phecda",	 0x680F0000},
> -      {"ares",		 0x411FD0C0},
> -      {"emag",		 0x503F0001},
> -      {"kunpeng920", 	 0x481FD010},
> -      {"a64fx",		 0x460F0010},
> -      {"generic", 	 0x0}
> +static const struct cpu_list cpu_list[] =
> +{
> +#define CPU_LIST_ENTRY(__str, __num) { __str, sizeof (__str) - 1, __num }
> +  CPU_LIST_ENTRY ("falkor",         0x510FC000),
> +  CPU_LIST_ENTRY ("thunderxt88",    0x430F0A10),
> +  CPU_LIST_ENTRY ("thunderx2t99",   0x431F0AF0),
> +  CPU_LIST_ENTRY ("thunderx2t99p1", 0x420F5160),
> +  CPU_LIST_ENTRY ("phecda",         0x680F0000),
> +  CPU_LIST_ENTRY ("ares",           0x411FD0C0),
> +  CPU_LIST_ENTRY ("emag",           0x503F0001),
> +  CPU_LIST_ENTRY ("kunpeng920",     0x481FD010),
> +  CPU_LIST_ENTRY ("a64fx",          0x460F0010),
> +  CPU_LIST_ENTRY ("generic",        0x0),
>   };
>   
>   static uint64_t
> -get_midr_from_mcpu (const char *mcpu)
> +get_midr_from_mcpu (const struct tunable_str_t *mcpu)
>   {
> -  for (int i = 0; i < sizeof (cpu_list) / sizeof (struct cpu_list); i++)
> -    if (strcmp (mcpu, cpu_list[i].name) == 0)
> +  for (int i = 0; i < array_length (cpu_list); i++) {
> +    if (tunable_strcmp (mcpu, cpu_list[i].name, cpu_list[i].len))
>         return cpu_list[i].midr;
> +  }

Redundant braces.

>   
>     return UINT64_MAX;
>   }
> @@ -65,7 +71,9 @@ init_cpu_features (struct cpu_features *cpu_features)
>     register uint64_t midr = UINT64_MAX;
>   
>     /* Get the tunable override.  */
> -  const char *mcpu = TUNABLE_GET (glibc, cpu, name, const char *, NULL);
> +  const struct tunable_str_t *mcpu = TUNABLE_GET (glibc, cpu, name,
> +						  struct tunable_str_t *,
> +						  NULL);
>     if (mcpu != NULL)
>       midr = get_midr_from_mcpu (mcpu);
>   
> diff --git a/sysdeps/unix/sysv/linux/powerpc/cpu-features.c b/sysdeps/unix/sysv/linux/powerpc/cpu-features.c
> index 7c6e20e702..390b3fd11a 100644
> --- a/sysdeps/unix/sysv/linux/powerpc/cpu-features.c
> +++ b/sysdeps/unix/sysv/linux/powerpc/cpu-features.c
> @@ -20,6 +20,7 @@
>   #include <stdint.h>
>   #include <cpu-features.h>
>   #include <elf/dl-tunables.h>
> +#include <dl-tunables-parse.h>
>   #include <unistd.h>
>   #include <string.h>
>   
> @@ -43,41 +44,26 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
>     struct cpu_features *cpu_features = &GLRO(dl_powerpc_cpu_features);
>     unsigned long int tcbv_hwcap = cpu_features->hwcap;
>     unsigned long int tcbv_hwcap2 = cpu_features->hwcap2;
> -  const char *token = valp->strval;
> -  do
> +
> +  struct tunable_str_comma_state_t ts;
> +  tunable_str_comma_init (&ts, valp);
> +
> +  struct tunable_str_comma_t t;
> +  while (tunable_str_comma_next (&ts, &t))
>       {
> -      const char *token_end, *feature;
> -      bool disable;
> -      size_t token_len, i, feature_len, offset = 0;
> -      /* Find token separator or end of string.  */
> -      for (token_end = token; *token_end != ','; token_end++)
> -	if (*token_end == '\0')
> -	  break;
> +      if (t.len == 0)
> +	continue;
>   
> -      /* Determine feature.  */
> -      token_len = token_end - token;
> -      if (*token == '-')
> -	{
> -	  disable = true;
> -	  feature = token + 1;
> -	  feature_len = token_len - 1;
> -	}
> -      else
> -	{
> -	  disable = false;
> -	  feature = token;
> -	  feature_len = token_len;
> -	}
> -      for (i = 0; i < array_length (hwcap_tunables); ++i)
> +      size_t offset = 0;
> +      for (int i = 0; i < array_length (hwcap_tunables); ++i)
>   	{
>   	  const char *hwcap_name = hwcap_names + offset;
>   	  size_t hwcap_name_len = strlen (hwcap_name);
>   	  /* Check the tunable name on the supported list.  */
> -	  if (hwcap_name_len == feature_len
> -	      && memcmp (feature, hwcap_name, feature_len) == 0)
> +	  if (tunable_str_comma_strcmp (&t, hwcap_name, hwcap_name_len))
>   	    {
>   	      /* Update the hwcap and hwcap2 bits.  */
> -	      if (disable)
> +	      if (t.disable)
>   		{
>   		  /* Id is 1 for hwcap2 tunable.  */
>   		  if (hwcap_tunables[i].id)
> @@ -98,12 +84,7 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
>   	    }
>   	  offset += hwcap_name_len + 1;
>   	}
> -	token += token_len;
> -	/* ... and skip token separator for next round.  */
> -	if (*token == ',')
> -	  token++;
>       }
> -  while (*token != '\0');
>   }
>   
>   static inline void
> diff --git a/sysdeps/unix/sysv/linux/powerpc/tst-hwcap-tunables.c b/sysdeps/unix/sysv/linux/powerpc/tst-hwcap-tunables.c
> index 2631016a3a..049164f841 100644
> --- a/sysdeps/unix/sysv/linux/powerpc/tst-hwcap-tunables.c
> +++ b/sysdeps/unix/sysv/linux/powerpc/tst-hwcap-tunables.c
> @@ -110,7 +110,11 @@ do_test (int argc, char *argv[])
>   	run_test ("-arch_2_06", "__memcpy_power7");
>         if (hwcap & PPC_FEATURE_ARCH_2_05)
>   	run_test ("-arch_2_06,-arch_2_05","__memcpy_power6");
> -      run_test ("-arch_2_06,-arch_2_05,-power5+,-power5,-power4", "__memcpy_power4");
> +      run_test ("-arch_2_06,-arch_2_05,-power5+,-power5,-power4",
> +		"__memcpy_power4");
> +      /* Also run with valid, but empty settings.  */
> +      run_test (",-,-arch_2_06,-arch_2_05,-power5+,-power5,,-power4,-",
> +		"__memcpy_power4");
>       }
>     else
>       {
> diff --git a/sysdeps/x86/Makefile b/sysdeps/x86/Makefile
> index 917c26f116..a64e5f002a 100644
> --- a/sysdeps/x86/Makefile
> +++ b/sysdeps/x86/Makefile
> @@ -12,7 +12,8 @@ CFLAGS-get-cpuid-feature-leaf.o += $(no-stack-protector)
>   
>   tests += tst-get-cpu-features tst-get-cpu-features-static \
>   	 tst-cpu-features-cpuinfo tst-cpu-features-cpuinfo-static \
> -	 tst-cpu-features-supports tst-cpu-features-supports-static
> +	 tst-cpu-features-supports tst-cpu-features-supports-static \
> +	 tst-hwcap-tunables
>   tests-static += tst-get-cpu-features-static \
>   		tst-cpu-features-cpuinfo-static \
>   		tst-cpu-features-supports-static
> @@ -65,6 +66,7 @@ $(objpfx)tst-isa-level-1.out: $(objpfx)tst-isa-level-mod-1-baseline.so \
>   endif
>   tst-ifunc-isa-2-ENV = GLIBC_TUNABLES=glibc.cpu.hwcaps=-SSE4_2,-AVX,-AVX2,-AVX512F
>   tst-ifunc-isa-2-static-ENV = $(tst-ifunc-isa-2-ENV)
> +tst-hwcap-tunables-ARGS = -- $(host-test-program-cmd)
>   endif
>   
>   ifeq ($(subdir),math)
> diff --git a/sysdeps/x86/cpu-tunables.c b/sysdeps/x86/cpu-tunables.c
> index 5697885226..0608209768 100644
> --- a/sysdeps/x86/cpu-tunables.c
> +++ b/sysdeps/x86/cpu-tunables.c
> @@ -24,11 +24,12 @@
>   #include <string.h>
>   #include <cpu-features.h>
>   #include <ldsodefs.h>
> +#include <dl-tunables-parse.h>
>   #include <dl-symbol-redir-ifunc.h>
>   
>   #define CHECK_GLIBC_IFUNC_CPU_OFF(f, cpu_features, name, len)		\
>     _Static_assert (sizeof (#name) - 1 == len, #name " != " #len);	\
> -  if (memcmp (f, #name, len) == 0)					\
> +  if (tunable_str_comma_strcmp_cte (&f, #name))				\
>       {									\
>         CPU_FEATURE_UNSET (cpu_features, name)				\
>         break;								\
> @@ -38,7 +39,7 @@
>      which isn't available.  */
>   #define CHECK_GLIBC_IFUNC_PREFERRED_OFF(f, cpu_features, name, len)	\
>     _Static_assert (sizeof (#name) - 1 == len, #name " != " #len);	\
> -  if (memcmp (f, #name, len) == 0)					\
> +  if (tunable_str_comma_strcmp_cte (&f, #name) == 0)			\
>       {									\
>         cpu_features->preferred[index_arch_##name]			\
>   	&= ~bit_arch_##name;						\
> @@ -46,12 +47,11 @@
>       }
>   
>   /* Enable/disable a preferred feature NAME.  */
> -#define CHECK_GLIBC_IFUNC_PREFERRED_BOTH(f, cpu_features, name,	\
> -					  disable, len)			\
> +#define CHECK_GLIBC_IFUNC_PREFERRED_BOTH(f, cpu_features, name,	len)	\

Spurious tab.

>     _Static_assert (sizeof (#name) - 1 == len, #name " != " #len);	\
> -  if (memcmp (f, #name, len) == 0)					\
> +  if (tunable_str_comma_strcmp_cte (&f, #name))				\
>       {									\
> -      if (disable)							\
> +      if (f.disable)							\
>   	cpu_features->preferred[index_arch_##name] &= ~bit_arch_##name;	\
>         else								\
>   	cpu_features->preferred[index_arch_##name] |= bit_arch_##name;	\
> @@ -61,11 +61,11 @@
>   /* Enable/disable a preferred feature NAME.  Enable a preferred feature
>      only if the feature NEED is usable.  */
>   #define CHECK_GLIBC_IFUNC_PREFERRED_NEED_BOTH(f, cpu_features, name,	\
> -					       need, disable, len)	\
> +					      need, len)		\
>     _Static_assert (sizeof (#name) - 1 == len, #name " != " #len);	\
> -  if (memcmp (f, #name, len) == 0)					\
> +  if (tunable_str_comma_strcmp_cte (&f, #name))				\
>       {									\
> -      if (disable)							\
> +      if (f.disable)							\
>   	cpu_features->preferred[index_arch_##name] &= ~bit_arch_##name;	\
>         else if (CPU_FEATURE_USABLE_P (cpu_features, need))		\
>   	cpu_features->preferred[index_arch_##name] |= bit_arch_##name;	\
> @@ -93,38 +93,20 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
>        NOTE: the IFUNC selection may change over time.  Please check all
>        multiarch implementations when experimenting.  */
>   
> -  const char *p = valp->strval, *c;
>     struct cpu_features *cpu_features = &GLRO(dl_x86_cpu_features);
> -  size_t len;
>   
> -  do
> -    {
> -      const char *n;
> -      bool disable;
> -      size_t nl;
> -
> -      for (c = p; *c != ','; c++)
> -	if (*c == '\0')
> -	  break;
> +  struct tunable_str_comma_state_t ts;
> +  tunable_str_comma_init (&ts, valp);
>   
> -      len = c - p;
> -      disable = *p == '-';
> -      if (disable)
> -	{
> -	  n = p + 1;
> -	  nl = len - 1;
> -	}
> -      else
> -	{
> -	  n = p;
> -	  nl = len;
> -	}
> -      switch (nl)
> +  struct tunable_str_comma_t n;
> +  while (tunable_str_comma_next (&ts, &n))
> +    {
> +      switch (n.len)
>   	{
>   	default:
>   	  break;
>   	case 3:
> -	  if (disable)
> +	  if (n.disable)
>   	    {
>   	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, AVX, 3);
>   	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, CX8, 3);
> @@ -135,7 +117,7 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
>   	    }
>   	  break;
>   	case 4:
> -	  if (disable)
> +	  if (n.disable)
>   	    {
>   	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, AVX2, 4);
>   	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, BMI1, 4);
> @@ -149,7 +131,7 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
>   	    }
>   	  break;
>   	case 5:
> -	  if (disable)
> +	  if (n.disable)
>   	    {
>   	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, LZCNT, 5);
>   	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, MOVBE, 5);
> @@ -159,12 +141,12 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
>   	    }
>   	  break;
>   	case 6:
> -	  if (disable)
> +	  if (n.disable)
>   	    {
>   	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, POPCNT, 6);
>   	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, SSE4_1, 6);
>   	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, SSE4_2, 6);
> -	      if (memcmp (n, "XSAVEC", 6) == 0)
> +	      if (memcmp (n.str, "XSAVEC", 6) == 0)

Why does this use the bare memcmp and not the tunables_strcmp?

>   		{
>   		  /* Update xsave_state_size to XSAVE state size.  */
>   		  cpu_features->xsave_state_size
> @@ -174,14 +156,14 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
>   	    }
>   	  break;
>   	case 7:
> -	  if (disable)
> +	  if (n.disable)
>   	    {
>   	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, AVX512F, 7);
>   	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, OSXSAVE, 7);
>   	    }
>   	  break;
>   	case 8:
> -	  if (disable)
> +	  if (n.disable)
>   	    {
>   	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, AVX512CD, 8);
>   	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, AVX512BW, 8);
> @@ -190,86 +172,72 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
>   	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, AVX512PF, 8);
>   	      CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, AVX512VL, 8);
>   	    }
> -	  CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features, Slow_BSF,
> -					    disable, 8);
> +	  CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features, Slow_BSF, 8);
>   	  break;
>   	case 11:
>   	    {
> -	      CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features,
> -						Prefer_ERMS,
> -						disable, 11);
> -	      CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features,
> -						Prefer_FSRM,
> -						disable, 11);
> +	      CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features, Prefer_ERMS,
> +						11);
> +	      CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features, Prefer_FSRM,
> +						11);
>   	      CHECK_GLIBC_IFUNC_PREFERRED_NEED_BOTH (n, cpu_features,
>   						     Slow_SSE4_2,
>   						     SSE4_2,
> -						     disable, 11);
> +						     11);
>   	    }
>   	  break;
>   	case 15:
>   	    {
>   	      CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features,
> -						Fast_Rep_String,
> -						disable, 15);
> +						Fast_Rep_String, 15);
>   	    }
>   	  break;
>   	case 16:
>   	    {
>   	      CHECK_GLIBC_IFUNC_PREFERRED_NEED_BOTH
> -		(n, cpu_features, Prefer_No_AVX512, AVX512F,
> -		 disable, 16);
> +		(n, cpu_features, Prefer_No_AVX512, AVX512F, 16);
>   	    }
>   	  break;
>   	case 18:
>   	    {
>   	      CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features,
> -						Fast_Copy_Backward,
> -						disable, 18);
> +						Fast_Copy_Backward, 18);
>   	    }
>   	  break;
>   	case 19:
>   	    {
>   	      CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features,
> -						Fast_Unaligned_Load,
> -						disable, 19);
> +						Fast_Unaligned_Load, 19);
>   	      CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features,
> -						Fast_Unaligned_Copy,
> -						disable, 19);
> +						Fast_Unaligned_Copy, 19);
>   	    }
>   	  break;
>   	case 20:
>   	    {
>   	      CHECK_GLIBC_IFUNC_PREFERRED_NEED_BOTH
> -		(n, cpu_features, Prefer_No_VZEROUPPER, AVX, disable,
> -		 20);
> +		(n, cpu_features, Prefer_No_VZEROUPPER, AVX, 20);
>   	    }
>   	  break;
>   	case 23:
>   	    {
>   	      CHECK_GLIBC_IFUNC_PREFERRED_NEED_BOTH
> -		(n, cpu_features, AVX_Fast_Unaligned_Load, AVX,
> -		 disable, 23);
> +		(n, cpu_features, AVX_Fast_Unaligned_Load, AVX, 23);
>   	    }
>   	  break;
>   	case 24:
>   	    {
>   	      CHECK_GLIBC_IFUNC_PREFERRED_NEED_BOTH
> -		(n, cpu_features, MathVec_Prefer_No_AVX512, AVX512F,
> -		 disable, 24);
> +		(n, cpu_features, MathVec_Prefer_No_AVX512, AVX512F, 24);
>   	    }
>   	  break;
>   	case 26:
>   	    {
>   	      CHECK_GLIBC_IFUNC_PREFERRED_NEED_BOTH
> -		(n, cpu_features, Prefer_PMINUB_for_stringop, SSE2,
> -		 disable, 26);
> +		(n, cpu_features, Prefer_PMINUB_for_stringop, SSE2, 26);
>   	    }
>   	  break;
>   	}
> -      p += len + 1;
>       }
> -  while (*c != '\0');
>   }
>   
>   #if CET_ENABLED
> @@ -277,11 +245,11 @@ attribute_hidden
>   void
>   TUNABLE_CALLBACK (set_x86_ibt) (tunable_val_t *valp)
>   {
> -  if (memcmp (valp->strval, "on", sizeof ("on")) == 0)
> +  if (tunable_strcmp_cte (valp, "on"))
>       GL(dl_x86_feature_control).ibt = cet_always_on;
> -  else if (memcmp (valp->strval, "off", sizeof ("off")) == 0)
> +  else if (tunable_strcmp_cte (valp, "off"))
>       GL(dl_x86_feature_control).ibt = cet_always_off;
> -  else if (memcmp (valp->strval, "permissive", sizeof ("permissive")) == 0)
> +  else if (tunable_strcmp_cte (valp, "permissive"))
>       GL(dl_x86_feature_control).ibt = cet_permissive;
>   }
>   
> @@ -289,11 +257,11 @@ attribute_hidden
>   void
>   TUNABLE_CALLBACK (set_x86_shstk) (tunable_val_t *valp)
>   {
> -  if (memcmp (valp->strval, "on", sizeof ("on")) == 0)
> +  if (tunable_strcmp_cte (valp, "on"))
>       GL(dl_x86_feature_control).shstk = cet_always_on;
> -  else if (memcmp (valp->strval, "off", sizeof ("off")) == 0)
> +  else if (tunable_strcmp_cte (valp, "off"))
>       GL(dl_x86_feature_control).shstk = cet_always_off;
> -  else if (memcmp (valp->strval, "permissive", sizeof ("permissive")) == 0)
> +  else if (tunable_strcmp_cte (valp, "permissive"))
>       GL(dl_x86_feature_control).shstk = cet_permissive;
>   }
>   #endif
> diff --git a/sysdeps/x86/tst-hwcap-tunables.c b/sysdeps/x86/tst-hwcap-tunables.c
> new file mode 100644
> index 0000000000..0d0a45fa93
> --- /dev/null
> +++ b/sysdeps/x86/tst-hwcap-tunables.c
> @@ -0,0 +1,148 @@
> +/* Tests for powerpc GLIBC_TUNABLES=glibc.cpu.hwcaps filter.

s/powerpc/x86/

> +   Copyright (C) 2023 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
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include <array_length.h>
> +#include <getopt.h>
> +#include <ifunc-impl-list.h>
> +#include <spawn.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <intprops.h>
> +#include <support/check.h>
> +#include <support/support.h>
> +#include <support/xunistd.h>
> +#include <support/capture_subprocess.h>
> +
> +/* Nonzero if the program gets called via `exec'.  */
> +#define CMDLINE_OPTIONS \
> +  { "restart", no_argument, &restart, 1 },
> +static int restart;
> +
> +/* Disable everything.  */
> +static const char *test_1[] =
> +{
> +  "__memcpy_avx512_no_vzeroupper",
> +  "__memcpy_avx512_unaligned",
> +  "__memcpy_avx512_unaligned_erms",
> +  "__memcpy_evex_unaligned",
> +  "__memcpy_evex_unaligned_erms",
> +  "__memcpy_avx_unaligned",
> +  "__memcpy_avx_unaligned_erms",
> +  "__memcpy_avx_unaligned_rtm",
> +  "__memcpy_avx_unaligned_erms_rtm",
> +  "__memcpy_ssse3",
> +};
> +
> +static const struct test_t
> +{
> +  const char *env;
> +  const char *const *funcs;
> +  size_t nfuncs;
> +} tests[] =
> +{
> +  {
> +    /* Disable everything.  */
> +    "-Prefer_ERMS,-Prefer_FSRM,-AVX,-AVX2,-AVX_Usable,-AVX2_Usable,"
> +    "-AVX512F_Usable,-SSE4_1,-SSE4_2,-SSSE3,-Fast_Unaligned_Load,-ERMS,"
> +    "-AVX_Fast_Unaligned_Load",
> +    test_1,
> +    array_length (test_1)
> +  },
> +  {
> +    /* Same as before, but with some empty suboptions.  */
> +    ",-,-Prefer_ERMS,-Prefer_FSRM,-AVX,-AVX2,-AVX_Usable,-AVX2_Usable,"
> +    "-AVX512F_Usable,-SSE4_1,-SSE4_2,,-SSSE3,-Fast_Unaligned_Load,,-,-ERMS,"
> +    "-AVX_Fast_Unaligned_Load,-,",
> +    test_1,
> +    array_length (test_1)
> +  }
> +};
> +
> +/* Called on process re-execution.  */
> +_Noreturn static void
> +handle_restart (int ntest)
> +{
> +  struct libc_ifunc_impl impls[32];
> +  int cnt = __libc_ifunc_impl_list ("memcpy", impls, array_length (impls));
> +  if (cnt == 0)
> +    _exit (EXIT_SUCCESS);
> +  TEST_VERIFY_EXIT (cnt >= 1);
> +  for (int i = 0; i < cnt; i++)
> +    {
> +      for (int f = 0; f < tests[ntest].nfuncs; f++)
> +	{
> +	  if (strcmp (impls[i].name, tests[ntest].funcs[f]) == 0)
> +	    TEST_COMPARE (impls[i].usable, false);
> +	}
> +    }
> +
> +  _exit (EXIT_SUCCESS);
> +}
> +
> +static int
> +do_test (int argc, char *argv[])
> +{
> +  /* We must have either:
> +     - One our fource parameters left if called initially:
> +       + path to ld.so         optional
> +       + "--library-path"      optional
> +       + the library path      optional
> +       + the application name
> +       + the test to check  */
> +
> +  TEST_VERIFY_EXIT (argc == 2 || argc == 5);
> +
> +  if (restart)
> +    handle_restart (atoi (argv[1]));
> +
> +  char nteststr[INT_BUFSIZE_BOUND (int)];
> +
> +  char *spargv[10];
> +  {
> +    int i = 0;
> +    for (; i < argc - 1; i++)
> +      spargv[i] = argv[i + 1];
> +    spargv[i++] = (char *) "--direct";
> +    spargv[i++] = (char *) "--restart";
> +    spargv[i++] = nteststr;
> +    spargv[i] = NULL;
> +  }
> +
> +  for (int i = 0; i < array_length (tests); i++)
> +    {
> +      snprintf (nteststr, sizeof nteststr, "%d", i);
> +
> +      printf ("[%d] Spawned test for %s\n", i, tests[i].env);
> +      char *tunable = xasprintf ("glibc.cpu.hwcaps=%s", tests[i].env);
> +      setenv ("GLIBC_TUNABLES", tunable, 1);
> +
> +      struct support_capture_subprocess result
> +	= support_capture_subprogram (spargv[0], spargv);
> +      support_capture_subprocess_check (&result, "tst-tunables", 0,
> +					sc_allow_stderr);
> +      support_capture_subprocess_free (&result);
> +
> +      free (tunable);
> +    }
> +
> +  return 0;
> +}
> +
> +#define TEST_FUNCTION_ARGV do_test
> +#include <support/test-driver.c>

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 12/19] elf: Ignore LD_PROFILE for setuid binaries
  2023-11-06 20:25 ` [PATCH v3 12/19] elf: Ignore LD_PROFILE for setuid binaries Adhemerval Zanella
@ 2023-11-20 22:47   ` Siddhesh Poyarekar
  0 siblings, 0 replies; 40+ messages in thread
From: Siddhesh Poyarekar @ 2023-11-20 22:47 UTC (permalink / raw)
  To: Adhemerval Zanella, libc-alpha

On 2023-11-06 15:25, Adhemerval Zanella wrote:
> 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
> ---

LGTM.

Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>

>   elf/Makefile         |  3 +++
>   elf/rtld.c           |  8 +++-----
>   elf/tst-env-setuid.c | 12 +++++++++++-
>   3 files changed, 17 insertions(+), 6 deletions(-)
> 
> diff --git a/elf/Makefile b/elf/Makefile
> index 52981c19d0..08896bb895 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -2983,3 +2983,6 @@ $(objpfx)tst-dlclose-lazy.out: \
>     $(objpfx)tst-dlclose-lazy-mod1.so $(objpfx)tst-dlclose-lazy-mod2.so
>   
>   tst-env-setuid-ARGS = -- $(host-test-program-cmd)
> +
> +# Reuse a module with a SONAME, to specific as the LD_PROFILE.
> +$(objpfx)tst-env-setuid: $(objpfx)tst-sonamemove-runmod2.so
> diff --git a/elf/rtld.c b/elf/rtld.c
> index 51b6d9f326..a09cf2a9df 100644
> --- a/elf/rtld.c
> +++ b/elf/rtld.c
> @@ -361,6 +361,7 @@ struct rtld_global_ro _rtld_global_ro attribute_relro =
>       ._dl_fpu_control = _FPU_DEFAULT,
>       ._dl_pagesize = EXEC_PAGESIZE,
>       ._dl_inhibit_cache = 0,
> +    ._dl_profile_output = "/var/tmp",
>   
>       /* Function pointers.  */
>       ._dl_debug_printf = _dl_debug_printf,
> @@ -2534,10 +2535,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;
> @@ -2586,7 +2583,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;
>   
> diff --git a/elf/tst-env-setuid.c b/elf/tst-env-setuid.c
> index ba295a6a14..76b8e1fb45 100644
> --- a/elf/tst-env-setuid.c
> +++ b/elf/tst-env-setuid.c
> @@ -34,6 +34,9 @@ static char SETGID_CHILD[] = "setgid-child";
>   
>   #define FILTERED_VALUE   "some-filtered-value"
>   #define UNFILTERED_VALUE "some-unfiltered-value"
> +/* It assumes no other programs is being profile with a library with same
> +   SONAME using the default folder.  */
> +#define PROFILE_LIB      "tst-sonamemove-runmod2.so"
>   
>   struct envvar_t
>   {
> @@ -50,7 +53,7 @@ static const struct envvar_t filtered_envvars[] =
>     { "LD_HWCAP_MASK",           FILTERED_VALUE },
>     { "LD_LIBRARY_PATH",         FILTERED_VALUE },
>     { "LD_PRELOAD",              FILTERED_VALUE },
> -  { "LD_PROFILE",              FILTERED_VALUE },
> +  { "LD_PROFILE",              "tst-sonamemove-runmod2.so" },
>     { "MALLOC_ARENA_MAX",        FILTERED_VALUE },
>     { "MALLOC_PERTURB_",         FILTERED_VALUE },
>     { "MALLOC_TRACE",            FILTERED_VALUE },
> @@ -87,6 +90,13 @@ test_child (void)
>         ret |= !(env != NULL && strcmp (env, e->value) == 0);
>       }
>   
> +  /* Also check if no profile file was created.  */
> +  {
> +    char *profilepath = xasprintf ("/var/tmp/%s.profile", PROFILE_LIB);
> +    ret |= !access (profilepath, R_OK);
> +    free (profilepath);
> +  }
> +
>     return ret;
>   }
>   

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 13/19] elf: Remove LD_PROFILE for static binaries
  2023-11-06 20:25 ` [PATCH v3 13/19] elf: Remove LD_PROFILE for static binaries Adhemerval Zanella
@ 2023-11-20 22:55   ` Siddhesh Poyarekar
  0 siblings, 0 replies; 40+ messages in thread
From: Siddhesh Poyarekar @ 2023-11-20 22:55 UTC (permalink / raw)
  To: Adhemerval Zanella, libc-alpha

On 2023-11-06 15:25, Adhemerval Zanella wrote:
> The _dl_non_dynamic_init does not parse LD_PROFILE, which does not
> enable profile for dlopen objects.  Since dlopen is deprecated for
> static objects, it is better to remove the support.
> 
> It also allows to trim down libc.a of profile support.
> 
> Checked on x86_64-linux-gnu.
> ---

LGTM, but please also do a bmg run to verify this change.

Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>

>   elf/Makefile                              | 10 ++--
>   elf/dl-load.c                             | 10 ++--
>   elf/dl-runtime.c                          | 12 ++---
>   elf/dl-support.c                          |  9 ----
>   elf/{dl-profstub.c => libc-dl-profstub.c} |  0
>   include/dlfcn.h                           |  5 ++
>   sysdeps/aarch64/dl-machine.h              |  4 +-
>   sysdeps/aarch64/dl-trampoline.S           |  2 +-
>   sysdeps/alpha/dl-machine.h                |  6 ++-
>   sysdeps/alpha/dl-trampoline.S             |  4 ++
>   sysdeps/arm/dl-machine.h                  |  4 +-
>   sysdeps/arm/dl-trampoline.S               |  2 +-
>   sysdeps/hppa/dl-machine.h                 | 36 +++++++------
>   sysdeps/hppa/dl-trampoline.S              |  2 +
>   sysdeps/i386/dl-machine.h                 |  2 +
>   sysdeps/i386/dl-trampoline.S              |  2 +-
>   sysdeps/ia64/dl-machine.h                 | 10 ++--
>   sysdeps/ia64/dl-trampoline.S              |  2 +-
>   sysdeps/loongarch/dl-machine.h            |  6 ++-
>   sysdeps/loongarch/dl-trampoline.h         |  2 +
>   sysdeps/m68k/dl-machine.h                 |  4 +-
>   sysdeps/m68k/dl-trampoline.S              |  2 +
>   sysdeps/powerpc/powerpc32/dl-machine.c    |  2 +-
>   sysdeps/powerpc/powerpc32/dl-machine.h    | 10 ++--
>   sysdeps/powerpc/powerpc32/dl-trampoline.S |  2 +-
>   sysdeps/powerpc/powerpc64/dl-machine.h    | 20 ++++---
>   sysdeps/powerpc/powerpc64/dl-trampoline.S |  2 +-
>   sysdeps/s390/s390-32/dl-machine.h         |  8 +--
>   sysdeps/s390/s390-32/dl-trampoline.h      |  2 +-
>   sysdeps/s390/s390-64/dl-machine.h         |  8 +--
>   sysdeps/s390/s390-64/dl-trampoline.h      |  2 +-
>   sysdeps/sh/dl-machine.h                   |  2 +
>   sysdeps/sh/dl-trampoline.S                |  2 +
>   sysdeps/sparc/sparc32/dl-machine.h        |  4 +-
>   sysdeps/sparc/sparc32/dl-trampoline.S     |  2 +
>   sysdeps/sparc/sparc64/dl-machine.h        |  4 +-
>   sysdeps/sparc/sparc64/dl-trampoline.S     |  2 +
>   sysdeps/x86_64/dl-machine.h               |  2 +
>   sysdeps/x86_64/dl-trampoline.S            | 64 ++++++++++++-----------
>   39 files changed, 165 insertions(+), 109 deletions(-)
>   rename elf/{dl-profstub.c => libc-dl-profstub.c} (100%)
> 
> diff --git a/elf/Makefile b/elf/Makefile
> index 08896bb895..d17ff5424e 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -37,12 +37,12 @@ routines = \
>     dl-iteratephdr \
>     dl-libc \
>     dl-origin \
> -  dl-profstub \
>     dl-reloc-static-pie \
>     dl-support \
>     dl-sym \
>     dl-sysdep \
>     enbl-secure \
> +  libc-dl-profstub  \
>     libc-dl_find_object \
>     libc_early_init \
>     rtld_static_init \
> @@ -72,7 +72,6 @@ dl-routines = \
>     dl-open \
>     dl-origin \
>     dl-printf \
> -  dl-profile \
>     dl-reloc \
>     dl-runtime \
>     dl-scope \
> @@ -117,7 +116,11 @@ elide-routines.os = \
>     # elide-routines.os
>   
>   # These object files are only included in the dynamically-linked libc.
> -shared-only-routines = libc-dl_find_object
> +shared-only-routines = \
> +  libc-dl-profile \
> +  libc-dl-profstub \
> +  libc-dl_find_object \
> +  # shared-only-routines
>   
>   # ld.so uses those routines, plus some special stuff for being the program
>   # interpreter and operating independent of libc.
> @@ -135,6 +138,7 @@ rtld-routines = \
>     dl-libc_freeres \
>     dl-minimal \
>     dl-mutex \
> +  dl-profile \
>     dl-sysdep \
>     dl-usage \
>     rtld \
> diff --git a/elf/dl-load.c b/elf/dl-load.c
> index 2923b1141d..7356a4fe48 100644
> --- a/elf/dl-load.c
> +++ b/elf/dl-load.c
> @@ -1443,11 +1443,6 @@ cannot enable executable stack as shared object requires");
>        name by which the DSO is actually known.  Add that as well.  */
>     if (__glibc_unlikely (origname != NULL))
>       add_name_to_object (l, origname);
> -#else
> -  /* Audit modules only exist when linking is dynamic so ORIGNAME
> -     cannot be non-NULL.  */
> -  assert (origname == NULL);
> -#endif
>   
>     /* When we profile the SONAME might be needed for something else but
>        loading.  Add it right away.  */
> @@ -1455,6 +1450,11 @@ cannot enable executable stack as shared object requires");
>         && l->l_info[DT_SONAME] != NULL)
>       add_name_to_object (l, ((const char *) D_PTR (l, l_info[DT_STRTAB])
>   			    + l->l_info[DT_SONAME]->d_un.d_val));
> +#else
> +  /* Audit modules only exist when linking is dynamic so ORIGNAME
> +     cannot be non-NULL.  */
> +  assert (origname == NULL);
> +#endif
>   
>     /* If we have newly loaded libc.so, update the namespace
>        description.  */
> diff --git a/elf/dl-runtime.c b/elf/dl-runtime.c
> index 32a8bfcf74..fe7deda32a 100644
> --- a/elf/dl-runtime.c
> +++ b/elf/dl-runtime.c
> @@ -162,14 +162,14 @@ _dl_fixup (
>     return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);
>   }
>   
> -#ifndef PROF
> +#if !defined PROF && defined SHARED
>   DL_FIXUP_VALUE_TYPE
>   __attribute ((noinline))
>   DL_ARCH_FIXUP_ATTRIBUTE
>   _dl_profile_fixup (
> -#ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS
> +# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS
>   		   ELF_MACHINE_RUNTIME_FIXUP_ARGS,
> -#endif
> +# endif
>   		   struct link_map *l, ElfW(Word) reloc_arg,
>   		   ElfW(Addr) retaddr, void *regs, long int *framesizep)
>   {
> @@ -309,14 +309,12 @@ _dl_profile_fixup (
>         /* And now perhaps the relocation addend.  */
>         value = elf_machine_plt_value (l, reloc, value);
>   
> -#ifdef SHARED
>         /* Auditing checkpoint: we have a new binding.  Provide the
>   	 auditing libraries the possibility to change the value and
>   	 tell us whether further auditing is wanted.  */
>         if (defsym != NULL && GLRO(dl_naudit) > 0)
>   	_dl_audit_symbind (l, reloc_result, reloc, defsym, &value, result,
>   			   true);
> -#endif
>   
>         /* Store the result for later runs.  */
>         if (__glibc_likely (! GLRO(dl_bind_not)))
> @@ -335,11 +333,9 @@ _dl_profile_fixup (
>     long int framesize = -1;
>   
>   
> -#ifdef SHARED
>     /* Auditing checkpoint: report the PLT entering and allow the
>        auditors to change the value.  */
>     _dl_audit_pltenter (l, reloc_result, &value, regs, &framesize);
> -#endif
>   
>     /* Store the frame size information.  */
>     *framesizep = framesize;
> @@ -349,4 +345,4 @@ _dl_profile_fixup (
>     return value;
>   }
>   
> -#endif /* PROF */
> +#endif /* !defined PROF && defined SHARED */
> diff --git a/elf/dl-support.c b/elf/dl-support.c
> index 44a54dea07..31a608df87 100644
> --- a/elf/dl-support.c
> +++ b/elf/dl-support.c
> @@ -60,10 +60,6 @@ int _dl_dynamic_weak;
>   /* If nonzero print warnings about problematic situations.  */
>   int _dl_verbose;
>   
> -/* We never do profiling.  */
> -const char *_dl_profile;
> -const char *_dl_profile_output;
> -
>   /* Names of shared object for which the RUNPATHs and RPATHs should be
>      ignored.  */
>   const char *_dl_inhibit_rpath;
> @@ -301,11 +297,6 @@ _dl_non_dynamic_init (void)
>   
>     _dl_dynamic_weak = *(getenv ("LD_DYNAMIC_WEAK") ?: "") == '\0';
>   
> -  _dl_profile_output = getenv ("LD_PROFILE_OUTPUT");
> -  if (_dl_profile_output == NULL || _dl_profile_output[0] == '\0')
> -    _dl_profile_output
> -      = &"/var/tmp\0/var/profile"[__libc_enable_secure ? 9 : 0];
> -
>     if (__libc_enable_secure)
>       {
>         static const char unsecure_envvars[] =
> diff --git a/elf/dl-profstub.c b/elf/libc-dl-profstub.c
> similarity index 100%
> rename from elf/dl-profstub.c
> rename to elf/libc-dl-profstub.c
> diff --git a/include/dlfcn.h b/include/dlfcn.h
> index ae25f05303..a44420fa37 100644
> --- a/include/dlfcn.h
> +++ b/include/dlfcn.h
> @@ -135,5 +135,10 @@ extern int __dladdr1 (const void *address, Dl_info *info,
>   extern int __dlinfo (void *handle, int request, void *arg);
>   extern char *__dlerror (void);
>   
> +#ifndef SHARED
> +# undef DL_CALL_FCT
> +# define DL_CALL_FCT(fctp, args) ((fctp) args)
> +#endif
> +
>   #endif
>   #endif
> diff --git a/sysdeps/aarch64/dl-machine.h b/sysdeps/aarch64/dl-machine.h
> index 4170b9269f..a56eb96a79 100644
> --- a/sysdeps/aarch64/dl-machine.h
> +++ b/sysdeps/aarch64/dl-machine.h
> @@ -68,7 +68,6 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>       {
>         ElfW(Addr) *got;
>         extern void _dl_runtime_resolve (ElfW(Word));
> -      extern void _dl_runtime_profile (ElfW(Word));
>   
>         got = (ElfW(Addr) *) D_PTR (l, l_info[DT_PLTGOT]);
>         if (got[1])
> @@ -83,6 +82,8 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   	 to intercept the calls to collect information.  In this case we
>   	 don't store the address in the GOT so that all future calls also
>   	 end in this function.  */
> +#ifdef SHARED
> +      extern void _dl_runtime_profile (ElfW(Word));
>         if ( profile)
>   	{
>   	   got[2] = (ElfW(Addr)) &_dl_runtime_profile;
> @@ -94,6 +95,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   	    GL(dl_profile_map) = l;
>   	}
>         else
> +#endif
>   	{
>   	  /* This function will get called to fix up the GOT entry
>   	     indicated by the offset on the stack, and then jump to
> diff --git a/sysdeps/aarch64/dl-trampoline.S b/sysdeps/aarch64/dl-trampoline.S
> index a3474ba741..fc82151ba2 100644
> --- a/sysdeps/aarch64/dl-trampoline.S
> +++ b/sysdeps/aarch64/dl-trampoline.S
> @@ -122,7 +122,7 @@ _dl_runtime_resolve:
>   
>   	cfi_endproc
>   	.size _dl_runtime_resolve, .-_dl_runtime_resolve
> -#ifndef PROF
> +#if !defined PROF && defined SHARED
>   	.globl _dl_runtime_profile
>   	.type _dl_runtime_profile, #function
>   	cfi_startproc
> diff --git a/sysdeps/alpha/dl-machine.h b/sysdeps/alpha/dl-machine.h
> index ed5389e3c5..7fe2afca93 100644
> --- a/sysdeps/alpha/dl-machine.h
> +++ b/sysdeps/alpha/dl-machine.h
> @@ -75,9 +75,7 @@ elf_machine_runtime_setup (struct link_map *map, struct r_scope_elem *scope[],
>   			   int lazy, int profile)
>   {
>     extern char _dl_runtime_resolve_new[] attribute_hidden;
> -  extern char _dl_runtime_profile_new[] attribute_hidden;
>     extern char _dl_runtime_resolve_old[] attribute_hidden;
> -  extern char _dl_runtime_profile_old[] attribute_hidden;
>   
>     struct pltgot {
>       char *resolve;
> @@ -109,6 +107,9 @@ elf_machine_runtime_setup (struct link_map *map, struct r_scope_elem *scope[],
>     else
>       resolve = _dl_runtime_resolve_old;
>   
> +#ifdef SHARED
> +  extern char _dl_runtime_profile_new[] attribute_hidden;
> +  extern char _dl_runtime_profile_old[] attribute_hidden;
>     if (__builtin_expect (profile, 0))
>       {
>         if (secureplt)
> @@ -123,6 +124,7 @@ elf_machine_runtime_setup (struct link_map *map, struct r_scope_elem *scope[],
>   	  GL(dl_profile_map) = map;
>   	}
>       }
> +#endif
>   
>     pg->resolve = resolve;
>     pg->link = map;
> diff --git a/sysdeps/alpha/dl-trampoline.S b/sysdeps/alpha/dl-trampoline.S
> index f8c3d33906..5dffa62587 100644
> --- a/sysdeps/alpha/dl-trampoline.S
> +++ b/sysdeps/alpha/dl-trampoline.S
> @@ -89,6 +89,7 @@ _dl_runtime_resolve_new:
>   	.globl	_dl_runtime_profile_new
>   	.type	_dl_runtime_profile_new, @function
>   
> +#ifdef SHARED
>   #undef FRAMESIZE
>   #define FRAMESIZE	20*8
>   
> @@ -207,6 +208,7 @@ _dl_runtime_profile_new:
>   
>   	cfi_endproc
>   	.size	_dl_runtime_profile_new, .-_dl_runtime_profile_new
> +#endif /* SHARED */
>   
>   	.align	4
>   	.globl	_dl_runtime_resolve_old
> @@ -340,6 +342,7 @@ _dl_runtime_resolve_old:
>   	.usepv	_dl_runtime_profile_old, no
>   	.type	_dl_runtime_profile_old, @function
>   
> +#ifdef SHARED
>   	/* We save the registers in a different order than desired by
>   	   .mask/.fmask, so we have to use explicit cfi directives.  */
>   	cfi_startproc
> @@ -538,3 +541,4 @@ _dl_runtime_profile_old:
>   
>   	cfi_endproc
>   	.size	_dl_runtime_profile_old, .-_dl_runtime_profile_old
> +#endif /* SHARED */
> diff --git a/sysdeps/arm/dl-machine.h b/sysdeps/arm/dl-machine.h
> index d720c02c96..a68679e653 100644
> --- a/sysdeps/arm/dl-machine.h
> +++ b/sysdeps/arm/dl-machine.h
> @@ -65,7 +65,6 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   {
>     Elf32_Addr *got;
>     extern void _dl_runtime_resolve (Elf32_Word);
> -  extern void _dl_runtime_profile (Elf32_Word);
>   
>     if (l->l_info[DT_JMPREL] && lazy)
>       {
> @@ -88,6 +87,8 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   	 to intercept the calls to collect information.  In this case we
>   	 don't store the address in the GOT so that all future calls also
>   	 end in this function.  */
> +#ifdef SHARED
> +      extern void _dl_runtime_profile (Elf32_Word);
>         if (profile)
>   	{
>   	  got[2] = (Elf32_Addr) &_dl_runtime_profile;
> @@ -99,6 +100,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   	    GL(dl_profile_map) = l;
>   	}
>         else
> +#endif
>   	/* This function will get called to fix up the GOT entry indicated by
>   	   the offset on the stack, and then jump to the resolved address.  */
>   	got[2] = (Elf32_Addr) &_dl_runtime_resolve;
> diff --git a/sysdeps/arm/dl-trampoline.S b/sysdeps/arm/dl-trampoline.S
> index 23c2476917..2df5b7ee36 100644
> --- a/sysdeps/arm/dl-trampoline.S
> +++ b/sysdeps/arm/dl-trampoline.S
> @@ -70,7 +70,7 @@ _dl_runtime_resolve:
>   	cfi_endproc
>   	.size _dl_runtime_resolve, .-_dl_runtime_resolve
>   
> -#ifndef PROF
> +#if !defined PROF && defined SHARED
>   	.globl _dl_runtime_profile
>   	.type _dl_runtime_profile, #function
>   	CFI_SECTIONS
> diff --git a/sysdeps/hppa/dl-machine.h b/sysdeps/hppa/dl-machine.h
> index 4e6e70b3c9..993593de5d 100644
> --- a/sysdeps/hppa/dl-machine.h
> +++ b/sysdeps/hppa/dl-machine.h
> @@ -195,7 +195,6 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>     end_jmprel = jmprel + l->l_info[DT_PLTRELSZ]->d_un.d_val;
>   
>     extern void _dl_runtime_resolve (void);
> -  extern void _dl_runtime_profile (void);
>   
>     /* Linking lazily */
>     if (lazy)
> @@ -235,22 +234,9 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   	      got[1] = (Elf32_Addr) l;
>   
>   	      /* This function will be called to perform the relocation. */
> -	      if (__builtin_expect (!profile, 1))
> -		{
> -		  /* If a static application called us, then _dl_runtime_resolve is not
> -		     a function descriptor, but the *real* address of the function... */
> -		  if((unsigned long) &_dl_runtime_resolve & 3)
> -		    {
> -		      got[-2] = (Elf32_Addr) ((struct fdesc *)
> -				  ((unsigned long) &_dl_runtime_resolve & ~3))->ip;
> -		    }
> -		  else
> -		    {
> -		      /* Static executable! */
> -		      got[-2] = (Elf32_Addr) &_dl_runtime_resolve;
> -		    }
> -		}
> -	      else
> +#ifdef SHARED
> +	      extern void _dl_runtime_profile (void);
> +	      if (__glibc_unlikely (profile))
>   		{
>   		  if (GLRO(dl_profile) != NULL
>   		      && _dl_name_match_p (GLRO(dl_profile), l))
> @@ -272,6 +258,22 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   		      got[-2] = (Elf32_Addr) &_dl_runtime_profile;
>   		    }
>   		}
> +	      else
> +#endif
> +		{
> +		  /* If a static application called us, then _dl_runtime_resolve is not
> +		     a function descriptor, but the *real* address of the function... */
> +		  if((unsigned long) &_dl_runtime_resolve & 3)
> +		    {
> +		      got[-2] = (Elf32_Addr) ((struct fdesc *)
> +				  ((unsigned long) &_dl_runtime_resolve & ~3))->ip;
> +		    }
> +		  else
> +		    {
> +		      /* Static executable! */
> +		      got[-2] = (Elf32_Addr) &_dl_runtime_resolve;
> +		    }
> +		}
>   	      /* Plunk in the gp of this function descriptor so we
>   		 can make the call to _dl_runtime_xxxxxx */
>   	      got[-1] = ltp;
> diff --git a/sysdeps/hppa/dl-trampoline.S b/sysdeps/hppa/dl-trampoline.S
> index 689c6e1a40..9e904df3d2 100644
> --- a/sysdeps/hppa/dl-trampoline.S
> +++ b/sysdeps/hppa/dl-trampoline.S
> @@ -156,6 +156,7 @@ _dl_runtime_resolve:
>   	cfi_endproc
>   	.size   _dl_runtime_resolve, . - _dl_runtime_resolve
>   
> +#ifdef SHARED
>           .text
>           .global _dl_runtime_profile
>           .type _dl_runtime_profile,@function
> @@ -359,3 +360,4 @@ L(cont):
>           .PROCEND
>   	cfi_endproc
>   	.size   _dl_runtime_profile, . - _dl_runtime_profile
> +#endif
> diff --git a/sysdeps/i386/dl-machine.h b/sysdeps/i386/dl-machine.h
> index 18749f2ec2..07469e99b0 100644
> --- a/sysdeps/i386/dl-machine.h
> +++ b/sysdeps/i386/dl-machine.h
> @@ -92,6 +92,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   	 to intercept the calls to collect information.  In this case we
>   	 don't store the address in the GOT so that all future calls also
>   	 end in this function.  */
> +#ifdef SHARED
>         if (__glibc_unlikely (profile))
>   	{
>   	  got[2] = (shstk_enabled
> @@ -105,6 +106,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   	    GL(dl_profile_map) = l;
>   	}
>         else
> +#endif
>   	/* This function will get called to fix up the GOT entry indicated by
>   	   the offset on the stack, and then jump to the resolved address.  */
>   	got[2] = (shstk_enabled
> diff --git a/sysdeps/i386/dl-trampoline.S b/sysdeps/i386/dl-trampoline.S
> index 2d55f373b4..3604aabe87 100644
> --- a/sysdeps/i386/dl-trampoline.S
> +++ b/sysdeps/i386/dl-trampoline.S
> @@ -70,7 +70,7 @@ _dl_runtime_resolve_shstk:
>   	cfi_endproc
>   	.size _dl_runtime_resolve_shstk, .-_dl_runtime_resolve_shstk
>   
> -#ifndef PROF
> +#if !defined PROF && defined SHARED
>   # The SHSTK compatible version.
>   	.globl _dl_runtime_profile_shstk
>   	.type _dl_runtime_profile_shstk, @function
> diff --git a/sysdeps/ia64/dl-machine.h b/sysdeps/ia64/dl-machine.h
> index e1da3dadcb..3ef6b0ef4b 100644
> --- a/sysdeps/ia64/dl-machine.h
> +++ b/sysdeps/ia64/dl-machine.h
> @@ -121,9 +121,8 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>         reserve[0] = (Elf64_Addr) l;
>   
>         /* This function will be called to perform the relocation.  */
> -      if (!profile)
> -	doit = (Elf64_Addr) ELF_PTR_TO_FDESC (&_dl_runtime_resolve)->ip;
> -      else
> +#ifdef SHARED
> +      if (__glibc_unlikely (profile))
>   	{
>   	  if (GLRO(dl_profile) != NULL
>   	      && _dl_name_match_p (GLRO(dl_profile), l))
> @@ -134,6 +133,11 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   	    }
>   	  doit = (Elf64_Addr) ELF_PTR_TO_FDESC (&_dl_runtime_profile)->ip;
>   	}
> +      else
> +#endif
> +	{
> +	  doit = (Elf64_Addr) ELF_PTR_TO_FDESC (&_dl_runtime_resolve)->ip;
> +	}
>   
>         reserve[1] = doit;
>         reserve[2] = gp;
> diff --git a/sysdeps/ia64/dl-trampoline.S b/sysdeps/ia64/dl-trampoline.S
> index 54b33c8c02..10d8432c8f 100644
> --- a/sysdeps/ia64/dl-trampoline.S
> +++ b/sysdeps/ia64/dl-trampoline.S
> @@ -188,7 +188,7 @@ END(_dl_runtime_resolve)
>   #define PLTENTER_FRAME_SIZE (4*8 + 8*8 + 8*16 + 2*8 + 16)
>   #define PLTEXIT_FRAME_SIZE (PLTENTER_FRAME_SIZE + 4*8 + 8*16)
>   
> -#ifndef PROF
> +#if !defined PROF && defined SHARED
>   ENTRY(_dl_runtime_profile)
>   	{ .mii
>   	  .prologue
> diff --git a/sysdeps/loongarch/dl-machine.h b/sysdeps/loongarch/dl-machine.h
> index 57913cefaa..0d17fd21e3 100644
> --- a/sysdeps/loongarch/dl-machine.h
> +++ b/sysdeps/loongarch/dl-machine.h
> @@ -287,15 +287,16 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   	 to intercept the calls to collect information.  In this case we
>   	 don't store the address in the GOT so that all future calls also
>   	 end in this function.  */
> +#ifdef SHARED
>         if (profile != 0)
>   	{
> -#if !defined __loongarch_soft_float
> +# if !defined __loongarch_soft_float
>   	  if (SUPPORT_LASX)
>   	    gotplt[0] = (ElfW(Addr)) &_dl_runtime_profile_lasx;
>   	  else if (SUPPORT_LSX)
>   	    gotplt[0] = (ElfW(Addr)) &_dl_runtime_profile_lsx;
>   	  else
> -#endif
> +# endif
>   	    gotplt[0] = (ElfW(Addr)) &_dl_runtime_profile;
>   
>   	  if (GLRO(dl_profile) != NULL
> @@ -305,6 +306,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   	    GL(dl_profile_map) = l;
>   	}
>         else
> +#endif
>   	{
>   	  /* This function will get called to fix up the GOT entry
>   	     indicated by the offset on the stack, and then jump to
> diff --git a/sysdeps/loongarch/dl-trampoline.h b/sysdeps/loongarch/dl-trampoline.h
> index e298439d39..1da70aeb23 100644
> --- a/sysdeps/loongarch/dl-trampoline.h
> +++ b/sysdeps/loongarch/dl-trampoline.h
> @@ -126,6 +126,7 @@ ENTRY (_dl_runtime_resolve)
>   	jirl	zero, t1, 0
>   END (_dl_runtime_resolve)
>   
> +#ifdef SHARED
>   #include "dl-link.h"
>   
>   ENTRY (_dl_runtime_profile)
> @@ -367,3 +368,4 @@ ENTRY (_dl_runtime_profile)
>   	jirl	zero, ra, 0
>   
>   END (_dl_runtime_profile)
> +#endif /* SHARED */
> diff --git a/sysdeps/m68k/dl-machine.h b/sysdeps/m68k/dl-machine.h
> index 5ee586b27b..8d7e733e2a 100644
> --- a/sysdeps/m68k/dl-machine.h
> +++ b/sysdeps/m68k/dl-machine.h
> @@ -75,7 +75,6 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   {
>     Elf32_Addr *got;
>     extern void _dl_runtime_resolve (Elf32_Word);
> -  extern void _dl_runtime_profile (Elf32_Word);
>   
>     if (l->l_info[DT_JMPREL] && lazy)
>       {
> @@ -93,6 +92,8 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   	 to intercept the calls to collect information.  In this case we
>   	 don't store the address in the GOT so that all future calls also
>   	 end in this function.  */
> +#ifdef SHARED
> +      extern void _dl_runtime_profile (Elf32_Word);
>         if (profile)
>   	{
>   	  got[2] = (Elf32_Addr) &_dl_runtime_profile;
> @@ -106,6 +107,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   	    }
>   	}
>         else
> +#endif
>   	/* This function will get called to fix up the GOT entry indicated by
>   	   the offset on the stack, and then jump to the resolved address.  */
>   	got[2] = (Elf32_Addr) &_dl_runtime_resolve;
> diff --git a/sysdeps/m68k/dl-trampoline.S b/sysdeps/m68k/dl-trampoline.S
> index dba3741400..f1b4943868 100644
> --- a/sysdeps/m68k/dl-trampoline.S
> +++ b/sysdeps/m68k/dl-trampoline.S
> @@ -60,6 +60,7 @@ _dl_runtime_resolve:
>   	cfi_endproc
>   	.size _dl_runtime_resolve, . - _dl_runtime_resolve
>   
> +#ifdef SHARED
>   	.text
>   	.globl _dl_runtime_profile
>   	.type _dl_runtime_profile, @function
> @@ -220,3 +221,4 @@ _dl_runtime_profile:
>   	rts
>   	cfi_endproc
>   	.size _dl_runtime_profile, . - _dl_runtime_profile
> +#endif /* SHARED */
> diff --git a/sysdeps/powerpc/powerpc32/dl-machine.c b/sysdeps/powerpc/powerpc32/dl-machine.c
> index ef84911ede..e6b603de94 100644
> --- a/sysdeps/powerpc/powerpc32/dl-machine.c
> +++ b/sysdeps/powerpc/powerpc32/dl-machine.c
> @@ -226,7 +226,7 @@ __elf_machine_runtime_setup (struct link_map *map, int lazy, int profile)
>   	  Elf32_Word dlrr;
>   	  Elf32_Word offset;
>   
> -#ifndef PROF
> +#if !defined PROF && defined SHARED
>   	  dlrr = (Elf32_Word) (profile
>   			       ? _dl_prof_resolve
>   			       : _dl_runtime_resolve);
> diff --git a/sysdeps/powerpc/powerpc32/dl-machine.h b/sysdeps/powerpc/powerpc32/dl-machine.h
> index a4cad7583c..1ff46d5f8a 100644
> --- a/sysdeps/powerpc/powerpc32/dl-machine.h
> +++ b/sysdeps/powerpc/powerpc32/dl-machine.h
> @@ -188,15 +188,19 @@ elf_machine_runtime_setup (struct link_map *map, struct r_scope_elem *scope[],
>         extern void _dl_runtime_resolve (void);
>         extern void _dl_prof_resolve (void);
>   
> -      if (__glibc_likely (!profile))
> -	dlrr = _dl_runtime_resolve;
> -      else
> +#ifdef SHARED
> +      if (__glibc_unlikely (profile))
>   	{
>   	  if (GLRO(dl_profile) != NULL
>   	      &&_dl_name_match_p (GLRO(dl_profile), map))
>   	    GL(dl_profile_map) = map;
>   	  dlrr = _dl_prof_resolve;
>   	}
> +      else
> +#endif
> +	{
> +	  dlrr = _dl_runtime_resolve;
> +	}
>         got = (Elf32_Addr *) map->l_info[DT_PPC(GOT)]->d_un.d_ptr;
>         glink = got[1];
>         got[1] = (Elf32_Addr) dlrr;
> diff --git a/sysdeps/powerpc/powerpc32/dl-trampoline.S b/sysdeps/powerpc/powerpc32/dl-trampoline.S
> index 93b1673ebb..be8de0e2dc 100644
> --- a/sysdeps/powerpc/powerpc32/dl-trampoline.S
> +++ b/sysdeps/powerpc/powerpc32/dl-trampoline.S
> @@ -70,7 +70,7 @@ _dl_runtime_resolve:
>   	cfi_endproc
>   	.size	 _dl_runtime_resolve,.-_dl_runtime_resolve
>   
> -#ifndef PROF
> +#if !defined PROF && defined SHARED
>   	.align 2
>   	.globl _dl_prof_resolve
>   	.type _dl_prof_resolve,@function
> diff --git a/sysdeps/powerpc/powerpc64/dl-machine.h b/sysdeps/powerpc/powerpc64/dl-machine.h
> index 449208e86f..601c3cba9d 100644
> --- a/sysdeps/powerpc/powerpc64/dl-machine.h
> +++ b/sysdeps/powerpc/powerpc64/dl-machine.h
> @@ -362,13 +362,19 @@ elf_machine_runtime_setup (struct link_map *map, struct r_scope_elem *scope[],
>   	  Elf64_Word offset;
>   	  Elf64_Addr dlrr;
>   
> -	  dlrr = (Elf64_Addr) (profile ? _dl_profile_resolve
> -				       : _dl_runtime_resolve);
> -	  if (profile && GLRO(dl_profile) != NULL
> -	      && _dl_name_match_p (GLRO(dl_profile), map))
> -	    /* This is the object we are looking for.  Say that we really
> -	       want profiling and the timers are started.  */
> -	    GL(dl_profile_map) = map;
> +#ifdef SHARED
> +	  if (__glibc_unlikely (profile))
> +	    {
> +	      dlrr = (Elf64_Addr) _dl_profile_resolve;
> +	      if (profile && GLRO(dl_profile) != NULL
> +		  && _dl_name_match_p (GLRO(dl_profile), map))
> +		/* This is the object we are looking for.  Say that we really
> +		   want profiling and the timers are started.  */
> +		GL(dl_profile_map) = map;
> +	    }
> +	  else
> +#endif
> +	    dlrr = (Elf64_Addr) _dl_runtime_resolve;
>   
>   #if _CALL_ELF != 2
>   	  /* We need to stuff the address/TOC of _dl_runtime_resolve
> diff --git a/sysdeps/powerpc/powerpc64/dl-trampoline.S b/sysdeps/powerpc/powerpc64/dl-trampoline.S
> index 1d04ec8109..b2fc2bb133 100644
> --- a/sysdeps/powerpc/powerpc64/dl-trampoline.S
> +++ b/sysdeps/powerpc/powerpc64/dl-trampoline.S
> @@ -195,7 +195,7 @@ END(_dl_runtime_resolve)
>      and r11 contains the link_map (from PLT0+16).  The link_map becomes
>      parm1 (r3) and the index (r0) needs to be converted to an offset
>      (index * 24) in parm2 (r4).  */
> -#ifndef PROF
> +#if !defined PROF && defined SHARED
>   	.hidden _dl_profile_resolve
>   ENTRY (_dl_profile_resolve, 4)
>   /* Spill r30, r31 to preserve the link_map* and reloc_addr, in case we
> diff --git a/sysdeps/s390/s390-32/dl-machine.h b/sysdeps/s390/s390-32/dl-machine.h
> index 100a3e05f6..b8bf2796c7 100644
> --- a/sysdeps/s390/s390-32/dl-machine.h
> +++ b/sysdeps/s390/s390-32/dl-machine.h
> @@ -124,16 +124,17 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   	 to intercept the calls to collect information.  In this case we
>   	 don't store the address in the GOT so that all future calls also
>   	 end in this function.  */
> +#ifdef SHARED
>         if (__glibc_unlikely (profile))
>   	{
> -#if defined HAVE_S390_VX_ASM_SUPPORT
> +# if defined HAVE_S390_VX_ASM_SUPPORT
>   	  if (GLRO(dl_hwcap) & HWCAP_S390_VX)
>   	    got[2] = (Elf32_Addr) &_dl_runtime_profile_vx;
>   	  else
>   	    got[2] = (Elf32_Addr) &_dl_runtime_profile;
> -#else
> +# else
>   	  got[2] = (Elf32_Addr) &_dl_runtime_profile;
> -#endif
> +# endif
>   
>   	  if (GLRO(dl_profile) != NULL
>   	      && _dl_name_match_p (GLRO(dl_profile), l))
> @@ -142,6 +143,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   	    GL(dl_profile_map) = l;
>   	}
>         else
> +#endif
>   	{
>   	  /* This function will get called to fix up the GOT entry indicated by
>   	     the offset on the stack, and then jump to the resolved address.  */
> diff --git a/sysdeps/s390/s390-32/dl-trampoline.h b/sysdeps/s390/s390-32/dl-trampoline.h
> index 78fdca9d53..8093ab08d3 100644
> --- a/sysdeps/s390/s390-32/dl-trampoline.h
> +++ b/sysdeps/s390/s390-32/dl-trampoline.h
> @@ -148,7 +148,7 @@ _dl_runtime_resolve:
>   #undef F0_OFF
>   #undef F2_OFF
>   
> -#ifndef PROF
> +#if !defined PROF && defined SHARED
>   # define SIZEOF_STRUCT_LA_S390_32_REGS 168
>   # define REGS_OFF -264
>   # define R2_OFF -264
> diff --git a/sysdeps/s390/s390-64/dl-machine.h b/sysdeps/s390/s390-64/dl-machine.h
> index 9fabb09750..82259dad64 100644
> --- a/sysdeps/s390/s390-64/dl-machine.h
> +++ b/sysdeps/s390/s390-64/dl-machine.h
> @@ -111,16 +111,17 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   	 to intercept the calls to collect information.	 In this case we
>   	 don't store the address in the GOT so that all future calls also
>   	 end in this function.	*/
> +#ifdef SHARED
>         if (__glibc_unlikely (profile))
>   	{
> -#if defined HAVE_S390_VX_ASM_SUPPORT
> +# if defined HAVE_S390_VX_ASM_SUPPORT
>   	  if (GLRO(dl_hwcap) & HWCAP_S390_VX)
>   	    got[2] = (Elf64_Addr) &_dl_runtime_profile_vx;
>   	  else
>   	    got[2] = (Elf64_Addr) &_dl_runtime_profile;
> -#else
> +# else
>   	  got[2] = (Elf64_Addr) &_dl_runtime_profile;
> -#endif
> +# endif
>   
>   	  if (GLRO(dl_profile) != NULL
>   	      && _dl_name_match_p (GLRO(dl_profile), l))
> @@ -129,6 +130,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   	    GL(dl_profile_map) = l;
>   	}
>         else
> +#endif
>   	{
>   	  /* This function will get called to fix up the GOT entry indicated by
>   	     the offset on the stack, and then jump to the resolved address.  */
> diff --git a/sysdeps/s390/s390-64/dl-trampoline.h b/sysdeps/s390/s390-64/dl-trampoline.h
> index 3a7cfc5f92..61188119e6 100644
> --- a/sysdeps/s390/s390-64/dl-trampoline.h
> +++ b/sysdeps/s390/s390-64/dl-trampoline.h
> @@ -150,7 +150,7 @@ _dl_runtime_resolve:
>   #undef F4_OFF
>   #undef F6_OFF
>   
> -#ifndef PROF
> +#if !defined PROF && defined SHARED
>   # define SIZEOF_STRUCT_LA_S390_64_REGS 200
>   # define REGS_OFF -360
>   # define R2_OFF -360
> diff --git a/sysdeps/sh/dl-machine.h b/sysdeps/sh/dl-machine.h
> index 0e4eac42c3..e0480eae5a 100644
> --- a/sysdeps/sh/dl-machine.h
> +++ b/sysdeps/sh/dl-machine.h
> @@ -101,6 +101,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   	 to intercept the calls to collect information.	 In this case we
>   	 don't store the address in the GOT so that all future calls also
>   	 end in this function.	*/
> +#ifdef SHARED
>         if (profile)
>   	{
>   	  got[2] = (Elf32_Addr) &_dl_runtime_profile;
> @@ -110,6 +111,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   	    GL(dl_profile_map) = l;
>   	}
>         else
> +#endif
>   	/* This function will get called to fix up the GOT entry indicated by
>   	   the offset on the stack, and then jump to the resolved address.  */
>   	got[2] = (Elf32_Addr) &_dl_runtime_resolve;
> diff --git a/sysdeps/sh/dl-trampoline.S b/sysdeps/sh/dl-trampoline.S
> index 5d703341ed..ecaae34db4 100644
> --- a/sysdeps/sh/dl-trampoline.S
> +++ b/sysdeps/sh/dl-trampoline.S
> @@ -142,6 +142,7 @@ _dl_runtime_resolve:
>   	.size _dl_runtime_resolve, .-_dl_runtime_resolve
>   
>   
> +#ifdef SHARED
>   	.globl _dl_runtime_profile
>   	.type _dl_runtime_profile,@function
>   	cfi_startproc
> @@ -428,3 +429,4 @@ _dl_runtime_profile:
>   8:	.long _dl_audit_pltexit
>   #endif
>   	.size _dl_runtime_profile, .-_dl_runtime_profile
> +#endif /* SHARED */
> diff --git a/sysdeps/sparc/sparc32/dl-machine.h b/sysdeps/sparc/sparc32/dl-machine.h
> index 9b57ae1a93..b10e541810 100644
> --- a/sysdeps/sparc/sparc32/dl-machine.h
> +++ b/sysdeps/sparc/sparc32/dl-machine.h
> @@ -116,7 +116,8 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   	 bits of %g1 with an offset into the .rela.plt section and jump to
>   	 the beginning of the PLT.  */
>         plt = (Elf32_Addr *) D_PTR (l, l_info[DT_PLTGOT]);
> -      if (__builtin_expect(profile, 0))
> +#ifdef SHARED
> +      if (__glibc_unlikely (profile))
>   	{
>   	  rfunc = (Elf32_Addr) &_dl_runtime_profile;
>   
> @@ -125,6 +126,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   	    GL(dl_profile_map) = l;
>   	}
>         else
> +#endif
>   	{
>   	  rfunc = (Elf32_Addr) &_dl_runtime_resolve;
>   	}
> diff --git a/sysdeps/sparc/sparc32/dl-trampoline.S b/sysdeps/sparc/sparc32/dl-trampoline.S
> index 08ff31b474..5e7d860ae4 100644
> --- a/sysdeps/sparc/sparc32/dl-trampoline.S
> +++ b/sysdeps/sparc/sparc32/dl-trampoline.S
> @@ -47,6 +47,7 @@ _dl_runtime_resolve:
>   
>   	.size	_dl_runtime_resolve, .-_dl_runtime_resolve
>   
> +#ifdef SHARED
>   	/* For the profiling cases we pass in our stack frame
>   	 * as the base of the La_sparc32_regs, so it looks
>   	 * like:
> @@ -185,3 +186,4 @@ _dl_runtime_profile:
>   	cfi_endproc
>   
>   	.size	_dl_runtime_profile, .-_dl_runtime_profile
> +#endif
> diff --git a/sysdeps/sparc/sparc64/dl-machine.h b/sysdeps/sparc/sparc64/dl-machine.h
> index 2f04ac550e..98469e7604 100644
> --- a/sysdeps/sparc/sparc64/dl-machine.h
> +++ b/sysdeps/sparc/sparc64/dl-machine.h
> @@ -136,7 +136,8 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>         Elf64_Addr res0_addr, res1_addr;
>         unsigned int *plt = (void *) D_PTR (l, l_info[DT_PLTGOT]);
>   
> -      if (__builtin_expect(profile, 0))
> +#ifdef SHARED
> +      if (__glibc_unlikely (profile))
>   	{
>   	  res0_addr = (Elf64_Addr) &_dl_runtime_profile_0;
>   	  res1_addr = (Elf64_Addr) &_dl_runtime_profile_1;
> @@ -146,6 +147,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   	    GL(dl_profile_map) = l;
>   	}
>         else
> +#endif
>   	{
>   	  res0_addr = (Elf64_Addr) &_dl_runtime_resolve_0;
>   	  res1_addr = (Elf64_Addr) &_dl_runtime_resolve_1;
> diff --git a/sysdeps/sparc/sparc64/dl-trampoline.S b/sysdeps/sparc/sparc64/dl-trampoline.S
> index 444690a71e..82b42681dd 100644
> --- a/sysdeps/sparc/sparc64/dl-trampoline.S
> +++ b/sysdeps/sparc/sparc64/dl-trampoline.S
> @@ -92,6 +92,7 @@ _dl_runtime_resolve_1:
>   
>   	.size	_dl_runtime_resolve_1, .-_dl_runtime_resolve_1
>   
> +#ifdef SHARED
>   	/* For the profiling cases we pass in our stack frame
>   	 * as the base of the La_sparc64_regs, so it looks
>   	 * like:
> @@ -323,3 +324,4 @@ _dl_runtime_profile_1:
>   	cfi_endproc
>   
>   	.size	_dl_runtime_resolve_1, .-_dl_runtime_resolve_1
> +#endif
> diff --git a/sysdeps/x86_64/dl-machine.h b/sysdeps/x86_64/dl-machine.h
> index 9ea2a70837..581a2f1a9e 100644
> --- a/sysdeps/x86_64/dl-machine.h
> +++ b/sysdeps/x86_64/dl-machine.h
> @@ -89,6 +89,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   
>         const struct cpu_features* cpu_features = __get_cpu_features ();
>   
> +#ifdef SHARED
>         /* The got[2] entry contains the address of a function which gets
>   	 called to get the address of a so far unresolved function and
>   	 jump to it.  The profiling extension of the dynamic linker allows
> @@ -111,6 +112,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[],
>   	    GL(dl_profile_map) = l;
>   	}
>         else
> +#endif
>   	{
>   	  /* This function will get called to fix up the GOT entry
>   	     indicated by the offset on the stack, and then jump to
> diff --git a/sysdeps/x86_64/dl-trampoline.S b/sysdeps/x86_64/dl-trampoline.S
> index a6b9a1826b..3fd30d58fc 100644
> --- a/sysdeps/x86_64/dl-trampoline.S
> +++ b/sysdeps/x86_64/dl-trampoline.S
> @@ -53,45 +53,49 @@
>   
>   #define RESTORE_AVX
>   
> -#define VEC_SIZE		64
> -#define VMOVA			vmovdqa64
> -#define VEC(i)			zmm##i
> -#define _dl_runtime_profile	_dl_runtime_profile_avx512
> -# define SECTION(p)		p##.evex512
> -#include "dl-trampoline.h"
> -#undef _dl_runtime_profile
> -#undef VEC
> -#undef VMOVA
> -#undef VEC_SIZE
> -#undef SECTION
> -
> -#if MINIMUM_X86_ISA_LEVEL <= AVX_X86_ISA_LEVEL
> -# define VEC_SIZE		32
> -# define VMOVA			vmovdqa
> -# define VEC(i)			ymm##i
> -# define SECTION(p)		p##.avx
> -# define _dl_runtime_profile	_dl_runtime_profile_avx
> +#ifdef SHARED
> +# define VEC_SIZE		64
> +# define VMOVA			vmovdqa64
> +# define VEC(i)			zmm##i
> +# define _dl_runtime_profile	_dl_runtime_profile_avx512
> +#  define SECTION(p)		p##.evex512
>   # include "dl-trampoline.h"
>   # undef _dl_runtime_profile
>   # undef VEC
>   # undef VMOVA
>   # undef VEC_SIZE
>   # undef SECTION
> -#endif
>   
> -#if MINIMUM_X86_ISA_LEVEL < AVX_X86_ISA_LEVEL
> +# if MINIMUM_X86_ISA_LEVEL <= AVX_X86_ISA_LEVEL
> +#  define VEC_SIZE		32
> +#  define VMOVA			vmovdqa
> +#  define VEC(i)			ymm##i
> +#  define SECTION(p)		p##.avx
> +#  define _dl_runtime_profile	_dl_runtime_profile_avx
> +#  include "dl-trampoline.h"
> +#  undef _dl_runtime_profile
> +#  undef VEC
> +#  undef VMOVA
> +#  undef VEC_SIZE
> +#  undef SECTION
> +# endif
> +
> +# if MINIMUM_X86_ISA_LEVEL < AVX_X86_ISA_LEVEL
>   /* movaps/movups is 1-byte shorter.  */
> -# define VEC_SIZE		16
> -# define VMOVA			movaps
> -# define VEC(i)			xmm##i
> -# define _dl_runtime_profile	_dl_runtime_profile_sse
> -# undef RESTORE_AVX
> -# include "dl-trampoline.h"
> -# undef _dl_runtime_profile
> -# undef VEC
> -# undef VMOVA
> -# undef VEC_SIZE
> +#  define VEC_SIZE		16
> +#  define VMOVA			movaps
> +#  define VEC(i)			xmm##i
> +#  define _dl_runtime_profile	_dl_runtime_profile_sse
> +#  undef RESTORE_AVX
> +#  include "dl-trampoline.h"
> +#  undef _dl_runtime_profile
> +#  undef VEC
> +#  undef VMOVA
> +#  undef VEC_SIZE
> +# endif
> +#endif /* SHARED */
>   
> +#if MINIMUM_X86_ISA_LEVEL < AVX_X86_ISA_LEVEL
>   # define USE_FXSAVE
>   # define STATE_SAVE_ALIGNMENT	16
>   # define _dl_runtime_resolve	_dl_runtime_resolve_fxsave

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 14/19] elf: Ignore loader debug env vars for setuid
  2023-11-06 20:25 ` [PATCH v3 14/19] elf: Ignore loader debug env vars for setuid Adhemerval Zanella
@ 2023-11-20 22:57   ` Siddhesh Poyarekar
  2023-11-21 18:24     ` Adhemerval Zanella Netto
  0 siblings, 1 reply; 40+ messages in thread
From: Siddhesh Poyarekar @ 2023-11-20 22:57 UTC (permalink / raw)
  To: Adhemerval Zanella, libc-alpha

On 2023-11-06 15:25, Adhemerval Zanella wrote:
> Loader already ignores LD_DEBUG, LD_DEBUG_OUTPUT, and
> LD_TRACE_LOADED_OBJECTS. Both LD_WARN and LD_VERBOSE are similar to
> LD_DEBUG, in the sense they enable additional checks and debug
> information, so it makes sense to disable them.
> 
> Checked on x86_64-linux-gnu.
> ---

Maybe also put it in unsecvars.h?

>   elf/rtld.c           | 22 ++++++++++++++--------
>   elf/tst-env-setuid.c |  2 ++
>   2 files changed, 16 insertions(+), 8 deletions(-)
> 
> diff --git a/elf/rtld.c b/elf/rtld.c
> index a09cf2a9df..e7f90d37e7 100644
> --- a/elf/rtld.c
> +++ b/elf/rtld.c
> @@ -2552,13 +2552,15 @@ process_envvars (struct dl_main_state *state)
>   	{
>   	case 4:
>   	  /* Warning level, verbose or not.  */
> -	  if (memcmp (envline, "WARN", 4) == 0)
> +	  if (!__libc_enable_secure
> +	      && memcmp (envline, "WARN", 4) == 0)
>   	    GLRO(dl_verbose) = envline[5] != '\0';
>   	  break;
>   
>   	case 5:
>   	  /* Debugging of the dynamic linker?  */
> -	  if (memcmp (envline, "DEBUG", 5) == 0)
> +	  if (!__libc_enable_secure
> +	      && memcmp (envline, "DEBUG", 5) == 0)
>   	    {
>   	      process_dl_debug (state, &envline[6]);
>   	      break;
> @@ -2569,7 +2571,8 @@ process_envvars (struct dl_main_state *state)
>   
>   	case 7:
>   	  /* Print information about versions.  */
> -	  if (memcmp (envline, "VERBOSE", 7) == 0)
> +	  if (!__libc_enable_secure
> +	      && memcmp (envline, "VERBOSE", 7) == 0)
>   	    {
>   	      state->version_info = envline[8] != '\0';
>   	      break;
> @@ -2625,7 +2628,8 @@ process_envvars (struct dl_main_state *state)
>   	    }
>   
>   	  /* Where to place the profiling data file.  */
> -	  if (memcmp (envline, "DEBUG_OUTPUT", 12) == 0)
> +	  if (!__libc_enable_secure
> +	      && memcmp (envline, "DEBUG_OUTPUT", 12) == 0)
>   	    {
>   	      debug_output = &envline[13];
>   	      break;
> @@ -2646,7 +2650,8 @@ process_envvars (struct dl_main_state *state)
>   
>   	case 20:
>   	  /* The mode of the dynamic linker can be set.  */
> -	  if (memcmp (envline, "TRACE_LOADED_OBJECTS", 20) == 0)
> +	  if (!__libc_enable_secure
> +	      && memcmp (envline, "TRACE_LOADED_OBJECTS", 20) == 0)
>   	    {
>   	      state->mode = rtld_mode_trace;
>   	      state->mode_trace_program
> @@ -2668,9 +2673,10 @@ process_envvars (struct dl_main_state *state)
>   	}
>         while (*nextp != '\0');
>   
> -      GLRO(dl_debug_mask) = 0;
> -
> -      if (state->mode != rtld_mode_normal)
> +      if (GLRO(dl_debug_mask) != 0
> +	  || GLRO(dl_verbose) != 0
> +	  || state->mode != rtld_mode_normal
> +	  || state->version_info)
>   	_exit (5);
>       }
>     /* If we have to run the dynamic linker in debugging mode and the
> diff --git a/elf/tst-env-setuid.c b/elf/tst-env-setuid.c
> index 76b8e1fb45..dcf213a4cd 100644
> --- a/elf/tst-env-setuid.c
> +++ b/elf/tst-env-setuid.c
> @@ -59,6 +59,8 @@ static const struct envvar_t filtered_envvars[] =
>     { "MALLOC_TRACE",            FILTERED_VALUE },
>     { "MALLOC_TRIM_THRESHOLD_",  FILTERED_VALUE },
>     { "RES_OPTIONS",             FILTERED_VALUE },
> +  { "LD_DEBUG",                "all" },
> +  { "LD_DEBUG_OUTPUT",         "/tmp/some-file" },
>   };
>   
>   static const struct envvar_t unfiltered_envvars[] =

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 15/19] elf: Remove any_debug from dl_main_state
  2023-11-06 20:25 ` [PATCH v3 15/19] elf: Remove any_debug from dl_main_state Adhemerval Zanella
@ 2023-11-20 22:58   ` Siddhesh Poyarekar
  0 siblings, 0 replies; 40+ messages in thread
From: Siddhesh Poyarekar @ 2023-11-20 22:58 UTC (permalink / raw)
  To: Adhemerval Zanella, libc-alpha



On 2023-11-06 15:25, Adhemerval Zanella wrote:
> Its usage can be implied by the GLRO(dl_debug_mask).
> ---

Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>

>   elf/dl-main.h | 3 ---
>   elf/rtld.c    | 4 +---
>   2 files changed, 1 insertion(+), 6 deletions(-)
> 
> diff --git a/elf/dl-main.h b/elf/dl-main.h
> index 92766d06b4..f876904cb6 100644
> --- a/elf/dl-main.h
> +++ b/elf/dl-main.h
> @@ -97,9 +97,6 @@ struct dl_main_state
>     /* True if program should be also printed for rtld_mode_trace.  */
>     bool mode_trace_program;
>   
> -  /* 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 e7f90d37e7..638b019670 100644
> --- a/elf/rtld.c
> +++ b/elf/rtld.c
> @@ -300,7 +300,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;
>   }
>   
> @@ -2481,7 +2480,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;
>   	      }
>   
> @@ -2682,7 +2680,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);

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 16/19] elf: Ignore LD_LIBRARY_PATH and debug env var for setuid for static
  2023-11-06 20:25 ` [PATCH v3 16/19] elf: Ignore LD_LIBRARY_PATH and debug env var for setuid for static Adhemerval Zanella
@ 2023-11-20 22:59   ` Siddhesh Poyarekar
  0 siblings, 0 replies; 40+ messages in thread
From: Siddhesh Poyarekar @ 2023-11-20 22:59 UTC (permalink / raw)
  To: Adhemerval Zanella, libc-alpha



On 2023-11-06 15:25, Adhemerval Zanella wrote:
> It mimics the ld.so behavior.
> 
> Checked on x86_64-linux-gnu.
> ---

Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>

>   elf/dl-support.c | 32 ++++++++++++++++----------------
>   1 file changed, 16 insertions(+), 16 deletions(-)
> 
> diff --git a/elf/dl-support.c b/elf/dl-support.c
> index 31a608df87..837fa1c836 100644
> --- a/elf/dl-support.c
> +++ b/elf/dl-support.c
> @@ -272,8 +272,6 @@ _dl_non_dynamic_init (void)
>     _dl_main_map.l_phdr = GL(dl_phdr);
>     _dl_main_map.l_phnum = GL(dl_phnum);
>   
> -  _dl_verbose = *(getenv ("LD_WARN") ?: "") == '\0' ? 0 : 1;
> -
>     /* Set up the data structures for the system-supplied DSO early,
>        so they can influence _dl_init_paths.  */
>     setup_vdso (NULL, NULL);
> @@ -281,6 +279,22 @@ _dl_non_dynamic_init (void)
>     /* With vDSO setup we can initialize the function pointers.  */
>     setup_vdso_pointers ();
>   
> +  if (__libc_enable_secure)
> +    {
> +      static const char unsecure_envvars[] =
> +	UNSECURE_ENVVARS
> +	;
> +      const char *cp = unsecure_envvars;
> +
> +      while (cp < unsecure_envvars + sizeof (unsecure_envvars))
> +	{
> +	  __unsetenv (cp);
> +	  cp = strchr (cp, '\0') + 1;
> +	}
> +    }
> +
> +  _dl_verbose = *(getenv ("LD_WARN") ?: "") == '\0' ? 0 : 1;
> +
>     /* Initialize the data structures for the search paths for shared
>        objects.  */
>     _dl_init_paths (getenv ("LD_LIBRARY_PATH"), "LD_LIBRARY_PATH",
> @@ -297,20 +311,6 @@ _dl_non_dynamic_init (void)
>   
>     _dl_dynamic_weak = *(getenv ("LD_DYNAMIC_WEAK") ?: "") == '\0';
>   
> -  if (__libc_enable_secure)
> -    {
> -      static const char unsecure_envvars[] =
> -	UNSECURE_ENVVARS
> -	;
> -      const char *cp = unsecure_envvars;
> -
> -      while (cp < unsecure_envvars + sizeof (unsecure_envvars))
> -	{
> -	  __unsetenv (cp);
> -	  cp = strchr (cp, '\0') + 1;
> -	}
> -    }
> -
>   #ifdef DL_PLATFORM_INIT
>     DL_PLATFORM_INIT;
>   #endif

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 18/19] elf: Ignore LD_BIND_NOW and LD_BIND_NOT for setuid binaries
  2023-11-06 20:25 ` [PATCH v3 18/19] elf: Ignore LD_BIND_NOW and LD_BIND_NOT for setuid binaries Adhemerval Zanella
@ 2023-11-20 23:02   ` Siddhesh Poyarekar
  0 siblings, 0 replies; 40+ messages in thread
From: Siddhesh Poyarekar @ 2023-11-20 23:02 UTC (permalink / raw)
  To: Adhemerval Zanella, libc-alpha



On 2023-11-06 15:25, Adhemerval Zanella wrote:
> To avoid any environment variable to change setuid binaries
> semantics.
> 
> Checked on x86_64-linux-gnu.
> ---

Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>

>   elf/rtld.c                  | 8 ++++++--
>   elf/tst-env-setuid.c        | 4 ++--
>   sysdeps/generic/unsecvars.h | 2 ++
>   3 files changed, 10 insertions(+), 4 deletions(-)
> 
> diff --git a/elf/rtld.c b/elf/rtld.c
> index d1017ba9e9..cfba30eba0 100644
> --- a/elf/rtld.c
> +++ b/elf/rtld.c
> @@ -2598,12 +2598,14 @@ process_envvars (struct dl_main_state *state)
>   
>   	case 8:
>   	  /* Do we bind early?  */
> -	  if (memcmp (envline, "BIND_NOW", 8) == 0)
> +	  if (!__libc_enable_secure
> +	      && memcmp (envline, "BIND_NOW", 8) == 0)
>   	    {
>   	      GLRO(dl_lazy) = envline[9] == '\0';
>   	      break;
>   	    }
> -	  if (memcmp (envline, "BIND_NOT", 8) == 0)
> +	  if (! __libc_enable_secure
> +	      && memcmp (envline, "BIND_NOT", 8) == 0)
>   	    GLRO(dl_bind_not) = envline[9] != '\0';
>   	  break;
>   
> @@ -2680,6 +2682,8 @@ process_envvars (struct dl_main_state *state)
>   
>         if (GLRO(dl_debug_mask) != 0
>   	  || GLRO(dl_verbose) != 0
> +	  || GLRO(dl_lazy) != 1
> +	  || GLRO(dl_bind_not) != 0
>   	  || state->mode != rtld_mode_normal
>   	  || state->version_info)
>   	_exit (5);
> diff --git a/elf/tst-env-setuid.c b/elf/tst-env-setuid.c
> index dcf213a4cd..eb9613d717 100644
> --- a/elf/tst-env-setuid.c
> +++ b/elf/tst-env-setuid.c
> @@ -61,12 +61,12 @@ static const struct envvar_t filtered_envvars[] =
>     { "RES_OPTIONS",             FILTERED_VALUE },
>     { "LD_DEBUG",                "all" },
>     { "LD_DEBUG_OUTPUT",         "/tmp/some-file" },
> +  { "LD_BIND_NOW",             "0" },
> +  { "LD_BIND_NOT",             "1" },
>   };
>   
>   static const struct envvar_t unfiltered_envvars[] =
>   {
> -  { "LD_BIND_NOW",             "0" },
> -  { "LD_BIND_NOT",             "1" },
>     /* Non longer supported option.  */
>     { "LD_ASSUME_KERNEL",        UNFILTERED_VALUE },
>   };
> diff --git a/sysdeps/generic/unsecvars.h b/sysdeps/generic/unsecvars.h
> index f7ebed60e5..c71b70ed95 100644
> --- a/sysdeps/generic/unsecvars.h
> +++ b/sysdeps/generic/unsecvars.h
> @@ -7,6 +7,8 @@
>     "GLIBC_TUNABLES\0"							      \
>     "HOSTALIASES\0"							      \
>     "LD_AUDIT\0"								      \
> +  "LD_BIND_NOT\0"							      \
> +  "LD_BIND_NOW\0"							      \
>     "LD_DEBUG\0"								      \
>     "LD_DEBUG_OUTPUT\0"							      \
>     "LD_DYNAMIC_WEAK\0"							      \

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 19/19] elf: Refactor process_envvars
  2023-11-06 20:25 ` [PATCH v3 19/19] elf: Refactor process_envvars Adhemerval Zanella
@ 2023-11-20 23:09   ` Siddhesh Poyarekar
  2023-11-21 19:00     ` Adhemerval Zanella Netto
  0 siblings, 1 reply; 40+ messages in thread
From: Siddhesh Poyarekar @ 2023-11-20 23:09 UTC (permalink / raw)
  To: Adhemerval Zanella, libc-alpha



On 2023-11-06 15:25, Adhemerval Zanella wrote:
> It splits between process_envvars_secure and process_envvars_default,
> with the former used to process arguments for __libc_enable_secure.
> It does not have any semantic change, just simplify the code so there
> is no need to handle __libc_enable_secure on each len switch.
> 
> Checked on x86_64-linux-gnu and aarch64-linux-gnu.
> ---
>   elf/rtld.c | 132 ++++++++++++++++++++++++++++++++++-------------------
>   1 file changed, 84 insertions(+), 48 deletions(-)
> 
> diff --git a/elf/rtld.c b/elf/rtld.c
> index cfba30eba0..95dcd32185 100644
> --- a/elf/rtld.c
> +++ b/elf/rtld.c
> @@ -2527,7 +2527,67 @@ a filename can be specified using the LD_DEBUG_OUTPUT environment variable.\n");
>   }
>   \f
>   static void
> -process_envvars (struct dl_main_state *state)
> +process_envvars_secure (struct dl_main_state *state)
> +{
> +  char **runp = _environ;
> +  char *envline;
> +
> +  while ((envline = _dl_next_ld_env_entry (&runp)) != NULL)
> +    {
> +      size_t len = 0;
> +
> +      while (envline[len] != '\0' && envline[len] != '=')
> +	++len;
> +
> +      if (envline[len] != '=')
> +	/* This is a "LD_" variable at the end of the string without
> +	   a '=' character.  Ignore it since otherwise we will access
> +	   invalid memory below.  */
> +	continue;
> +
> +      switch (len)
> +	{
> +	case 5:
> +	  /* For __libc_enable_secure mode, audit pathnames containing slashes
> +	     are ignored.  Also, shared audit objects are only loaded only from
> +	     the standard search directories and only if they have set-user-ID
> +	     mode bit enabled.  */
> +	  if (memcmp (envline, "AUDIT", 5) == 0)
> +	    audit_list_add_string (&state->audit_list, &envline[6]);
> +	  break;
> +
> +	case 7:
> +	  /* For __libc_enable_secure mode, preload pathnames containing slashes
> +	     are ignored.  Also, shared objects are only preloaded from the
> +	     standard search directories and only if they have set-user-ID mode
> +	     bit enabled.  */
> +	  if (memcmp (envline, "PRELOAD", 7) == 0)
> +	    state->preloadlist = &envline[8];
> +	  break;
> +	}
> +    }

We need to talk about whether we should get rid of these too in the 
context of setuid programs, but that's a separate discussion.  LGTM.

Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>

> +
> +  /* Extra security for SUID binaries.  Remove all dangerous environment
> +     variables.  */
> +  const char *nextp = UNSECURE_ENVVARS;
> +  do
> +    {
> +      unsetenv (nextp);
> +      nextp = strchr (nextp, '\0') + 1;
> +    }
> +  while (*nextp != '\0');
> +
> +  if (GLRO(dl_debug_mask) != 0
> +      || GLRO(dl_verbose) != 0
> +      || GLRO(dl_lazy) != 1
> +      || GLRO(dl_bind_not) != 0
> +      || state->mode != rtld_mode_normal
> +      || state->version_info)
> +    _exit (5);
> +}
> +
> +static void
> +process_envvars_default (struct dl_main_state *state)
>   {
>     char **runp = _environ;
>     char *envline;

I wondered if we should add an assert here on __libc_enable_secure ==0, 
but there is only one caller, so maybe we can get away without it.

> @@ -2550,15 +2610,13 @@ process_envvars (struct dl_main_state *state)
>   	{
>   	case 4:
>   	  /* Warning level, verbose or not.  */
> -	  if (!__libc_enable_secure
> -	      && memcmp (envline, "WARN", 4) == 0)
> +	  if (memcmp (envline, "WARN", 4) == 0)
>   	    GLRO(dl_verbose) = envline[5] != '\0';
>   	  break;
>   
>   	case 5:
>   	  /* Debugging of the dynamic linker?  */
> -	  if (!__libc_enable_secure
> -	      && memcmp (envline, "DEBUG", 5) == 0)
> +	  if (memcmp (envline, "DEBUG", 5) == 0)
>   	    {
>   	      process_dl_debug (state, &envline[6]);
>   	      break;
> @@ -2573,8 +2631,7 @@ process_envvars (struct dl_main_state *state)
>   
>   	case 7:
>   	  /* Print information about versions.  */
> -	  if (!__libc_enable_secure
> -	      && memcmp (envline, "VERBOSE", 7) == 0)
> +	  if (memcmp (envline, "VERBOSE", 7) == 0)
>   	    {
>   	      state->version_info = envline[8] != '\0';
>   	      break;
> @@ -2591,43 +2648,37 @@ process_envvars (struct dl_main_state *state)
>   	    }
>   
>   	  /* Which shared object shall be profiled.  */
> -	  if (!__libc_enable_secure
> -	      && memcmp (envline, "PROFILE", 7) == 0 && envline[8] != '\0')
> +	  if (memcmp (envline, "PROFILE", 7) == 0 && envline[8] != '\0')
>   	    GLRO(dl_profile) = &envline[8];
>   	  break;
>   
>   	case 8:
>   	  /* Do we bind early?  */
> -	  if (!__libc_enable_secure
> -	      && memcmp (envline, "BIND_NOW", 8) == 0)
> +	  if (memcmp (envline, "BIND_NOW", 8) == 0)
>   	    {
>   	      GLRO(dl_lazy) = envline[9] == '\0';
>   	      break;
>   	    }
> -	  if (! __libc_enable_secure
> -	      && memcmp (envline, "BIND_NOT", 8) == 0)
> +	  if (memcmp (envline, "BIND_NOT", 8) == 0)
>   	    GLRO(dl_bind_not) = envline[9] != '\0';
>   	  break;
>   
>   	case 9:
>   	  /* Test whether we want to see the content of the auxiliary
>   	     array passed up from the kernel.  */
> -	  if (!__libc_enable_secure
> -	      && memcmp (envline, "SHOW_AUXV", 9) == 0)
> +	  if (memcmp (envline, "SHOW_AUXV", 9) == 0)
>   	    _dl_show_auxv ();
>   	  break;
>   
>   	case 11:
>   	  /* Path where the binary is found.  */
> -	  if (!__libc_enable_secure
> -	      && memcmp (envline, "ORIGIN_PATH", 11) == 0)
> +	  if (memcmp (envline, "ORIGIN_PATH", 11) == 0)
>   	    GLRO(dl_origin_path) = &envline[12];
>   	  break;
>   
>   	case 12:
>   	  /* The library search path.  */
> -	  if (!__libc_enable_secure
> -	      && memcmp (envline, "LIBRARY_PATH", 12) == 0)
> +	  if (memcmp (envline, "LIBRARY_PATH", 12) == 0)
>   	    {
>   	      state->library_path = &envline[13];
>   	      state->library_path_source = "LD_LIBRARY_PATH";
> @@ -2635,30 +2686,26 @@ process_envvars (struct dl_main_state *state)
>   	    }
>   
>   	  /* Where to place the profiling data file.  */
> -	  if (!__libc_enable_secure
> -	      && memcmp (envline, "DEBUG_OUTPUT", 12) == 0)
> +	  if (memcmp (envline, "DEBUG_OUTPUT", 12) == 0)
>   	    {
>   	      debug_output = &envline[13];
>   	      break;
>   	    }
>   
> -	  if (!__libc_enable_secure
> -	      && memcmp (envline, "DYNAMIC_WEAK", 12) == 0)
> +	  if (memcmp (envline, "DYNAMIC_WEAK", 12) == 0)
>   	    GLRO(dl_dynamic_weak) = 1;
>   	  break;
>   
>   	case 14:
>   	  /* Where to place the profiling data file.  */
> -	  if (!__libc_enable_secure
> -	      && memcmp (envline, "PROFILE_OUTPUT", 14) == 0
> +	  if (memcmp (envline, "PROFILE_OUTPUT", 14) == 0
>   	      && envline[15] != '\0')
>   	    GLRO(dl_profile_output) = &envline[15];
>   	  break;
>   
>   	case 20:
>   	  /* The mode of the dynamic linker can be set.  */
> -	  if (!__libc_enable_secure
> -	      && memcmp (envline, "TRACE_LOADED_OBJECTS", 20) == 0)
> +	  if (memcmp (envline, "TRACE_LOADED_OBJECTS", 20) == 0)
>   	    {
>   	      state->mode = rtld_mode_trace;
>   	      state->mode_trace_program
> @@ -2668,30 +2715,10 @@ process_envvars (struct dl_main_state *state)
>   	}
>       }
>   
> -  /* Extra security for SUID binaries.  Remove all dangerous environment
> -     variables.  */
> -  if (__glibc_unlikely (__libc_enable_secure))
> -    {
> -      const char *nextp = UNSECURE_ENVVARS;
> -      do
> -	{
> -	  unsetenv (nextp);
> -	  nextp = strchr (nextp, '\0') + 1;
> -	}
> -      while (*nextp != '\0');
> -
> -      if (GLRO(dl_debug_mask) != 0
> -	  || GLRO(dl_verbose) != 0
> -	  || GLRO(dl_lazy) != 1
> -	  || GLRO(dl_bind_not) != 0
> -	  || state->mode != rtld_mode_normal
> -	  || state->version_info)
> -	_exit (5);
> -    }
>     /* 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 (GLRO(dl_debug_mask) != 0 && debug_output != NULL)
> +  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);
> @@ -2710,6 +2737,15 @@ process_envvars (struct dl_main_state *state)
>       }
>   }
>   
> +static void
> +process_envvars (struct dl_main_state *state)
> +{
> +  if (__glibc_unlikely (__libc_enable_secure))
> +    process_envvars_secure (state);
> +  else
> +    process_envvars_default (state);
> +}
> +
>   #if HP_TIMING_INLINE
>   static void
>   print_statistics_item (const char *title, hp_timing_t time,

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 00/19] Improve loader environment variable handling
  2023-11-06 20:25 [PATCH v3 00/19] Improve loader environment variable handling Adhemerval Zanella
                   ` (18 preceding siblings ...)
  2023-11-06 20:25 ` [PATCH v3 19/19] elf: Refactor process_envvars Adhemerval Zanella
@ 2023-11-20 23:12 ` Siddhesh Poyarekar
  2023-11-21 19:37   ` Adhemerval Zanella Netto
  19 siblings, 1 reply; 40+ messages in thread
From: Siddhesh Poyarekar @ 2023-11-20 23:12 UTC (permalink / raw)
  To: Adhemerval Zanella, libc-alpha



On 2023-11-06 15:25, Adhemerval Zanella wrote:
> The recent CVE-2023-4911 fix [1] and tunable change to SXID_ERASE
> discussion [2] brought some issues with the current environment handling
> by the loader. Besides the bugs in tuning parsing, some other questions
> are:

Overall I think 1-10 are ready to be committed since they handle a full 
block of work, i.e. tunables validation.  I know 11 kinda belongs in 
that block, but it would be nice to reduce this set to 10 from 19 for 
the next version :)

Also maybe push the independent patches in 11-19 that have R-b already.

Thanks,
Sid

> 
>    * What should be the security boundaries for tunable and other tuning
>      environment variables?
> 
>    * Should tunables be filtered out or be disabled altogether in setuid
>      binaries [3]?
> 
>    * How should ld.so handle security-sensitive tunable (like malloc
>      options)?
> 
>    * How to handle ill-formatted tunable definition [4]?
> 
>    * Is tunable copy/parsing (through tunable_strdup) required [5]?
> 
>    * Which other environment variables the loader should ignore and/or
>      filter in security-sensitive context.
> 
> On this patchset, I followed the idea laid out in the discussion on
> whether to apply SXID_ERASE to all tunables [6]:
> 
>    1. ignore any tunable on AT_SECURE binaries (as some Linux distributions
>        do already [7]);
> 
>    2. Add malloc tunables along with GLIBC_TUNABLES to unsecvars;
> 
>    3. Do not parse ill-formatted GLIBC_TUNABLES strings;
> 
>    4. Remove the requirement of duplicating the GLIBC_TUNABLES string for
>       parsing.
> 
>    5. Ignore most of the environment variables on security-sensitive
>       mode (AT_SECURE/setuid/setgid).
> 
> Patch #1 removes '/etc/suid-debug', which has not been working since
> malloc debugging supported moved to libc_malloc_debug.so. It is one
> thing less that might change AT_SECURE binaries' behavior
> due to environment configurations.
> 
> Patch #2 removed tunables parsing and applying for setuid/setgid
> binaries (similar to Alt Linux patch).
> 
> Patch #3 and #4 add all malloc tunable and GLIBC_TUNABLES to unsecvars
> and improve tst-env-setuid.c to test all possible environment variables.
> 
> Patch #5 and #6 improved the GLIBC_TUNABLES handling to avoid handling
> ill-formatted inputs.
> 
> Patch #7 makes _dl_debug_vdprintf usable before self-relocation so patch
> #8 can add a loader warning that ill-formatted GLIBC_TUNABLES inputs are
> ignored (it also fixes the issue where the GLIBC_TUNABLE allocation
> failure will trigger a SEGFAULT on some architecture for PIE).
> 
> Patch #9, #10, and #11 remove the tunable_strdup and make the
> GLIBC_TUNABLE parsing in place (no more possible allocation failure).
> The parsing now tracks the tunable start and its size. The
> dl-tunable-parse.h adds helper functions to help to parse, like an
> strcmp that also checks for size and an iterator for suboptions that are
> comma-separated (used on hwcap parsing by x86, powerpc, and s390x).
> 
> Patch #12, #13, #14, #16, and #18 make loader ignore all but just
> LD_PRELOAD and LD_AUDIT for setuid binaries.  For both options, loader
> ensures that pathnames containing slashes are ignored and shared
> libraries are loaded only from the standard search directories and only
> if they have set-user-ID mode bit enabled.
> 
> [1] https://sourceware.org/pipermail/libc-alpha/2023-October/151921.html
> [2] https://sourceware.org/pipermail/libc-alpha/2023-October/151936.html
> [3] https://www.openwall.com/lists/oss-security/2023/10/03/3
> [4] https://sourceware.org/pipermail/libc-alpha/2023-October/151927.html
> [5] https://sourceware.org/pipermail/libc-alpha/2023-October/151959.html
> [6] https://sourceware.org/pipermail/libc-alpha/2023-October/152011.html
> [7] https://git.altlinux.org/gears/g/glibc.git?p=glibc.git;a=commitdiff;h=5d1686416ab766f3dd0780ab730650c4c0f76ca9
> 
> Changes from v2:
> * Extend tst-tunables with tunables aliases tests.
> * Use warning instead of an error to indicate invalid tunables.
> * Fixed tunable_initialize for string aliases.
> Changes from v1:
> * Ignore most of the environment variables on security-sensitive mode.
> * Extend tests.
> 
> Adhemerval Zanella (19):
>    elf: Remove /etc/suid-debug support
>    elf: Add GLIBC_TUNABLES to unsecvars
>    elf: Ignore GLIBC_TUNABLES for setuid/setgid binaries
>    elf: Add all malloc tunable to unsecvars
>    elf: Do not process invalid tunable format
>    elf: Do not parse ill-formatted strings
>    elf: Fix _dl_debug_vdprintf to work before self-relocation
>    elf: Emit warning if tunable is ill-formatted
>    x86: Use dl-symbol-redir-ifunc.h on cpu-tunables
>    s390: Use dl-symbol-redir-ifunc.h on cpu-tunables
>    elf: Do not duplicate the GLIBC_TUNABLES string
>    elf: Ignore LD_PROFILE for setuid binaries
>    elf: Remove LD_PROFILE for static binaries
>    elf: Ignore loader debug env vars for setuid
>    elf: Remove any_debug from dl_main_state
>    elf: Ignore LD_LIBRARY_PATH and debug env var for setuid for static
>    elf: Add comments on how LD_AUDIT and LD_PRELOAD handle
>      __libc_enable_secure
>    elf: Ignore LD_BIND_NOW and LD_BIND_NOT for setuid binaries
>    elf: Refactor process_envvars
> 
>   elf/Makefile                                  |  26 +-
>   elf/dl-load.c                                 |  10 +-
>   elf/dl-main.h                                 |   3 -
>   elf/dl-printf.c                               |  16 +-
>   elf/dl-runtime.c                              |  12 +-
>   elf/dl-support.c                              |  41 +--
>   elf/dl-tunable-types.h                        |  10 -
>   elf/dl-tunables.c                             | 219 ++++--------
>   elf/dl-tunables.h                             |   6 +-
>   elf/dl-tunables.list                          |  17 -
>   elf/{dl-profstub.c => libc-dl-profstub.c}     |   0
>   elf/rtld.c                                    | 122 +++++--
>   elf/tst-env-setuid-static.c                   |   1 +
>   elf/tst-env-setuid-tunables.c                 |  59 ++--
>   elf/tst-env-setuid.c                          | 140 +++++---
>   elf/tst-tunables.c                            | 321 ++++++++++++++++++
>   include/dlfcn.h                               |   5 +
>   manual/README.tunables                        |   9 -
>   manual/memory.texi                            |   4 +-
>   manual/tunables.texi                          |   4 +-
>   scripts/gen-tunables.awk                      |  18 +-
>   stdio-common/Makefile                         |   5 +
>   stdio-common/_itoa.c                          |   5 +
>   sysdeps/aarch64/dl-machine.h                  |   4 +-
>   sysdeps/aarch64/dl-trampoline.S               |   2 +-
>   sysdeps/alpha/dl-machine.h                    |   6 +-
>   sysdeps/alpha/dl-trampoline.S                 |   4 +
>   sysdeps/arm/dl-machine.h                      |   4 +-
>   sysdeps/arm/dl-trampoline.S                   |   2 +-
>   sysdeps/generic/dl-tunables-parse.h           | 129 +++++++
>   sysdeps/generic/unsecvars.h                   |  10 +
>   sysdeps/hppa/dl-machine.h                     |  36 +-
>   sysdeps/hppa/dl-trampoline.S                  |   2 +
>   sysdeps/i386/dl-machine.h                     |   2 +
>   sysdeps/i386/dl-trampoline.S                  |   2 +-
>   .../i686/multiarch/dl-symbol-redir-ifunc.h    |   5 +
>   sysdeps/ia64/dl-machine.h                     |  10 +-
>   sysdeps/ia64/dl-trampoline.S                  |   2 +-
>   sysdeps/loongarch/dl-machine.h                |   6 +-
>   sysdeps/loongarch/dl-trampoline.h             |   2 +
>   sysdeps/m68k/dl-machine.h                     |   4 +-
>   sysdeps/m68k/dl-trampoline.S                  |   2 +
>   sysdeps/powerpc/powerpc32/dl-machine.c        |   2 +-
>   sysdeps/powerpc/powerpc32/dl-machine.h        |  10 +-
>   sysdeps/powerpc/powerpc32/dl-trampoline.S     |   2 +-
>   sysdeps/powerpc/powerpc64/dl-machine.h        |  20 +-
>   sysdeps/powerpc/powerpc64/dl-trampoline.S     |   2 +-
>   sysdeps/s390/cpu-features.c                   | 169 ++++-----
>   .../s390/multiarch/dl-symbol-redir-ifunc.h    |   2 +
>   sysdeps/s390/s390-32/dl-machine.h             |   8 +-
>   sysdeps/s390/s390-32/dl-trampoline.h          |   2 +-
>   sysdeps/s390/s390-64/dl-machine.h             |   8 +-
>   sysdeps/s390/s390-64/dl-trampoline.h          |   2 +-
>   sysdeps/sh/dl-machine.h                       |   2 +
>   sysdeps/sh/dl-trampoline.S                    |   2 +
>   sysdeps/sparc/sparc32/dl-machine.h            |   4 +-
>   sysdeps/sparc/sparc32/dl-trampoline.S         |   2 +
>   sysdeps/sparc/sparc64/dl-machine.h            |   4 +-
>   sysdeps/sparc/sparc64/dl-trampoline.S         |   2 +
>   .../unix/sysv/linux/aarch64/cpu-features.c    |  38 ++-
>   .../sysv/linux/i386/dl-writev.h}              |  18 +-
>   .../unix/sysv/linux/powerpc/cpu-features.c    |  45 +--
>   .../sysv/linux/powerpc/tst-hwcap-tunables.c   |   6 +-
>   sysdeps/x86/Makefile                          |   4 +-
>   sysdeps/x86/cpu-tunables.c                    | 135 +++-----
>   sysdeps/x86/tst-hwcap-tunables.c              | 148 ++++++++
>   sysdeps/x86_64/64/dl-tunables.list            |   1 -
>   sysdeps/x86_64/dl-machine.h                   |   2 +
>   sysdeps/x86_64/dl-trampoline.S                |  64 ++--
>   .../x86_64/multiarch/dl-symbol-redir-ifunc.h  |  15 +
>   70 files changed, 1281 insertions(+), 725 deletions(-)
>   rename elf/{dl-profstub.c => libc-dl-profstub.c} (100%)
>   create mode 100644 elf/tst-env-setuid-static.c
>   create mode 100644 elf/tst-tunables.c
>   create mode 100644 sysdeps/generic/dl-tunables-parse.h
>   rename sysdeps/{x86_64/memcmp-isa-default-impl.h => unix/sysv/linux/i386/dl-writev.h} (62%)
>   create mode 100644 sysdeps/x86/tst-hwcap-tunables.c
> 

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 11/19] elf: Do not duplicate the GLIBC_TUNABLES string
  2023-11-20 22:44   ` Siddhesh Poyarekar
@ 2023-11-21 18:12     ` Adhemerval Zanella Netto
  2023-11-22 11:39       ` Adhemerval Zanella Netto
  2023-11-22 12:23       ` Siddhesh Poyarekar
  0 siblings, 2 replies; 40+ messages in thread
From: Adhemerval Zanella Netto @ 2023-11-21 18:12 UTC (permalink / raw)
  To: Siddhesh Poyarekar, libc-alpha



On 20/11/23 19:44, Siddhesh Poyarekar wrote:
> On 2023-11-06 15:25, Adhemerval Zanella wrote:
>> The tunable parsing duplicates the tunable environment variable so it
>> null-terminates each one since it simplifies the later parsing. It has
>> the drawback of adding another point of failure (__minimal_malloc
>> failing), and the memory copy requires tuning the compiler to avoid mem
>> operations calls.
>>
>> The parsing now tracks the tunable start and its size. The
>> dl-tunable-parse.h adds helper functions to help parsing, like a strcmp
>> that also checks for size and an iterator for suboptions that are
>> comma-separated (used on hwcap parsing by x86, powerpc, and s390x).
>>
>> Since the environment variable is allocated on the stack by the kernel,
>> it is safe to keep the references to the suboptions for later parsing
>> of string tunables (as done by set_hwcaps by multiple architectures).
>>
>> Checked on x86_64-linux-gnu, powerpc64le-linux-gnu, and
>> aarch64-linux-gnu.
>> ---
>>   elf/dl-tunables.c                             |  66 +++----
>>   elf/dl-tunables.h                             |   6 +-
>>   elf/tst-tunables.c                            |  66 ++++++-
>>   sysdeps/generic/dl-tunables-parse.h           | 129 ++++++++++++++
>>   sysdeps/s390/cpu-features.c                   | 167 +++++++-----------
>>   .../unix/sysv/linux/aarch64/cpu-features.c    |  38 ++--
>>   .../unix/sysv/linux/powerpc/cpu-features.c    |  45 ++---
>>   .../sysv/linux/powerpc/tst-hwcap-tunables.c   |   6 +-
>>   sysdeps/x86/Makefile                          |   4 +-
>>   sysdeps/x86/cpu-tunables.c                    | 118 +++++--------
>>   sysdeps/x86/tst-hwcap-tunables.c              | 148 ++++++++++++++++
>>   11 files changed, 517 insertions(+), 276 deletions(-)
>>   create mode 100644 sysdeps/generic/dl-tunables-parse.h
>>   create mode 100644 sysdeps/x86/tst-hwcap-tunables.c
>>
>> diff --git a/elf/dl-tunables.c b/elf/dl-tunables.c
>> index 6e3e6a2cf8..f406521735 100644
>> --- a/elf/dl-tunables.c
>> +++ b/elf/dl-tunables.c
>> @@ -36,28 +36,6 @@
>>   #define TUNABLES_INTERNAL 1
>>   #include "dl-tunables.h"
>>   -#include <not-errno.h>
>> -
>> -static char *
>> -tunables_strdup (const char *in)
>> -{
>> -  size_t i = 0;
>> -
>> -  while (in[i++] != '\0');
>> -  char *out = __minimal_malloc (i + 1);
>> -
>> -  /* For most of the tunables code, we ignore user errors.  However,
>> -     this is a system error - and running out of memory at program
>> -     startup should be reported, so we do.  */
>> -  if (out == NULL)
>> -    _dl_fatal_printf ("failed to allocate memory to process tunables\n");
>> -
>> -  while (i-- > 0)
>> -    out[i] = in[i];
>> -
>> -  return out;
>> -}
>> -
>>   static char **
>>   get_next_env (char **envp, char **name, size_t *namelen, char **val,
>>             char ***prev_envp)
>> @@ -134,14 +112,14 @@ do_tunable_update_val (tunable_t *cur, const tunable_val_t *valp,
>>   /* Validate range of the input value and initialize the tunable CUR if it looks
>>      good.  */
>>   static void
>> -tunable_initialize (tunable_t *cur, const char *strval)
>> +tunable_initialize (tunable_t *cur, const char *strval, size_t len)
>>   {
>> -  tunable_val_t val;
>> +  tunable_val_t val = { 0 };
>>       if (cur->type.type_code != TUNABLE_TYPE_STRING)
>>       val.numval = (tunable_num_t) _dl_strtoul (strval, NULL);
> 
> There's an implicit assumption that strval is NULL terminated for numeric values.  Is that safe?  Maybe all you need to do here is copy strval into a static buffer of size 21 (basically size of the string representing -1ULL) and pass that to _dl_strtoul.

Afaiu the environment variable will always be NULL terminated (although some 
might overlap).  This is due how the kernel will layout the argv/envp on 
process creation at sys_execve:

fs/exec.c

1886 static int do_execveat_common(int fd, struct filename *filename,
1887                               struct user_arg_ptr argv,
1888                               struct user_arg_ptr envp,
1889                               int flags)
1890 {
[...]
1941         retval = copy_strings(bprm->envc, envp, bprm);
1942         if (retval < 0)
1943                 goto out_free;
1944
1945         retval = copy_strings(bprm->argc, argv, bprm);
1946         if (retval < 0)
1947                 goto out_free;
[...]

 518 /*
 519  * 'copy_strings()' copies argument/environment strings from the old
 520  * processes's memory to the new process's stack.  The call to get_user_pages()
 521  * ensures the destination page is created and not swapped out.
 522  */
 523 static int copy_strings(int argc, struct user_arg_ptr argv,
 524                         struct linux_binprm *bprm)
 525 {
[...]
 531         while (argc-- > 0) {
[...]
 536                 ret = -EFAULT;
 537                 str = get_user_arg_ptr(argv, argc);
 538                 if (IS_ERR(str))
 539                         goto out;
 540
 541                 len = strnlen_user(str, MAX_ARG_STRLEN);
 542                 if (!len)
 543                         goto out;

So even if caller construct a bogus argv/envp with non null-terminated
strings, if the kernel can not found a NULL execve will eventually fail 
with EFAULT:

$ cat spawn.c
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>

extern char **environ;

int main (int argc, char *argv[])
{
  pid_t pid = fork ();
  assert (pid != -1);
  if (pid != 0)
    exit (EXIT_FAILURE);

  long int pz = sysconf (_SC_PAGESIZE);
  assert (pz != -1);

  void *buf = mmap (NULL, 2 * pz, PROT_READ | PROT_WRITE,
                    MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
  assert (buf != MAP_FAILED);

  assert (mprotect (buf + pz, pz, PROT_NONE) != -1);

  char *env = (buf + pz) - 1;
  env[0] = 'a';

  char *new_argv[] = {  "./spawn", NULL };
  char *new_envp[] = { env, NULL };
  int r = execve (new_argv[0], new_argv, new_envp);
  printf ("r=%d (errno=%s)\n", r, strerrorname_np (errno));

  return 0;
}
$ gcc -Wall -O2 spawn.c -D_GNU_SOURCE -o spawn && ./spawn
r=-1 (errno=EFAULT)

> 
>>     else
>> -    val.strval = strval;
>> +    val.strval = (struct tunable_str_t) { strval, len };
>>     do_tunable_update_val (cur, &val, NULL, NULL);
>>   }
>>   @@ -158,29 +136,29 @@ struct tunable_toset_t
>>   {
>>     tunable_t *t;
>>     const char *value;
>> +  size_t len;
>>   };
>>     enum { tunables_list_size = array_length (tunable_list) };
>>     /* Parse the tunable string VALSTRING and set TUNABLES with the found tunables
>> -   and their respectibles values.  VALSTRING is a duplicated values,  where
>> -   delimiters ':' are replaced with '\0', so string tunables are null
>> -   terminated.
>> +   and their respectibles values.  The VALSTRING is parsed in place, with the
>> +   tunable start and size recorded in TUNABLES.
>>      Return the number of tunables found (including 0 if the string is empty)
>>      or -1 if for a ill-formatted definition.  */
>>   static int
>> -parse_tunables_string (char *valstring, struct tunable_toset_t *tunables)
>> +parse_tunables_string (const char *valstring, struct tunable_toset_t *tunables)
>>   {
>>     if (valstring == NULL || *valstring == '\0')
>>       return 0;
>>   -  char *p = valstring;
>> +  const char *p = valstring;
>>     bool done = false;
>>     int ntunables = 0;
>>       while (!done)
>>       {
>> -      char *name = p;
>> +      const char *name = p;
>>           /* First, find where the name ends.  */
>>         while (*p != '=' && *p != ':' && *p != '\0')
>> @@ -202,7 +180,7 @@ parse_tunables_string (char *valstring, struct tunable_toset_t *tunables)
>>         /* Skip the '='.  */
>>         p++;
>>   -      char *value = p;
>> +      const char *value = p;
>>           while (*p != '=' && *p != ':' && *p != '\0')
>>       p++;
>> @@ -211,8 +189,6 @@ parse_tunables_string (char *valstring, struct tunable_toset_t *tunables)
>>       return -1;
>>         else if (*p == '\0')
>>       done = true;
>> -      else
>> -    *p++ = '\0';
>>           /* Add the tunable if it exists.  */
>>         for (size_t i = 0; i < tunables_list_size; i++)
>> @@ -221,7 +197,8 @@ parse_tunables_string (char *valstring, struct tunable_toset_t *tunables)
>>           if (tunable_is_name (cur->name, name))
>>           {
>> -          tunables[ntunables++] = (struct tunable_toset_t) { cur, value };
>> +          tunables[ntunables++] =
>> +        (struct tunable_toset_t) { cur, value, p - value };
>>             break;
>>           }
>>       }
>> @@ -231,7 +208,7 @@ parse_tunables_string (char *valstring, struct tunable_toset_t *tunables)
>>   }
>>     static void
>> -parse_tunables (char *valstring)
>> +parse_tunables (const char *valstring)
>>   {
>>     struct tunable_toset_t tunables[tunables_list_size];
>>     int ntunables = parse_tunables_string (valstring, tunables);
>> @@ -243,7 +220,7 @@ parse_tunables (char *valstring)
>>       }
>>       for (int i = 0; i < ntunables; i++)
>> -    tunable_initialize (tunables[i].t, tunables[i].value);
>> +    tunable_initialize (tunables[i].t, tunables[i].value, tunables[i].len);
>>   }
>>     /* Initialize the tunables list from the environment.  For now we only use the
>> @@ -264,9 +241,12 @@ __tunables_init (char **envp)
>>     while ((envp = get_next_env (envp, &envname, &len, &envval,
>>                      &prev_envp)) != NULL)
>>       {
>> +      /* The environment variable is allocated on the stack by the kernel, so
>> +     it is safe to keep the references to the suboptions for later parsing
>> +     of string tunables.  */
>>         if (tunable_is_name ("GLIBC_TUNABLES", envname))
>>       {
>> -      parse_tunables (tunables_strdup (envval));
>> +      parse_tunables (envval);
>>         continue;
>>       }
>>   @@ -284,7 +264,7 @@ __tunables_init (char **envp)
>>         /* We have a match.  Initialize and move on to the next line.  */
>>         if (tunable_is_name (name, envname))
>>           {
>> -          tunable_initialize (cur, envval);
>> +          tunable_initialize (cur, envval, len);
>>             break;
>>           }
>>       }
>> @@ -298,7 +278,7 @@ __tunables_print (void)
>>       {
>>         const tunable_t *cur = &tunable_list[i];
>>         if (cur->type.type_code == TUNABLE_TYPE_STRING
>> -      && cur->val.strval == NULL)
>> +      && cur->val.strval.str == NULL)
>>       _dl_printf ("%s:\n", cur->name);
>>         else
>>       {
>> @@ -324,7 +304,9 @@ __tunables_print (void)
>>                 (size_t) cur->type.max);
>>             break;
>>           case TUNABLE_TYPE_STRING:
>> -          _dl_printf ("%s\n", cur->val.strval);
>> +          _dl_printf ("%.*s\n",
>> +              (int) cur->val.strval.len,
>> +              cur->val.strval.str);
>>             break;
>>           default:
>>             __builtin_unreachable ();
>> @@ -359,7 +341,7 @@ __tunable_get_val (tunable_id_t id, void *valp, tunable_callback_t callback)
>>       }
>>       case TUNABLE_TYPE_STRING:
>>       {
>> -      *((const char **)valp) = cur->val.strval;
>> +      *((struct tunable_str_t **) valp) = &cur->val.strval;
>>         break;
>>       }
>>       default:
>> diff --git a/elf/dl-tunables.h b/elf/dl-tunables.h
>> index 45c191e021..0e777d7d37 100644
>> --- a/elf/dl-tunables.h
>> +++ b/elf/dl-tunables.h
>> @@ -30,7 +30,11 @@ typedef intmax_t tunable_num_t;
>>   typedef union
>>   {
>>     tunable_num_t numval;
>> -  const char *strval;
>> +  struct tunable_str_t
>> +  {
>> +    const char *str;
>> +    size_t len;
>> +  } strval;
>>   } tunable_val_t;
>>     typedef void (*tunable_callback_t) (tunable_val_t *);
>> diff --git a/elf/tst-tunables.c b/elf/tst-tunables.c
>> index e1ad44f27c..188345b070 100644
>> --- a/elf/tst-tunables.c
>> +++ b/elf/tst-tunables.c
>> @@ -31,7 +31,8 @@ static int restart;
>>     static const struct test_t
>>   {
>> -  const char *env;
>> +  const char *name;
>> +  const char *value;
>>     int32_t expected_malloc_check;
>>     size_t expected_mmap_threshold;
>>     int32_t expected_perturb;
>> @@ -39,12 +40,14 @@ static const struct test_t
>>   {
>>     /* Expected tunable format.  */
>>     {
>> +    "GLIBC_TUNABLES",
>>       "glibc.malloc.check=2",
>>       2,
>>       0,
>>       0,
>>     },
>>     {
>> +    "GLIBC_TUNABLES",
>>       "glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096",
>>       2,
>>       4096,
>> @@ -52,6 +55,7 @@ static const struct test_t
>>     },
>>     /* Empty tunable are ignored.  */
>>     {
>> +    "GLIBC_TUNABLES",
>>       "glibc.malloc.check=2::glibc.malloc.mmap_threshold=4096",
>>       2,
>>       4096,
>> @@ -59,6 +63,7 @@ static const struct test_t
>>     },
>>     /* As well empty values.  */
>>     {
>> +    "GLIBC_TUNABLES",
>>       "glibc.malloc.check=:glibc.malloc.mmap_threshold=4096",
>>       0,
>>       4096,
>> @@ -66,18 +71,21 @@ static const struct test_t
>>     },
>>     /* Tunable are processed from left to right, so last one is the one set.  */
>>     {
>> +    "GLIBC_TUNABLES",
>>       "glibc.malloc.check=1:glibc.malloc.check=2",
>>       2,
>>       0,
>>       0,
>>     },
>>     {
>> +    "GLIBC_TUNABLES",
>>       "glibc.malloc.check=1:glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096",
>>       2,
>>       4096,
>>       0,
>>     },
>>     {
>> +    "GLIBC_TUNABLES",
>>       "glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096:glibc.malloc.check=1",
>>       1,
>>       4096,
>> @@ -85,12 +93,14 @@ static const struct test_t
>>     },
>>     /* 0x800 is larger than tunable maxval (0xff), so the tunable is unchanged.  */
>>     {
>> +    "GLIBC_TUNABLES",
>>       "glibc.malloc.perturb=0x800",
>>       0,
>>       0,
>>       0,
>>     },
>>     {
>> +    "GLIBC_TUNABLES",
>>       "glibc.malloc.perturb=0x55",
>>       0,
>>       0,
>> @@ -98,6 +108,7 @@ static const struct test_t
>>     },
>>     /* Out of range values are just ignored.  */
>>     {
>> +    "GLIBC_TUNABLES",
>>       "glibc.malloc.perturb=0x800:glibc.malloc.mmap_threshold=4096",
>>       0,
>>       4096,
>> @@ -105,24 +116,28 @@ static const struct test_t
>>     },
>>     /* Invalid keys are ignored.  */
>>     {
>> +    "GLIBC_TUNABLES",
>>       ":glibc.malloc.garbage=2:glibc.malloc.check=1",
>>       1,
>>       0,
>>       0,
>>     },
>>     {
>> +    "GLIBC_TUNABLES",
>>       "glibc.malloc.perturb=0x800:not_valid.malloc.check=2:glibc.malloc.mmap_threshold=4096",
>>       0,
>>       4096,
>>       0,
>>     },
>>     {
>> +    "GLIBC_TUNABLES",
>>       "glibc.not_valid.check=2:glibc.malloc.mmap_threshold=4096",
>>       0,
>>       4096,
>>       0,
>>     },
>>     {
>> +    "GLIBC_TUNABLES",
>>       "not_valid.malloc.check=2:glibc.malloc.mmap_threshold=4096",
>>       0,
>>       4096,
>> @@ -130,24 +145,28 @@ static const struct test_t
>>     },
>>     /* Invalid subkeys are ignored.  */
>>     {
>> +    "GLIBC_TUNABLES",
>>       "glibc.malloc.garbage=2:glibc.maoc.mmap_threshold=4096:glibc.malloc.check=2",
>>       2,
>>       0,
>>       0,
>>     },
>>     {
>> +    "GLIBC_TUNABLES",
>>       "glibc.malloc.check=4:glibc.malloc.garbage=2:glibc.maoc.mmap_threshold=4096",
>>       0,
>>       0,
>>       0,
>>     },
>>     {
>> +    "GLIBC_TUNABLES",
>>       "not_valid.malloc.check=2",
>>       0,
>>       0,
>>       0,
>>     },
>>     {
>> +    "GLIBC_TUNABLES",
>>       "glibc.not_valid.check=2",
>>       0,
>>       0,
>> @@ -156,6 +175,7 @@ static const struct test_t
>>     /* An ill-formatted tunable in the for key=key=value will considere the
>>        value as 'key=value' (which can not be parsed as an integer).  */
>>     {
>> +    "GLIBC_TUNABLES",
>>       "glibc.malloc.mmap_threshold=glibc.malloc.mmap_threshold=4096",
>>       0,
>>       0,
>> @@ -163,41 +183,77 @@ static const struct test_t
>>     },
>>     /* Ill-formatted tunables string is not parsed.  */
>>     {
>> +    "GLIBC_TUNABLES",
>>       "glibc.malloc.mmap_threshold=glibc.malloc.mmap_threshold=4096:glibc.malloc.check=2",
>>       0,
>>       0,
>>       0,
>>     },
>>     {
>> +    "GLIBC_TUNABLES",
>>       "glibc.malloc.check=2=2",
>>       0,
>>       0,
>>       0,
>>     },
>>     {
>> +    "GLIBC_TUNABLES",
>>       "glibc.malloc.check=2=2:glibc.malloc.mmap_threshold=4096",
>>       0,
>>       0,
>>       0,
>>     },
>>     {
>> +    "GLIBC_TUNABLES",
>>       "glibc.malloc.check=2=2:glibc.malloc.check=2",
>>       0,
>>       0,
>>       0,
>>     },
>>     {
>> +    "GLIBC_TUNABLES",
>>       "glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096=4096",
>>       0,
>>       0,
>>       0,
>>     },
>>     {
>> +    "GLIBC_TUNABLES",
>>       "glibc.malloc.check=2:glibc.malloc.mmap_threshold=4096=4096",
>>       0,
>>       0,
>>       0,
>>     },
>> +  /* Also check some tunable aliases.  */
>> +  {
>> +    "MALLOC_CHECK_",
>> +    "2",
>> +    2,
>> +    0,
>> +    0,
>> +  },
>> +  {
>> +    "MALLOC_MMAP_THRESHOLD_",
>> +    "4096",
>> +    0,
>> +    4096,
>> +    0,
>> +  },
>> +  {
>> +    "MALLOC_PERTURB_",
>> +    "0x55",
>> +    0,
>> +    0,
>> +    0x55,
>> +  },
>> +  /* 0x800 is larger than tunable maxval (0xff), so the tunable is unchanged.  */
>> +  {
>> +    "MALLOC_PERTURB_",
>> +    "0x800",
>> +    0,
>> +    0,
>> +    0,
>> +  },
>>   };
>>     static int
>> @@ -245,13 +301,17 @@ do_test (int argc, char *argv[])
>>       {
>>         snprintf (nteststr, sizeof nteststr, "%d", i);
>>   -      printf ("[%d] Spawned test for %s\n", i, tests[i].env);
>> -      setenv ("GLIBC_TUNABLES", tests[i].env, 1);
>> +      printf ("[%d] Spawned test for %s=%s\n",
>> +          i,
>> +          tests[i].name,
>> +          tests[i].value);
>> +      setenv (tests[i].name, tests[i].value, 1);
>>         struct support_capture_subprocess result
>>       = support_capture_subprogram (spargv[0], spargv);
>>         support_capture_subprocess_check (&result, "tst-tunables", 0,
>>                       sc_allow_stderr);
>>         support_capture_subprocess_free (&result);
>> +      unsetenv (tests[i].name);
>>       }
>>       return 0;
>> diff --git a/sysdeps/generic/dl-tunables-parse.h b/sysdeps/generic/dl-tunables-parse.h
>> new file mode 100644
>> index 0000000000..5b399f852d
>> --- /dev/null
>> +++ b/sysdeps/generic/dl-tunables-parse.h
>> @@ -0,0 +1,129 @@
>> +/* Helper functions to handle tunable strings.
>> +   Copyright (C) 2023 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/>.  */
>> +
>> +#ifndef _DL_TUNABLES_PARSE_H
>> +#define _DL_TUNABLES_PARSE_H 1
>> +
>> +#include <string.h>
>> +
>> +/* Compare the contents of STRVAL with STR of size LEN.  The STR might not
>> +   be null-terminated.   */
>> +static __always_inline bool
>> +tunable_strcmp (const struct tunable_str_t *strval, const char *str,
>> +        size_t len)
>> +{
>> +  return strval->len == len && memcmp (strval->str, str, len) == 0;
>> +}
>> +#define tunable_strcmp_cte(__tunable, __str) \
>> + tunable_strcmp (&__tunable->strval, __str, sizeof (__str) - 1)
>> +
>> +/*
>> +   Helper functions to iterate over a tunable string composed by multiple
>> +   suboptions separated by commaxi; this is a common pattern for CPU.  Each
>> +   suboptions is return in the form of { address, size } (no null terminated).
>> +   For instance:
>> +
>> +     struct tunable_str_comma_t ts;
>> +     tunable_str_comma_init (&ts, valp);
>> +
>> +     struct tunable_str_t t;
>> +     while (tunable_str_comma_next (&ts, &t))
>> +      {
>> +    _dl_printf ("[%s] %.*s (%d)\n",
>> +            __func__,
>> +            (int) tstr.len,
>> +            tstr.str,
>> +            (int) tstr.len);
>> +
>> +        if (tunable_str_comma_strcmp (&t, opt, opt1_len))
>> +      {
>> +        [...]
>> +      }
>> +    else if (tunable_str_comma_strcmp_cte (&t, "opt2"))
>> +      {
>> +        [...]
>> +      }
>> +      }
>> +*/
>> +
>> +struct tunable_str_comma_state_t
>> +{
>> +  const char *p;
>> +  size_t plen;
>> +  size_t maxplen;
>> +};
>> +
>> +struct tunable_str_comma_t
>> +{
>> +  const char *str;
>> +  size_t len;
>> +  bool disable;
>> +};
>> +
>> +static inline void
>> +tunable_str_comma_init (struct tunable_str_comma_state_t *state,
>> +            tunable_val_t *valp)
>> +{
> 
> Maybe add an assertion here that valp->strval.str is not NULL since all functions assume that?  Or at least a comment somewhere.

An assert won't work because tunable_val_t is an union.  I will add
the comment:

   NB: These function are expected to be called from tunable callback
   functions along with tunable_val_t with string types.

> 
>> +  state->p = valp->strval.str;
>> +  state->plen = 0;
>> +  state->maxplen = valp->strval.len;
>> +}
>> +
>> +static inline bool
>> +tunable_str_comma_next (struct tunable_str_comma_state_t *state,
>> +            struct tunable_str_comma_t *str)
>> +{
>> +  if (*state->p == '\0' || state->plen >= state->maxplen)
>> +    return false;
>> +
>> +  const char *c;
>> +  for (c = state->p; *c != ','; c++, state->plen++)
>> +    if (*c == '\0' || state->plen == state->maxplen)
>> +      break;
>> +
>> +  str->str = state->p;
>> +  str->len = c - state->p;
>> +
>> +  if (str->len > 0)
>> +    {
>> +      str->disable = *str->str == '-';
>> +      if (str->disable)
>> +    {
>> +      str->str = str->str + 1;
>> +      str->len = str->len - 1;
>> +    }
>> +    }
>> +
>> +  state->p = c + 1;
>> +  state->plen++;
>> +
>> +  return true;
>> +}
>> +
>> +/* Compare the contents of T with STR of size LEN.  The STR might not be
>> +   null-terminated.   */
>> +static __always_inline bool
>> +tunable_str_comma_strcmp (const struct tunable_str_comma_t *t, const char *str,
>> +              size_t len)
>> +{
>> +  return t->len == len && memcmp (t->str, str, len) == 0;
>> +}
>> +#define tunable_str_comma_strcmp_cte(__t, __str) \
>> +  tunable_str_comma_strcmp (__t, __str, sizeof (__str) - 1)
>> +
>> +#endif
>> diff --git a/sysdeps/s390/cpu-features.c b/sysdeps/s390/cpu-features.c
>> index 55449ba07f..8b1466fa7b 100644
>> --- a/sysdeps/s390/cpu-features.c
>> +++ b/sysdeps/s390/cpu-features.c
>> @@ -22,6 +22,7 @@
>>   #include <ifunc-memcmp.h>
>>   #include <string.h>
>>   #include <dl-symbol-redir-ifunc.h>
>> +#include <dl-tunables-parse.h>
>>     #define S390_COPY_CPU_FEATURES(SRC_PTR, DEST_PTR)    \
>>     (DEST_PTR)->hwcap = (SRC_PTR)->hwcap;            \
>> @@ -51,33 +52,14 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
>>     struct cpu_features cpu_features_curr;
>>     S390_COPY_CPU_FEATURES (cpu_features, &cpu_features_curr);
>>   -  const char *token = valp->strval;
>> -  do
>> +  struct tunable_str_comma_state_t ts;
>> +  tunable_str_comma_init (&ts, valp);
>> +
>> +  struct tunable_str_comma_t t;
>> +  while (tunable_str_comma_next (&ts, &t))
>>       {
>> -      const char *token_end, *feature;
>> -      bool disable;
>> -      size_t token_len;
>> -      size_t feature_len;
>> -
>> -      /* Find token separator or end of string.  */
>> -      for (token_end = token; *token_end != ','; token_end++)
>> -    if (*token_end == '\0')
>> -      break;
>> -
>> -      /* Determine feature.  */
>> -      token_len = token_end - token;
>> -      if (*token == '-')
>> -    {
>> -      disable = true;
>> -      feature = token + 1;
>> -      feature_len = token_len - 1;
>> -    }
>> -      else
>> -    {
>> -      disable = false;
>> -      feature = token;
>> -      feature_len = token_len;
>> -    }
>> +      if (t.len == 0)
>> +    continue;
>>           /* Handle only the features here which are really used in the
>>        IFUNC-resolvers.  All others are ignored as the values are only used
>> @@ -85,85 +67,65 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
>>         bool reset_features = false;
>>         unsigned long int hwcap_mask = 0UL;
>>         unsigned long long stfle_bits0_mask = 0ULL;
>> +      bool disable = t.disable;
>>   -      if ((*feature == 'z' || *feature == 'a'))
>> +      if (tunable_str_comma_strcmp_cte (&t, "zEC12")
>> +      || tunable_str_comma_strcmp_cte (&t, "arch10"))
>> +    {
>> +      reset_features = true;
>> +      disable = true;
>> +      hwcap_mask = HWCAP_S390_VXRS | HWCAP_S390_VXRS_EXT
>> +        | HWCAP_S390_VXRS_EXT2;
>> +      stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
>> +    }
>> +      else if (tunable_str_comma_strcmp_cte (&t, "z13")
>> +           || tunable_str_comma_strcmp_cte (&t, "arch11"))
>> +    {
>> +      reset_features = true;
>> +      disable = true;
>> +      hwcap_mask = HWCAP_S390_VXRS_EXT | HWCAP_S390_VXRS_EXT2;
>> +      stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
>> +    }
>> +      else if (tunable_str_comma_strcmp_cte (&t, "z14")
>> +           || tunable_str_comma_strcmp_cte (&t, "arch12"))
>> +    {
>> +      reset_features = true;
>> +      disable = true;
>> +      hwcap_mask = HWCAP_S390_VXRS_EXT2;
>> +      stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
>> +    }
>> +      else if (tunable_str_comma_strcmp_cte (&t, "z15")
>> +           || tunable_str_comma_strcmp_cte (&t, "z16")
>> +           || tunable_str_comma_strcmp_cte (&t, "arch13")
>> +           || tunable_str_comma_strcmp_cte (&t, "arch14"))
>>       {
>> -      if ((feature_len == 5 && *feature == 'z'
>> -           && memcmp (feature, "zEC12", 5) == 0)
>> -          || (feature_len == 6 && *feature == 'a'
>> -          && memcmp (feature, "arch10", 6) == 0))
>> -        {
>> -          reset_features = true;
>> -          disable = true;
>> -          hwcap_mask = HWCAP_S390_VXRS | HWCAP_S390_VXRS_EXT
>> -        | HWCAP_S390_VXRS_EXT2;
>> -          stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
>> -        }
>> -      else if ((feature_len == 3 && *feature == 'z'
>> -            && memcmp (feature, "z13", 3) == 0)
>> -           || (feature_len == 6 && *feature == 'a'
>> -               && memcmp (feature, "arch11", 6) == 0))
>> -        {
>> -          reset_features = true;
>> -          disable = true;
>> -          hwcap_mask = HWCAP_S390_VXRS_EXT | HWCAP_S390_VXRS_EXT2;
>> -          stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
>> -        }
>> -      else if ((feature_len == 3 && *feature == 'z'
>> -            && memcmp (feature, "z14", 3) == 0)
>> -           || (feature_len == 6 && *feature == 'a'
>> -               && memcmp (feature, "arch12", 6) == 0))
>> -        {
>> -          reset_features = true;
>> -          disable = true;
>> -          hwcap_mask = HWCAP_S390_VXRS_EXT2;
>> -          stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
>> -        }
>> -      else if ((feature_len == 3 && *feature == 'z'
>> -            && (memcmp (feature, "z15", 3) == 0
>> -            || memcmp (feature, "z16", 3) == 0))
>> -           || (feature_len == 6
>> -               && (memcmp (feature, "arch13", 6) == 0
>> -               || memcmp (feature, "arch14", 6) == 0)))
>> -        {
>> -          /* For z15 or newer we don't have to disable something,
>> -         but we have to reset to the original values.  */
>> -          reset_features = true;
>> -        }
>> +      /* For z15 or newer we don't have to disable something, but we have
>> +         to reset to the original values.  */
>> +      reset_features = true;
>>       }
>> -      else if (*feature == 'H')
>> +      else if (tunable_str_comma_strcmp_cte (&t, "HWCAP_S390_VXRS"))
>>       {
>> -      if (feature_len == 15
>> -          && memcmp (feature, "HWCAP_S390_VXRS", 15) == 0)
>> -        {
>> -          hwcap_mask = HWCAP_S390_VXRS;
>> -          if (disable)
>> -        hwcap_mask |= HWCAP_S390_VXRS_EXT | HWCAP_S390_VXRS_EXT2;
>> -        }
>> -      else if (feature_len == 19
>> -           && memcmp (feature, "HWCAP_S390_VXRS_EXT", 19) == 0)
>> -        {
>> -          hwcap_mask = HWCAP_S390_VXRS_EXT;
>> -          if (disable)
>> -        hwcap_mask |= HWCAP_S390_VXRS_EXT2;
>> -          else
>> -        hwcap_mask |= HWCAP_S390_VXRS;
>> -        }
>> -      else if (feature_len == 20
>> -           && memcmp (feature, "HWCAP_S390_VXRS_EXT2", 20) == 0)
>> -        {
>> -          hwcap_mask = HWCAP_S390_VXRS_EXT2;
>> -          if (!disable)
>> -        hwcap_mask |= HWCAP_S390_VXRS | HWCAP_S390_VXRS_EXT;
>> -        }
>> +      hwcap_mask = HWCAP_S390_VXRS;
>> +      if (t.disable)
>> +        hwcap_mask |= HWCAP_S390_VXRS_EXT | HWCAP_S390_VXRS_EXT2;
>>       }
>> -      else if (*feature == 'S')
>> +      else if (tunable_str_comma_strcmp_cte (&t, "HWCAP_S390_VXRS_EXT"))
>>       {
>> -      if (feature_len == 10
>> -          && memcmp (feature, "STFLE_MIE3", 10) == 0)
>> -        {
>> -          stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
>> -        }
>> +      hwcap_mask = HWCAP_S390_VXRS_EXT;
>> +      if (t.disable)
>> +        hwcap_mask |= HWCAP_S390_VXRS_EXT2;
>> +      else
>> +        hwcap_mask |= HWCAP_S390_VXRS;
>> +    }
>> +      else if (tunable_str_comma_strcmp_cte (&t, "HWCAP_S390_VXRS_EXT2"))
>> +    {
>> +      hwcap_mask = HWCAP_S390_VXRS_EXT2;
>> +      if (!t.disable)
>> +        hwcap_mask |= HWCAP_S390_VXRS | HWCAP_S390_VXRS_EXT;
>> +    }
>> +      else if (tunable_str_comma_strcmp_cte (&t, "STFLE_MIE3"))
>> +    {
> 
> Redundant braces.

Ack.

> 
>> +      stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
>>       }
>>           /* Perform the actions determined above.  */
>> @@ -187,14 +149,7 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
>>         else
>>           cpu_features_curr.stfle_bits[0] |= stfle_bits0_mask;
>>       }
>> -
>> -      /* Jump over current token ... */
>> -      token += token_len;
>> -
>> -      /* ... and skip token separator for next round.  */
>> -      if (*token == ',') token++;
>>       }
>> -  while (*token != '\0');
>>       /* Copy back the features after checking that no unsupported features were
>>        enabled by user.  */
>> diff --git a/sysdeps/unix/sysv/linux/aarch64/cpu-features.c b/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
>> index 233d5b2407..9b76cb89c7 100644
>> --- a/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
>> +++ b/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
>> @@ -16,10 +16,12 @@
>>      License along with the GNU C Library; if not, see
>>      <https://www.gnu.org/licenses/>.  */
>>   +#include <array_length.h>
>>   #include <cpu-features.h>
>>   #include <sys/auxv.h>
>>   #include <elf/dl-hwcaps.h>
>>   #include <sys/prctl.h>
>> +#include <dl-tunables-parse.h>
>>     #define DCZID_DZP_MASK (1 << 4)
>>   #define DCZID_BS_MASK (0xf)
>> @@ -33,28 +35,32 @@
>>   struct cpu_list
>>   {
>>     const char *name;
>> +  size_t len;
>>     uint64_t midr;
>>   };
>>   -static struct cpu_list cpu_list[] = {
>> -      {"falkor",     0x510FC000},
>> -      {"thunderxt88",     0x430F0A10},
>> -      {"thunderx2t99",   0x431F0AF0},
>> -      {"thunderx2t99p1", 0x420F5160},
>> -      {"phecda",     0x680F0000},
>> -      {"ares",         0x411FD0C0},
>> -      {"emag",         0x503F0001},
>> -      {"kunpeng920",      0x481FD010},
>> -      {"a64fx",         0x460F0010},
>> -      {"generic",      0x0}
>> +static const struct cpu_list cpu_list[] =
>> +{
>> +#define CPU_LIST_ENTRY(__str, __num) { __str, sizeof (__str) - 1, __num }
>> +  CPU_LIST_ENTRY ("falkor",         0x510FC000),
>> +  CPU_LIST_ENTRY ("thunderxt88",    0x430F0A10),
>> +  CPU_LIST_ENTRY ("thunderx2t99",   0x431F0AF0),
>> +  CPU_LIST_ENTRY ("thunderx2t99p1", 0x420F5160),
>> +  CPU_LIST_ENTRY ("phecda",         0x680F0000),
>> +  CPU_LIST_ENTRY ("ares",           0x411FD0C0),
>> +  CPU_LIST_ENTRY ("emag",           0x503F0001),
>> +  CPU_LIST_ENTRY ("kunpeng920",     0x481FD010),
>> +  CPU_LIST_ENTRY ("a64fx",          0x460F0010),
>> +  CPU_LIST_ENTRY ("generic",        0x0),
>>   };
>>     static uint64_t
>> -get_midr_from_mcpu (const char *mcpu)
>> +get_midr_from_mcpu (const struct tunable_str_t *mcpu)
>>   {
>> -  for (int i = 0; i < sizeof (cpu_list) / sizeof (struct cpu_list); i++)
>> -    if (strcmp (mcpu, cpu_list[i].name) == 0)
>> +  for (int i = 0; i < array_length (cpu_list); i++) {
>> +    if (tunable_strcmp (mcpu, cpu_list[i].name, cpu_list[i].len))
>>         return cpu_list[i].midr;
>> +  }
> 
> Redundant braces.
> 

Ack.

>>       return UINT64_MAX;
>>   }
>> @@ -65,7 +71,9 @@ init_cpu_features (struct cpu_features *cpu_features)
>>     register uint64_t midr = UINT64_MAX;
>>       /* Get the tunable override.  */
>> -  const char *mcpu = TUNABLE_GET (glibc, cpu, name, const char *, NULL);
>> +  const struct tunable_str_t *mcpu = TUNABLE_GET (glibc, cpu, name,
>> +                          struct tunable_str_t *,
>> +                          NULL);
>>     if (mcpu != NULL)
>>       midr = get_midr_from_mcpu (mcpu);
>>   diff --git a/sysdeps/unix/sysv/linux/powerpc/cpu-features.c b/sysdeps/unix/sysv/linux/powerpc/cpu-features.c
>> index 7c6e20e702..390b3fd11a 100644
>> --- a/sysdeps/unix/sysv/linux/powerpc/cpu-features.c
>> +++ b/sysdeps/unix/sysv/linux/powerpc/cpu-features.c
>> @@ -20,6 +20,7 @@
>>   #include <stdint.h>
>>   #include <cpu-features.h>
>>   #include <elf/dl-tunables.h>
>> +#include <dl-tunables-parse.h>
>>   #include <unistd.h>
>>   #include <string.h>
>>   @@ -43,41 +44,26 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
>>     struct cpu_features *cpu_features = &GLRO(dl_powerpc_cpu_features);
>>     unsigned long int tcbv_hwcap = cpu_features->hwcap;
>>     unsigned long int tcbv_hwcap2 = cpu_features->hwcap2;
>> -  const char *token = valp->strval;
>> -  do
>> +
>> +  struct tunable_str_comma_state_t ts;
>> +  tunable_str_comma_init (&ts, valp);
>> +
>> +  struct tunable_str_comma_t t;
>> +  while (tunable_str_comma_next (&ts, &t))
>>       {
>> -      const char *token_end, *feature;
>> -      bool disable;
>> -      size_t token_len, i, feature_len, offset = 0;
>> -      /* Find token separator or end of string.  */
>> -      for (token_end = token; *token_end != ','; token_end++)
>> -    if (*token_end == '\0')
>> -      break;
>> +      if (t.len == 0)
>> +    continue;
>>   -      /* Determine feature.  */
>> -      token_len = token_end - token;
>> -      if (*token == '-')
>> -    {
>> -      disable = true;
>> -      feature = token + 1;
>> -      feature_len = token_len - 1;
>> -    }
>> -      else
>> -    {
>> -      disable = false;
>> -      feature = token;
>> -      feature_len = token_len;
>> -    }
>> -      for (i = 0; i < array_length (hwcap_tunables); ++i)
>> +      size_t offset = 0;
>> +      for (int i = 0; i < array_length (hwcap_tunables); ++i)
>>       {
>>         const char *hwcap_name = hwcap_names + offset;
>>         size_t hwcap_name_len = strlen (hwcap_name);
>>         /* Check the tunable name on the supported list.  */
>> -      if (hwcap_name_len == feature_len
>> -          && memcmp (feature, hwcap_name, feature_len) == 0)
>> +      if (tunable_str_comma_strcmp (&t, hwcap_name, hwcap_name_len))
>>           {
>>             /* Update the hwcap and hwcap2 bits.  */
>> -          if (disable)
>> +          if (t.disable)
>>           {
>>             /* Id is 1 for hwcap2 tunable.  */
>>             if (hwcap_tunables[i].id)
>> @@ -98,12 +84,7 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
>>           }
>>         offset += hwcap_name_len + 1;
>>       }
>> -    token += token_len;
>> -    /* ... and skip token separator for next round.  */
>> -    if (*token == ',')
>> -      token++;
>>       }
>> -  while (*token != '\0');
>>   }
>>     static inline void
>> diff --git a/sysdeps/unix/sysv/linux/powerpc/tst-hwcap-tunables.c b/sysdeps/unix/sysv/linux/powerpc/tst-hwcap-tunables.c
>> index 2631016a3a..049164f841 100644
>> --- a/sysdeps/unix/sysv/linux/powerpc/tst-hwcap-tunables.c
>> +++ b/sysdeps/unix/sysv/linux/powerpc/tst-hwcap-tunables.c
>> @@ -110,7 +110,11 @@ do_test (int argc, char *argv[])
>>       run_test ("-arch_2_06", "__memcpy_power7");
>>         if (hwcap & PPC_FEATURE_ARCH_2_05)
>>       run_test ("-arch_2_06,-arch_2_05","__memcpy_power6");
>> -      run_test ("-arch_2_06,-arch_2_05,-power5+,-power5,-power4", "__memcpy_power4");
>> +      run_test ("-arch_2_06,-arch_2_05,-power5+,-power5,-power4",
>> +        "__memcpy_power4");
>> +      /* Also run with valid, but empty settings.  */
>> +      run_test (",-,-arch_2_06,-arch_2_05,-power5+,-power5,,-power4,-",
>> +        "__memcpy_power4");
>>       }
>>     else
>>       {
>> diff --git a/sysdeps/x86/Makefile b/sysdeps/x86/Makefile
>> index 917c26f116..a64e5f002a 100644
>> --- a/sysdeps/x86/Makefile
>> +++ b/sysdeps/x86/Makefile
>> @@ -12,7 +12,8 @@ CFLAGS-get-cpuid-feature-leaf.o += $(no-stack-protector)
>>     tests += tst-get-cpu-features tst-get-cpu-features-static \
>>        tst-cpu-features-cpuinfo tst-cpu-features-cpuinfo-static \
>> -     tst-cpu-features-supports tst-cpu-features-supports-static
>> +     tst-cpu-features-supports tst-cpu-features-supports-static \
>> +     tst-hwcap-tunables
>>   tests-static += tst-get-cpu-features-static \
>>           tst-cpu-features-cpuinfo-static \
>>           tst-cpu-features-supports-static
>> @@ -65,6 +66,7 @@ $(objpfx)tst-isa-level-1.out: $(objpfx)tst-isa-level-mod-1-baseline.so \
>>   endif
>>   tst-ifunc-isa-2-ENV = GLIBC_TUNABLES=glibc.cpu.hwcaps=-SSE4_2,-AVX,-AVX2,-AVX512F
>>   tst-ifunc-isa-2-static-ENV = $(tst-ifunc-isa-2-ENV)
>> +tst-hwcap-tunables-ARGS = -- $(host-test-program-cmd)
>>   endif
>>     ifeq ($(subdir),math)
>> diff --git a/sysdeps/x86/cpu-tunables.c b/sysdeps/x86/cpu-tunables.c
>> index 5697885226..0608209768 100644
>> --- a/sysdeps/x86/cpu-tunables.c
>> +++ b/sysdeps/x86/cpu-tunables.c
>> @@ -24,11 +24,12 @@
>>   #include <string.h>
>>   #include <cpu-features.h>
>>   #include <ldsodefs.h>
>> +#include <dl-tunables-parse.h>
>>   #include <dl-symbol-redir-ifunc.h>
>>     #define CHECK_GLIBC_IFUNC_CPU_OFF(f, cpu_features, name, len)        \
>>     _Static_assert (sizeof (#name) - 1 == len, #name " != " #len);    \
>> -  if (memcmp (f, #name, len) == 0)                    \
>> +  if (tunable_str_comma_strcmp_cte (&f, #name))                \
>>       {                                    \
>>         CPU_FEATURE_UNSET (cpu_features, name)                \
>>         break;                                \
>> @@ -38,7 +39,7 @@
>>      which isn't available.  */
>>   #define CHECK_GLIBC_IFUNC_PREFERRED_OFF(f, cpu_features, name, len)    \
>>     _Static_assert (sizeof (#name) - 1 == len, #name " != " #len);    \
>> -  if (memcmp (f, #name, len) == 0)                    \
>> +  if (tunable_str_comma_strcmp_cte (&f, #name) == 0)            \
>>       {                                    \
>>         cpu_features->preferred[index_arch_##name]            \
>>       &= ~bit_arch_##name;                        \
>> @@ -46,12 +47,11 @@
>>       }
>>     /* Enable/disable a preferred feature NAME.  */
>> -#define CHECK_GLIBC_IFUNC_PREFERRED_BOTH(f, cpu_features, name,    \
>> -                      disable, len)            \
>> +#define CHECK_GLIBC_IFUNC_PREFERRED_BOTH(f, cpu_features, name,    len)    \
> 
> Spurious tab.

Ack.

> 
>>     _Static_assert (sizeof (#name) - 1 == len, #name " != " #len);    \
>> -  if (memcmp (f, #name, len) == 0)                    \
>> +  if (tunable_str_comma_strcmp_cte (&f, #name))                \
>>       {                                    \
>> -      if (disable)                            \
>> +      if (f.disable)                            \
>>       cpu_features->preferred[index_arch_##name] &= ~bit_arch_##name;    \
>>         else                                \
>>       cpu_features->preferred[index_arch_##name] |= bit_arch_##name;    \
>> @@ -61,11 +61,11 @@
>>   /* Enable/disable a preferred feature NAME.  Enable a preferred feature
>>      only if the feature NEED is usable.  */
>>   #define CHECK_GLIBC_IFUNC_PREFERRED_NEED_BOTH(f, cpu_features, name,    \
>> -                           need, disable, len)    \
>> +                          need, len)        \
>>     _Static_assert (sizeof (#name) - 1 == len, #name " != " #len);    \
>> -  if (memcmp (f, #name, len) == 0)                    \
>> +  if (tunable_str_comma_strcmp_cte (&f, #name))                \
>>       {                                    \
>> -      if (disable)                            \
>> +      if (f.disable)                            \
>>       cpu_features->preferred[index_arch_##name] &= ~bit_arch_##name;    \
>>         else if (CPU_FEATURE_USABLE_P (cpu_features, need))        \
>>       cpu_features->preferred[index_arch_##name] |= bit_arch_##name;    \
>> @@ -93,38 +93,20 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
>>        NOTE: the IFUNC selection may change over time.  Please check all
>>        multiarch implementations when experimenting.  */
>>   -  const char *p = valp->strval, *c;
>>     struct cpu_features *cpu_features = &GLRO(dl_x86_cpu_features);
>> -  size_t len;
>>   -  do
>> -    {
>> -      const char *n;
>> -      bool disable;
>> -      size_t nl;
>> -
>> -      for (c = p; *c != ','; c++)
>> -    if (*c == '\0')
>> -      break;
>> +  struct tunable_str_comma_state_t ts;
>> +  tunable_str_comma_init (&ts, valp);
>>   -      len = c - p;
>> -      disable = *p == '-';
>> -      if (disable)
>> -    {
>> -      n = p + 1;
>> -      nl = len - 1;
>> -    }
>> -      else
>> -    {
>> -      n = p;
>> -      nl = len;
>> -    }
>> -      switch (nl)
>> +  struct tunable_str_comma_t n;
>> +  while (tunable_str_comma_next (&ts, &n))
>> +    {
>> +      switch (n.len)
>>       {
>>       default:
>>         break;
>>       case 3:
>> -      if (disable)
>> +      if (n.disable)
>>           {
>>             CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, AVX, 3);
>>             CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, CX8, 3);
>> @@ -135,7 +117,7 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
>>           }
>>         break;
>>       case 4:
>> -      if (disable)
>> +      if (n.disable)
>>           {
>>             CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, AVX2, 4);
>>             CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, BMI1, 4);
>> @@ -149,7 +131,7 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
>>           }
>>         break;
>>       case 5:
>> -      if (disable)
>> +      if (n.disable)
>>           {
>>             CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, LZCNT, 5);
>>             CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, MOVBE, 5);
>> @@ -159,12 +141,12 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
>>           }
>>         break;
>>       case 6:
>> -      if (disable)
>> +      if (n.disable)
>>           {
>>             CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, POPCNT, 6);
>>             CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, SSE4_1, 6);
>>             CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, SSE4_2, 6);
>> -          if (memcmp (n, "XSAVEC", 6) == 0)
>> +          if (memcmp (n.str, "XSAVEC", 6) == 0)
> 
> Why does this use the bare memcmp and not the tunables_strcmp?

If I recall correctly, I did tried it and it resulted in worse codegen.  The
tunable_str_comma_strcmp also checks the size, which on the x86 code is not
required and for some reason compiler can not eliminate.

> 
>>           {
>>             /* Update xsave_state_size to XSAVE state size.  */
>>             cpu_features->xsave_state_size
>> @@ -174,14 +156,14 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
>>           }
>>         break;
>>       case 7:
>> -      if (disable)
>> +      if (n.disable)
>>           {
>>             CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, AVX512F, 7);
>>             CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, OSXSAVE, 7);
>>           }
>>         break;
>>       case 8:
>> -      if (disable)
>> +      if (n.disable)
>>           {
>>             CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, AVX512CD, 8);
>>             CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, AVX512BW, 8);
>> @@ -190,86 +172,72 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
>>             CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, AVX512PF, 8);
>>             CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, AVX512VL, 8);
>>           }
>> -      CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features, Slow_BSF,
>> -                        disable, 8);
>> +      CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features, Slow_BSF, 8);
>>         break;
>>       case 11:
>>           {
>> -          CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features,
>> -                        Prefer_ERMS,
>> -                        disable, 11);
>> -          CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features,
>> -                        Prefer_FSRM,
>> -                        disable, 11);
>> +          CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features, Prefer_ERMS,
>> +                        11);
>> +          CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features, Prefer_FSRM,
>> +                        11);
>>             CHECK_GLIBC_IFUNC_PREFERRED_NEED_BOTH (n, cpu_features,
>>                                Slow_SSE4_2,
>>                                SSE4_2,
>> -                             disable, 11);
>> +                             11);
>>           }
>>         break;
>>       case 15:
>>           {
>>             CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features,
>> -                        Fast_Rep_String,
>> -                        disable, 15);
>> +                        Fast_Rep_String, 15);
>>           }
>>         break;
>>       case 16:
>>           {
>>             CHECK_GLIBC_IFUNC_PREFERRED_NEED_BOTH
>> -        (n, cpu_features, Prefer_No_AVX512, AVX512F,
>> -         disable, 16);
>> +        (n, cpu_features, Prefer_No_AVX512, AVX512F, 16);
>>           }
>>         break;
>>       case 18:
>>           {
>>             CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features,
>> -                        Fast_Copy_Backward,
>> -                        disable, 18);
>> +                        Fast_Copy_Backward, 18);
>>           }
>>         break;
>>       case 19:
>>           {
>>             CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features,
>> -                        Fast_Unaligned_Load,
>> -                        disable, 19);
>> +                        Fast_Unaligned_Load, 19);
>>             CHECK_GLIBC_IFUNC_PREFERRED_BOTH (n, cpu_features,
>> -                        Fast_Unaligned_Copy,
>> -                        disable, 19);
>> +                        Fast_Unaligned_Copy, 19);
>>           }
>>         break;
>>       case 20:
>>           {
>>             CHECK_GLIBC_IFUNC_PREFERRED_NEED_BOTH
>> -        (n, cpu_features, Prefer_No_VZEROUPPER, AVX, disable,
>> -         20);
>> +        (n, cpu_features, Prefer_No_VZEROUPPER, AVX, 20);
>>           }
>>         break;
>>       case 23:
>>           {
>>             CHECK_GLIBC_IFUNC_PREFERRED_NEED_BOTH
>> -        (n, cpu_features, AVX_Fast_Unaligned_Load, AVX,
>> -         disable, 23);
>> +        (n, cpu_features, AVX_Fast_Unaligned_Load, AVX, 23);
>>           }
>>         break;
>>       case 24:
>>           {
>>             CHECK_GLIBC_IFUNC_PREFERRED_NEED_BOTH
>> -        (n, cpu_features, MathVec_Prefer_No_AVX512, AVX512F,
>> -         disable, 24);
>> +        (n, cpu_features, MathVec_Prefer_No_AVX512, AVX512F, 24);
>>           }
>>         break;
>>       case 26:
>>           {
>>             CHECK_GLIBC_IFUNC_PREFERRED_NEED_BOTH
>> -        (n, cpu_features, Prefer_PMINUB_for_stringop, SSE2,
>> -         disable, 26);
>> +        (n, cpu_features, Prefer_PMINUB_for_stringop, SSE2, 26);
>>           }
>>         break;
>>       }
>> -      p += len + 1;
>>       }
>> -  while (*c != '\0');
>>   }
>>     #if CET_ENABLED
>> @@ -277,11 +245,11 @@ attribute_hidden
>>   void
>>   TUNABLE_CALLBACK (set_x86_ibt) (tunable_val_t *valp)
>>   {
>> -  if (memcmp (valp->strval, "on", sizeof ("on")) == 0)
>> +  if (tunable_strcmp_cte (valp, "on"))
>>       GL(dl_x86_feature_control).ibt = cet_always_on;
>> -  else if (memcmp (valp->strval, "off", sizeof ("off")) == 0)
>> +  else if (tunable_strcmp_cte (valp, "off"))
>>       GL(dl_x86_feature_control).ibt = cet_always_off;
>> -  else if (memcmp (valp->strval, "permissive", sizeof ("permissive")) == 0)
>> +  else if (tunable_strcmp_cte (valp, "permissive"))
>>       GL(dl_x86_feature_control).ibt = cet_permissive;
>>   }
>>   @@ -289,11 +257,11 @@ attribute_hidden
>>   void
>>   TUNABLE_CALLBACK (set_x86_shstk) (tunable_val_t *valp)
>>   {
>> -  if (memcmp (valp->strval, "on", sizeof ("on")) == 0)
>> +  if (tunable_strcmp_cte (valp, "on"))
>>       GL(dl_x86_feature_control).shstk = cet_always_on;
>> -  else if (memcmp (valp->strval, "off", sizeof ("off")) == 0)
>> +  else if (tunable_strcmp_cte (valp, "off"))
>>       GL(dl_x86_feature_control).shstk = cet_always_off;
>> -  else if (memcmp (valp->strval, "permissive", sizeof ("permissive")) == 0)
>> +  else if (tunable_strcmp_cte (valp, "permissive"))
>>       GL(dl_x86_feature_control).shstk = cet_permissive;
>>   }
>>   #endif
>> diff --git a/sysdeps/x86/tst-hwcap-tunables.c b/sysdeps/x86/tst-hwcap-tunables.c
>> new file mode 100644
>> index 0000000000..0d0a45fa93
>> --- /dev/null
>> +++ b/sysdeps/x86/tst-hwcap-tunables.c
>> @@ -0,0 +1,148 @@
>> +/* Tests for powerpc GLIBC_TUNABLES=glibc.cpu.hwcaps filter.
> 
> s/powerpc/x86/

Ack.

> 
>> +   Copyright (C) 2023 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
>> +   <http://www.gnu.org/licenses/>.  */
>> +
>> +#include <array_length.h>
>> +#include <getopt.h>
>> +#include <ifunc-impl-list.h>
>> +#include <spawn.h>
>> +#include <stdio.h>
>> +#include <stdlib.h>
>> +#include <string.h>
>> +#include <intprops.h>
>> +#include <support/check.h>
>> +#include <support/support.h>
>> +#include <support/xunistd.h>
>> +#include <support/capture_subprocess.h>
>> +
>> +/* Nonzero if the program gets called via `exec'.  */
>> +#define CMDLINE_OPTIONS \
>> +  { "restart", no_argument, &restart, 1 },
>> +static int restart;
>> +
>> +/* Disable everything.  */
>> +static const char *test_1[] =
>> +{
>> +  "__memcpy_avx512_no_vzeroupper",
>> +  "__memcpy_avx512_unaligned",
>> +  "__memcpy_avx512_unaligned_erms",
>> +  "__memcpy_evex_unaligned",
>> +  "__memcpy_evex_unaligned_erms",
>> +  "__memcpy_avx_unaligned",
>> +  "__memcpy_avx_unaligned_erms",
>> +  "__memcpy_avx_unaligned_rtm",
>> +  "__memcpy_avx_unaligned_erms_rtm",
>> +  "__memcpy_ssse3",
>> +};
>> +
>> +static const struct test_t
>> +{
>> +  const char *env;
>> +  const char *const *funcs;
>> +  size_t nfuncs;
>> +} tests[] =
>> +{
>> +  {
>> +    /* Disable everything.  */
>> +    "-Prefer_ERMS,-Prefer_FSRM,-AVX,-AVX2,-AVX_Usable,-AVX2_Usable,"
>> +    "-AVX512F_Usable,-SSE4_1,-SSE4_2,-SSSE3,-Fast_Unaligned_Load,-ERMS,"
>> +    "-AVX_Fast_Unaligned_Load",
>> +    test_1,
>> +    array_length (test_1)
>> +  },
>> +  {
>> +    /* Same as before, but with some empty suboptions.  */
>> +    ",-,-Prefer_ERMS,-Prefer_FSRM,-AVX,-AVX2,-AVX_Usable,-AVX2_Usable,"
>> +    "-AVX512F_Usable,-SSE4_1,-SSE4_2,,-SSSE3,-Fast_Unaligned_Load,,-,-ERMS,"
>> +    "-AVX_Fast_Unaligned_Load,-,",
>> +    test_1,
>> +    array_length (test_1)
>> +  }
>> +};
>> +
>> +/* Called on process re-execution.  */
>> +_Noreturn static void
>> +handle_restart (int ntest)
>> +{
>> +  struct libc_ifunc_impl impls[32];
>> +  int cnt = __libc_ifunc_impl_list ("memcpy", impls, array_length (impls));
>> +  if (cnt == 0)
>> +    _exit (EXIT_SUCCESS);
>> +  TEST_VERIFY_EXIT (cnt >= 1);
>> +  for (int i = 0; i < cnt; i++)
>> +    {
>> +      for (int f = 0; f < tests[ntest].nfuncs; f++)
>> +    {
>> +      if (strcmp (impls[i].name, tests[ntest].funcs[f]) == 0)
>> +        TEST_COMPARE (impls[i].usable, false);
>> +    }
>> +    }
>> +
>> +  _exit (EXIT_SUCCESS);
>> +}
>> +
>> +static int
>> +do_test (int argc, char *argv[])
>> +{
>> +  /* We must have either:
>> +     - One our fource parameters left if called initially:
>> +       + path to ld.so         optional
>> +       + "--library-path"      optional
>> +       + the library path      optional
>> +       + the application name
>> +       + the test to check  */
>> +
>> +  TEST_VERIFY_EXIT (argc == 2 || argc == 5);
>> +
>> +  if (restart)
>> +    handle_restart (atoi (argv[1]));
>> +
>> +  char nteststr[INT_BUFSIZE_BOUND (int)];
>> +
>> +  char *spargv[10];
>> +  {
>> +    int i = 0;
>> +    for (; i < argc - 1; i++)
>> +      spargv[i] = argv[i + 1];
>> +    spargv[i++] = (char *) "--direct";
>> +    spargv[i++] = (char *) "--restart";
>> +    spargv[i++] = nteststr;
>> +    spargv[i] = NULL;
>> +  }
>> +
>> +  for (int i = 0; i < array_length (tests); i++)
>> +    {
>> +      snprintf (nteststr, sizeof nteststr, "%d", i);
>> +
>> +      printf ("[%d] Spawned test for %s\n", i, tests[i].env);
>> +      char *tunable = xasprintf ("glibc.cpu.hwcaps=%s", tests[i].env);
>> +      setenv ("GLIBC_TUNABLES", tunable, 1);
>> +
>> +      struct support_capture_subprocess result
>> +    = support_capture_subprogram (spargv[0], spargv);
>> +      support_capture_subprocess_check (&result, "tst-tunables", 0,
>> +                    sc_allow_stderr);
>> +      support_capture_subprocess_free (&result);
>> +
>> +      free (tunable);
>> +    }
>> +
>> +  return 0;
>> +}
>> +
>> +#define TEST_FUNCTION_ARGV do_test
>> +#include <support/test-driver.c>

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 14/19] elf: Ignore loader debug env vars for setuid
  2023-11-20 22:57   ` Siddhesh Poyarekar
@ 2023-11-21 18:24     ` Adhemerval Zanella Netto
  0 siblings, 0 replies; 40+ messages in thread
From: Adhemerval Zanella Netto @ 2023-11-21 18:24 UTC (permalink / raw)
  To: Siddhesh Poyarekar, libc-alpha



On 20/11/23 19:57, Siddhesh Poyarekar wrote:
> On 2023-11-06 15:25, Adhemerval Zanella wrote:
>> Loader already ignores LD_DEBUG, LD_DEBUG_OUTPUT, and
>> LD_TRACE_LOADED_OBJECTS. Both LD_WARN and LD_VERBOSE are similar to
>> LD_DEBUG, in the sense they enable additional checks and debug
>> information, so it makes sense to disable them.
>>
>> Checked on x86_64-linux-gnu.
>> ---
> 
> Maybe also put it in unsecvars.h?

Ack.

> 
>>   elf/rtld.c           | 22 ++++++++++++++--------
>>   elf/tst-env-setuid.c |  2 ++
>>   2 files changed, 16 insertions(+), 8 deletions(-)
>>
>> diff --git a/elf/rtld.c b/elf/rtld.c
>> index a09cf2a9df..e7f90d37e7 100644
>> --- a/elf/rtld.c
>> +++ b/elf/rtld.c
>> @@ -2552,13 +2552,15 @@ process_envvars (struct dl_main_state *state)
>>       {
>>       case 4:
>>         /* Warning level, verbose or not.  */
>> -      if (memcmp (envline, "WARN", 4) == 0)
>> +      if (!__libc_enable_secure
>> +          && memcmp (envline, "WARN", 4) == 0)
>>           GLRO(dl_verbose) = envline[5] != '\0';
>>         break;
>>         case 5:
>>         /* Debugging of the dynamic linker?  */
>> -      if (memcmp (envline, "DEBUG", 5) == 0)
>> +      if (!__libc_enable_secure
>> +          && memcmp (envline, "DEBUG", 5) == 0)
>>           {
>>             process_dl_debug (state, &envline[6]);
>>             break;
>> @@ -2569,7 +2571,8 @@ process_envvars (struct dl_main_state *state)
>>         case 7:
>>         /* Print information about versions.  */
>> -      if (memcmp (envline, "VERBOSE", 7) == 0)
>> +      if (!__libc_enable_secure
>> +          && memcmp (envline, "VERBOSE", 7) == 0)
>>           {
>>             state->version_info = envline[8] != '\0';
>>             break;
>> @@ -2625,7 +2628,8 @@ process_envvars (struct dl_main_state *state)
>>           }
>>           /* Where to place the profiling data file.  */
>> -      if (memcmp (envline, "DEBUG_OUTPUT", 12) == 0)
>> +      if (!__libc_enable_secure
>> +          && memcmp (envline, "DEBUG_OUTPUT", 12) == 0)
>>           {
>>             debug_output = &envline[13];
>>             break;
>> @@ -2646,7 +2650,8 @@ process_envvars (struct dl_main_state *state)
>>         case 20:
>>         /* The mode of the dynamic linker can be set.  */
>> -      if (memcmp (envline, "TRACE_LOADED_OBJECTS", 20) == 0)
>> +      if (!__libc_enable_secure
>> +          && memcmp (envline, "TRACE_LOADED_OBJECTS", 20) == 0)
>>           {
>>             state->mode = rtld_mode_trace;
>>             state->mode_trace_program
>> @@ -2668,9 +2673,10 @@ process_envvars (struct dl_main_state *state)
>>       }
>>         while (*nextp != '\0');
>>   -      GLRO(dl_debug_mask) = 0;
>> -
>> -      if (state->mode != rtld_mode_normal)
>> +      if (GLRO(dl_debug_mask) != 0
>> +      || GLRO(dl_verbose) != 0
>> +      || state->mode != rtld_mode_normal
>> +      || state->version_info)
>>       _exit (5);
>>       }
>>     /* If we have to run the dynamic linker in debugging mode and the
>> diff --git a/elf/tst-env-setuid.c b/elf/tst-env-setuid.c
>> index 76b8e1fb45..dcf213a4cd 100644
>> --- a/elf/tst-env-setuid.c
>> +++ b/elf/tst-env-setuid.c
>> @@ -59,6 +59,8 @@ static const struct envvar_t filtered_envvars[] =
>>     { "MALLOC_TRACE",            FILTERED_VALUE },
>>     { "MALLOC_TRIM_THRESHOLD_",  FILTERED_VALUE },
>>     { "RES_OPTIONS",             FILTERED_VALUE },
>> +  { "LD_DEBUG",                "all" },
>> +  { "LD_DEBUG_OUTPUT",         "/tmp/some-file" },
>>   };
>>     static const struct envvar_t unfiltered_envvars[] =

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 19/19] elf: Refactor process_envvars
  2023-11-20 23:09   ` Siddhesh Poyarekar
@ 2023-11-21 19:00     ` Adhemerval Zanella Netto
  0 siblings, 0 replies; 40+ messages in thread
From: Adhemerval Zanella Netto @ 2023-11-21 19:00 UTC (permalink / raw)
  To: Siddhesh Poyarekar, libc-alpha



On 20/11/23 20:09, Siddhesh Poyarekar wrote:
> 
> 
> On 2023-11-06 15:25, Adhemerval Zanella wrote:
>> It splits between process_envvars_secure and process_envvars_default,
>> with the former used to process arguments for __libc_enable_secure.
>> It does not have any semantic change, just simplify the code so there
>> is no need to handle __libc_enable_secure on each len switch.
>>
>> Checked on x86_64-linux-gnu and aarch64-linux-gnu.
>> ---
>>   elf/rtld.c | 132 ++++++++++++++++++++++++++++++++++-------------------
>>   1 file changed, 84 insertions(+), 48 deletions(-)
>>
>> diff --git a/elf/rtld.c b/elf/rtld.c
>> index cfba30eba0..95dcd32185 100644
>> --- a/elf/rtld.c
>> +++ b/elf/rtld.c
>> @@ -2527,7 +2527,67 @@ a filename can be specified using the LD_DEBUG_OUTPUT environment variable.\n");
>>   }
>>   \f
>>   static void
>> -process_envvars (struct dl_main_state *state)
>> +process_envvars_secure (struct dl_main_state *state)
>> +{
>> +  char **runp = _environ;
>> +  char *envline;
>> +
>> +  while ((envline = _dl_next_ld_env_entry (&runp)) != NULL)
>> +    {
>> +      size_t len = 0;
>> +
>> +      while (envline[len] != '\0' && envline[len] != '=')
>> +    ++len;
>> +
>> +      if (envline[len] != '=')
>> +    /* This is a "LD_" variable at the end of the string without
>> +       a '=' character.  Ignore it since otherwise we will access
>> +       invalid memory below.  */
>> +    continue;
>> +
>> +      switch (len)
>> +    {
>> +    case 5:
>> +      /* For __libc_enable_secure mode, audit pathnames containing slashes
>> +         are ignored.  Also, shared audit objects are only loaded only from
>> +         the standard search directories and only if they have set-user-ID
>> +         mode bit enabled.  */
>> +      if (memcmp (envline, "AUDIT", 5) == 0)
>> +        audit_list_add_string (&state->audit_list, &envline[6]);
>> +      break;
>> +
>> +    case 7:
>> +      /* For __libc_enable_secure mode, preload pathnames containing slashes
>> +         are ignored.  Also, shared objects are only preloaded from the
>> +         standard search directories and only if they have set-user-ID mode
>> +         bit enabled.  */
>> +      if (memcmp (envline, "PRELOAD", 7) == 0)
>> +        state->preloadlist = &envline[8];
>> +      break;
>> +    }
>> +    }
> 
> We need to talk about whether we should get rid of these too in the context of setuid programs, but that's a separate discussion.  LGTM.

Indeed, it is not clear to me the utility of LD_AUDIT and LD_PRELOAD for setuid
binarie.  It requires some specific setup (library in default search list, with
setuid bit set), LD_AUDIT is essentially a debug setup (although some setups
really stretch its original design), and LD_PRELOADS might change the setuid
semantic depending of which library is used.

> 
> Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
> 
>> +
>> +  /* Extra security for SUID binaries.  Remove all dangerous environment
>> +     variables.  */
>> +  const char *nextp = UNSECURE_ENVVARS;
>> +  do
>> +    {
>> +      unsetenv (nextp);
>> +      nextp = strchr (nextp, '\0') + 1;
>> +    }
>> +  while (*nextp != '\0');
>> +
>> +  if (GLRO(dl_debug_mask) != 0
>> +      || GLRO(dl_verbose) != 0
>> +      || GLRO(dl_lazy) != 1
>> +      || GLRO(dl_bind_not) != 0
>> +      || state->mode != rtld_mode_normal
>> +      || state->version_info)
>> +    _exit (5);
>> +}
>> +
>> +static void
>> +process_envvars_default (struct dl_main_state *state)
>>   {
>>     char **runp = _environ;
>>     char *envline;
> 
> I wondered if we should add an assert here on __libc_enable_secure ==0, but there is only one caller, so maybe we can get away without it.
> 
>> @@ -2550,15 +2610,13 @@ process_envvars (struct dl_main_state *state)
>>       {
>>       case 4:
>>         /* Warning level, verbose or not.  */
>> -      if (!__libc_enable_secure
>> -          && memcmp (envline, "WARN", 4) == 0)
>> +      if (memcmp (envline, "WARN", 4) == 0)
>>           GLRO(dl_verbose) = envline[5] != '\0';
>>         break;
>>         case 5:
>>         /* Debugging of the dynamic linker?  */
>> -      if (!__libc_enable_secure
>> -          && memcmp (envline, "DEBUG", 5) == 0)
>> +      if (memcmp (envline, "DEBUG", 5) == 0)
>>           {
>>             process_dl_debug (state, &envline[6]);
>>             break;
>> @@ -2573,8 +2631,7 @@ process_envvars (struct dl_main_state *state)
>>         case 7:
>>         /* Print information about versions.  */
>> -      if (!__libc_enable_secure
>> -          && memcmp (envline, "VERBOSE", 7) == 0)
>> +      if (memcmp (envline, "VERBOSE", 7) == 0)
>>           {
>>             state->version_info = envline[8] != '\0';
>>             break;
>> @@ -2591,43 +2648,37 @@ process_envvars (struct dl_main_state *state)
>>           }
>>           /* Which shared object shall be profiled.  */
>> -      if (!__libc_enable_secure
>> -          && memcmp (envline, "PROFILE", 7) == 0 && envline[8] != '\0')
>> +      if (memcmp (envline, "PROFILE", 7) == 0 && envline[8] != '\0')
>>           GLRO(dl_profile) = &envline[8];
>>         break;
>>         case 8:
>>         /* Do we bind early?  */
>> -      if (!__libc_enable_secure
>> -          && memcmp (envline, "BIND_NOW", 8) == 0)
>> +      if (memcmp (envline, "BIND_NOW", 8) == 0)
>>           {
>>             GLRO(dl_lazy) = envline[9] == '\0';
>>             break;
>>           }
>> -      if (! __libc_enable_secure
>> -          && memcmp (envline, "BIND_NOT", 8) == 0)
>> +      if (memcmp (envline, "BIND_NOT", 8) == 0)
>>           GLRO(dl_bind_not) = envline[9] != '\0';
>>         break;
>>         case 9:
>>         /* Test whether we want to see the content of the auxiliary
>>            array passed up from the kernel.  */
>> -      if (!__libc_enable_secure
>> -          && memcmp (envline, "SHOW_AUXV", 9) == 0)
>> +      if (memcmp (envline, "SHOW_AUXV", 9) == 0)
>>           _dl_show_auxv ();
>>         break;
>>         case 11:
>>         /* Path where the binary is found.  */
>> -      if (!__libc_enable_secure
>> -          && memcmp (envline, "ORIGIN_PATH", 11) == 0)
>> +      if (memcmp (envline, "ORIGIN_PATH", 11) == 0)
>>           GLRO(dl_origin_path) = &envline[12];
>>         break;
>>         case 12:
>>         /* The library search path.  */
>> -      if (!__libc_enable_secure
>> -          && memcmp (envline, "LIBRARY_PATH", 12) == 0)
>> +      if (memcmp (envline, "LIBRARY_PATH", 12) == 0)
>>           {
>>             state->library_path = &envline[13];
>>             state->library_path_source = "LD_LIBRARY_PATH";
>> @@ -2635,30 +2686,26 @@ process_envvars (struct dl_main_state *state)
>>           }
>>           /* Where to place the profiling data file.  */
>> -      if (!__libc_enable_secure
>> -          && memcmp (envline, "DEBUG_OUTPUT", 12) == 0)
>> +      if (memcmp (envline, "DEBUG_OUTPUT", 12) == 0)
>>           {
>>             debug_output = &envline[13];
>>             break;
>>           }
>>   -      if (!__libc_enable_secure
>> -          && memcmp (envline, "DYNAMIC_WEAK", 12) == 0)
>> +      if (memcmp (envline, "DYNAMIC_WEAK", 12) == 0)
>>           GLRO(dl_dynamic_weak) = 1;
>>         break;
>>         case 14:
>>         /* Where to place the profiling data file.  */
>> -      if (!__libc_enable_secure
>> -          && memcmp (envline, "PROFILE_OUTPUT", 14) == 0
>> +      if (memcmp (envline, "PROFILE_OUTPUT", 14) == 0
>>             && envline[15] != '\0')
>>           GLRO(dl_profile_output) = &envline[15];
>>         break;
>>         case 20:
>>         /* The mode of the dynamic linker can be set.  */
>> -      if (!__libc_enable_secure
>> -          && memcmp (envline, "TRACE_LOADED_OBJECTS", 20) == 0)
>> +      if (memcmp (envline, "TRACE_LOADED_OBJECTS", 20) == 0)
>>           {
>>             state->mode = rtld_mode_trace;
>>             state->mode_trace_program
>> @@ -2668,30 +2715,10 @@ process_envvars (struct dl_main_state *state)
>>       }
>>       }
>>   -  /* Extra security for SUID binaries.  Remove all dangerous environment
>> -     variables.  */
>> -  if (__glibc_unlikely (__libc_enable_secure))
>> -    {
>> -      const char *nextp = UNSECURE_ENVVARS;
>> -      do
>> -    {
>> -      unsetenv (nextp);
>> -      nextp = strchr (nextp, '\0') + 1;
>> -    }
>> -      while (*nextp != '\0');
>> -
>> -      if (GLRO(dl_debug_mask) != 0
>> -      || GLRO(dl_verbose) != 0
>> -      || GLRO(dl_lazy) != 1
>> -      || GLRO(dl_bind_not) != 0
>> -      || state->mode != rtld_mode_normal
>> -      || state->version_info)
>> -    _exit (5);
>> -    }
>>     /* 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 (GLRO(dl_debug_mask) != 0 && debug_output != NULL)
>> +  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);
>> @@ -2710,6 +2737,15 @@ process_envvars (struct dl_main_state *state)
>>       }
>>   }
>>   +static void
>> +process_envvars (struct dl_main_state *state)
>> +{
>> +  if (__glibc_unlikely (__libc_enable_secure))
>> +    process_envvars_secure (state);
>> +  else
>> +    process_envvars_default (state);
>> +}
>> +
>>   #if HP_TIMING_INLINE
>>   static void
>>   print_statistics_item (const char *title, hp_timing_t time,

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 00/19] Improve loader environment variable handling
  2023-11-20 23:12 ` [PATCH v3 00/19] Improve loader environment variable handling Siddhesh Poyarekar
@ 2023-11-21 19:37   ` Adhemerval Zanella Netto
  0 siblings, 0 replies; 40+ messages in thread
From: Adhemerval Zanella Netto @ 2023-11-21 19:37 UTC (permalink / raw)
  To: Siddhesh Poyarekar, libc-alpha



On 20/11/23 20:12, Siddhesh Poyarekar wrote:
> 
> 
> On 2023-11-06 15:25, Adhemerval Zanella wrote:
>> The recent CVE-2023-4911 fix [1] and tunable change to SXID_ERASE
>> discussion [2] brought some issues with the current environment handling
>> by the loader. Besides the bugs in tuning parsing, some other questions
>> are:
> 
> Overall I think 1-10 are ready to be committed since they handle a full block of work, i.e. tunables validation.  I know 11 kinda belongs in that block, but it would be nice to reduce this set to 10 from 19 for the next version :)
> 
> Also maybe push the independent patches in 11-19 that have R-b already.

Alright, I will install the approved patches and send a newer version with
the changes requested.

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 11/19] elf: Do not duplicate the GLIBC_TUNABLES string
  2023-11-21 18:12     ` Adhemerval Zanella Netto
@ 2023-11-22 11:39       ` Adhemerval Zanella Netto
  2023-11-22 12:23       ` Siddhesh Poyarekar
  1 sibling, 0 replies; 40+ messages in thread
From: Adhemerval Zanella Netto @ 2023-11-22 11:39 UTC (permalink / raw)
  To: Siddhesh Poyarekar, libc-alpha

>>> +
>>> +static inline void
>>> +tunable_str_comma_init (struct tunable_str_comma_state_t *state,
>>> +            tunable_val_t *valp)
>>> +{
>>
>> Maybe add an assertion here that valp->strval.str is not NULL since all functions assume that?  Or at least a comment somewhere.
> 
> An assert won't work because tunable_val_t is an union.  I will add
> the comment:
> 
>    NB: These function are expected to be called from tunable callback
>    functions along with tunable_val_t with string types.
> 

On a second though, assert would work here. 

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 11/19] elf: Do not duplicate the GLIBC_TUNABLES string
  2023-11-21 18:12     ` Adhemerval Zanella Netto
  2023-11-22 11:39       ` Adhemerval Zanella Netto
@ 2023-11-22 12:23       ` Siddhesh Poyarekar
  2023-11-22 13:03         ` Adhemerval Zanella Netto
  1 sibling, 1 reply; 40+ messages in thread
From: Siddhesh Poyarekar @ 2023-11-22 12:23 UTC (permalink / raw)
  To: Adhemerval Zanella Netto, libc-alpha

On 2023-11-21 13:12, Adhemerval Zanella Netto wrote:
>>> -tunable_initialize (tunable_t *cur, const char *strval)
>>> +tunable_initialize (tunable_t *cur, const char *strval, size_t len)
>>>    {
>>> -  tunable_val_t val;
>>> +  tunable_val_t val = { 0 };
>>>        if (cur->type.type_code != TUNABLE_TYPE_STRING)
>>>        val.numval = (tunable_num_t) _dl_strtoul (strval, NULL);
>>
>> There's an implicit assumption that strval is NULL terminated for numeric values.  Is that safe?  Maybe all you need to do here is copy strval into a static buffer of size 21 (basically size of the string representing -1ULL) and pass that to _dl_strtoul.
> 
> Afaiu the environment variable will always be NULL terminated (although some
> might overlap).  This is due how the kernel will layout the argv/envp on
> process creation at sys_execve:

Sure, but the strval here should not be the entire envvar, just the 
value portion of it.  Granted that _dl_strtoul will bail out on the 
first non-numeric character, so maybe it's not

>>> -      if (disable)
>>> +      if (n.disable)
>>>            {
>>>              CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, POPCNT, 6);
>>>              CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, SSE4_1, 6);
>>>              CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, SSE4_2, 6);
>>> -          if (memcmp (n, "XSAVEC", 6) == 0)
>>> +          if (memcmp (n.str, "XSAVEC", 6) == 0)
>>
>> Why does this use the bare memcmp and not the tunables_strcmp?
> 
> If I recall correctly, I did tried it and it resulted in worse codegen.  The
> tunable_str_comma_strcmp also checks the size, which on the x86 code is not
> required and for some reason compiler can not eliminate.

Why isn't it required? Couldn't n.str be smaller than 6 chars?

Thanks,
Sid

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 11/19] elf: Do not duplicate the GLIBC_TUNABLES string
  2023-11-22 12:23       ` Siddhesh Poyarekar
@ 2023-11-22 13:03         ` Adhemerval Zanella Netto
  2023-11-22 13:24           ` Siddhesh Poyarekar
  0 siblings, 1 reply; 40+ messages in thread
From: Adhemerval Zanella Netto @ 2023-11-22 13:03 UTC (permalink / raw)
  To: Siddhesh Poyarekar, libc-alpha



On 22/11/23 09:23, Siddhesh Poyarekar wrote:
> On 2023-11-21 13:12, Adhemerval Zanella Netto wrote:
>>>> -tunable_initialize (tunable_t *cur, const char *strval)
>>>> +tunable_initialize (tunable_t *cur, const char *strval, size_t len)
>>>>    {
>>>> -  tunable_val_t val;
>>>> +  tunable_val_t val = { 0 };
>>>>        if (cur->type.type_code != TUNABLE_TYPE_STRING)
>>>>        val.numval = (tunable_num_t) _dl_strtoul (strval, NULL);
>>>
>>> There's an implicit assumption that strval is NULL terminated for numeric values.  Is that safe?  Maybe all you need to do here is copy strval into a static buffer of size 21 (basically size of the string representing -1ULL) and pass that to _dl_strtoul.
>>
>> Afaiu the environment variable will always be NULL terminated (although some
>> might overlap).  This is due how the kernel will layout the argv/envp on
>> process creation at sys_execve:
> 
> Sure, but the strval here should not be the entire envvar, just the value portion of it.  Granted that _dl_strtoul will bail out on the first non-numeric character, so maybe it's not

For the GLIBC_TUNABLES itself, parse_tunables will pass the correct length
for each tunable.  For the alias case, get_next_env will pass the value
length (which I fixed on v4 btw). So I think there is no need to copy the
value on a temporary value, parsing can be done in-place since kernel ensures
the string will be null-terminated.

> 
>>>> -      if (disable)
>>>> +      if (n.disable)
>>>>            {
>>>>              CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, POPCNT, 6);
>>>>              CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, SSE4_1, 6);
>>>>              CHECK_GLIBC_IFUNC_CPU_OFF (n, cpu_features, SSE4_2, 6);
>>>> -          if (memcmp (n, "XSAVEC", 6) == 0)
>>>> +          if (memcmp (n.str, "XSAVEC", 6) == 0)
>>>
>>> Why does this use the bare memcmp and not the tunables_strcmp?
>>
>> If I recall correctly, I did tried it and it resulted in worse codegen.  The
>> tunable_str_comma_strcmp also checks the size, which on the x86 code is not
>> required and for some reason compiler can not eliminate.
> 
> Why isn't it required? Couldn't n.str be smaller than 6 chars?

No because on the start of the loop the switch checks for 'n.len'.

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 11/19] elf: Do not duplicate the GLIBC_TUNABLES string
  2023-11-22 13:03         ` Adhemerval Zanella Netto
@ 2023-11-22 13:24           ` Siddhesh Poyarekar
  2023-11-22 14:13             ` Adhemerval Zanella Netto
  0 siblings, 1 reply; 40+ messages in thread
From: Siddhesh Poyarekar @ 2023-11-22 13:24 UTC (permalink / raw)
  To: Adhemerval Zanella Netto, libc-alpha

On 2023-11-22 08:03, Adhemerval Zanella Netto wrote:
> 
> 
> On 22/11/23 09:23, Siddhesh Poyarekar wrote:
>> On 2023-11-21 13:12, Adhemerval Zanella Netto wrote:
>>>>> -tunable_initialize (tunable_t *cur, const char *strval)
>>>>> +tunable_initialize (tunable_t *cur, const char *strval, size_t len)
>>>>>     {
>>>>> -  tunable_val_t val;
>>>>> +  tunable_val_t val = { 0 };
>>>>>         if (cur->type.type_code != TUNABLE_TYPE_STRING)
>>>>>         val.numval = (tunable_num_t) _dl_strtoul (strval, NULL);
>>>>
>>>> There's an implicit assumption that strval is NULL terminated for numeric values.  Is that safe?  Maybe all you need to do here is copy strval into a static buffer of size 21 (basically size of the string representing -1ULL) and pass that to _dl_strtoul.
>>>
>>> Afaiu the environment variable will always be NULL terminated (although some
>>> might overlap).  This is due how the kernel will layout the argv/envp on
>>> process creation at sys_execve:
>>
>> Sure, but the strval here should not be the entire envvar, just the value portion of it.  Granted that _dl_strtoul will bail out on the first non-numeric character, so maybe it's not
> 
> For the GLIBC_TUNABLES itself, parse_tunables will pass the correct length
> for each tunable.  For the alias case, get_next_env will pass the value
> length (which I fixed on v4 btw). So I think there is no need to copy the
> value on a temporary value, parsing can be done in-place since kernel ensures
> the string will be null-terminated.

Yes, but the length is not passed into _dl_strtoul, which will then 
parse until it finds a non-numeric character.  That's probably not an 
issue for a properly validated tunable string (and your patch to bail 
out in case of invalid tunable strings ensures that), but at the minimum 
there should be a comment stating that assumption for future you, or me.

Thanks,
Sid

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 11/19] elf: Do not duplicate the GLIBC_TUNABLES string
  2023-11-22 13:24           ` Siddhesh Poyarekar
@ 2023-11-22 14:13             ` Adhemerval Zanella Netto
  0 siblings, 0 replies; 40+ messages in thread
From: Adhemerval Zanella Netto @ 2023-11-22 14:13 UTC (permalink / raw)
  To: Siddhesh Poyarekar, libc-alpha



On 22/11/23 10:24, Siddhesh Poyarekar wrote:
> On 2023-11-22 08:03, Adhemerval Zanella Netto wrote:
>>
>>
>> On 22/11/23 09:23, Siddhesh Poyarekar wrote:
>>> On 2023-11-21 13:12, Adhemerval Zanella Netto wrote:
>>>>>> -tunable_initialize (tunable_t *cur, const char *strval)
>>>>>> +tunable_initialize (tunable_t *cur, const char *strval, size_t len)
>>>>>>     {
>>>>>> -  tunable_val_t val;
>>>>>> +  tunable_val_t val = { 0 };
>>>>>>         if (cur->type.type_code != TUNABLE_TYPE_STRING)
>>>>>>         val.numval = (tunable_num_t) _dl_strtoul (strval, NULL);
>>>>>
>>>>> There's an implicit assumption that strval is NULL terminated for numeric values.  Is that safe?  Maybe all you need to do here is copy strval into a static buffer of size 21 (basically size of the string representing -1ULL) and pass that to _dl_strtoul.
>>>>
>>>> Afaiu the environment variable will always be NULL terminated (although some
>>>> might overlap).  This is due how the kernel will layout the argv/envp on
>>>> process creation at sys_execve:
>>>
>>> Sure, but the strval here should not be the entire envvar, just the value portion of it.  Granted that _dl_strtoul will bail out on the first non-numeric character, so maybe it's not
>>
>> For the GLIBC_TUNABLES itself, parse_tunables will pass the correct length
>> for each tunable.  For the alias case, get_next_env will pass the value
>> length (which I fixed on v4 btw). So I think there is no need to copy the
>> value on a temporary value, parsing can be done in-place since kernel ensures
>> the string will be null-terminated.
> 
> Yes, but the length is not passed into _dl_strtoul, which will then parse until it finds a non-numeric character.  That's probably not an issue for a properly validated tunable string (and your patch to bail out in case of invalid tunable strings ensures that), but at the minimum there should be a comment stating that assumption for future you, or me.
Right, so maybe it would be better to add a check for 'endptr' and bail 
out if the value is not a number.  And maybe it would be also better to
extend _dl_strtoul to return if the value is out-of-bounds.  I will add
an extra patch on set for this.

^ permalink raw reply	[flat|nested] 40+ messages in thread

end of thread, other threads:[~2023-11-22 14:13 UTC | newest]

Thread overview: 40+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-11-06 20:25 [PATCH v3 00/19] Improve loader environment variable handling Adhemerval Zanella
2023-11-06 20:25 ` [PATCH v3 01/19] elf: Remove /etc/suid-debug support Adhemerval Zanella
2023-11-06 20:25 ` [PATCH v3 02/19] elf: Add GLIBC_TUNABLES to unsecvars Adhemerval Zanella
2023-11-06 20:25 ` [PATCH v3 03/19] elf: Ignore GLIBC_TUNABLES for setuid/setgid binaries Adhemerval Zanella
2023-11-06 20:25 ` [PATCH v3 04/19] elf: Add all malloc tunable to unsecvars Adhemerval Zanella
2023-11-06 20:25 ` [PATCH v3 05/19] elf: Do not process invalid tunable format Adhemerval Zanella
2023-11-06 20:25 ` [PATCH v3 06/19] elf: Do not parse ill-formatted strings Adhemerval Zanella
2023-11-20 21:48   ` Siddhesh Poyarekar
2023-11-06 20:25 ` [PATCH v3 07/19] elf: Fix _dl_debug_vdprintf to work before self-relocation Adhemerval Zanella
2023-11-06 20:25 ` [PATCH v3 08/19] elf: Emit warning if tunable is ill-formatted Adhemerval Zanella
2023-11-20 21:50   ` Siddhesh Poyarekar
2023-11-06 20:25 ` [PATCH v3 09/19] x86: Use dl-symbol-redir-ifunc.h on cpu-tunables Adhemerval Zanella
2023-11-06 20:25 ` [PATCH v3 10/19] s390: " Adhemerval Zanella
2023-11-06 20:25 ` [PATCH v3 11/19] elf: Do not duplicate the GLIBC_TUNABLES string Adhemerval Zanella
2023-11-20 22:44   ` Siddhesh Poyarekar
2023-11-21 18:12     ` Adhemerval Zanella Netto
2023-11-22 11:39       ` Adhemerval Zanella Netto
2023-11-22 12:23       ` Siddhesh Poyarekar
2023-11-22 13:03         ` Adhemerval Zanella Netto
2023-11-22 13:24           ` Siddhesh Poyarekar
2023-11-22 14:13             ` Adhemerval Zanella Netto
2023-11-06 20:25 ` [PATCH v3 12/19] elf: Ignore LD_PROFILE for setuid binaries Adhemerval Zanella
2023-11-20 22:47   ` Siddhesh Poyarekar
2023-11-06 20:25 ` [PATCH v3 13/19] elf: Remove LD_PROFILE for static binaries Adhemerval Zanella
2023-11-20 22:55   ` Siddhesh Poyarekar
2023-11-06 20:25 ` [PATCH v3 14/19] elf: Ignore loader debug env vars for setuid Adhemerval Zanella
2023-11-20 22:57   ` Siddhesh Poyarekar
2023-11-21 18:24     ` Adhemerval Zanella Netto
2023-11-06 20:25 ` [PATCH v3 15/19] elf: Remove any_debug from dl_main_state Adhemerval Zanella
2023-11-20 22:58   ` Siddhesh Poyarekar
2023-11-06 20:25 ` [PATCH v3 16/19] elf: Ignore LD_LIBRARY_PATH and debug env var for setuid for static Adhemerval Zanella
2023-11-20 22:59   ` Siddhesh Poyarekar
2023-11-06 20:25 ` [PATCH v3 17/19] elf: Add comments on how LD_AUDIT and LD_PRELOAD handle __libc_enable_secure Adhemerval Zanella
2023-11-06 20:25 ` [PATCH v3 18/19] elf: Ignore LD_BIND_NOW and LD_BIND_NOT for setuid binaries Adhemerval Zanella
2023-11-20 23:02   ` Siddhesh Poyarekar
2023-11-06 20:25 ` [PATCH v3 19/19] elf: Refactor process_envvars Adhemerval Zanella
2023-11-20 23:09   ` Siddhesh Poyarekar
2023-11-21 19:00     ` Adhemerval Zanella Netto
2023-11-20 23:12 ` [PATCH v3 00/19] Improve loader environment variable handling Siddhesh Poyarekar
2023-11-21 19:37   ` Adhemerval Zanella Netto

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).