* [PATCH v9 0/5] Add system-wide tunables
@ 2026-06-26 3:30 DJ Delorie
2026-06-26 3:30 ` [PATCH v9 4/5] Add system-wide tunables: Filters DJ Delorie
` (5 more replies)
0 siblings, 6 replies; 14+ messages in thread
From: DJ Delorie @ 2026-06-26 3:30 UTC (permalink / raw)
To: libc-alpha
This patch series is the initial implementation of system-wide
tunables, which I mentioned a while ago.
The first part has ldconfig read tunable information from
/etc/tunables.conf and store it to /etc/ld.so.cache. Reusing ldconfig
means no new documentation or training would be required of sysadmins,
or reprogramming of distro packaging.
The second part optimizes the loading of /etc/ld.so.cache and allows
for it to be loaded independently of searching for libraries, so that
tunables information can be loaded as needed.
The third part applies the stored tunable information to the running
application.
The fourth part adds simple filtering, allowing the admin to override
or augment tunables on a per-process basis, like this:
glibc.malloc.whatever=10
[proc:/usr/bin/qemu]
glibc.malloc.whatever=20
Note that such filters only last until EOF or the beginning of the
next included config file.
The fifth part is the manual additions.
Changes since v8:
- overwritable -> overridable
- style fixes
- parse unsigned values with %llu
- _dl_load_cache_tunables tests stricter, test flag_offset too
- add "none" filter, reject unknown filters
- use xmalloc in ldconfig
- fix onlysecure length
Changes since v7:
- added word-sized options to tunables.conf
- removed STRICT option
- make secure/insecure into onlysecure/anysecure/insecure
- add name/value length check
- unmap tmp cache if extensions are corrupt
- set initialized flag for string tunables
- make ldconfig -p more readable, including flag names.
- make "nonsecure" the default
- fix [proc:foo] parser
- no tunconf extension if no entries
- remove chroot_canon decl from tunconf.c
- make entry_list_next local
- warn for unrecognized tunables
Changes since v6:
- elf/tunconf.c : fix whitespace issues
- elf/cache.c : free tunable_data
- elf/cache.c : check extensions for overflow
- elf/dl-cache.c : fix tmp_cachesize logic
- elf/dl-cache.c : comment style tweaks
- elf/dl-cache.c : remove superfluous comment
- elf/dl-cache.c : additional data validation
- elf/tunconf.c : fix whitespace issues
- elf/tunconf.c : use error_at_line instead of printf
- elf/tunconf.c : fix buffer overflow, add comments.
Changes since v5:
- rebase on ldconfig parseconf breakout
- makes callback's line non-const
- use error_at_line instead of printf
- fix tunable list length logic
- add ld.so.cache caching test
- optimize cache "dirty" logic
- remove unneded -DUSE_LDCONFIG
- add AT_SECURE logic and filters
- ldconfig parser: pass NULL line at file boundaries
Changes since v3/v4: printf cleanups, remove custom dl_str*()
functions, off-by-one error, new [proc:*] syntax.
Changes since v2: Added per-process filter. Rebased. Probably various
tweaks and minor changes.
Changes since v1: fixed format bug in error() and rebased. No other
changes.
DJ Delorie (5):
Add system-wide tunables: ldconfig part
Add system-wide tunables: cache ld.so.cache
Add system-wide tunables: Apply tunables part
Add system-wide tunables: Filters
Add system-wide tunables: manual
csu/libc-start.c | 2 +-
elf/Makefile | 9 +
elf/cache.c | 108 ++++-
elf/dl-cache.c | 312 +++++++++----
elf/dl-tunables.c | 140 +++++-
elf/dl-tunables.h | 2 +-
elf/ldconfig-parse.c | 6 +-
elf/ldconfig.c | 26 +-
elf/tst-ldconfig-cache.c | 134 ++++++
elf/tst-ldconfig-cache.root/etc/ld.so.conf | 3 +
elf/tst-ldconfig-cache.root/ldconfig.req | 0
.../tst-ldconfig-cache.script | 7 +
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 | 434 ++++++++++++++++++
elf/tunconf.h | 45 ++
manual/tunables.texi | 94 ++++
sysdeps/generic/dl-cache.h | 20 +
sysdeps/generic/ldconfig.h | 6 +-
sysdeps/mach/hurd/dl-sysdep.c | 2 +-
sysdeps/unix/sysv/linux/dl-sysdep.c | 2 +-
23 files changed, 1305 insertions(+), 97 deletions(-)
create mode 100644 elf/tst-ldconfig-cache.c
create mode 100644 elf/tst-ldconfig-cache.root/etc/ld.so.conf
create mode 100644 elf/tst-ldconfig-cache.root/ldconfig.req
create mode 100644 elf/tst-ldconfig-cache.root/tst-ldconfig-cache.script
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
create mode 100644 elf/tunconf.c
create mode 100644 elf/tunconf.h
--
2.47.3
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v9 3/5] Add system-wide tunables: Apply tunables part
2026-06-26 3:30 [PATCH v9 0/5] Add system-wide tunables DJ Delorie
2026-06-26 3:30 ` [PATCH v9 4/5] Add system-wide tunables: Filters DJ Delorie
2026-06-26 3:30 ` [PATCH v9 5/5] Add system-wide tunables: manual DJ Delorie
@ 2026-06-26 3:30 ` DJ Delorie
2026-06-30 15:41 ` Arjun Shankar
2026-06-26 3:30 ` [PATCH v9 2/5] Add system-wide tunables: cache ld.so.cache DJ Delorie
` (2 subsequent siblings)
5 siblings, 1 reply; 14+ messages in thread
From: DJ Delorie @ 2026-06-26 3:30 UTC (permalink / raw)
To: libc-alpha
Load ld.so.cache and fetch the tunables extension. Apply
those tunables to the current program. We do not yet apply
security policies.
---
elf/dl-cache.c | 54 +++++++++++++++++++++++++++++++++++++++
elf/dl-tunables.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++
elf/tunconf.h | 3 +++
3 files changed, 122 insertions(+)
diff --git a/elf/dl-cache.c b/elf/dl-cache.c
index 07c7e639f5..8b50ad038e 100644
--- a/elf/dl-cache.c
+++ b/elf/dl-cache.c
@@ -28,6 +28,7 @@
#include <dl-isa-level.h>
#include <fcntl.h>
#include <sys/stat.h>
+#include "tunconf.h"
/* This is the starting address and the size of the mmap()ed file. */
static struct cache_file *cache;
@@ -613,3 +614,56 @@ _dl_unload_cache (void)
now. */
}
#endif
+
+const struct tunable_header_cached *
+_dl_load_cache_tunables (const char **data)
+{
+ struct cache_extension_all_loaded ext;
+ struct tunable_header_cached *thc;
+ struct tunable_entry_cached *tec;
+ int i, count;
+
+ if (_dl_check_ldsocache_needs_loading ())
+ _dl_maybe_load_ldsocache ();
+
+ if (cache_new)
+ *data = (const char *) cache_new;
+ else
+ return NULL;
+
+ if (!cache_extension_load (cache_new, cache, cachesize, &ext))
+ return NULL;
+
+ /* Validate length/contents here. */
+ if (ext.sections[cache_extension_tag_tunables].size
+ < sizeof(struct tunable_header_cached))
+ return NULL;
+
+ thc = (struct tunable_header_cached *)
+ ext.sections[cache_extension_tag_tunables].base;
+ tec = thc->tunables;
+ count = thc->num_tunables;
+
+ if (ext.sections[cache_extension_tag_tunables].base
+ + ext.sections[cache_extension_tag_tunables].size
+ != (void *) & tec[count])
+ return NULL;
+
+ /* Validate each entry. */
+ int s_start = (const char *) (&cache_new->libs[cache_new->nlibs]) - *data;
+ int s_end = s_start + cache_new->len_strings;
+ for (i = 0; i < count; i ++)
+ {
+ if (thc->tunables[i].name_offset < s_start
+ || thc->tunables[i].name_offset >= s_end
+ || thc->tunables[i].value_offset < s_start
+ || thc->tunables[i].value_offset >= s_end)
+ return NULL;
+ if (thc->tunables[i].flag_offset != 0
+ && (thc->tunables[i].flag_offset < s_start
+ || thc->tunables[i].flag_offset >= s_end))
+ return NULL;
+ }
+
+ return thc;
+}
diff --git a/elf/dl-tunables.c b/elf/dl-tunables.c
index 5c65e8b458..f183e99855 100644
--- a/elf/dl-tunables.c
+++ b/elf/dl-tunables.c
@@ -37,6 +37,7 @@
#define TUNABLES_INTERNAL 1
#include "dl-tunables.h"
+#include "tunconf.h"
static char **
get_next_env (char **envp, char **name, char **val, char ***prev_envp)
@@ -302,6 +303,70 @@ __tunables_init (char **envp)
if (MALLOC_DEFAULT_THP_PAGESIZE > 0)
TUNABLE_SET (glibc, malloc, hugetlb, 1);
+#if defined(SHARED) && defined (USE_LDCONFIG)
+ const struct tunable_header_cached *thc;
+ const char *td;
+
+ thc = _dl_load_cache_tunables (&td);
+ if (thc != NULL)
+ {
+ for (int t = 0; t < thc->num_tunables; ++ t)
+ {
+ const struct tunable_entry_cached *tec = &( thc->tunables[t] );
+ int tid = tec->tunable_id;
+ const char *name = td + tec->name_offset;
+ const char *value = td + tec->value_offset;
+
+ /* Check that we have the correct tunable, and search by
+ name if needed. We rely on order of operations here to
+ avoid mis-indexing tunables[]. */
+ if (tid < 0 || tid >= tunables_list_size
+ || strcmp (name, tunable_list[tid].name) != 0)
+ {
+ /* It does not, search by name instead. */
+ tid = -1;
+ for (int i = 0; i < tunables_list_size; i++)
+ {
+ if (strcmp (name, tunable_list[i].name) == 0)
+ {
+ tid = i;
+ break;
+ }
+ }
+ if (tid == -1)
+ continue;
+ }
+ /* At this point, TID is valid for the tunable we want. 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
+ it, as the mapping will go away if the disk file is
+ updated and the cache is reloaded. */
+ tunable_list[tid].val.strval.str = __strdup (value);
+ tunable_list[tid].val.strval.len = strlen (value);
+ tunable_list[tid].initialized = true;
+ }
+ else
+ {
+ tunable_val_t tval;
+ if (tec->flags & TUNCONF_FLAG_PARSED)
+ {
+ tval.numval = tec->parsed_value;
+ do_tunable_update_val (& tunable_list[tid],
+ &tval, NULL, NULL);
+ }
+ else
+ {
+ tunable_initialize (& tunable_list[tid],
+ value, strlen (value));
+ }
+ }
+ }
+ }
+#endif /* defined(SHARED) && defined (USE_LDCONFIG) */
+
/* Ignore tunables for AT_SECURE programs. */
if (__libc_enable_secure)
return;
diff --git a/elf/tunconf.h b/elf/tunconf.h
index b446211f94..81c0dd6b06 100644
--- a/elf/tunconf.h
+++ b/elf/tunconf.h
@@ -37,3 +37,6 @@ void parse_tunconf (const char *filename, char *opt_chroot);
struct tunable_header_cached * get_tunconf_ext (uint32_t str_offset);
#define TUNCONF_SIZE(thc_p) (sizeof(struct tunable_header_cached) \
+ thc_p->num_tunables * sizeof (struct tunable_entry_cached))
+
+extern const struct tunable_header_cached *
+_dl_load_cache_tunables (const char **data);
--
2.47.3
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v9 5/5] Add system-wide tunables: manual
2026-06-26 3:30 [PATCH v9 0/5] Add system-wide tunables DJ Delorie
2026-06-26 3:30 ` [PATCH v9 4/5] Add system-wide tunables: Filters DJ Delorie
@ 2026-06-26 3:30 ` DJ Delorie
2026-06-30 15:46 ` Arjun Shankar
2026-06-26 3:30 ` [PATCH v9 3/5] Add system-wide tunables: Apply tunables part DJ Delorie
` (3 subsequent siblings)
5 siblings, 1 reply; 14+ messages in thread
From: DJ Delorie @ 2026-06-26 3:30 UTC (permalink / raw)
To: libc-alpha
Document the syntax and operation.
---
manual/tunables.texi | 94 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 94 insertions(+)
diff --git a/manual/tunables.texi b/manual/tunables.texi
index e0f141077b..847903e6ee 100644
--- a/manual/tunables.texi
+++ b/manual/tunables.texi
@@ -67,6 +67,7 @@ glibc.elf.thp: 0 (min: 0, max: 1)
@end example
@menu
+* System-wide Tunables:: Tunables that affect every process
* Tunable names:: The structure of a tunable name
* Memory Allocation Tunables:: Tunables in the memory allocation subsystem
* Dynamic Linking Tunables:: Tunables in the dynamic linking subsystem
@@ -82,6 +83,99 @@ glibc.elf.thp: 0 (min: 0, max: 1)
@end menu
+@node System-wide Tunables
+@section System-wide Tunables
+@cindex System-wide Tunables
+@cindex /etc/tunables.conf
+
+In addition to setting the @code{GLIBC_TUNABLES} environment variable,
+tunables may be provided globally via the file
+@file{/etc/tunables.conf}, which gets stored in glibc's dynamic
+library cache @file{/etc/ld.so.cache} and read by every program at
+startup. @file{/etc/tunables.conf} contains one tunable per line:
+
+@example
+glibc.malloc.trim_threshold=128
+glibc.malloc.check=3
+@end example
+
+@file{ldconfig} (with whatever options you normally use) will read the
+tunables in @file{/etc/tunables.conf} and save them as an extension
+in @file{/etc/ld.so.cache}. Tunables in the cache are applied to
+every program at startup, programs which are already running are not
+affected.
+
+@file{/etc/tunables.conf} supports the same ``include @var{file}''
+syntax as @file{ld.so.conf}.
+
+Tunables in @file{/etc/tunables.conf} serve as defaults. They
+override the built-in defaults in each program, but may be overridden
+by the @code{GLIBC_TUNABLES} environment variable. Each tunable may
+have one or more prefixes which modifies the behavior of the tunable.
+
+@table @code
+
+@item overridable
+@item +
+@item nonoverridable
+@item -
+Prefixing the tunable name with @code{nonoverridable} or @code{-}
+blocks changes from the environment variable, giving the global
+setting precedence. Prefixing with @code{overridable} or @code{+}
+(the default) reverts this behavior, which is only useful with the
+filters (below).
+
+@item onlysecure
+@item @@
+Tunables prefixed with @code{onlysecure} or @code{@@} will apply only
+to processes that are AT_SECURE (i.e. setuid or setgid binaries, or
+elevated capabilities).
+
+@item nonsecure
+@item $
+Tunables prefixed with @code{nonsecure} or @code{$} will apply only to
+processes that aren't AT_SECURE.
+
+@item anysecure
+@item *
+Tunables prefixed with @code{anysecure} or @code{*} will apply to all
+processes.
+
+@end table
+
+Filters make the system-wide tunables only affect certain programs.
+This allows having a non-overridable default for most of the system
+but a different, overridable, value for certain programs that might
+not work at all with the default setting. The syntax for filters is
+to have each filter on its own line, followed by tunables that are
+applied when the filter matches, with this format:
+
+@example
+[ @var{filtername} : @var{pattern} ]
+@end example
+
+Existing filters are:
+
+@table @code
+@item proc
+Matches the process name. The pattern is either a fully qualified
+path, or the basename of such a path. The process name is read from
+@file{/proc/self/exe} (if available) or @file{argv[0]} (unless
+AT_SECURE is in effect). Example:
+
+@example
+-glibc.cpu.x86_shstk=1
+[proc:/usr/bin/program_that_crashes_with_shstk]
++glibc.cpu.x86_shstk=0
+@end example
+
+@end table
+
+Note that the effects of a filter only last until the next filter, or
+a line with @code{[]} on it (``no filter''), or the end of the file
+(if the filter appears in an included file, at the end of the included
+file).
+
@node Tunable names
@section Tunable names
@cindex Tunable names
--
2.47.3
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v9 2/5] Add system-wide tunables: cache ld.so.cache
2026-06-26 3:30 [PATCH v9 0/5] Add system-wide tunables DJ Delorie
` (2 preceding siblings ...)
2026-06-26 3:30 ` [PATCH v9 3/5] Add system-wide tunables: Apply tunables part DJ Delorie
@ 2026-06-26 3:30 ` DJ Delorie
2026-06-30 15:40 ` Arjun Shankar
2026-06-26 3:30 ` [PATCH v9 1/5] Add system-wide tunables: ldconfig part DJ Delorie
2026-06-30 15:52 ` [PATCH v9 0/5] Add system-wide tunables Arjun Shankar
5 siblings, 1 reply; 14+ messages in thread
From: DJ Delorie @ 2026-06-26 3:30 UTC (permalink / raw)
To: libc-alpha
The purpose of this change is twofold:
1. The ld.so.cache is cached in memory and only re-read if/when
it changes on disk. This allows us to have much more intensive
security checks in the future, without impacting performance as
much. It also allows for cases where the cache is corrupted -
we continue using the last valid one.
2. We break out the load/check logic so that the cache can be
loaded independently of the library lookup, such as for
code that only needs to look at the extensions.
---
elf/Makefile | 4 +
elf/dl-cache.c | 260 ++++++++++++------
elf/tst-ldconfig-cache.c | 134 +++++++++
elf/tst-ldconfig-cache.root/etc/ld.so.conf | 3 +
elf/tst-ldconfig-cache.root/ldconfig.req | 0
.../tst-ldconfig-cache.script | 7 +
6 files changed, 324 insertions(+), 84 deletions(-)
create mode 100644 elf/tst-ldconfig-cache.c
create mode 100644 elf/tst-ldconfig-cache.root/etc/ld.so.conf
create mode 100644 elf/tst-ldconfig-cache.root/ldconfig.req
create mode 100644 elf/tst-ldconfig-cache.root/tst-ldconfig-cache.script
diff --git a/elf/Makefile b/elf/Makefile
index d01db49171..f5625c8986 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -578,6 +578,7 @@ endif
tests-container += \
tst-dlopen-self-container \
tst-dlopen-tlsmodid-container \
+ tst-ldconfig-cache \
tst-pldd \
tst-preload-pthread-libc \
tst-ptrguard-static-dlopen \
@@ -735,6 +736,9 @@ one-hundred = $(foreach x,0 1 2 3 4 5 6 7 8 9, \
0$x 1$x 2$x 3$x 4$x 5$x 6$x 7$x 8$x 9$x)
tst-tls-many-dynamic-modules := \
$(foreach n,$(one-hundred),tst-tls-manydynamic$(n)mod)
+tst-ldconfig-cache-modules := \
+ $(foreach n,01 02 03 04 05,tst-tls-manydynamic$(n)mod)
+$(objpfx)tst-ldconfig-cache.out: $(tst-ldconfig-cache-modules:%=$(objpfx)%.so)
tst-tls-many-dynamic-modules-dep-suffixes = 0 1 2 3 4 5 6 7 8 9 10 11 12 13 \
14 15 16 17 18 19
tst-tls-many-dynamic-modules-dep = \
diff --git a/elf/dl-cache.c b/elf/dl-cache.c
index c1de93f204..07c7e639f5 100644
--- a/elf/dl-cache.c
+++ b/elf/dl-cache.c
@@ -26,11 +26,21 @@
#include <_itoa.h>
#include <dl-hwcaps.h>
#include <dl-isa-level.h>
+#include <fcntl.h>
+#include <sys/stat.h>
/* This is the starting address and the size of the mmap()ed file. */
static struct cache_file *cache;
static struct cache_file_new *cache_new;
static size_t cachesize;
+static struct cache_extension_all_loaded ext;
+
+static struct {
+ typeof ((*(struct __stat64_t64 *)0).st_mtime) mtime;
+ typeof ((*(struct __stat64_t64 *)0).st_ino) ino;
+ typeof ((*(struct __stat64_t64 *)0).st_size) size;
+ typeof ((*(struct __stat64_t64 *)0).st_dev) dev;
+} cache_file_time, new_cache_file_time;
#ifdef SHARED
/* This is used to cache the priorities of glibc-hwcaps
@@ -53,6 +63,7 @@ glibc_hwcaps_priorities_free (void)
free (glibc_hwcaps_priorities);
glibc_hwcaps_priorities = NULL;
glibc_hwcaps_priorities_allocated = 0;
+ glibc_hwcaps_priorities_length = 0;
}
/* Ordered comparison of a hwcaps string from the cache on the left
@@ -84,10 +95,6 @@ glibc_hwcaps_compare (uint32_t left_index, struct dl_hwcaps_priority *right)
static void
glibc_hwcaps_priorities_init (void)
{
- struct cache_extension_all_loaded ext;
- if (!cache_extension_load (cache_new, cache, cachesize, &ext))
- return;
-
uint32_t length = (ext.sections[cache_extension_tag_glibc_hwcaps].size
/ sizeof (uint32_t));
if (length > glibc_hwcaps_priorities_allocated)
@@ -374,6 +381,165 @@ _dl_cache_libcmp (const char *p1, const char *p2)
return *p1 - *p2;
}
+/* Set the cache back to the "no cache" state, which may include
+ cleaning up a loaded cache. */
+static void
+_dl_maybe_unload_ldsocache (void)
+{
+ if (cache != NULL)
+ __munmap (cache, cachesize);
+
+ cache = NULL;
+ cache_new = NULL;
+ cachesize = 0;
+
+#ifdef SHARED
+ glibc_hwcaps_priorities_free ();
+#endif
+}
+
+/* Returns TRUE if for any reason the cache needs to be reloaded
+ (including, the first time, loaded). */
+static bool
+_dl_check_ldsocache_needs_loading (void)
+{
+ int rv;
+ static bool copy_old_time = 0;
+ struct __stat64_t64 new_cache_file_stat;
+
+ /* Save the previous stat every time. We only care when this
+ changes, and we only stat it here, so we can get away with doing
+ the copy now instead of at every single return statement in this
+ function. However, we only need to copy it if the previous stat
+ succeeded. The only way this could be subverted is if the admin
+ moves the file aside, then moves it back, but CACHE would be set
+ to NULL in the interim so that would be detected. */
+ if (copy_old_time)
+ cache_file_time = new_cache_file_time;
+ rv = __fstatat64_time64 (AT_FDCWD, LD_SO_CACHE, &new_cache_file_stat, 0);
+ copy_old_time = (rv >= 0);
+
+ /* No file to load, but there used to be. Assume user intentionally
+ deleted the cache and act accordingly. */
+ if (rv < 0 && cache != NULL)
+ {
+ _dl_maybe_unload_ldsocache ();
+ return false;
+ }
+
+ /* No file to load and no loaded cache, so nothing to do. */
+ if (rv < 0)
+ return false;
+
+ /* Any file is better than no file (likely the first time
+ through). */
+ if (cache == NULL)
+ return true;
+
+ /* Store the fields we check, in order they're likely to differ. */
+ new_cache_file_time.mtime = new_cache_file_stat.st_mtime;
+ new_cache_file_time.ino = new_cache_file_stat.st_ino;
+ new_cache_file_time.size = new_cache_file_stat.st_size;
+ new_cache_file_time.dev = new_cache_file_stat.st_dev;
+
+ /* At this point, NEW_CACHE_FILE_TIME is valid as well as
+ CACHE_FILE_TIME, so we compare them. */
+ return (memcmp (&new_cache_file_time, &cache_file_time,
+ sizeof(new_cache_file_time)));
+}
+
+/* Attempts to load and validate the cache. On return, CACHE is either
+ unchanged (still loaded or still not loaded) or valid. */
+static void
+_dl_maybe_load_ldsocache (void)
+{
+ struct cache_file *tmp_cache = NULL;
+ struct cache_file_new *tmp_cache_new = NULL;
+ size_t tmp_cachesize = 0;
+
+ /* Read the contents of the file. */
+ void *file = _dl_sysdep_read_whole_file (LD_SO_CACHE, &tmp_cachesize,
+ PROT_READ);
+
+ /* We can handle three different cache file formats here:
+ - only the new format
+ - the old libc5/glibc2.0/2.1 format
+ - the old format with the new format in it
+ The following checks if the cache contains any of these formats. */
+ if (file != MAP_FAILED && tmp_cachesize > sizeof *cache_new
+ && memcmp (file, CACHEMAGIC_VERSION_NEW,
+ sizeof CACHEMAGIC_VERSION_NEW - 1) == 0
+ /* Check for corruption, avoiding overflow. */
+ && ((tmp_cachesize - sizeof *cache_new) / sizeof (struct file_entry_new)
+ >= ((struct cache_file_new *) file)->nlibs))
+ {
+ if (! cache_file_new_matches_endian (file))
+ {
+ __munmap (file, tmp_cachesize);
+ return;
+ }
+
+ tmp_cache_new = file;
+ tmp_cache = file;
+ }
+ else if (file != MAP_FAILED && tmp_cachesize > sizeof *cache
+ && memcmp (file, CACHEMAGIC, sizeof CACHEMAGIC - 1) == 0
+ /* Check for corruption, avoiding overflow. */
+ && ((tmp_cachesize - sizeof *cache) / sizeof (struct file_entry)
+ >= ((struct cache_file *) file)->nlibs))
+ {
+ size_t offset;
+ /* Looks ok. */
+ tmp_cache = file;
+
+ /* Check for new version. */
+ offset = ALIGN_CACHE (sizeof (struct cache_file)
+ + tmp_cache->nlibs * sizeof (struct file_entry));
+
+ tmp_cache_new = (struct cache_file_new *) ((void *) tmp_cache + offset);
+ if (tmp_cachesize < (offset + sizeof (struct cache_file_new))
+ || memcmp (tmp_cache_new->magic, CACHEMAGIC_VERSION_NEW,
+ sizeof CACHEMAGIC_VERSION_NEW - 1) != 0)
+ tmp_cache_new = NULL;
+ else
+ {
+ if (! cache_file_new_matches_endian (tmp_cache_new))
+ /* The old-format part of the cache is bogus as well
+ if the endianness does not match. (But it is
+ unclear how the new header can be located if the
+ endianness does not match.) */
+ {
+ __munmap (file, tmp_cachesize);
+ return;
+ }
+ }
+ }
+ else
+ {
+ if (file != MAP_FAILED)
+ __munmap (file, tmp_cachesize);
+ return;
+ }
+
+ struct cache_extension_all_loaded tmp_ext;
+ if (!cache_extension_load (tmp_cache_new, tmp_cache, tmp_cachesize, &tmp_ext))
+ {
+ /* The extension is corrupt, so the cache is corrupt. */
+ __munmap (file, tmp_cachesize);
+ return;
+ }
+
+ /* If we've gotten here, the loaded cache is good and we need to
+ save it. */
+ _dl_maybe_unload_ldsocache ();
+ cache = tmp_cache;
+ cache_new = tmp_cache_new;
+ cachesize = tmp_cachesize;
+ ext = tmp_ext;
+
+ assert (cache != NULL);
+}
+
/* Look up NAME in ld.so.cache and return the file name stored there, or null
if none is found. The cache is loaded if it was not already. If loading
@@ -389,81 +555,14 @@ _dl_load_cache_lookup (const char *name)
if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
_dl_debug_printf (" search cache=%s\n", LD_SO_CACHE);
- if (cache == NULL)
- {
- /* Read the contents of the file. */
- void *file = _dl_sysdep_read_whole_file (LD_SO_CACHE, &cachesize,
- PROT_READ);
-
- /* We can handle three different cache file formats here:
- - only the new format
- - the old libc5/glibc2.0/2.1 format
- - the old format with the new format in it
- The following checks if the cache contains any of these formats. */
- if (file != MAP_FAILED && cachesize > sizeof *cache_new
- && memcmp (file, CACHEMAGIC_VERSION_NEW,
- sizeof CACHEMAGIC_VERSION_NEW - 1) == 0
- /* Check for corruption, avoiding overflow. */
- && ((cachesize - sizeof *cache_new) / sizeof (struct file_entry_new)
- >= ((struct cache_file_new *) file)->nlibs))
- {
- if (! cache_file_new_matches_endian (file))
- {
- __munmap (file, cachesize);
- file = (void *) -1;
- }
- cache_new = file;
- cache = file;
- }
- else if (file != MAP_FAILED && cachesize > sizeof *cache
- && memcmp (file, CACHEMAGIC, sizeof CACHEMAGIC - 1) == 0
- /* Check for corruption, avoiding overflow. */
- && ((cachesize - sizeof *cache) / sizeof (struct file_entry)
- >= ((struct cache_file *) file)->nlibs))
- {
- size_t offset;
- /* Looks ok. */
- cache = file;
-
- /* Check for new version. */
- offset = ALIGN_CACHE (sizeof (struct cache_file)
- + cache->nlibs * sizeof (struct file_entry));
-
- cache_new = (struct cache_file_new *) ((void *) cache + offset);
- if (cachesize < (offset + sizeof (struct cache_file_new))
- || memcmp (cache_new->magic, CACHEMAGIC_VERSION_NEW,
- sizeof CACHEMAGIC_VERSION_NEW - 1) != 0)
- cache_new = (void *) -1;
- else
- {
- if (! cache_file_new_matches_endian (cache_new))
- {
- /* The old-format part of the cache is bogus as well
- if the endianness does not match. (But it is
- unclear how the new header can be located if the
- endianness does not match.) */
- cache = (void *) -1;
- cache_new = (void *) -1;
- __munmap (file, cachesize);
- }
- }
- }
- else
- {
- if (file != MAP_FAILED)
- __munmap (file, cachesize);
- cache = (void *) -1;
- }
+ if (_dl_check_ldsocache_needs_loading ())
+ _dl_maybe_load_ldsocache ();
- assert (cache != NULL);
- }
-
- if (cache == (void *) -1)
- /* Previously looked for the cache file and didn't find it. */
+ if (cache == NULL)
return NULL;
const char *best;
- if (cache_new != (void *) -1)
+ if (cache_new != NULL)
{
const char *string_table = (const char *) cache_new;
best = search_cache (string_table, cachesize,
@@ -510,14 +609,7 @@ _dl_load_cache_lookup (const char *name)
void
_dl_unload_cache (void)
{
- if (cache != NULL && cache != (struct cache_file *) -1)
- {
- __munmap (cache, cachesize);
- cache = NULL;
- }
-#ifdef SHARED
- /* This marks the glibc_hwcaps_priorities array as out-of-date. */
- glibc_hwcaps_priorities_length = 0;
-#endif
+ /* Functionality is no longer needed, but kept for internal ABI for
+ now. */
}
#endif
diff --git a/elf/tst-ldconfig-cache.c b/elf/tst-ldconfig-cache.c
new file mode 100644
index 0000000000..9f71418b3a
--- /dev/null
+++ b/elf/tst-ldconfig-cache.c
@@ -0,0 +1,134 @@
+/* Test ldconfig cache is correctly used when changed.
+ 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; see the file COPYING.LIB. If
+ not, see <https://www.gnu.org/licenses/>. */
+
+/* What we're testing for: We initially load ld.so.cache at startup
+ and remember it. If we detect that ld.so.cache has changed, and we
+ can load it successfully, we replace our remember it. If it
+ doesn't change, or if the new version is corrupted, we continue
+ using the old remembered copy. */
+
+#include <fcntl.h>
+
+#include <support/support.h>
+#include <support/check.h>
+
+#include <support/xstdio.h>
+#include <support/xstdlib.h>
+#include <support/xdlfcn.h>
+#include <support/xunistd.h>
+
+
+/* Verify that we can (or can't) load one of our test objects. */
+static void
+try (int i, int invert)
+{
+ char dlname[100];
+ char symname[100];
+ int (*proc)(int);
+ void *dl;
+
+ /* These match the objects copied by tst-ldconfig-cache.script,
+ copied from tst-tls-manydynamic*.so. */
+ sprintf (dlname, "libcache%d.so", i);
+ sprintf (symname, "set_value_%02d", i);
+
+ dl = dlopen (dlname, RTLD_NOW);
+
+ if (invert)
+ {
+ /* This is a negative test; if the object doesn't load the test
+ passes. */
+ TEST_VERIFY (dl == NULL);
+ return;
+ }
+ else
+ {
+ /* This is a positive test; if the object doesn't load the test
+ fails. */
+ if (dl == NULL)
+ FAIL_EXIT1 ("error: dlopen: %s\n", dlerror ());
+ }
+
+ proc = xdlsym (dl, symname);
+ /* We don't need to call the symbol, just make sure it exists. */
+ TEST_VERIFY (proc != NULL);
+
+ xdlclose (dl);
+}
+
+/* Cause corruption in the cache that should prevent loading it. */
+static void
+corrupt (void)
+{
+ int fd = xopen ("/etc/ld.so.cache", O_RDWR, 0);
+ char bytes[] = { 15, 32, 184, 4 };
+ xwrite (fd, bytes, sizeof(bytes));
+ xclose (fd);
+}
+
+/* Regenerate the cache from ld.so.conf. */
+static void
+ldconfig (void)
+{
+ xsystem ("/sbin/ldconfig -X");
+}
+
+/* Change ld.so.conf to refer to the new directory, and generate a new
+ cache. */
+static void
+newpath (const char *p)
+{
+ FILE *f = xfopen ("/etc/ld.so.conf", "w");
+ fprintf (f, "%s\n", p);
+ xfclose (f);
+
+ ldconfig ();
+}
+
+static int
+do_test (void)
+{
+ /* Test that the cache we started with can still load objects in
+ /a. */
+ try (1, 0);
+
+ /* Create a new cache that doesn't include /a but corrupt it. Test
+ that we still use the cache with /a in it. */
+ newpath ("/c");
+ corrupt ();
+ try (2, 0);
+
+ /* Regenerate a clean cache with /a in it and verify we can load
+ objects in /a. */
+ newpath ("/a");
+ try (3, 0);
+
+ /* Generate a new cache with /b but not /a and make sure objects
+ in /a can't be loaded. */
+ newpath ("/b");
+ try (3, 1);
+
+ /* But objects in /b can be loaded. */
+ try (4, 0);
+ /* Even multiple times. */
+ try (5, 0);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/elf/tst-ldconfig-cache.root/etc/ld.so.conf b/elf/tst-ldconfig-cache.root/etc/ld.so.conf
new file mode 100644
index 0000000000..2b4c2d7817
--- /dev/null
+++ b/elf/tst-ldconfig-cache.root/etc/ld.so.conf
@@ -0,0 +1,3 @@
+/lib
+/lib64
+/a
diff --git a/elf/tst-ldconfig-cache.root/ldconfig.req b/elf/tst-ldconfig-cache.root/ldconfig.req
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/elf/tst-ldconfig-cache.root/tst-ldconfig-cache.script b/elf/tst-ldconfig-cache.root/tst-ldconfig-cache.script
new file mode 100644
index 0000000000..4f258cac1e
--- /dev/null
+++ b/elf/tst-ldconfig-cache.root/tst-ldconfig-cache.script
@@ -0,0 +1,7 @@
+mkdirp 0755 /a
+cp $B/elf/tst-tls-manydynamic01mod.so /a/libcache1.so
+cp $B/elf/tst-tls-manydynamic02mod.so /a/libcache2.so
+cp $B/elf/tst-tls-manydynamic03mod.so /a/libcache3.so
+mkdirp 0755 /b
+cp $B/elf/tst-tls-manydynamic04mod.so /b/libcache4.so
+cp $B/elf/tst-tls-manydynamic05mod.so /b/libcache5.so
--
2.47.3
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v9 4/5] Add system-wide tunables: Filters
2026-06-26 3:30 [PATCH v9 0/5] Add system-wide tunables DJ Delorie
@ 2026-06-26 3:30 ` DJ Delorie
2026-06-30 15:46 ` Arjun Shankar
2026-06-26 3:30 ` [PATCH v9 5/5] Add system-wide tunables: manual DJ Delorie
` (4 subsequent siblings)
5 siblings, 1 reply; 14+ messages in thread
From: DJ Delorie @ 2026-06-26 3:30 UTC (permalink / raw)
To: libc-alpha
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
---
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;
+ 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
+ <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);
+ /* 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 ();
--
2.47.3
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v9 1/5] Add system-wide tunables: ldconfig part
2026-06-26 3:30 [PATCH v9 0/5] Add system-wide tunables DJ Delorie
` (3 preceding siblings ...)
2026-06-26 3:30 ` [PATCH v9 2/5] Add system-wide tunables: cache ld.so.cache DJ Delorie
@ 2026-06-26 3:30 ` DJ Delorie
2026-06-30 15:39 ` Arjun Shankar
2026-06-30 15:52 ` [PATCH v9 0/5] Add system-wide tunables Arjun Shankar
5 siblings, 1 reply; 14+ messages in thread
From: DJ Delorie @ 2026-06-26 3:30 UTC (permalink / raw)
To: libc-alpha
Adds support for reading /etc/tunables.conf
The file contains one line per tunable, like this:
glibc.foo.bar=14
glibc.malloc.more=0
Additionally, each line can be prefixed with a single word or character
that controls overridability by the GLIBC_TUNABLES env var:
overridable glibc.foo=0
+glibc.foo=0
^ May be overridden (the default)
nonoverridable glibc.foo=0
-glibc.foo=0
^ May not be overridden
The tunable cache format allows for a filter to be assigned to
each tunable, to be used at program start to decide if a tunable
applies to that program. No such filters have yet been specified.
The cache format also stores a pre-parsed value for the tunable, and
the ID of the tunable, to improve load-time performance.
---
elf/Makefile | 1 +
elf/cache.c | 86 ++++++++++-
elf/ldconfig.c | 23 ++-
elf/tunconf.c | 293 +++++++++++++++++++++++++++++++++++++
elf/tunconf.h | 39 +++++
sysdeps/generic/dl-cache.h | 20 +++
sysdeps/generic/ldconfig.h | 6 +-
7 files changed, 461 insertions(+), 7 deletions(-)
create mode 100644 elf/tunconf.c
create mode 100644 elf/tunconf.h
diff --git a/elf/Makefile b/elf/Makefile
index 6514428294..d01db49171 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -225,6 +225,7 @@ ldconfig-modules := \
readlib \
static-stubs \
stringtable \
+ tunconf \
xmalloc \
xstrdup \
# ldconfig-modules
diff --git a/elf/cache.c b/elf/cache.c
index a6dc85dc0f..58b1412b32 100644
--- a/elf/cache.c
+++ b/elf/cache.c
@@ -36,6 +36,7 @@
#include <dl-cache.h>
#include <version.h>
#include <stringtable.h>
+#include <tunconf.h>
/* Used to store library names, paths, and other strings. */
static struct stringtable strings;
@@ -275,7 +276,8 @@ check_new_cache (struct cache_file_new *cache)
/* Print the extension information in *EXT. */
static void
-print_extensions (struct cache_extension_all_loaded *ext)
+print_extensions (struct cache_extension_all_loaded *ext,
+ const char *cache_data)
{
if (ext->sections[cache_extension_tag_generator].base != NULL)
{
@@ -284,6 +286,43 @@ print_extensions (struct cache_extension_all_loaded *ext)
ext->sections[cache_extension_tag_generator].size, stdout);
putchar ('\n');
}
+ if (ext->sections[cache_extension_tag_tunables].base != NULL)
+ {
+ struct tunable_header_cached *thc;
+ struct tunable_entry_cached *tec;
+ int i, count;
+
+ thc = (struct tunable_header_cached *)
+ ext->sections[cache_extension_tag_tunables].base;
+ tec = thc->tunables;
+ count = thc->num_tunables;
+ printf("tunables sig 0x%08x ver 0x%08x count %u\n",
+ thc->signature, thc->version, thc->num_tunables);
+ /* Check that COUNT won't overflow our data block. */
+ assert (ext->sections[cache_extension_tag_tunables].base
+ + ext->sections[cache_extension_tag_tunables].size
+ == (void *) & tec[count]);
+ for (i = 0; i < count; ++ i)
+ {
+ printf (" [%d] %s = %s [flags 0x%08x (",
+ i,
+ cache_data + tec[i].name_offset,
+ cache_data + tec[i].value_offset,
+ tec[i].flags);
+ if (tec[i].flags & TUNCONF_FLAG_PARSED)
+ printf ("parsed,");
+ if (tec[i].flags & TUNCONF_FLAG_NEGATIVE)
+ printf ("negative,");
+ if ((tec[i].flags & TUNCONF_FLAG_OVERRIDABLE)
+ == TUNCONF_OVERRIDE_ALLOW)
+ printf ("overridable");
+ else
+ printf ("nonoverridable");
+ if (tec[i].flag_offset != 0)
+ printf (",'%s'", cache_data + tec[i].flag_offset);
+ printf (")]\n");
+ }
+ }
}
/* Print the whole cache file, if a file contains the new cache format
@@ -394,7 +433,7 @@ print_cache (const char *cache_name)
cache_new->libs[i].hwcap, hwcaps_string,
cache_data + cache_new->libs[i].value);
}
- print_extensions (&ext);
+ print_extensions (&ext, cache_data);
}
/* Cleanup. */
munmap (cache, cache_size);
@@ -466,6 +505,18 @@ write_extensions (int fd, uint32_t str_offset,
if (p->used)
hwcaps_array[p->section_index] = str_offset + p->name->offset;
+ struct tunable_header_cached *tunable_data;
+ size_t tunable_size;
+ size_t tunable_aligner = 0;
+
+ tunable_data = get_tunconf_ext (str_offset);
+
+ if (tunable_data == NULL)
+ {
+ /* There is no section for tunables data. */
+ hwcaps_offset -= sizeof (struct cache_extension_section);
+ }
+
/* This is the offset of the generator string. */
uint32_t generator_offset = hwcaps_offset;
if (hwcaps_count == 0)
@@ -498,6 +549,23 @@ write_extensions (int fd, uint32_t str_offset,
ext->sections[xid].size = hwcaps_size;
}
+ if (tunable_data != NULL)
+ {
+ uint32_t tunable_offset_ua;
+ uint32_t tunable_offset;
+
+ tunable_size = TUNCONF_SIZE (tunable_data);
+ tunable_offset_ua = generator_offset + strlen (generator);
+ tunable_offset = ALIGN_UP (tunable_offset_ua, 8);
+ tunable_aligner = tunable_offset - tunable_offset_ua;
+
+ ++xid;
+ ext->sections[xid].tag = cache_extension_tag_tunables;
+ ext->sections[xid].flags = 0;
+ ext->sections[xid].offset = tunable_offset;
+ ext->sections[xid].size = tunable_size;
+ }
+
++xid;
ext->count = xid;
assert (xid <= cache_extension_count);
@@ -509,6 +577,14 @@ write_extensions (int fd, uint32_t str_offset,
|| write (fd, generator, strlen (generator)) != strlen (generator))
error (EXIT_FAILURE, errno, _("Writing of cache extension data failed"));
+ if (tunable_data)
+ {
+ if (write (fd, " ", tunable_aligner) != tunable_aligner
+ || write (fd, tunable_data, tunable_size) != tunable_size)
+ error (EXIT_FAILURE, errno, _("Writing of cache tunable data failed"));
+ free (tunable_data);
+ }
+
free (hwcaps_array);
free (ext);
}
@@ -1106,3 +1182,9 @@ out_fail:
free (temp_name);
free (file_entries);
}
+
+struct stringtable_entry *
+cache_store_string (const char *string)
+{
+ return stringtable_add (&strings, string);
+}
diff --git a/elf/ldconfig.c b/elf/ldconfig.c
index 070e933df6..11b063eb5c 100644
--- a/elf/ldconfig.c
+++ b/elf/ldconfig.c
@@ -44,12 +44,17 @@
#include <dl-cache.h>
#include <dl-hwcaps.h>
#include <dl-is_dso.h>
+#include "tunconf.h"
#ifndef LD_SO_CONF
# define LD_SO_CONF SYSCONFDIR "/ld.so.conf"
#endif
+#ifndef TUNABLES_CONF
+# define TUNABLES_CONF SYSCONFDIR "/tunables.conf"
+#endif
+
/* Get libc version number. */
#include <version.h>
@@ -107,9 +112,12 @@ static int opt_ignore_aux_cache;
/* Cache file to use. */
static char *cache_file;
-/* Configuration file. */
+/* Configuration file for libraries. */
static const char *config_file;
+/* Configuration file for tunables. */
+static const char *tunconfig_file;
+
/* Name and version of program. */
static void print_version (FILE *stream, struct argp_state *state);
void (*argp_program_version_hook) (FILE *, struct argp_state *)
@@ -127,7 +135,8 @@ static const struct argp_option options[] =
{ NULL, 'X', NULL, 0, N_("Don't update symbolic links"), 0},
{ NULL, 'r', N_("ROOT"), 0, N_("Change to and use ROOT as root directory"), 0},
{ NULL, 'C', N_("CACHE"), 0, N_("Use CACHE as cache file"), 0},
- { NULL, 'f', N_("CONF"), 0, N_("Use CONF as configuration file"), 0},
+ { NULL, 'f', N_("CONF"), 0, N_("Use CONF as configuration file for libraries"), 0},
+ { NULL, 't', N_("TUNCONF"), 0, N_("Use TUNCONF as configuration file for tunables"), 0},
{ NULL, 'n', NULL, 0, N_("Only process directories specified on the command line. Don't build cache."), 0},
{ NULL, 'l', NULL, 0, N_("Manually link individual libraries."), 0},
{ "format", 'c', N_("FORMAT"), 0, N_("Format to use: new (default), old, or compat"), 0},
@@ -164,6 +173,9 @@ parse_opt (int key, char *arg, struct argp_state *state)
case 'f':
config_file = arg;
break;
+ case 't':
+ tunconfig_file = arg;
+ break;
case 'i':
opt_ignore_aux_cache = 1;
break;
@@ -421,7 +433,7 @@ add_dir_1 (const char *line, const char *from_file, int from_line)
}
static void
-add_dir_callback (const char *line, const char *from_file, int from_line)
+add_dir_callback (char *line, const char *from_file, int from_line)
{
if (!strncasecmp (line, "hwcap", 5) && isblank (line[5]))
error (0, 0, _("%s:%u: hwcap directive ignored"), from_file, from_line);
@@ -1089,6 +1101,9 @@ main (int argc, char **argv)
if (config_file == NULL)
config_file = LD_SO_CONF;
+ if (tunconfig_file == NULL)
+ tunconfig_file = TUNABLES_CONF;
+
if (opt_print_cache)
{
if (opt_chroot != NULL)
@@ -1164,6 +1179,8 @@ main (int argc, char **argv)
search_dirs ();
+ parse_tunconf (tunconfig_file, opt_chroot);
+
if (opt_build_cache)
{
save_cache (cache_file);
diff --git a/elf/tunconf.c b/elf/tunconf.c
new file mode 100644
index 0000000000..5e77729e22
--- /dev/null
+++ b/elf/tunconf.c
@@ -0,0 +1,293 @@
+/* Manage /etc/tunables.*
+ Copyright (C) 1999-2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; version 2 of the License, or
+ (at your option) any later version.
+
+ This program 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses/>. */
+
+#include <alloca.h>
+#include <argp.h>
+#include <assert.h>
+#include <error.h>
+#include <inttypes.h>
+#include <glob.h>
+#include <libgen.h>
+#include <libintl.h>
+#include <locale.h>
+#include <programs/xmalloc.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <stdlib.h>
+#include <string.h>
+#define TUNABLES_INTERNAL
+#include <elf/dl-tunables.h>
+#include <unistd.h>
+
+#include <ldconfig.h>
+#include <dl-cache.h>
+#include <version.h>
+#include <stringtable.h>
+#include <array_length.h>
+
+#include "tunconf.h"
+
+/*----------------------------------------------------------------------*/
+
+#ifndef TUNABLES_CONF
+# define TUNABLES_CONF SYSCONFDIR "/tunables.conf"
+#endif
+
+#ifndef TUNABLES_CACHE
+# define TUNABLES_CACHE SYSCONFDIR "/tunables.cache"
+#endif
+
+/* Tunable Override Policies. */
+typedef enum {
+ TOP_ALLOW = 0, /* let the environment variable override */
+ TOP_DENY /* no override allowed */
+} TOP;
+
+struct tunable_entry_int {
+ struct stringtable_entry *name;
+ struct stringtable_entry *value;
+ 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;
+
+ struct tunable_entry_int *next;
+};
+
+struct tunable_entry_int *entry_list;
+
+/*----------------------------------------------------------------------*/
+
+static void
+add_tunable (char *line, const char *filename, int lineno)
+{
+ TOP top = TOP_ALLOW;
+ char *name;
+ char *value;
+ char *eq;
+ char *orig_line;
+ struct tunable_entry_int *entry;
+ int i, id;
+ static struct tunable_entry_int **entry_list_next = &entry_list;
+
+ orig_line = line;
+
+ /* Leading whitespace has already been stripped. */
+
+ /* Canonicalize the line. */
+ for (i=0; line[i]; i++)
+ {
+ if (line[i] == '\t')
+ line[i] = ' ';
+ if (line[i] == '\n' || line[i] == '\r')
+ {
+ line[i] = '\0';
+ break;
+ }
+ }
+
+ /* Parse modifiers. */
+ while (*line)
+ {
+ if (strncmp (line, "overridable ", 13) == 0)
+ {
+ top = TOP_ALLOW;
+ /* The line++ below skips the space. */
+ line += 12;
+ }
+ else if (strncmp (line, "nonoverridable ", 16) == 0)
+ {
+ top = TOP_DENY;
+ line += 15;
+ }
+ else switch (*line)
+ {
+ case '+':
+ top = TOP_ALLOW;
+ break;
+ case '-':
+ top = TOP_DENY;
+ break;
+ case ' ':
+ break;
+
+ default:
+ goto done;
+ }
+ line ++;
+ }
+ done:
+
+ /* NAME now points to the start of the tunable name. */
+ name = line;
+
+ /* Look for the '=' separator. */
+ eq = strchr (line, '=');
+ if (eq == NULL)
+ {
+ error_at_line (0, 0, filename, lineno,
+ "syntax error, line ignored: `%s' (missing '=')",
+ orig_line);
+ return;
+ }
+
+ if (eq == name)
+ {
+ error_at_line (0, 0, filename, lineno,
+ "syntax error, line ignored: `%s' (missing tunable name)",
+ orig_line);
+ return;
+ }
+
+ /* At this point, EQ actually points to '='. */
+ value = eq + 1;
+
+ while (*value && isspace(*value))
+ value ++;
+
+ if (*value == 0)
+ {
+ error_at_line (0, 0, filename, lineno,
+ "syntax error, line ignored: `%s' (missing value)",
+ orig_line);
+ return;
+ }
+
+ /* VALUE now points to the start of the value. */
+
+ /* Split the string into name and value c-strings. */
+ *eq = 0;
+ /* Trim trailing whitespace off NAME. */
+ while (*name && isspace (name[strlen(name)-1]))
+ name[strlen(name)-1] = 0;
+ /* Trim trailing whitespace off VALUE. */
+ while (*value && isspace (value[strlen(value)-1]))
+ value[strlen(value)-1] = 0;
+
+ id = -1;
+ for (i = 0; i < array_length (tunable_list); i ++)
+ if (strcmp (tunable_list[i].name, name) == 0)
+ {
+ id = i;
+ break;
+ }
+ if (id == -1)
+ printf ("%s:%d: Warning: tunable %s not recognized.\n",
+ filename, lineno, name);
+
+ entry = (struct tunable_entry_int *) xcalloc (sizeof (struct tunable_entry_int), 1);
+ entry->name = cache_store_string (name);
+ entry->value = cache_store_string (value);
+ entry->tunable_id = id;
+ entry->top = top;
+
+ if (value[0] == '-')
+ {
+ entry->value_is_negative = 1;
+ if (sscanf (value, "%lld", &entry->value_sll) == 1)
+ entry->value_was_parsed = 1;
+ }
+ else
+ {
+ entry->value_is_negative = 0;
+ if (sscanf (value, "%llu", &entry->value_ull) == 1)
+ entry->value_was_parsed = 1;
+ }
+
+ *entry_list_next = entry;
+ entry_list_next = & (entry->next);
+}
+
+void
+parse_tunconf (const char *filename, char *opt_chroot)
+{
+ ldconfig_parse_config (filename, opt_chroot, add_tunable);
+}
+
+struct tunable_header_cached *
+get_tunconf_ext (uint32_t string_table_offset)
+{
+ struct tunable_entry_int *tei;
+ struct tunable_header_cached *thc;
+ size_t count;
+ size_t size;
+
+ /* First, count the number of entries we have. */
+ tei = entry_list;
+ count = 0;
+ while (tei != NULL)
+ {
+ ++ count;
+ tei = tei->next;
+ }
+ if (count == 0)
+ return NULL;
+
+ /* Allocate enough space for the whole cached block. */
+ size = sizeof (struct tunable_header_cached)
+ + sizeof (struct tunable_entry_cached) * count;
+ thc = (struct tunable_header_cached *) xmalloc (size);
+
+ /* Now, fill in the structures. */
+
+ thc->signature = TUNCONF_SIGNATURE;
+ thc->version = TUNCONF_VERSION;
+ thc->num_tunables = count;
+ thc->unused_1 = 0;
+
+ tei = entry_list;
+ count = 0;
+ while (tei != NULL)
+ {
+ struct tunable_entry_cached *tec;
+
+ tec = & ( thc->tunables[count] );
+
+ tec->flags = 0;
+ if (tei->value_was_parsed)
+ tec->flags |= TUNCONF_FLAG_PARSED;
+ if (tei->value_is_negative)
+ tec->flags |= TUNCONF_FLAG_NEGATIVE;
+ switch (tei->top)
+ {
+ case TOP_ALLOW:
+ tec->flags |= TUNCONF_OVERRIDE_ALLOW;
+ break;
+ case TOP_DENY:
+ tec->flags |= TUNCONF_OVERRIDE_DENY;
+ break;
+ }
+
+ 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;
+ tec->unused_1 = 0;
+ if (tei->value_is_negative)
+ tec->parsed_value = (uint64_t) tei->value_sll;
+ else
+ tec->parsed_value = (uint64_t) tei->value_ull;
+
+ ++ count;
+ tei = tei->next;
+ }
+
+ return thc;
+}
diff --git a/elf/tunconf.h b/elf/tunconf.h
new file mode 100644
index 0000000000..b446211f94
--- /dev/null
+++ b/elf/tunconf.h
@@ -0,0 +1,39 @@
+#define TUNCONF_SIGNATURE 0x7c3ba94f
+#define TUNCONF_VERSION 0x01000000
+
+#define TUNCONF_FLAG_PARSED 0x00000001
+#define TUNCONF_FLAG_NEGATIVE 0x00000002
+
+#define TUNCONF_FLAG_OVERRIDABLE 0x0000000C
+#define TUNCONF_OVERRIDE_DENY 0x00000004
+#define TUNCONF_OVERRIDE_ALLOW 0x00000000
+
+#define TUNCONF_FLAG_FILTER 0x0000ff00
+#define TUNCONF_FILTER_NONE 0x00000000
+#define TUNCONF_FILTER_PERPROC 0x00000100
+
+/* An array of [num_tunables] of these follows the below. */
+struct tunable_entry_cached {
+ uint32_t flags;
+ uint32_t tunable_id;
+ uint32_t name_offset;
+ uint32_t value_offset;
+ uint32_t flag_offset;
+ uint32_t unused_1; /* for alignment */
+ uint64_t parsed_value;
+};
+
+/* One of these is at the beginning of the tunable data block. */
+struct tunable_header_cached {
+ uint32_t signature;
+ uint32_t version;
+ uint32_t num_tunables;
+ uint32_t unused_1; /* for alignment */
+ struct tunable_entry_cached tunables[0 /* num_tunables */];
+};
+
+void parse_tunconf (const char *filename, char *opt_chroot);
+
+struct tunable_header_cached * get_tunconf_ext (uint32_t str_offset);
+#define TUNCONF_SIZE(thc_p) (sizeof(struct tunable_header_cached) \
+ + thc_p->num_tunables * sizeof (struct tunable_entry_cached))
diff --git a/sysdeps/generic/dl-cache.h b/sysdeps/generic/dl-cache.h
index 26f0c1e0bd..972ab32b86 100644
--- a/sysdeps/generic/dl-cache.h
+++ b/sysdeps/generic/dl-cache.h
@@ -220,6 +220,12 @@ enum cache_extension_tag
size must be a multiple of 4. */
cache_extension_tag_glibc_hwcaps,
+ /* Array of system-wide tunable information.
+
+ For this section, 8-byte alignment is required, and the section
+ size must be a multiple of 8. */
+ cache_extension_tag_tunables,
+
/* Total number of known cache extension tags. */
cache_extension_count
};
@@ -293,6 +299,20 @@ cache_extension_verify (struct cache_extension_all_loaded *loaded)
hwcaps->flags = 0;
}
}
+ {
+ /* Section must not be empty, it must be aligned at 8 bytes, and
+ the size must be a multiple of 8. */
+ struct cache_extension_loaded *tun
+ = &loaded->sections[cache_extension_tag_tunables];
+ if (tun->size == 0
+ || ((uintptr_t) tun->base % 8) != 0
+ || (tun->size % 8) != 0)
+ {
+ tun->base = NULL;
+ tun->size = 0;
+ tun->flags = 0;
+ }
+ }
}
static bool __attribute__ ((unused))
diff --git a/sysdeps/generic/ldconfig.h b/sysdeps/generic/ldconfig.h
index 22d0fd0f82..800714659e 100644
--- a/sysdeps/generic/ldconfig.h
+++ b/sysdeps/generic/ldconfig.h
@@ -74,6 +74,8 @@ extern void add_to_cache (const char *path, const char *filename,
unsigned int isa_level,
struct glibc_hwcaps_subdirectory *);
+extern struct stringtable_entry *cache_store_string (const char *string);
+
extern void init_aux_cache (void);
extern void load_aux_cache (const char *aux_cache_name);
@@ -112,8 +114,8 @@ enum opt_format
extern enum opt_format opt_format;
/* Declared in ldconfig-parse.c */
-typedef void (*ldconfig_parse_config_cb) (const char *line,
- const char *from_file, int from_line);
+typedef void (*ldconfig_parse_config_cb) (char *line,
+ const char *from_file, int from_line);
void ldconfig_parse_config (const char *filename, char *opt_chroot,
ldconfig_parse_config_cb cb);
--
2.47.3
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v9 1/5] Add system-wide tunables: ldconfig part
2026-06-26 3:30 ` [PATCH v9 1/5] Add system-wide tunables: ldconfig part DJ Delorie
@ 2026-06-30 15:39 ` Arjun Shankar
0 siblings, 0 replies; 14+ messages in thread
From: Arjun Shankar @ 2026-06-30 15:39 UTC (permalink / raw)
To: DJ Delorie; +Cc: libc-alpha
Hi DJ,
> Adds support for reading /etc/tunables.conf
>
> The file contains one line per tunable, like this:
>
> glibc.foo.bar=14
> glibc.malloc.more=0
>
> Additionally, each line can be prefixed with a single word or character
> that controls overridability by the GLIBC_TUNABLES env var:
>
> overridable glibc.foo=0
> +glibc.foo=0
> ^ May be overridden (the default)
> nonoverridable glibc.foo=0
> -glibc.foo=0
> ^ May not be overridden
>
> The tunable cache format allows for a filter to be assigned to
> each tunable, to be used at program start to decide if a tunable
> applies to that program. No such filters have yet been specified.
>
> The cache format also stores a pre-parsed value for the tunable, and
> the ID of the tunable, to improve load-time performance.
This version looks good to me.
Reviewed-by: Arjun Shankar <arjun@redhat.com>
> ---
> elf/Makefile | 1 +
> elf/cache.c | 86 ++++++++++-
> elf/ldconfig.c | 23 ++-
> elf/tunconf.c | 293 +++++++++++++++++++++++++++++++++++++
> elf/tunconf.h | 39 +++++
> sysdeps/generic/dl-cache.h | 20 +++
> sysdeps/generic/ldconfig.h | 6 +-
> 7 files changed, 461 insertions(+), 7 deletions(-)
> create mode 100644 elf/tunconf.c
> create mode 100644 elf/tunconf.h
>
> diff --git a/elf/Makefile b/elf/Makefile
> index 6514428294..d01db49171 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -225,6 +225,7 @@ ldconfig-modules := \
> readlib \
> static-stubs \
> stringtable \
> + tunconf \
> xmalloc \
> xstrdup \
> # ldconfig-modules
> diff --git a/elf/cache.c b/elf/cache.c
> index a6dc85dc0f..58b1412b32 100644
> --- a/elf/cache.c
> +++ b/elf/cache.c
> @@ -36,6 +36,7 @@
> #include <dl-cache.h>
> #include <version.h>
> #include <stringtable.h>
> +#include <tunconf.h>
>
> /* Used to store library names, paths, and other strings. */
> static struct stringtable strings;
> @@ -275,7 +276,8 @@ check_new_cache (struct cache_file_new *cache)
>
> /* Print the extension information in *EXT. */
> static void
> -print_extensions (struct cache_extension_all_loaded *ext)
> +print_extensions (struct cache_extension_all_loaded *ext,
> + const char *cache_data)
> {
> if (ext->sections[cache_extension_tag_generator].base != NULL)
> {
> @@ -284,6 +286,43 @@ print_extensions (struct cache_extension_all_loaded *ext)
> ext->sections[cache_extension_tag_generator].size, stdout);
> putchar ('\n');
> }
> + if (ext->sections[cache_extension_tag_tunables].base != NULL)
> + {
> + struct tunable_header_cached *thc;
> + struct tunable_entry_cached *tec;
> + int i, count;
> +
> + thc = (struct tunable_header_cached *)
> + ext->sections[cache_extension_tag_tunables].base;
> + tec = thc->tunables;
> + count = thc->num_tunables;
> + printf("tunables sig 0x%08x ver 0x%08x count %u\n",
> + thc->signature, thc->version, thc->num_tunables);
> + /* Check that COUNT won't overflow our data block. */
> + assert (ext->sections[cache_extension_tag_tunables].base
> + + ext->sections[cache_extension_tag_tunables].size
> + == (void *) & tec[count]);
> + for (i = 0; i < count; ++ i)
> + {
> + printf (" [%d] %s = %s [flags 0x%08x (",
> + i,
> + cache_data + tec[i].name_offset,
> + cache_data + tec[i].value_offset,
> + tec[i].flags);
> + if (tec[i].flags & TUNCONF_FLAG_PARSED)
> + printf ("parsed,");
> + if (tec[i].flags & TUNCONF_FLAG_NEGATIVE)
> + printf ("negative,");
> + if ((tec[i].flags & TUNCONF_FLAG_OVERRIDABLE)
> + == TUNCONF_OVERRIDE_ALLOW)
> + printf ("overridable");
> + else
> + printf ("nonoverridable");
> + if (tec[i].flag_offset != 0)
> + printf (",'%s'", cache_data + tec[i].flag_offset);
> + printf (")]\n");
> + }
> + }
> }
>
> /* Print the whole cache file, if a file contains the new cache format
> @@ -394,7 +433,7 @@ print_cache (const char *cache_name)
> cache_new->libs[i].hwcap, hwcaps_string,
> cache_data + cache_new->libs[i].value);
> }
> - print_extensions (&ext);
> + print_extensions (&ext, cache_data);
> }
> /* Cleanup. */
> munmap (cache, cache_size);
> @@ -466,6 +505,18 @@ write_extensions (int fd, uint32_t str_offset,
> if (p->used)
> hwcaps_array[p->section_index] = str_offset + p->name->offset;
>
> + struct tunable_header_cached *tunable_data;
> + size_t tunable_size;
> + size_t tunable_aligner = 0;
> +
> + tunable_data = get_tunconf_ext (str_offset);
> +
> + if (tunable_data == NULL)
> + {
> + /* There is no section for tunables data. */
> + hwcaps_offset -= sizeof (struct cache_extension_section);
> + }
> +
> /* This is the offset of the generator string. */
> uint32_t generator_offset = hwcaps_offset;
> if (hwcaps_count == 0)
> @@ -498,6 +549,23 @@ write_extensions (int fd, uint32_t str_offset,
> ext->sections[xid].size = hwcaps_size;
> }
>
> + if (tunable_data != NULL)
> + {
> + uint32_t tunable_offset_ua;
> + uint32_t tunable_offset;
> +
> + tunable_size = TUNCONF_SIZE (tunable_data);
> + tunable_offset_ua = generator_offset + strlen (generator);
> + tunable_offset = ALIGN_UP (tunable_offset_ua, 8);
> + tunable_aligner = tunable_offset - tunable_offset_ua;
> +
> + ++xid;
> + ext->sections[xid].tag = cache_extension_tag_tunables;
> + ext->sections[xid].flags = 0;
> + ext->sections[xid].offset = tunable_offset;
> + ext->sections[xid].size = tunable_size;
> + }
> +
> ++xid;
> ext->count = xid;
> assert (xid <= cache_extension_count);
> @@ -509,6 +577,14 @@ write_extensions (int fd, uint32_t str_offset,
> || write (fd, generator, strlen (generator)) != strlen (generator))
> error (EXIT_FAILURE, errno, _("Writing of cache extension data failed"));
>
> + if (tunable_data)
> + {
> + if (write (fd, " ", tunable_aligner) != tunable_aligner
> + || write (fd, tunable_data, tunable_size) != tunable_size)
> + error (EXIT_FAILURE, errno, _("Writing of cache tunable data failed"));
> + free (tunable_data);
> + }
> +
> free (hwcaps_array);
> free (ext);
> }
> @@ -1106,3 +1182,9 @@ out_fail:
> free (temp_name);
> free (file_entries);
> }
> +
> +struct stringtable_entry *
> +cache_store_string (const char *string)
> +{
> + return stringtable_add (&strings, string);
> +}
> diff --git a/elf/ldconfig.c b/elf/ldconfig.c
> index 070e933df6..11b063eb5c 100644
> --- a/elf/ldconfig.c
> +++ b/elf/ldconfig.c
> @@ -44,12 +44,17 @@
> #include <dl-cache.h>
> #include <dl-hwcaps.h>
> #include <dl-is_dso.h>
> +#include "tunconf.h"
>
>
> #ifndef LD_SO_CONF
> # define LD_SO_CONF SYSCONFDIR "/ld.so.conf"
> #endif
>
> +#ifndef TUNABLES_CONF
> +# define TUNABLES_CONF SYSCONFDIR "/tunables.conf"
> +#endif
> +
> /* Get libc version number. */
> #include <version.h>
>
> @@ -107,9 +112,12 @@ static int opt_ignore_aux_cache;
> /* Cache file to use. */
> static char *cache_file;
>
> -/* Configuration file. */
> +/* Configuration file for libraries. */
> static const char *config_file;
>
> +/* Configuration file for tunables. */
> +static const char *tunconfig_file;
> +
> /* Name and version of program. */
> static void print_version (FILE *stream, struct argp_state *state);
> void (*argp_program_version_hook) (FILE *, struct argp_state *)
> @@ -127,7 +135,8 @@ static const struct argp_option options[] =
> { NULL, 'X', NULL, 0, N_("Don't update symbolic links"), 0},
> { NULL, 'r', N_("ROOT"), 0, N_("Change to and use ROOT as root directory"), 0},
> { NULL, 'C', N_("CACHE"), 0, N_("Use CACHE as cache file"), 0},
> - { NULL, 'f', N_("CONF"), 0, N_("Use CONF as configuration file"), 0},
> + { NULL, 'f', N_("CONF"), 0, N_("Use CONF as configuration file for libraries"), 0},
> + { NULL, 't', N_("TUNCONF"), 0, N_("Use TUNCONF as configuration file for tunables"), 0},
> { NULL, 'n', NULL, 0, N_("Only process directories specified on the command line. Don't build cache."), 0},
> { NULL, 'l', NULL, 0, N_("Manually link individual libraries."), 0},
> { "format", 'c', N_("FORMAT"), 0, N_("Format to use: new (default), old, or compat"), 0},
> @@ -164,6 +173,9 @@ parse_opt (int key, char *arg, struct argp_state *state)
> case 'f':
> config_file = arg;
> break;
> + case 't':
> + tunconfig_file = arg;
> + break;
> case 'i':
> opt_ignore_aux_cache = 1;
> break;
> @@ -421,7 +433,7 @@ add_dir_1 (const char *line, const char *from_file, int from_line)
> }
>
> static void
> -add_dir_callback (const char *line, const char *from_file, int from_line)
> +add_dir_callback (char *line, const char *from_file, int from_line)
> {
> if (!strncasecmp (line, "hwcap", 5) && isblank (line[5]))
> error (0, 0, _("%s:%u: hwcap directive ignored"), from_file, from_line);
> @@ -1089,6 +1101,9 @@ main (int argc, char **argv)
> if (config_file == NULL)
> config_file = LD_SO_CONF;
>
> + if (tunconfig_file == NULL)
> + tunconfig_file = TUNABLES_CONF;
> +
> if (opt_print_cache)
> {
> if (opt_chroot != NULL)
> @@ -1164,6 +1179,8 @@ main (int argc, char **argv)
>
> search_dirs ();
>
> + parse_tunconf (tunconfig_file, opt_chroot);
> +
> if (opt_build_cache)
> {
> save_cache (cache_file);
> diff --git a/elf/tunconf.c b/elf/tunconf.c
> new file mode 100644
> index 0000000000..5e77729e22
> --- /dev/null
> +++ b/elf/tunconf.c
> @@ -0,0 +1,293 @@
> +/* Manage /etc/tunables.*
> + Copyright (C) 1999-2023 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + This program is free software; you can redistribute it and/or modify
> + it under the terms of the GNU General Public License as published
> + by the Free Software Foundation; version 2 of the License, or
> + (at your option) any later version.
> +
> + This program 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 General Public License for more details.
> +
> + You should have received a copy of the GNU General Public License
> + along with this program; if not, see <https://www.gnu.org/licenses/>. */
> +
> +#include <alloca.h>
> +#include <argp.h>
> +#include <assert.h>
> +#include <error.h>
> +#include <inttypes.h>
> +#include <glob.h>
> +#include <libgen.h>
> +#include <libintl.h>
> +#include <locale.h>
> +#include <programs/xmalloc.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdio_ext.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#define TUNABLES_INTERNAL
> +#include <elf/dl-tunables.h>
> +#include <unistd.h>
> +
> +#include <ldconfig.h>
> +#include <dl-cache.h>
> +#include <version.h>
> +#include <stringtable.h>
> +#include <array_length.h>
> +
> +#include "tunconf.h"
> +
> +/*----------------------------------------------------------------------*/
> +
> +#ifndef TUNABLES_CONF
> +# define TUNABLES_CONF SYSCONFDIR "/tunables.conf"
> +#endif
> +
> +#ifndef TUNABLES_CACHE
> +# define TUNABLES_CACHE SYSCONFDIR "/tunables.cache"
> +#endif
> +
> +/* Tunable Override Policies. */
> +typedef enum {
> + TOP_ALLOW = 0, /* let the environment variable override */
> + TOP_DENY /* no override allowed */
> +} TOP;
> +
> +struct tunable_entry_int {
> + struct stringtable_entry *name;
> + struct stringtable_entry *value;
> + 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;
> +
> + struct tunable_entry_int *next;
> +};
> +
> +struct tunable_entry_int *entry_list;
> +
> +/*----------------------------------------------------------------------*/
> +
> +static void
> +add_tunable (char *line, const char *filename, int lineno)
> +{
> + TOP top = TOP_ALLOW;
> + char *name;
> + char *value;
> + char *eq;
> + char *orig_line;
> + struct tunable_entry_int *entry;
> + int i, id;
> + static struct tunable_entry_int **entry_list_next = &entry_list;
> +
> + orig_line = line;
> +
> + /* Leading whitespace has already been stripped. */
> +
> + /* Canonicalize the line. */
> + for (i=0; line[i]; i++)
> + {
> + if (line[i] == '\t')
> + line[i] = ' ';
> + if (line[i] == '\n' || line[i] == '\r')
> + {
> + line[i] = '\0';
> + break;
> + }
> + }
> +
> + /* Parse modifiers. */
> + while (*line)
> + {
> + if (strncmp (line, "overridable ", 13) == 0)
OK. Changed to "overridable".
> + {
> + top = TOP_ALLOW;
> + /* The line++ below skips the space. */
> + line += 12;
> + }
> + else if (strncmp (line, "nonoverridable ", 16) == 0)
OK. Also corrected.
> + {
> + top = TOP_DENY;
> + line += 15;
> + }
> + else switch (*line)
> + {
> + case '+':
> + top = TOP_ALLOW;
> + break;
> + case '-':
> + top = TOP_DENY;
> + break;
> + case ' ':
> + break;
> +
> + default:
> + goto done;
> + }
> + line ++;
> + }
> + done:
> +
> + /* NAME now points to the start of the tunable name. */
> + name = line;
> +
> + /* Look for the '=' separator. */
> + eq = strchr (line, '=');
> + if (eq == NULL)
> + {
> + error_at_line (0, 0, filename, lineno,
> + "syntax error, line ignored: `%s' (missing '=')",
> + orig_line);
> + return;
> + }
> +
> + if (eq == name)
> + {
> + error_at_line (0, 0, filename, lineno,
> + "syntax error, line ignored: `%s' (missing tunable name)",
> + orig_line);
> + return;
> + }
> +
> + /* At this point, EQ actually points to '='. */
> + value = eq + 1;
> +
> + while (*value && isspace(*value))
> + value ++;
> +
> + if (*value == 0)
> + {
> + error_at_line (0, 0, filename, lineno,
> + "syntax error, line ignored: `%s' (missing value)",
> + orig_line);
> + return;
> + }
> +
> + /* VALUE now points to the start of the value. */
> +
> + /* Split the string into name and value c-strings. */
> + *eq = 0;
> + /* Trim trailing whitespace off NAME. */
> + while (*name && isspace (name[strlen(name)-1]))
> + name[strlen(name)-1] = 0;
> + /* Trim trailing whitespace off VALUE. */
> + while (*value && isspace (value[strlen(value)-1]))
> + value[strlen(value)-1] = 0;
> +
> + id = -1;
> + for (i = 0; i < array_length (tunable_list); i ++)
> + if (strcmp (tunable_list[i].name, name) == 0)
> + {
> + id = i;
> + break;
> + }
> + if (id == -1)
> + printf ("%s:%d: Warning: tunable %s not recognized.\n",
> + filename, lineno, name);
> +
> + entry = (struct tunable_entry_int *) xcalloc (sizeof (struct tunable_entry_int), 1);
> + entry->name = cache_store_string (name);
> + entry->value = cache_store_string (value);
> + entry->tunable_id = id;
> + entry->top = top;
> +
> + if (value[0] == '-')
> + {
> + entry->value_is_negative = 1;
> + if (sscanf (value, "%lld", &entry->value_sll) == 1)
> + entry->value_was_parsed = 1;
> + }
> + else
> + {
> + entry->value_is_negative = 0;
> + if (sscanf (value, "%llu", &entry->value_ull) == 1)
OK. Using "%llu".
> + entry->value_was_parsed = 1;
> + }
> +
> + *entry_list_next = entry;
> + entry_list_next = & (entry->next);
> +}
> +
> +void
> +parse_tunconf (const char *filename, char *opt_chroot)
> +{
> + ldconfig_parse_config (filename, opt_chroot, add_tunable);
> +}
> +
> +struct tunable_header_cached *
> +get_tunconf_ext (uint32_t string_table_offset)
> +{
> + struct tunable_entry_int *tei;
> + struct tunable_header_cached *thc;
> + size_t count;
> + size_t size;
> +
> + /* First, count the number of entries we have. */
> + tei = entry_list;
> + count = 0;
> + while (tei != NULL)
> + {
> + ++ count;
> + tei = tei->next;
> + }
> + if (count == 0)
> + return NULL;
> +
> + /* Allocate enough space for the whole cached block. */
> + size = sizeof (struct tunable_header_cached)
> + + sizeof (struct tunable_entry_cached) * count;
> + thc = (struct tunable_header_cached *) xmalloc (size);
OK. Checked malloc.
> +
> + /* Now, fill in the structures. */
> +
> + thc->signature = TUNCONF_SIGNATURE;
> + thc->version = TUNCONF_VERSION;
> + thc->num_tunables = count;
> + thc->unused_1 = 0;
> +
> + tei = entry_list;
> + count = 0;
> + while (tei != NULL)
> + {
> + struct tunable_entry_cached *tec;
> +
> + tec = & ( thc->tunables[count] );
> +
> + tec->flags = 0;
> + if (tei->value_was_parsed)
> + tec->flags |= TUNCONF_FLAG_PARSED;
> + if (tei->value_is_negative)
> + tec->flags |= TUNCONF_FLAG_NEGATIVE;
> + switch (tei->top)
> + {
> + case TOP_ALLOW:
> + tec->flags |= TUNCONF_OVERRIDE_ALLOW;
> + break;
> + case TOP_DENY:
> + tec->flags |= TUNCONF_OVERRIDE_DENY;
> + break;
> + }
> +
> + 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;
> + tec->unused_1 = 0;
> + if (tei->value_is_negative)
> + tec->parsed_value = (uint64_t) tei->value_sll;
> + else
> + tec->parsed_value = (uint64_t) tei->value_ull;
> +
> + ++ count;
> + tei = tei->next;
> + }
> +
> + return thc;
> +}
> diff --git a/elf/tunconf.h b/elf/tunconf.h
> new file mode 100644
> index 0000000000..b446211f94
> --- /dev/null
> +++ b/elf/tunconf.h
> @@ -0,0 +1,39 @@
> +#define TUNCONF_SIGNATURE 0x7c3ba94f
> +#define TUNCONF_VERSION 0x01000000
> +
> +#define TUNCONF_FLAG_PARSED 0x00000001
> +#define TUNCONF_FLAG_NEGATIVE 0x00000002
> +
> +#define TUNCONF_FLAG_OVERRIDABLE 0x0000000C
> +#define TUNCONF_OVERRIDE_DENY 0x00000004
> +#define TUNCONF_OVERRIDE_ALLOW 0x00000000
> +
> +#define TUNCONF_FLAG_FILTER 0x0000ff00
> +#define TUNCONF_FILTER_NONE 0x00000000
OK. This will be used later to distinguish "no filter" from "unknown
filter" to handle them separately.
> +#define TUNCONF_FILTER_PERPROC 0x00000100
> +
> +/* An array of [num_tunables] of these follows the below. */
> +struct tunable_entry_cached {
> + uint32_t flags;
> + uint32_t tunable_id;
> + uint32_t name_offset;
> + uint32_t value_offset;
> + uint32_t flag_offset;
> + uint32_t unused_1; /* for alignment */
> + uint64_t parsed_value;
> +};
> +
> +/* One of these is at the beginning of the tunable data block. */
> +struct tunable_header_cached {
> + uint32_t signature;
> + uint32_t version;
> + uint32_t num_tunables;
> + uint32_t unused_1; /* for alignment */
> + struct tunable_entry_cached tunables[0 /* num_tunables */];
> +};
> +
> +void parse_tunconf (const char *filename, char *opt_chroot);
> +
> +struct tunable_header_cached * get_tunconf_ext (uint32_t str_offset);
> +#define TUNCONF_SIZE(thc_p) (sizeof(struct tunable_header_cached) \
> + + thc_p->num_tunables * sizeof (struct tunable_entry_cached))
> diff --git a/sysdeps/generic/dl-cache.h b/sysdeps/generic/dl-cache.h
> index 26f0c1e0bd..972ab32b86 100644
> --- a/sysdeps/generic/dl-cache.h
> +++ b/sysdeps/generic/dl-cache.h
> @@ -220,6 +220,12 @@ enum cache_extension_tag
> size must be a multiple of 4. */
> cache_extension_tag_glibc_hwcaps,
>
> + /* Array of system-wide tunable information.
> +
> + For this section, 8-byte alignment is required, and the section
> + size must be a multiple of 8. */
> + cache_extension_tag_tunables,
> +
> /* Total number of known cache extension tags. */
> cache_extension_count
> };
> @@ -293,6 +299,20 @@ cache_extension_verify (struct cache_extension_all_loaded *loaded)
> hwcaps->flags = 0;
> }
> }
> + {
> + /* Section must not be empty, it must be aligned at 8 bytes, and
> + the size must be a multiple of 8. */
> + struct cache_extension_loaded *tun
> + = &loaded->sections[cache_extension_tag_tunables];
> + if (tun->size == 0
> + || ((uintptr_t) tun->base % 8) != 0
> + || (tun->size % 8) != 0)
> + {
> + tun->base = NULL;
> + tun->size = 0;
> + tun->flags = 0;
> + }
> + }
> }
>
> static bool __attribute__ ((unused))
> diff --git a/sysdeps/generic/ldconfig.h b/sysdeps/generic/ldconfig.h
> index 22d0fd0f82..800714659e 100644
> --- a/sysdeps/generic/ldconfig.h
> +++ b/sysdeps/generic/ldconfig.h
> @@ -74,6 +74,8 @@ extern void add_to_cache (const char *path, const char *filename,
> unsigned int isa_level,
> struct glibc_hwcaps_subdirectory *);
>
> +extern struct stringtable_entry *cache_store_string (const char *string);
> +
> extern void init_aux_cache (void);
>
> extern void load_aux_cache (const char *aux_cache_name);
> @@ -112,8 +114,8 @@ enum opt_format
> extern enum opt_format opt_format;
>
> /* Declared in ldconfig-parse.c */
> -typedef void (*ldconfig_parse_config_cb) (const char *line,
> - const char *from_file, int from_line);
> +typedef void (*ldconfig_parse_config_cb) (char *line,
> + const char *from_file, int from_line);
>
> void ldconfig_parse_config (const char *filename, char *opt_chroot,
> ldconfig_parse_config_cb cb);
> --
> 2.47.3
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v9 2/5] Add system-wide tunables: cache ld.so.cache
2026-06-26 3:30 ` [PATCH v9 2/5] Add system-wide tunables: cache ld.so.cache DJ Delorie
@ 2026-06-30 15:40 ` Arjun Shankar
0 siblings, 0 replies; 14+ messages in thread
From: Arjun Shankar @ 2026-06-30 15:40 UTC (permalink / raw)
To: DJ Delorie; +Cc: libc-alpha
Hi DJ,
> The purpose of this change is twofold:
>
> 1. The ld.so.cache is cached in memory and only re-read if/when
> it changes on disk. This allows us to have much more intensive
> security checks in the future, without impacting performance as
> much. It also allows for cases where the cache is corrupted -
> we continue using the last valid one.
>
> 2. We break out the load/check logic so that the cache can be
> loaded independently of the library lookup, such as for
> code that only needs to look at the extensions.
No changes since last version. This looks good to me.
Reviewed-by: Arjun Shankar <arjun@redhat.com>
> ---
> elf/Makefile | 4 +
> elf/dl-cache.c | 260 ++++++++++++------
> elf/tst-ldconfig-cache.c | 134 +++++++++
> elf/tst-ldconfig-cache.root/etc/ld.so.conf | 3 +
> elf/tst-ldconfig-cache.root/ldconfig.req | 0
> .../tst-ldconfig-cache.script | 7 +
> 6 files changed, 324 insertions(+), 84 deletions(-)
> create mode 100644 elf/tst-ldconfig-cache.c
> create mode 100644 elf/tst-ldconfig-cache.root/etc/ld.so.conf
> create mode 100644 elf/tst-ldconfig-cache.root/ldconfig.req
> create mode 100644 elf/tst-ldconfig-cache.root/tst-ldconfig-cache.script
>
> diff --git a/elf/Makefile b/elf/Makefile
> index d01db49171..f5625c8986 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -578,6 +578,7 @@ endif
> tests-container += \
> tst-dlopen-self-container \
> tst-dlopen-tlsmodid-container \
> + tst-ldconfig-cache \
> tst-pldd \
> tst-preload-pthread-libc \
> tst-ptrguard-static-dlopen \
> @@ -735,6 +736,9 @@ one-hundred = $(foreach x,0 1 2 3 4 5 6 7 8 9, \
> 0$x 1$x 2$x 3$x 4$x 5$x 6$x 7$x 8$x 9$x)
> tst-tls-many-dynamic-modules := \
> $(foreach n,$(one-hundred),tst-tls-manydynamic$(n)mod)
> +tst-ldconfig-cache-modules := \
> + $(foreach n,01 02 03 04 05,tst-tls-manydynamic$(n)mod)
> +$(objpfx)tst-ldconfig-cache.out: $(tst-ldconfig-cache-modules:%=$(objpfx)%.so)
> tst-tls-many-dynamic-modules-dep-suffixes = 0 1 2 3 4 5 6 7 8 9 10 11 12 13 \
> 14 15 16 17 18 19
> tst-tls-many-dynamic-modules-dep = \
> diff --git a/elf/dl-cache.c b/elf/dl-cache.c
> index c1de93f204..07c7e639f5 100644
> --- a/elf/dl-cache.c
> +++ b/elf/dl-cache.c
> @@ -26,11 +26,21 @@
> #include <_itoa.h>
> #include <dl-hwcaps.h>
> #include <dl-isa-level.h>
> +#include <fcntl.h>
> +#include <sys/stat.h>
>
> /* This is the starting address and the size of the mmap()ed file. */
> static struct cache_file *cache;
> static struct cache_file_new *cache_new;
> static size_t cachesize;
> +static struct cache_extension_all_loaded ext;
> +
> +static struct {
> + typeof ((*(struct __stat64_t64 *)0).st_mtime) mtime;
> + typeof ((*(struct __stat64_t64 *)0).st_ino) ino;
> + typeof ((*(struct __stat64_t64 *)0).st_size) size;
> + typeof ((*(struct __stat64_t64 *)0).st_dev) dev;
> +} cache_file_time, new_cache_file_time;
>
> #ifdef SHARED
> /* This is used to cache the priorities of glibc-hwcaps
> @@ -53,6 +63,7 @@ glibc_hwcaps_priorities_free (void)
> free (glibc_hwcaps_priorities);
> glibc_hwcaps_priorities = NULL;
> glibc_hwcaps_priorities_allocated = 0;
> + glibc_hwcaps_priorities_length = 0;
> }
>
> /* Ordered comparison of a hwcaps string from the cache on the left
> @@ -84,10 +95,6 @@ glibc_hwcaps_compare (uint32_t left_index, struct dl_hwcaps_priority *right)
> static void
> glibc_hwcaps_priorities_init (void)
> {
> - struct cache_extension_all_loaded ext;
> - if (!cache_extension_load (cache_new, cache, cachesize, &ext))
> - return;
> -
> uint32_t length = (ext.sections[cache_extension_tag_glibc_hwcaps].size
> / sizeof (uint32_t));
> if (length > glibc_hwcaps_priorities_allocated)
> @@ -374,6 +381,165 @@ _dl_cache_libcmp (const char *p1, const char *p2)
> return *p1 - *p2;
> }
>
> +/* Set the cache back to the "no cache" state, which may include
> + cleaning up a loaded cache. */
> +static void
> +_dl_maybe_unload_ldsocache (void)
> +{
> + if (cache != NULL)
> + __munmap (cache, cachesize);
> +
> + cache = NULL;
> + cache_new = NULL;
> + cachesize = 0;
> +
> +#ifdef SHARED
> + glibc_hwcaps_priorities_free ();
> +#endif
> +}
> +
> +/* Returns TRUE if for any reason the cache needs to be reloaded
> + (including, the first time, loaded). */
> +static bool
> +_dl_check_ldsocache_needs_loading (void)
> +{
> + int rv;
> + static bool copy_old_time = 0;
> + struct __stat64_t64 new_cache_file_stat;
> +
> + /* Save the previous stat every time. We only care when this
> + changes, and we only stat it here, so we can get away with doing
> + the copy now instead of at every single return statement in this
> + function. However, we only need to copy it if the previous stat
> + succeeded. The only way this could be subverted is if the admin
> + moves the file aside, then moves it back, but CACHE would be set
> + to NULL in the interim so that would be detected. */
> + if (copy_old_time)
> + cache_file_time = new_cache_file_time;
> + rv = __fstatat64_time64 (AT_FDCWD, LD_SO_CACHE, &new_cache_file_stat, 0);
> + copy_old_time = (rv >= 0);
> +
> + /* No file to load, but there used to be. Assume user intentionally
> + deleted the cache and act accordingly. */
> + if (rv < 0 && cache != NULL)
> + {
> + _dl_maybe_unload_ldsocache ();
> + return false;
> + }
> +
> + /* No file to load and no loaded cache, so nothing to do. */
> + if (rv < 0)
> + return false;
> +
> + /* Any file is better than no file (likely the first time
> + through). */
> + if (cache == NULL)
> + return true;
> +
> + /* Store the fields we check, in order they're likely to differ. */
> + new_cache_file_time.mtime = new_cache_file_stat.st_mtime;
> + new_cache_file_time.ino = new_cache_file_stat.st_ino;
> + new_cache_file_time.size = new_cache_file_stat.st_size;
> + new_cache_file_time.dev = new_cache_file_stat.st_dev;
> +
> + /* At this point, NEW_CACHE_FILE_TIME is valid as well as
> + CACHE_FILE_TIME, so we compare them. */
> + return (memcmp (&new_cache_file_time, &cache_file_time,
> + sizeof(new_cache_file_time)));
> +}
> +
> +/* Attempts to load and validate the cache. On return, CACHE is either
> + unchanged (still loaded or still not loaded) or valid. */
> +static void
> +_dl_maybe_load_ldsocache (void)
> +{
> + struct cache_file *tmp_cache = NULL;
> + struct cache_file_new *tmp_cache_new = NULL;
> + size_t tmp_cachesize = 0;
> +
> + /* Read the contents of the file. */
> + void *file = _dl_sysdep_read_whole_file (LD_SO_CACHE, &tmp_cachesize,
> + PROT_READ);
> +
> + /* We can handle three different cache file formats here:
> + - only the new format
> + - the old libc5/glibc2.0/2.1 format
> + - the old format with the new format in it
> + The following checks if the cache contains any of these formats. */
> + if (file != MAP_FAILED && tmp_cachesize > sizeof *cache_new
> + && memcmp (file, CACHEMAGIC_VERSION_NEW,
> + sizeof CACHEMAGIC_VERSION_NEW - 1) == 0
> + /* Check for corruption, avoiding overflow. */
> + && ((tmp_cachesize - sizeof *cache_new) / sizeof (struct file_entry_new)
> + >= ((struct cache_file_new *) file)->nlibs))
> + {
> + if (! cache_file_new_matches_endian (file))
> + {
> + __munmap (file, tmp_cachesize);
> + return;
> + }
> +
> + tmp_cache_new = file;
> + tmp_cache = file;
> + }
> + else if (file != MAP_FAILED && tmp_cachesize > sizeof *cache
> + && memcmp (file, CACHEMAGIC, sizeof CACHEMAGIC - 1) == 0
> + /* Check for corruption, avoiding overflow. */
> + && ((tmp_cachesize - sizeof *cache) / sizeof (struct file_entry)
> + >= ((struct cache_file *) file)->nlibs))
> + {
> + size_t offset;
> + /* Looks ok. */
> + tmp_cache = file;
> +
> + /* Check for new version. */
> + offset = ALIGN_CACHE (sizeof (struct cache_file)
> + + tmp_cache->nlibs * sizeof (struct file_entry));
> +
> + tmp_cache_new = (struct cache_file_new *) ((void *) tmp_cache + offset);
> + if (tmp_cachesize < (offset + sizeof (struct cache_file_new))
> + || memcmp (tmp_cache_new->magic, CACHEMAGIC_VERSION_NEW,
> + sizeof CACHEMAGIC_VERSION_NEW - 1) != 0)
> + tmp_cache_new = NULL;
> + else
> + {
> + if (! cache_file_new_matches_endian (tmp_cache_new))
> + /* The old-format part of the cache is bogus as well
> + if the endianness does not match. (But it is
> + unclear how the new header can be located if the
> + endianness does not match.) */
> + {
> + __munmap (file, tmp_cachesize);
> + return;
> + }
> + }
> + }
> + else
> + {
> + if (file != MAP_FAILED)
> + __munmap (file, tmp_cachesize);
> + return;
> + }
> +
> + struct cache_extension_all_loaded tmp_ext;
> + if (!cache_extension_load (tmp_cache_new, tmp_cache, tmp_cachesize, &tmp_ext))
> + {
> + /* The extension is corrupt, so the cache is corrupt. */
> + __munmap (file, tmp_cachesize);
> + return;
> + }
> +
> + /* If we've gotten here, the loaded cache is good and we need to
> + save it. */
> + _dl_maybe_unload_ldsocache ();
> + cache = tmp_cache;
> + cache_new = tmp_cache_new;
> + cachesize = tmp_cachesize;
> + ext = tmp_ext;
> +
> + assert (cache != NULL);
> +}
> +
>
> /* Look up NAME in ld.so.cache and return the file name stored there, or null
> if none is found. The cache is loaded if it was not already. If loading
> @@ -389,81 +555,14 @@ _dl_load_cache_lookup (const char *name)
> if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
> _dl_debug_printf (" search cache=%s\n", LD_SO_CACHE);
>
> - if (cache == NULL)
> - {
> - /* Read the contents of the file. */
> - void *file = _dl_sysdep_read_whole_file (LD_SO_CACHE, &cachesize,
> - PROT_READ);
> -
> - /* We can handle three different cache file formats here:
> - - only the new format
> - - the old libc5/glibc2.0/2.1 format
> - - the old format with the new format in it
> - The following checks if the cache contains any of these formats. */
> - if (file != MAP_FAILED && cachesize > sizeof *cache_new
> - && memcmp (file, CACHEMAGIC_VERSION_NEW,
> - sizeof CACHEMAGIC_VERSION_NEW - 1) == 0
> - /* Check for corruption, avoiding overflow. */
> - && ((cachesize - sizeof *cache_new) / sizeof (struct file_entry_new)
> - >= ((struct cache_file_new *) file)->nlibs))
> - {
> - if (! cache_file_new_matches_endian (file))
> - {
> - __munmap (file, cachesize);
> - file = (void *) -1;
> - }
> - cache_new = file;
> - cache = file;
> - }
> - else if (file != MAP_FAILED && cachesize > sizeof *cache
> - && memcmp (file, CACHEMAGIC, sizeof CACHEMAGIC - 1) == 0
> - /* Check for corruption, avoiding overflow. */
> - && ((cachesize - sizeof *cache) / sizeof (struct file_entry)
> - >= ((struct cache_file *) file)->nlibs))
> - {
> - size_t offset;
> - /* Looks ok. */
> - cache = file;
> -
> - /* Check for new version. */
> - offset = ALIGN_CACHE (sizeof (struct cache_file)
> - + cache->nlibs * sizeof (struct file_entry));
> -
> - cache_new = (struct cache_file_new *) ((void *) cache + offset);
> - if (cachesize < (offset + sizeof (struct cache_file_new))
> - || memcmp (cache_new->magic, CACHEMAGIC_VERSION_NEW,
> - sizeof CACHEMAGIC_VERSION_NEW - 1) != 0)
> - cache_new = (void *) -1;
> - else
> - {
> - if (! cache_file_new_matches_endian (cache_new))
> - {
> - /* The old-format part of the cache is bogus as well
> - if the endianness does not match. (But it is
> - unclear how the new header can be located if the
> - endianness does not match.) */
> - cache = (void *) -1;
> - cache_new = (void *) -1;
> - __munmap (file, cachesize);
> - }
> - }
> - }
> - else
> - {
> - if (file != MAP_FAILED)
> - __munmap (file, cachesize);
> - cache = (void *) -1;
> - }
> + if (_dl_check_ldsocache_needs_loading ())
> + _dl_maybe_load_ldsocache ();
>
> - assert (cache != NULL);
> - }
> -
> - if (cache == (void *) -1)
> - /* Previously looked for the cache file and didn't find it. */
> + if (cache == NULL)
> return NULL;
>
> const char *best;
> - if (cache_new != (void *) -1)
> + if (cache_new != NULL)
> {
> const char *string_table = (const char *) cache_new;
> best = search_cache (string_table, cachesize,
> @@ -510,14 +609,7 @@ _dl_load_cache_lookup (const char *name)
> void
> _dl_unload_cache (void)
> {
> - if (cache != NULL && cache != (struct cache_file *) -1)
> - {
> - __munmap (cache, cachesize);
> - cache = NULL;
> - }
> -#ifdef SHARED
> - /* This marks the glibc_hwcaps_priorities array as out-of-date. */
> - glibc_hwcaps_priorities_length = 0;
> -#endif
> + /* Functionality is no longer needed, but kept for internal ABI for
> + now. */
> }
> #endif
> diff --git a/elf/tst-ldconfig-cache.c b/elf/tst-ldconfig-cache.c
> new file mode 100644
> index 0000000000..9f71418b3a
> --- /dev/null
> +++ b/elf/tst-ldconfig-cache.c
> @@ -0,0 +1,134 @@
> +/* Test ldconfig cache is correctly used when changed.
> + 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; see the file COPYING.LIB. If
> + not, see <https://www.gnu.org/licenses/>. */
> +
> +/* What we're testing for: We initially load ld.so.cache at startup
> + and remember it. If we detect that ld.so.cache has changed, and we
> + can load it successfully, we replace our remember it. If it
> + doesn't change, or if the new version is corrupted, we continue
> + using the old remembered copy. */
> +
> +#include <fcntl.h>
> +
> +#include <support/support.h>
> +#include <support/check.h>
> +
> +#include <support/xstdio.h>
> +#include <support/xstdlib.h>
> +#include <support/xdlfcn.h>
> +#include <support/xunistd.h>
> +
> +
> +/* Verify that we can (or can't) load one of our test objects. */
> +static void
> +try (int i, int invert)
> +{
> + char dlname[100];
> + char symname[100];
> + int (*proc)(int);
> + void *dl;
> +
> + /* These match the objects copied by tst-ldconfig-cache.script,
> + copied from tst-tls-manydynamic*.so. */
> + sprintf (dlname, "libcache%d.so", i);
> + sprintf (symname, "set_value_%02d", i);
> +
> + dl = dlopen (dlname, RTLD_NOW);
> +
> + if (invert)
> + {
> + /* This is a negative test; if the object doesn't load the test
> + passes. */
> + TEST_VERIFY (dl == NULL);
> + return;
> + }
> + else
> + {
> + /* This is a positive test; if the object doesn't load the test
> + fails. */
> + if (dl == NULL)
> + FAIL_EXIT1 ("error: dlopen: %s\n", dlerror ());
> + }
> +
> + proc = xdlsym (dl, symname);
> + /* We don't need to call the symbol, just make sure it exists. */
> + TEST_VERIFY (proc != NULL);
> +
> + xdlclose (dl);
> +}
> +
> +/* Cause corruption in the cache that should prevent loading it. */
> +static void
> +corrupt (void)
> +{
> + int fd = xopen ("/etc/ld.so.cache", O_RDWR, 0);
> + char bytes[] = { 15, 32, 184, 4 };
> + xwrite (fd, bytes, sizeof(bytes));
> + xclose (fd);
> +}
> +
> +/* Regenerate the cache from ld.so.conf. */
> +static void
> +ldconfig (void)
> +{
> + xsystem ("/sbin/ldconfig -X");
> +}
> +
> +/* Change ld.so.conf to refer to the new directory, and generate a new
> + cache. */
> +static void
> +newpath (const char *p)
> +{
> + FILE *f = xfopen ("/etc/ld.so.conf", "w");
> + fprintf (f, "%s\n", p);
> + xfclose (f);
> +
> + ldconfig ();
> +}
> +
> +static int
> +do_test (void)
> +{
> + /* Test that the cache we started with can still load objects in
> + /a. */
> + try (1, 0);
> +
> + /* Create a new cache that doesn't include /a but corrupt it. Test
> + that we still use the cache with /a in it. */
> + newpath ("/c");
> + corrupt ();
> + try (2, 0);
> +
> + /* Regenerate a clean cache with /a in it and verify we can load
> + objects in /a. */
> + newpath ("/a");
> + try (3, 0);
> +
> + /* Generate a new cache with /b but not /a and make sure objects
> + in /a can't be loaded. */
> + newpath ("/b");
> + try (3, 1);
> +
> + /* But objects in /b can be loaded. */
> + try (4, 0);
> + /* Even multiple times. */
> + try (5, 0);
> +
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/elf/tst-ldconfig-cache.root/etc/ld.so.conf b/elf/tst-ldconfig-cache.root/etc/ld.so.conf
> new file mode 100644
> index 0000000000..2b4c2d7817
> --- /dev/null
> +++ b/elf/tst-ldconfig-cache.root/etc/ld.so.conf
> @@ -0,0 +1,3 @@
> +/lib
> +/lib64
> +/a
> diff --git a/elf/tst-ldconfig-cache.root/ldconfig.req b/elf/tst-ldconfig-cache.root/ldconfig.req
> new file mode 100644
> index 0000000000..e69de29bb2
> diff --git a/elf/tst-ldconfig-cache.root/tst-ldconfig-cache.script b/elf/tst-ldconfig-cache.root/tst-ldconfig-cache.script
> new file mode 100644
> index 0000000000..4f258cac1e
> --- /dev/null
> +++ b/elf/tst-ldconfig-cache.root/tst-ldconfig-cache.script
> @@ -0,0 +1,7 @@
> +mkdirp 0755 /a
> +cp $B/elf/tst-tls-manydynamic01mod.so /a/libcache1.so
> +cp $B/elf/tst-tls-manydynamic02mod.so /a/libcache2.so
> +cp $B/elf/tst-tls-manydynamic03mod.so /a/libcache3.so
> +mkdirp 0755 /b
> +cp $B/elf/tst-tls-manydynamic04mod.so /b/libcache4.so
> +cp $B/elf/tst-tls-manydynamic05mod.so /b/libcache5.so
> --
> 2.47.3
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v9 3/5] Add system-wide tunables: Apply tunables part
2026-06-26 3:30 ` [PATCH v9 3/5] Add system-wide tunables: Apply tunables part DJ Delorie
@ 2026-06-30 15:41 ` Arjun Shankar
0 siblings, 0 replies; 14+ messages in thread
From: Arjun Shankar @ 2026-06-30 15:41 UTC (permalink / raw)
To: DJ Delorie; +Cc: libc-alpha
Hi DJ,
> Load ld.so.cache and fetch the tunables extension. Apply
> those tunables to the current program. We do not yet apply
> security policies.
The two changes since last version look good.
Reviewed-by: Arjun Shankar <arjun@redhat.com>
> ---
> elf/dl-cache.c | 54 +++++++++++++++++++++++++++++++++++++++
> elf/dl-tunables.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++
> elf/tunconf.h | 3 +++
> 3 files changed, 122 insertions(+)
>
> diff --git a/elf/dl-cache.c b/elf/dl-cache.c
> index 07c7e639f5..8b50ad038e 100644
> --- a/elf/dl-cache.c
> +++ b/elf/dl-cache.c
> @@ -28,6 +28,7 @@
> #include <dl-isa-level.h>
> #include <fcntl.h>
> #include <sys/stat.h>
> +#include "tunconf.h"
>
> /* This is the starting address and the size of the mmap()ed file. */
> static struct cache_file *cache;
> @@ -613,3 +614,56 @@ _dl_unload_cache (void)
> now. */
> }
> #endif
> +
> +const struct tunable_header_cached *
> +_dl_load_cache_tunables (const char **data)
> +{
> + struct cache_extension_all_loaded ext;
> + struct tunable_header_cached *thc;
> + struct tunable_entry_cached *tec;
> + int i, count;
> +
> + if (_dl_check_ldsocache_needs_loading ())
> + _dl_maybe_load_ldsocache ();
> +
> + if (cache_new)
> + *data = (const char *) cache_new;
> + else
> + return NULL;
> +
> + if (!cache_extension_load (cache_new, cache, cachesize, &ext))
> + return NULL;
> +
> + /* Validate length/contents here. */
> + if (ext.sections[cache_extension_tag_tunables].size
> + < sizeof(struct tunable_header_cached))
> + return NULL;
> +
> + thc = (struct tunable_header_cached *)
> + ext.sections[cache_extension_tag_tunables].base;
> + tec = thc->tunables;
> + count = thc->num_tunables;
> +
> + if (ext.sections[cache_extension_tag_tunables].base
> + + ext.sections[cache_extension_tag_tunables].size
> + != (void *) & tec[count])
OK. Stricter check.
> + return NULL;
> +
> + /* Validate each entry. */
> + int s_start = (const char *) (&cache_new->libs[cache_new->nlibs]) - *data;
> + int s_end = s_start + cache_new->len_strings;
> + for (i = 0; i < count; i ++)
> + {
> + if (thc->tunables[i].name_offset < s_start
> + || thc->tunables[i].name_offset >= s_end
> + || thc->tunables[i].value_offset < s_start
> + || thc->tunables[i].value_offset >= s_end)
> + return NULL;
> + if (thc->tunables[i].flag_offset != 0
> + && (thc->tunables[i].flag_offset < s_start
> + || thc->tunables[i].flag_offset >= s_end))
> + return NULL;
OK. Validate flag offset when flag offset is non-zero.
> + }
> +
> + return thc;
> +}
> diff --git a/elf/dl-tunables.c b/elf/dl-tunables.c
> index 5c65e8b458..f183e99855 100644
> --- a/elf/dl-tunables.c
> +++ b/elf/dl-tunables.c
> @@ -37,6 +37,7 @@
>
> #define TUNABLES_INTERNAL 1
> #include "dl-tunables.h"
> +#include "tunconf.h"
>
> static char **
> get_next_env (char **envp, char **name, char **val, char ***prev_envp)
> @@ -302,6 +303,70 @@ __tunables_init (char **envp)
> if (MALLOC_DEFAULT_THP_PAGESIZE > 0)
> TUNABLE_SET (glibc, malloc, hugetlb, 1);
>
> +#if defined(SHARED) && defined (USE_LDCONFIG)
> + const struct tunable_header_cached *thc;
> + const char *td;
> +
> + thc = _dl_load_cache_tunables (&td);
> + if (thc != NULL)
> + {
> + for (int t = 0; t < thc->num_tunables; ++ t)
> + {
> + const struct tunable_entry_cached *tec = &( thc->tunables[t] );
> + int tid = tec->tunable_id;
> + const char *name = td + tec->name_offset;
> + const char *value = td + tec->value_offset;
> +
> + /* Check that we have the correct tunable, and search by
> + name if needed. We rely on order of operations here to
> + avoid mis-indexing tunables[]. */
> + if (tid < 0 || tid >= tunables_list_size
> + || strcmp (name, tunable_list[tid].name) != 0)
> + {
> + /* It does not, search by name instead. */
> + tid = -1;
> + for (int i = 0; i < tunables_list_size; i++)
> + {
> + if (strcmp (name, tunable_list[i].name) == 0)
> + {
> + tid = i;
> + break;
> + }
> + }
> + if (tid == -1)
> + continue;
> + }
> + /* At this point, TID is valid for the tunable we want. 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
> + it, as the mapping will go away if the disk file is
> + updated and the cache is reloaded. */
> + tunable_list[tid].val.strval.str = __strdup (value);
> + tunable_list[tid].val.strval.len = strlen (value);
> + tunable_list[tid].initialized = true;
> + }
> + else
> + {
> + tunable_val_t tval;
> + if (tec->flags & TUNCONF_FLAG_PARSED)
> + {
> + tval.numval = tec->parsed_value;
> + do_tunable_update_val (& tunable_list[tid],
> + &tval, NULL, NULL);
> + }
> + else
> + {
> + tunable_initialize (& tunable_list[tid],
> + value, strlen (value));
> + }
> + }
> + }
> + }
> +#endif /* defined(SHARED) && defined (USE_LDCONFIG) */
> +
> /* Ignore tunables for AT_SECURE programs. */
> if (__libc_enable_secure)
> return;
> diff --git a/elf/tunconf.h b/elf/tunconf.h
> index b446211f94..81c0dd6b06 100644
> --- a/elf/tunconf.h
> +++ b/elf/tunconf.h
> @@ -37,3 +37,6 @@ void parse_tunconf (const char *filename, char *opt_chroot);
> struct tunable_header_cached * get_tunconf_ext (uint32_t str_offset);
> #define TUNCONF_SIZE(thc_p) (sizeof(struct tunable_header_cached) \
> + thc_p->num_tunables * sizeof (struct tunable_entry_cached))
> +
> +extern const struct tunable_header_cached *
> +_dl_load_cache_tunables (const char **data);
> --
> 2.47.3
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v9 4/5] Add system-wide tunables: Filters
2026-06-26 3:30 ` [PATCH v9 4/5] Add system-wide tunables: Filters DJ Delorie
@ 2026-06-30 15:46 ` Arjun Shankar
0 siblings, 0 replies; 14+ messages in thread
From: Arjun Shankar @ 2026-06-30 15:46 UTC (permalink / raw)
To: DJ Delorie; +Cc: libc-alpha
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
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v9 5/5] Add system-wide tunables: manual
2026-06-26 3:30 ` [PATCH v9 5/5] Add system-wide tunables: manual DJ Delorie
@ 2026-06-30 15:46 ` Arjun Shankar
0 siblings, 0 replies; 14+ messages in thread
From: Arjun Shankar @ 2026-06-30 15:46 UTC (permalink / raw)
To: DJ Delorie; +Cc: libc-alpha
Hi DJ,
> Document the syntax and operation.
This only needed a clarification. No changes since last time.
Reviewed-by: Arjun Shankar <arjun@redhat.com>
> ---
> manual/tunables.texi | 94 ++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 94 insertions(+)
>
> diff --git a/manual/tunables.texi b/manual/tunables.texi
> index e0f141077b..847903e6ee 100644
> --- a/manual/tunables.texi
> +++ b/manual/tunables.texi
> @@ -67,6 +67,7 @@ glibc.elf.thp: 0 (min: 0, max: 1)
> @end example
>
> @menu
> +* System-wide Tunables:: Tunables that affect every process
> * Tunable names:: The structure of a tunable name
> * Memory Allocation Tunables:: Tunables in the memory allocation subsystem
> * Dynamic Linking Tunables:: Tunables in the dynamic linking subsystem
> @@ -82,6 +83,99 @@ glibc.elf.thp: 0 (min: 0, max: 1)
>
> @end menu
>
> +@node System-wide Tunables
> +@section System-wide Tunables
> +@cindex System-wide Tunables
> +@cindex /etc/tunables.conf
> +
> +In addition to setting the @code{GLIBC_TUNABLES} environment variable,
> +tunables may be provided globally via the file
> +@file{/etc/tunables.conf}, which gets stored in glibc's dynamic
> +library cache @file{/etc/ld.so.cache} and read by every program at
> +startup. @file{/etc/tunables.conf} contains one tunable per line:
> +
> +@example
> +glibc.malloc.trim_threshold=128
> +glibc.malloc.check=3
> +@end example
> +
> +@file{ldconfig} (with whatever options you normally use) will read the
> +tunables in @file{/etc/tunables.conf} and save them as an extension
> +in @file{/etc/ld.so.cache}. Tunables in the cache are applied to
> +every program at startup, programs which are already running are not
> +affected.
> +
> +@file{/etc/tunables.conf} supports the same ``include @var{file}''
> +syntax as @file{ld.so.conf}.
> +
> +Tunables in @file{/etc/tunables.conf} serve as defaults. They
> +override the built-in defaults in each program, but may be overridden
> +by the @code{GLIBC_TUNABLES} environment variable. Each tunable may
> +have one or more prefixes which modifies the behavior of the tunable.
> +
> +@table @code
> +
> +@item overridable
> +@item +
> +@item nonoverridable
> +@item -
> +Prefixing the tunable name with @code{nonoverridable} or @code{-}
> +blocks changes from the environment variable, giving the global
> +setting precedence. Prefixing with @code{overridable} or @code{+}
> +(the default) reverts this behavior, which is only useful with the
> +filters (below).
> +
> +@item onlysecure
> +@item @@
> +Tunables prefixed with @code{onlysecure} or @code{@@} will apply only
> +to processes that are AT_SECURE (i.e. setuid or setgid binaries, or
> +elevated capabilities).
> +
> +@item nonsecure
> +@item $
> +Tunables prefixed with @code{nonsecure} or @code{$} will apply only to
> +processes that aren't AT_SECURE.
> +
> +@item anysecure
> +@item *
> +Tunables prefixed with @code{anysecure} or @code{*} will apply to all
> +processes.
> +
> +@end table
> +
> +Filters make the system-wide tunables only affect certain programs.
> +This allows having a non-overridable default for most of the system
> +but a different, overridable, value for certain programs that might
> +not work at all with the default setting. The syntax for filters is
> +to have each filter on its own line, followed by tunables that are
> +applied when the filter matches, with this format:
> +
> +@example
> +[ @var{filtername} : @var{pattern} ]
> +@end example
> +
> +Existing filters are:
> +
> +@table @code
> +@item proc
> +Matches the process name. The pattern is either a fully qualified
> +path, or the basename of such a path. The process name is read from
> +@file{/proc/self/exe} (if available) or @file{argv[0]} (unless
> +AT_SECURE is in effect). Example:
> +
> +@example
> +-glibc.cpu.x86_shstk=1
> +[proc:/usr/bin/program_that_crashes_with_shstk]
> ++glibc.cpu.x86_shstk=0
> +@end example
> +
> +@end table
> +
> +Note that the effects of a filter only last until the next filter, or
> +a line with @code{[]} on it (``no filter''), or the end of the file
> +(if the filter appears in an included file, at the end of the included
> +file).
> +
> @node Tunable names
> @section Tunable names
> @cindex Tunable names
> --
> 2.47.3
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v9 0/5] Add system-wide tunables
2026-06-26 3:30 [PATCH v9 0/5] Add system-wide tunables DJ Delorie
` (4 preceding siblings ...)
2026-06-26 3:30 ` [PATCH v9 1/5] Add system-wide tunables: ldconfig part DJ Delorie
@ 2026-06-30 15:52 ` Arjun Shankar
2026-06-30 16:59 ` Andreas K. Huettel
2026-06-30 20:51 ` DJ Delorie
5 siblings, 2 replies; 14+ messages in thread
From: Arjun Shankar @ 2026-06-30 15:52 UTC (permalink / raw)
To: DJ Delorie; +Cc: libc-alpha, Andreas K. Huettel
Hi DJ,
> This patch series is the initial implementation of system-wide
> tunables, which I mentioned a while ago.
I just finished reviewing this.
We briefly discussed [1] this feature during yesterday's upstream
patch review meeting [2] and I got an OK from Andreas to include it in
the current release. So I think we are ready to apply this to master.
[1] https://inbox.sourceware.org/libc-alpha/da937cb1-f5ff-4ff6-bebb-4b1e6b696084@redhat.com/T/#u
[2] https://sourceware.org/glibc/wiki/PatchworkReviewMeetings
Cheers!
--
Arjun Shankar
he/him/his
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v9 0/5] Add system-wide tunables
2026-06-30 15:52 ` [PATCH v9 0/5] Add system-wide tunables Arjun Shankar
@ 2026-06-30 16:59 ` Andreas K. Huettel
2026-06-30 20:51 ` DJ Delorie
1 sibling, 0 replies; 14+ messages in thread
From: Andreas K. Huettel @ 2026-06-30 16:59 UTC (permalink / raw)
To: DJ Delorie, Arjun Shankar; +Cc: libc-alpha
[-- Attachment #1: Type: text/plain, Size: 908 bytes --]
Am Mittwoch, 1. Juli 2026, 00:52:06 Japanische Normalzeit schrieb Arjun Shankar:
> Hi DJ,
>
> > This patch series is the initial implementation of system-wide
> > tunables, which I mentioned a while ago.
>
> I just finished reviewing this.
>
> We briefly discussed [1] this feature during yesterday's upstream
> patch review meeting [2] and I got an OK from Andreas to include it in
> the current release. So I think we are ready to apply this to master.
>
OK great, let's push it then! One desirable less to go...
> [1] https://inbox.sourceware.org/libc-alpha/da937cb1-f5ff-4ff6-bebb-4b1e6b696084@redhat.com/T/#u
> [2] https://sourceware.org/glibc/wiki/PatchworkReviewMeetings
>
> Cheers!
>
--
PD Dr. Andreas K. Hüttel
dilfridge@gentoo.org
Gentoo Linux developer
(council, comrel, toolchain, base-system, perl, libreoffice)
https://wiki.gentoo.org/wiki/User:Dilfridge
[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 870 bytes --]
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v9 0/5] Add system-wide tunables
2026-06-30 15:52 ` [PATCH v9 0/5] Add system-wide tunables Arjun Shankar
2026-06-30 16:59 ` Andreas K. Huettel
@ 2026-06-30 20:51 ` DJ Delorie
1 sibling, 0 replies; 14+ messages in thread
From: DJ Delorie @ 2026-06-30 20:51 UTC (permalink / raw)
To: Arjun Shankar; +Cc: libc-alpha, dilfridge
Many thanks! And thanks to everyone who provided feedback over the
years it took to get this patch in ;-)
PUSHED!
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-06-30 20:51 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-06-26 3:30 [PATCH v9 0/5] Add system-wide tunables DJ Delorie
2026-06-26 3:30 ` [PATCH v9 4/5] Add system-wide tunables: Filters DJ Delorie
2026-06-30 15:46 ` Arjun Shankar
2026-06-26 3:30 ` [PATCH v9 5/5] Add system-wide tunables: manual DJ Delorie
2026-06-30 15:46 ` Arjun Shankar
2026-06-26 3:30 ` [PATCH v9 3/5] Add system-wide tunables: Apply tunables part DJ Delorie
2026-06-30 15:41 ` Arjun Shankar
2026-06-26 3:30 ` [PATCH v9 2/5] Add system-wide tunables: cache ld.so.cache DJ Delorie
2026-06-30 15:40 ` Arjun Shankar
2026-06-26 3:30 ` [PATCH v9 1/5] Add system-wide tunables: ldconfig part DJ Delorie
2026-06-30 15:39 ` Arjun Shankar
2026-06-30 15:52 ` [PATCH v9 0/5] Add system-wide tunables Arjun Shankar
2026-06-30 16:59 ` Andreas K. Huettel
2026-06-30 20:51 ` DJ Delorie
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).