[PATCH v5 4/4] Add system-wide tunables: Filters

DJ Delorie dj@redhat.com
Wed Feb 11 04:33:51 GMT 2026


Add support for [proc:*] syntax where * matches /proc/self/exe
(fallback: argv[0]).  Tunables after such a line are limited to
matching processes.

Note that this filter is reset when including a file or at
end of file.

If the filename starts with a slash (example: [proc:/bin/foo]) the
full path must match.  If not (example: [proc:foo]) the basename is
matched.
---
 csu/libc-start.c                        |  2 +-
 elf/Makefile                            |  4 +
 elf/cache.c                             |  3 +-
 elf/dl-tunables.c                       | 68 ++++++++++++++++-
 elf/dl-tunables.h                       |  2 +-
 elf/tst-tunconf1.c                      | 36 +++++++++
 elf/tst-tunconf1.root/etc/tunables.conf |  6 ++
 elf/tst-tunconf1.root/ldconfig.run      |  0
 elf/tst-tunconf1.root/postclean.req     |  0
 elf/tunconf.c                           | 97 ++++++++++++++++++++++++-
 sysdeps/mach/hurd/dl-sysdep.c           |  2 +-
 sysdeps/unix/sysv/linux/dl-sysdep.c     |  2 +-
 12 files changed, 210 insertions(+), 12 deletions(-)
 create mode 100644 elf/tst-tunconf1.c
 create mode 100644 elf/tst-tunconf1.root/etc/tunables.conf
 create mode 100644 elf/tst-tunconf1.root/ldconfig.run
 create mode 100644 elf/tst-tunconf1.root/postclean.req

diff --git a/csu/libc-start.c b/csu/libc-start.c
index 1c58561bce..ae36170045 100644
--- a/csu/libc-start.c
+++ b/csu/libc-start.c
@@ -264,7 +264,7 @@ LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
   _dl_aux_init (auxvec);
 # endif
 
-  __tunables_init (__environ);
+  __tunables_init (__environ, argv);
 
   ARCH_INIT_CPU_FEATURES ();
 
diff --git a/elf/Makefile b/elf/Makefile
index 282af88e71..b407e9e761 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -329,6 +329,7 @@ tests-internal := \
   $(tests-static-internal) \
   tst-tls1 \
   tst-tls_tp_offset \
+  tst-tunconf1 \
   # tests-internal
 
 tests-static := $(tests-static-normal) $(tests-static-internal)
@@ -339,6 +340,8 @@ tests-static += \
   tst-tls9-static \
   # tests-static
 
+tst-tunconf1-ENV = GLIBC_TUNABLES=glibc.malloc.tcache_count=5
+
 static-dlopen-environment = \
   LD_LIBRARY_PATH=$(ld-library-path):$(common-objpfx)dlfcn
 tst-tls9-static-ENV = $(static-dlopen-environment)
@@ -568,6 +571,7 @@ tests-container += \
   tst-pldd \
   tst-preload-pthread-libc \
   tst-rootdir \
+  tst-tunconf1 \
   # tests-container
 
 test-srcs = \
