From: DJ Delorie Date: Fri, 26 Jun 2026 03:30:39 +0000 (-0400) Subject: Add system-wide tunables: Filters X-Git-Tag: glibc-2.44~84 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=fcea66cd4685f9f3719e09cac52c73b9ece4bade;p=thirdparty%2Fglibc.git Add system-wide tunables: Filters 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 --- 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; + 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) */ 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 + . */ + +#include +#include + +#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 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); + /* 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; 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 ();