[PATCH v9 4/5] Add system-wide tunables: Filters
Arjun Shankar
arjun@redhat.com
Tue Jun 30 15:46:04 GMT 2026
Hi DJ,
> 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
Changes look good to me.
Reviewed-by: Arjun Shankar <arjun@redhat.com>
> ---
> csu/libc-start.c | 2 +-
> elf/Makefile | 4 +
> elf/cache.c | 22 ++++
> elf/dl-tunables.c | 81 +++++++++++++-
> elf/dl-tunables.h | 2 +-
> elf/ldconfig-parse.c | 6 +-
> elf/ldconfig.c | 3 +
> elf/tst-tunconf1.c | 36 ++++++
> elf/tst-tunconf1.root/etc/tunables.conf | 14 +++
> elf/tst-tunconf1.root/ldconfig.run | 0
> elf/tst-tunconf1.root/postclean.req | 0
> elf/tunconf.c | 143 +++++++++++++++++++++++-
> elf/tunconf.h | 3 +
> sysdeps/mach/hurd/dl-sysdep.c | 2 +-
> sysdeps/unix/sysv/linux/dl-sysdep.c | 2 +-
> 15 files changed, 309 insertions(+), 11 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 67aa981246..ec42f23981 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 f5625c8986..789c504da9 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -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 = \
> diff --git a/elf/cache.c b/elf/cache.c
> index 58b1412b32..d46ab02ad6 100644
> --- a/elf/cache.c
> +++ b/elf/cache.c
> @@ -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");
> diff --git a/elf/dl-tunables.c b/elf/dl-tunables.c
> index f183e99855..197d940d38 100644
> --- a/elf/dl-tunables.c
> +++ b/elf/dl-tunables.c
> @@ -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;
OK. This new case matches and applies filter-less tunables. In v8,
filter-less tunables and tunables with unknown filters were processed
together as "default," so tunables with unknown filters were applied.
We now handle unknown filters separately below.
> + 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;
OK. Now, tunables with unknown filters are skipped.
> + }
> +
> + /* 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) */
> 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/ldconfig-parse.c b/elf/ldconfig-parse.c
> index b7bb664eb5..baddfdbac0 100644
> --- a/elf/ldconfig-parse.c
> +++ b/elf/ldconfig-parse.c
> @@ -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
> diff --git a/elf/ldconfig.c b/elf/ldconfig.c
> index 11b063eb5c..1ea55400f3 100644
> --- a/elf/ldconfig.c
> +++ b/elf/ldconfig.c
> @@ -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
> 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..f373a67902
> --- /dev/null
> +++ b/elf/tst-tunconf1.root/etc/tunables.conf
> @@ -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
> 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 5e77729e22..a5ca755abd 100644
> --- a/elf/tunconf.c
> +++ b/elf/tunconf.c
> @@ -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);
OK. Checked malloc.
> + /* 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)
OK. Correct length.
> + {
> + 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;
> diff --git a/elf/tunconf.h b/elf/tunconf.h
> index 81c0dd6b06..016120ac3a 100644
> --- a/elf/tunconf.h
> +++ b/elf/tunconf.h
> @@ -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
> 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