diff --git a/elf/cache.c b/elf/cache.c
index e798f7e2d8..2809f255f5 100644
--- a/elf/cache.c
+++ b/elf/cache.c
@@ -300,9 +300,10 @@ print_extensions (struct cache_extension_all_loaded *ext,
 	     thc->signature, thc->version, thc->num_tunables);
       for (i = 0; i < count; ++ i)
 	{
-	  printf("  [%d] %s : %s [flags 0x%08x",
+	  printf("  [%d] %s (%d) : %s [flags 0x%08x",
 		 i,
 		 cache_data + tec[i].name_offset,
+		 tec[i].tunable_id,
 		 cache_data + tec[i].value_offset,
 		 tec[i].flags);
 	  if (tec[i].flag_offset != 0)
diff --git a/elf/dl-tunables.c b/elf/dl-tunables.c
index 83808f75e7..4637f47875 100644
--- a/elf/dl-tunables.c
+++ b/elf/dl-tunables.c
@@ -301,13 +301,22 @@ parse_tunables (const char *valstring)
    ENV_ALIAS to find values.  Later we will also use the tunable names to find
    values.  */
 void
-__tunables_init (char **envp)
+__tunables_init (char **envp, char **argv)
 {
   char *envname = NULL;
   char *envval = NULL;
   char **prev_envp = envp;
 
 #if defined(SHARED) && defined (USE_LDCONFIG)
+  const char *prog_name = (argv && argv[0]) ? argv[0] : "";
+  int prog_name_len = -1;
+  const char *base_name = NULL;
+  int base_name_len = -1;
+#ifdef PATH_MAX
+  char exebuf[PATH_MAX];
+#else
+  char exebuf[256];
+#endif
   const struct tunable_header_cached *thc;
   const char *td;
 
@@ -340,9 +349,58 @@ __tunables_init (char **envp)
 	      if (tid == -1)
 		continue;
 	    }
-	  /* At this point, TID is valid for the tunable we want.  See
-	     if the parsed type matches the desired type.  */
-
+	  /* At this point, TID is valid for the tunable we want.  */
+
+	  /* Apply selected filter, if any.  */
+	  switch (tec->flags & TUNCONF_FLAG_FILTER) {
+	  case TUNCONF_FILTER_PERPROC:
+	    /* Perform one-time calculations that aren't needed if we
+	       don't use this filter.  */
+	    if (prog_name_len == -1)
+	      {
+		ssize_t n = readlink ("/proc/self/exe",
+				      exebuf, sizeof (exebuf) - 1);
+		if (n > 0 && n < sizeof(exebuf)-1)
+		  {
+		    /* If /proc/self/exe exists and we can read it,
+		       it's more reliable than argv[] so use it.  */
+		    exebuf[n] = '\0';
+		    prog_name = exebuf;
+		  }
+		if (prog_name != NULL)
+		  {
+		    const char *slash = NULL, *cp;
+		    for (cp = prog_name; *cp; ++ cp)
+		      if (*cp == '/')
+			slash = cp;
+		    if (slash)
+		      base_name = slash + 1;
+		    else
+		      base_name = prog_name;
+		    prog_name_len = strlen (prog_name);
+		    base_name_len = strlen (base_name);
+		  }
+	      }
+	    /* prog_name and the cached string are both NUL terminated.  */
+	    if (prog_name)
+	      {
+		if (((const char *)(td + tec->flag_offset))[0] == '/')
+		  {
+		    if (memcmp (prog_name, td + tec->flag_offset, prog_name_len) != 0)
+		      goto skip_due_to_filter;
+		  }
+		else
+		  {
+		    if (memcmp (base_name, td + tec->flag_offset, base_name_len) != 0)
+		      goto skip_due_to_filter;
+		  }
+	      }
+	    break;
+	  default:
+	    break;
+	  }
+
+	  /* See if the parsed type matches the desired type.  */
 	  if (tunable_list[tid].type.type_code == TUNABLE_TYPE_STRING)
 	    {
 	      /* This is a memory leak but there's no easy way around
@@ -365,6 +423,8 @@ __tunables_init (char **envp)
 				      value, strlen (value));
 		}
 	    }
+
+	skip_due_to_filter:
 	}
     }
 #endif
diff --git a/elf/dl-tunables.h b/elf/dl-tunables.h
index 45aeed47bc..3f34329614 100644
--- a/elf/dl-tunables.h
+++ b/elf/dl-tunables.h
@@ -47,7 +47,7 @@ typedef void (*tunable_callback_t) (tunable_val_t *);
 
 #include "dl-tunable-list.h"
 
-extern void __tunables_init (char **);
+extern void __tunables_init (char **, char **);
 extern void __tunables_print (void);
 extern bool __tunable_is_initialized (tunable_id_t);
 extern void __tunable_get_val (tunable_id_t, void *, tunable_callback_t);
diff --git a/elf/tst-tunconf1.c b/elf/tst-tunconf1.c
new file mode 100644
index 0000000000..c95a7cb8ba
--- /dev/null
+++ b/elf/tst-tunconf1.c
@@ -0,0 +1,36 @@
+/* Test that the tunables cache can override env vars.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <stdio.h>
+#include <support/check.h>
+
+#include "dl-tunables.h"
+
+static int
+do_test (void)
+{
+  size_t tcache_count = TUNABLE_GET_FULL (glibc, malloc, tcache_count, size_t, NULL);
+  size_t tcache_max = TUNABLE_GET_FULL (glibc, malloc, tcache_max, size_t, NULL);
+  printf("tcache count is %ld (should be 5, from env)\n", (long)tcache_count);
+  TEST_COMPARE ((long)tcache_count, 5);
+  printf("tcache max is %ld (should be 4, from /etc)\n", (long)tcache_max);
+  TEST_COMPARE ((long)tcache_max, 4);
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/elf/tst-tunconf1.root/etc/tunables.conf b/elf/tst-tunconf1.root/etc/tunables.conf
new file mode 100644
index 0000000000..3c7b33c399
--- /dev/null
+++ b/elf/tst-tunconf1.root/etc/tunables.conf
@@ -0,0 +1,6 @@
+glibc.malloc.tcache_max=6
+glibc.malloc.tcache_count=3
+[proc:/bin/ls]
+glibc.malloc.tcache_max=7
+[proc:tst-tunconf1]
+glibc.malloc.tcache_max=4
diff --git a/elf/tst-tunconf1.root/ldconfig.run b/elf/tst-tunconf1.root/ldconfig.run
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/elf/tst-tunconf1.root/postclean.req b/elf/tst-tunconf1.root/postclean.req
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/elf/tunconf.c b/elf/tunconf.c
index ea547e95d5..cb26d47816 100644
--- a/elf/tunconf.c
+++ b/elf/tunconf.c
@@ -65,12 +65,14 @@ typedef enum {
 struct tunable_entry_int {
   struct stringtable_entry *name;
   struct stringtable_entry *value;
+  struct stringtable_entry *filter;
   TOP top;
   int tunable_id;
   int value_is_negative:1;
   int value_was_parsed:1;
   unsigned long long value_ull;
   signed long long value_sll;
+  long filter_flags;
 
   struct tunable_entry_int *next;
 };
@@ -78,11 +80,80 @@ struct tunable_entry_int {
 struct tunable_entry_int *entry_list;
 struct tunable_entry_int **entry_list_next = &entry_list;
 
+static int filter_flags = 0;
+static char *filter_string = NULL;
+
 /*----------------------------------------------------------------------*/
 
 static void parse_tunconf_include (const char *tunconfig_file, unsigned int lineno,
 				   bool do_chroot, const char *pattern, const char *opt_chroot);
 
+
+static void
+clear_filter (void)
+{
+  free (filter_string);
+  filter_string = NULL;
+  filter_flags = 0;
+}
+
+/* Filters are lines the are bracketed, like
+   [prog:foo]
+*/
+static void
+parse_filter (char *line, const char *filename, int lineno)
+{
+  const char *colon = NULL;
+  const char *right_bracket = NULL;
+  const char *cp;
+
+  for (cp=line; *cp != 0; cp++)
+    {
+      if (*cp == ':')
+	colon = cp;
+      if (*cp == ']')
+	{
+	  right_bracket = cp;
+	  break;
+	}
+    }
+  /* Special case: [] means "no filter" */
+  if (right_bracket != NULL && right_bracket == line + 1)
+    {
+      clear_filter ();
+      return;
+    }
+  if (colon == NULL)
+    {
+      printf("%s:%d: syntax error, filter line ignored: `%s' (missing ':')\n",
+	     filename, lineno, line);
+      return;
+    }
+  if (right_bracket == NULL)
+    {
+      printf("%s:%d: syntax error, filter line ignored: `%s' (missing ']')\n",
+	     filename, lineno, line);
+      return;
+    }
+
+  if (filter_string != NULL)
+    {
+      clear_filter ();
+    }
+
+  if (memcmp ("proc", line + 1, colon - line - 1) == 0)
+    {
+      filter_string = (char *) malloc (right_bracket - colon);
+      memcpy (filter_string, colon + 1, right_bracket - colon - 1);
+      filter_string [right_bracket - colon] = 0;
+      filter_flags = TUNCONF_FILTER_PERPROC;
+    }
+
+  else
+    printf("%s:%d: unrecognized filter `%.*s', ignored\n", filename, lineno, (int)(colon - line - 1), line + 1);
+}
+
+
 static void
 add_tunable (char *line, const char *filename, int lineno)
 {
@@ -98,7 +169,7 @@ add_tunable (char *line, const char *filename, int lineno)
 
   /* Leading whitespace has already been stripped.  */
 
-  if (*line == '!' || *line == '+' || *line == '-')
+  if (*line == '!' || *line == '+' || *line == '-' || *line == '[')
     {
       switch (*line)
 	{
@@ -111,8 +182,10 @@ add_tunable (char *line, const char *filename, int lineno)
 	case '-':
 	  top = TOP_DENY;
 	  break;
+	case '[':
+	  parse_filter (line, filename, lineno);
+	  return;
 	}
-      printf("TOP: %d\n", top);
       line ++;
       while (*line && isspace(*line))
 	line ++;
@@ -175,6 +248,12 @@ add_tunable (char *line, const char *filename, int lineno)
   entry->tunable_id = id;
   entry->top = top;
 
+  if (filter_flags)
+    {
+      entry->filter_flags = filter_flags;
+      entry->filter = cache_store_string (filter_string);
+    }
+
   *entry_list_next = entry;
   entry_list_next = & (entry->next);
 }
@@ -188,6 +267,9 @@ parse_tunconf (const char *filename, int do_chroot, char *opt_chroot)
   size_t len = 0;
   unsigned int lineno;
 
+  /* Filters do not live across file boundaries.  */
+  clear_filter ();
+
   if (do_chroot && opt_chroot)
     {
       canon = chroot_canon (opt_chroot, filename);
@@ -259,6 +341,7 @@ Warning: ignoring configuration file that cannot be opened: %s"),
   /* Free buffer and close file.  */
   free (line);
   fclose (file);
+  clear_filter ();
 }
 
 /* Handle one word in an `include' line, a glob pattern of additional
@@ -388,7 +471,15 @@ get_tunconf_ext (uint32_t string_table_offset)
       tec->tunable_id = tei->tunable_id;
       tec->name_offset = tei->name->offset + string_table_offset;
       tec->value_offset = tei->value->offset + string_table_offset;
-      tec->flag_offset = 0;
+
+      if (tei->filter_flags != 0)
+	{
+	  tec->flag_offset = tei->filter->offset + string_table_offset;
+	  tec->flags |= tei->filter_flags;
+	}
+      else
+	tec->flag_offset = 0;
+
       tec->unused_1 = 0;
       if (tei->value_is_negative)
 	tec->parsed_value = (uint64_t) tei->value_sll;
diff --git a/sysdeps/mach/hurd/dl-sysdep.c b/sysdeps/mach/hurd/dl-sysdep.c
index 0e348d6440..fe6d453756 100644
--- a/sysdeps/mach/hurd/dl-sysdep.c
+++ b/sysdeps/mach/hurd/dl-sysdep.c
@@ -98,7 +98,7 @@ _dl_sysdep_start (void **start_argptr,
 
       __libc_enable_secure = _dl_hurd_data->flags & EXEC_SECURE;
 
-      __tunables_init (_environ);
+      __tunables_init (_environ, _dl_argv);
 
       /* Initialize DSO sorting algorithm after tunables.  */
       _dl_sort_maps_init ();
diff --git a/sysdeps/unix/sysv/linux/dl-sysdep.c b/sysdeps/unix/sysv/linux/dl-sysdep.c
index cb1f94ee23..c2701f274c 100644
--- a/sysdeps/unix/sysv/linux/dl-sysdep.c
+++ b/sysdeps/unix/sysv/linux/dl-sysdep.c
@@ -107,7 +107,7 @@ _dl_sysdep_start (void **start_argptr,
 
   dl_hwcap_check ();
 
-  __tunables_init (_environ);
+  __tunables_init (_environ, (char **) (start_argptr + 1));
 
   /* Initialize DSO sorting algorithm after tunables.  */
   _dl_sort_maps_init ();
-- 
2.47.3



More information about the Libc-alpha mailing list