]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
Add system-wide tunables: Filters
authorDJ Delorie <dj@redhat.com>
Fri, 26 Jun 2026 03:30:39 +0000 (23:30 -0400)
committerDJ Delorie <dj@redhat.com>
Tue, 30 Jun 2026 20:49:44 +0000 (16:49 -0400)
Add support for [proc:*] syntax where * matches /proc/self/exe
(fallback: argv[0] unless AT_SECURE).  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.

Add support for filtering out AT_SECURE or non-AT_SECURE binaries:

  $glibc.only-for.nonsecure-binaries=1
  @glibc.only-for.secure-binaries=1

Reviewed-by: Arjun Shankar <arjun@redhat.com>
15 files changed:
csu/libc-start.c
elf/Makefile
elf/cache.c
elf/dl-tunables.c
elf/dl-tunables.h
elf/ldconfig-parse.c
elf/ldconfig.c
elf/tst-tunconf1.c [new file with mode: 0644]
elf/tst-tunconf1.root/etc/tunables.conf [new file with mode: 0644]
elf/tst-tunconf1.root/ldconfig.run [new file with mode: 0644]
elf/tst-tunconf1.root/postclean.req [new file with mode: 0644]
elf/tunconf.c
elf/tunconf.h
sysdeps/mach/hurd/dl-sysdep.c
sysdeps/unix/sysv/linux/dl-sysdep.c

index 67aa9812468f7677fb35f3cab2474a0c0ff34174..ec42f23981b4062a356ba1bca7b1494885afe836 100644 (file)
@@ -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 ();
 
index f5625c898622559a0e5f6fa32028f546d82e1ee1..789c504da919efc32aa9c44b449d155bef3a7248 100644 (file)
@@ -336,6 +336,7 @@ tests-internal := \
   $(tests-static-internal) \
   tst-tls1 \
   tst-tls_tp_offset \
+  tst-tunconf1 \
   # tests-internal
 
 tests-static := $(tests-static-normal) $(tests-static-internal)
@@ -346,6 +347,8 @@ tests-static += \
   tst-tls9-static \
   # tests-static
 
+tst-tunconf1-TUNABLES-only = glibc.malloc.tcache_count=5
+
 static-dlopen-environment = \
   LD_LIBRARY_PATH=$(ld-library-path):$(common-objpfx)dlfcn
 tst-tls9-static-ENV = $(static-dlopen-environment)
@@ -583,6 +586,7 @@ tests-container += \
   tst-preload-pthread-libc \
   tst-ptrguard-static-dlopen \
   tst-rootdir \
+  tst-tunconf1 \
   # tests-container
 
 test-srcs = \
index 58b1412b321d3c2843078958b573b51006c90363..d46ab02ad659520035319cb56f15103cefb08289 100644 (file)
@@ -318,6 +318,28 @@ print_extensions (struct cache_extension_all_loaded *ext,
            printf ("overridable");
          else
            printf ("nonoverridable");
+         switch (tec[i].flags & (TUNCONF_EXCLUDE_SECURE
+                                 | TUNCONF_EXCLUDE_UNSECURE))
+           {
+           case TUNCONF_EXCLUDE_SECURE:
+             printf(",nonsecure");
+             break;
+           case TUNCONF_EXCLUDE_UNSECURE:
+             printf(",onlysecure");
+             break;
+           case TUNCONF_EXCLUDE_SECURE | TUNCONF_EXCLUDE_UNSECURE:
+             printf(",ignore");
+             break;
+           case 0:
+             printf(",anysecure");
+             break;
+           }
+         switch (tec[i].flags & TUNCONF_FLAG_FILTER)
+           {
+           case TUNCONF_FILTER_PERPROC:
+             printf(",[proc]");
+             break;
+           }
          if (tec[i].flag_offset != 0)
            printf (",'%s'", cache_data + tec[i].flag_offset);
          printf (")]\n");
index f183e998550cfedc07cbbc107934c2c1aeefa2ae..197d940d38f6bebc966fd637d57e134d3d3ba46a 100644 (file)
@@ -292,7 +292,7 @@ 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;
@@ -304,6 +304,14 @@ __tunables_init (char **envp)
     TUNABLE_SET (glibc, malloc, hugetlb, 1);
 
 #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;
+#ifdef PATH_MAX
+  char exebuf[PATH_MAX];
+#else
+  char exebuf[256];
+#endif
   const struct tunable_header_cached *thc;
   const char *td;
 
@@ -336,9 +344,72 @@ __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.  */
+
+         if (tec->flags & TUNCONF_EXCLUDE_SECURE && __libc_enable_secure)
+           goto skip_due_to_filter;
+         if (tec->flags & TUNCONF_EXCLUDE_UNSECURE && !__libc_enable_secure)
+           goto skip_due_to_filter;
+
+         /* Apply selected filter, if any.  */
+         switch (tec->flags & TUNCONF_FLAG_FILTER) {
+         case TUNCONF_FILTER_NONE:
+           break;
+         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;
+                 }
+               else if (__libc_enable_secure)
+                 prog_name = NULL;
+               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);
+                 }
+             }
+           /* prog_name and the cached string are both NUL terminated.  */
+           if (prog_name)
+             {
+               if (((const char *)(td + tec->flag_offset))[0] == '/')
+                 {
+                   if (strcmp (prog_name, td + tec->flag_offset) != 0)
+                     goto skip_due_to_filter;
+                 }
+               else
+                 {
+                   if (strcmp (base_name, td + tec->flag_offset) != 0)
+                     goto skip_due_to_filter;
+                 }
+             }
+           else
+             /* Program is AT_SECURE but the only source of program
+                name is argv[0], which is not secure, so we do not
+                match any name-based filter.  */
+             goto skip_due_to_filter;
+           break;
+         default:
+           /* Unknown filter.  */
+           goto skip_due_to_filter;
+         }
+
+         /* 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
@@ -363,6 +434,8 @@ __tunables_init (char **envp)
                                      value, strlen (value));
                }
            }
+
+       skip_due_to_filter:;
        }
     }
 #endif /* defined(SHARED) && defined (USE_LDCONFIG) */
