_dl_aux_init (auxvec);
# endif
- __tunables_init (__environ);
+ __tunables_init (__environ, argv);
ARCH_INIT_CPU_FEATURES ();
$(tests-static-internal) \
tst-tls1 \
tst-tls_tp_offset \
+ tst-tunconf1 \
# tests-internal
tests-static := $(tests-static-normal) $(tests-static-internal)
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)
tst-preload-pthread-libc \
tst-ptrguard-static-dlopen \
tst-rootdir \
+ tst-tunconf1 \
# tests-container
test-srcs = \
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");
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;
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;
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
value, strlen (value));
}
}
+
+ skip_due_to_filter:;
}
}
#endif /* defined(SHARED) && defined (USE_LDCONFIG) */
#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);
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
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
--- /dev/null
+/* 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>
--- /dev/null
+# 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
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)
{
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;
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 '+':
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;
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] == '-')
{
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;
#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
__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 ();
dl_hwcap_check ();
- __tunables_init (_environ);
+ __tunables_init (_environ, (char **) (start_argptr + 1));
/* Initialize DSO sorting algorithm after tunables. */
_dl_sort_maps_init ();