index 45aeed47bc0f3850b9b168d12cdb2b798579badd..3f34329614fcdb8d1b95c47f3de914ff22099096 100644 (file)
@@ -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);
index b7bb664eb5b1688c3dc8582bceb4809a18dbaf73..baddfdbac0ac4092ff170729050ddd8a7d8d20e8 100644 (file)
@@ -47,8 +47,10 @@ ldconfig_parse_config_1 (const char *filename, bool do_chroot,
 
      opt_chroot - If non-NULL, all paths are relative to this.
 
-     callback - for each non-blank line in the file, this function is called
-       with the line and it's location.
+     callback - for each non-blank line in the file, this function is
+       called with the line and it's location.  Will also be called
+       with a NULL line at the start and end of each file, for
+       file-scoped config items.
  */
 
 void
index 11b063eb5c269e62751b2710cbab71c298f8a35c..1ea55400f3df1372b220ff17d100307c4d671a93 100644 (file)
@@ -435,6 +435,9 @@ add_dir_1 (const char *line, const char *from_file, int from_line)
 static void
 add_dir_callback (char *line, const char *from_file, int from_line)
 {
+  /* Denotes file boundaries.  Not needed here.  */
+  if (line == NULL)
+    return;
   if (!strncasecmp (line, "hwcap", 5) && isblank (line[5]))
     error (0, 0, _("%s:%u: hwcap directive ignored"), from_file, from_line);
   else
diff --git a/elf/tst-tunconf1.c b/elf/tst-tunconf1.c
new file mode 100644 (file)
index 0000000..c95a7cb
--- /dev/null
@@ -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 (file)
index 0000000..f373a67
--- /dev/null
@@ -0,0 +1,14 @@
+# These test the parser for both the overridability characters as well as
+# tunables that either never exist, or only exist on some platforms.
+-glibc.cpu.cached_memopt=1
++glibc.cpu.hwcaps=some,random,string
+@glibc.test_secure=1
+$glibc.test_unsecure=1
+
+# These are checked inside the test case
+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 (file)
index 0000000..e69de29
diff --git a/elf/tst-tunconf1.root/postclean.req b/elf/tst-tunconf1.root/postclean.req
new file mode 100644 (file)
index 0000000..e69de29
index 5e77729e22aa56ec8a565e29e7d2c09da3d2daa8..a5ca755abdb775fc121a1c749e2191a27da190c1 100644 (file)
@@ -61,20 +61,100 @@ typedef enum {
 struct tunable_entry_int {
   struct stringtable_entry *name;
   struct stringtable_entry *value;
+  struct stringtable_entry *filter;
   TOP top;
+  bool exclude_secure:1;
+  bool exclude_nonsecure:1;
   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;
 };
 
 struct tunable_entry_int *entry_list;
 
+static int filter_flags = 0;
+static char *filter_string = NULL;
+
 /*----------------------------------------------------------------------*/
 
+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)
+    {
+      error_at_line (0, 0, filename, lineno,
+                    "syntax error, filter line ignored: `%s' (missing ':')\n",
+                    line);
+      return;
+    }
+  if (right_bracket == NULL)
+    {
+      error_at_line (0, 0, filename, lineno,
+                    "syntax error, filter line ignored: `%s' (missing ']')\n",
+                    line);
+      return;
+    }
+
+  if (filter_string != NULL)
+    {
+      clear_filter ();
+    }
+
+  if (colon - line - 1 == 4 && memcmp ("proc", line + 1, 4) == 0)
+    {
+      /* Consider this example: [proc:foo] ..."  */
+      /* We allocate 4 bytes, [0] through [3].  */
+      filter_string = (char *) xmalloc (right_bracket - colon);
+      /* We copy "foo" for 3 bytes, [0] through [2].  */
+      memcpy (filter_string, colon + 1, right_bracket - colon - 1);
+      /*  [3] = 0 so now "foo\0".  */
+      filter_string [right_bracket - colon - 1] = 0;
+      filter_flags = TUNCONF_FILTER_PERPROC;
+    }
+
+  else
+    error_at_line (0, 0, filename, lineno,
+                  "unrecognized filter `%.*s', ignored\n",
+                  (int)(colon - line - 1), line + 1);
+}
+
+
 static void
 add_tunable (char *line, const char *filename, int lineno)
 {
@@ -86,6 +166,14 @@ add_tunable (char *line, const char *filename, int lineno)
   struct tunable_entry_int *entry;
   int i, id;
   static struct tunable_entry_int **entry_list_next = &entry_list;
+  bool exclude_secure = 1, exclude_nonsecure = 0;
+
+  /* Denotes file boundaries.  */
+  if (line == NULL)
+    {
+      clear_filter();
+      return;
+    }
 
   orig_line = line;
 
@@ -117,6 +205,24 @@ add_tunable (char *line, const char *filename, int lineno)
          top = TOP_DENY;
          line += 15;
        }
+      else if (strncmp (line, "onlysecure ", 11) == 0)
+       {
+         exclude_nonsecure = 1;
+         exclude_secure = 0;
+         line += 10;
+       }
+      else if (strncmp (line, "nonsecure ", 10) == 0)
+       {
+         exclude_secure = 1;
+         exclude_nonsecure = 0;
+         line += 9;
+       }
+      else if (strncmp (line, "anysecure ", 10) == 0)
+       {
+         exclude_secure = 0;
+         exclude_nonsecure = 0;
+         line += 9;
+       }
       else switch (*line)
        {
        case '+':
@@ -125,6 +231,21 @@ add_tunable (char *line, const char *filename, int lineno)
        case '-':
          top = TOP_DENY;
          break;
+       case '@':
+         exclude_nonsecure = 1;
+         exclude_secure = 0;
+         break;
+       case '$':
+         exclude_nonsecure = 0;
+         exclude_secure = 1;
+         break;
+       case '*':
+         exclude_nonsecure = 0;
+         exclude_secure = 0;
+         break;
+       case '[':
+         parse_filter (line, filename, lineno);
+         return;
        case ' ':
          break;
 
@@ -197,6 +318,14 @@ add_tunable (char *line, const char *filename, int lineno)
   entry->value = cache_store_string (value);
   entry->tunable_id = id;
   entry->top = top;
+  entry->exclude_secure = exclude_secure;
+  entry->exclude_nonsecure = exclude_nonsecure;
+
+  if (filter_flags)
+    {
+      entry->filter_flags = filter_flags;
+      entry->filter = cache_store_string (filter_string);
+    }
 
   if (value[0] == '-')
     {
@@ -274,11 +403,23 @@ get_tunconf_ext (uint32_t string_table_offset)
          tec->flags |= TUNCONF_OVERRIDE_DENY;
          break;
        }
+      if (tei->exclude_secure)
+       tec->flags |= TUNCONF_EXCLUDE_SECURE;
+      if (tei->exclude_nonsecure)
+       tec->flags |= TUNCONF_EXCLUDE_UNSECURE;
 
       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;
index 81c0dd6b06a62906e7f1841a3d49bfcc38b1228c..016120ac3acd25a6a96cf2785798be5e68f87e62 100644 (file)
@@ -8,6 +8,9 @@
 #define TUNCONF_OVERRIDE_DENY          0x00000004
 #define TUNCONF_OVERRIDE_ALLOW         0x00000000
 
+#define TUNCONF_EXCLUDE_SECURE         0x00000010
+#define TUNCONF_EXCLUDE_UNSECURE       0x00000020
+
 #define TUNCONF_FLAG_FILTER            0x0000ff00
 #define TUNCONF_FILTER_NONE            0x00000000
 #define TUNCONF_FILTER_PERPROC         0x00000100
index 0e348d6440771c78f2e2548583338acb42bc1c63..fe6d453756dee7e4657547f194ea38ef5bf058f5 100644 (file)
@@ -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 ();
index cb1f94ee23a79de27c07aabfc613fe0efce7b5c8..c2701f274c937380f569c602c3eead02d0238879 100644 (file)
@@ -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 ();