From patchwork Tue Mar 17 01:37:37 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: DJ Delorie X-Patchwork-Id: 131845 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id B005F4C31879 for ; Tue, 17 Mar 2026 01:38:19 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org B005F4C31879 Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=KN1z6Q/G X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id 11B784BC7EFD for ; Tue, 17 Mar 2026 01:37:43 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 11B784BC7EFD Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 11B784BC7EFD Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1773711463; cv=none; b=VwskAKufxgLNwkQRr+f6uRn+G89oPSgWP9PqTGFJbSH1odnoZXFrvYXyTLvF1lzYrACKVuNGXuVWapQWa8dTtHfVjODkDFVSw+I48gKZuPgZ81jWuJAYkSvKdNYjIDk1LKHZd2QqSNUzkd25FCe1sz6TYuJV0/VT2oAAvTLUvGI= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1773711463; c=relaxed/simple; bh=1R9HEFLXAAjTMjz6WaF5JwHS+4NanoZy5WjaiuY3WH8=; h=DKIM-Signature:Date:Message-Id:From:To:Subject; b=VIGx/C3Ww5Kltr1sny4Vig+Mkb3fsu4ON3Yh5zKuC04KXH/phhLcz03mqE8CXJ7aA+cRldWgwJAMgi6mAmpgATwuWhrvFih5+7qWmCQX1uLbeoLpqDDBIBDHcmzhrVSsoUlxvYtd4xN5tKCRBdvJ/5QyQWcbrvdBPH+BkS6J0+8= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 11B784BC7EFD DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1773711462; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:content-type:content-type; bh=bzKVJJBLIsWm3JefG1QEeSUuMcDqXgnEdHakLRL9kyY=; b=KN1z6Q/G5thORnLqkrxSbQUriEl3LFM0nkgRqS+i1uraq28py22NoyCqVofn92KPX9wafI WL6YVZAX/rL2Sqv9VIYmAIxLrX30u4q42XpOnh4XxcdGJElTiyNuZn2qfsLpH8z//xMHVe nBr6PiV1dkrVqsfJh97SEpyvsP6NQzg= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-584-S5JLJeZjMcy_3gpaIzuXeA-1; Mon, 16 Mar 2026 21:37:40 -0400 X-MC-Unique: S5JLJeZjMcy_3gpaIzuXeA-1 X-Mimecast-MFC-AGG-ID: S5JLJeZjMcy_3gpaIzuXeA_1773711460 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id E1CB1195609F for ; Tue, 17 Mar 2026 01:37:39 +0000 (UTC) Received: from greed.delorie.com (unknown [10.22.88.83]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 9D0CD1954102 for ; Tue, 17 Mar 2026 01:37:39 +0000 (UTC) Received: from greed.delorie.com.redhat.com (localhost [127.0.0.1]) by greed.delorie.com (8.16.1/8.16.1) with ESMTP id 62H1bbLq1583346 for ; Mon, 16 Mar 2026 21:37:37 -0400 Date: Mon, 16 Mar 2026 21:37:37 -0400 Message-Id: From: DJ Delorie To: libc-alpha@sourceware.org Subject: [PATCH v6 1/4] Add system-wide tunables: ldconfig part X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: VlpWb67l4otZRCqPuBK2H8vY50nsOHj1MKFHmJh_Ops_1773711460 X-Mimecast-Originator: redhat.com content-type: text/plain; charset="US-ASCII"; x-default=true X-Spam-Status: No, score=-9.3 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_ASCII_DIVIDERS, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H5, RCVD_IN_MSPIKE_WL, RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED, SPF_HELO_PASS, SPF_NONE, TXREP, URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org 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 character that controls overridability by the GLIBC_TUNABLES env var: !glibc.foo=0 ^ May be made more secure +glibc.foo=0 ^ May be overridden -glibc.foo=0 ^ May not be overridden Internally, each tunable will later have a default "overridability" and logic for what "more secure" means. 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 | 65 ++++++++- elf/ldconfig.c | 23 +++- elf/tunconf.c | 267 +++++++++++++++++++++++++++++++++++++ elf/tunconf.h | 40 ++++++ sysdeps/generic/dl-cache.h | 6 + sysdeps/generic/ldconfig.h | 6 +- 7 files changed, 401 insertions(+), 7 deletions(-) create mode 100644 elf/tunconf.c create mode 100644 elf/tunconf.h diff --git a/elf/Makefile b/elf/Makefile index 7f039b5563..6b366df91b 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -224,6 +224,7 @@ ldconfig-modules := \ readlib \ static-stubs \ stringtable \ + tunconf \ xmalloc \ xstrdup \ # ldconfig-modules diff --git a/elf/cache.c b/elf/cache.c index a6dc85dc0f..e798f7e2d8 100644 --- a/elf/cache.c +++ b/elf/cache.c @@ -36,6 +36,7 @@ #include #include #include +#include /* 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,30 @@ 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); + 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].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 +420,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); @@ -498,6 +524,28 @@ write_extensions (int fd, uint32_t str_offset, ext->sections[xid].size = hwcaps_size; } + 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) + { + 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 +557,13 @@ 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 (hwcaps_array); free (ext); } @@ -1106,3 +1161,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 #include #include +#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 @@ -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..d0fc1b7352 --- /dev/null +++ b/elf/tunconf.c @@ -0,0 +1,267 @@ +/* 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 . */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define TUNABLES_INTERNAL +#include +#include + +#include +#include +#include +#include +#include + +#include "tunconf.h" + +/* Declared in chroot_canon.c. */ +extern char *chroot_canon (const char *chroot, const char *name); + +/*----------------------------------------------------------------------*/ + +#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_DEFAULT = 0, /* let the internal code decide */ + TOP_ALLOW, /* let the environment variable override */ + TOP_STRICT, /* internal code will only allow "stricter" setting */ + 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; +struct tunable_entry_int **entry_list_next = &entry_list; + +/*----------------------------------------------------------------------*/ + +static void +add_tunable (char *line, const char *filename, int lineno) +{ + TOP top = TOP_DEFAULT; + char *name; + char *value; + char *eq; + char *orig_line; + struct tunable_entry_int *entry; + int i, id; + + orig_line = line; + + /* Leading whitespace has already been stripped. */ + + if (*line == '!' || *line == '+' || *line == '-') + { + switch (*line) + { + case '!': + top = TOP_STRICT; + break; + case '+': + top = TOP_ALLOW; + break; + case '-': + top = TOP_DENY; + break; + } + line ++; + while (*line && isspace(*line)) + line ++; + } + + /* 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 '=')\n", + orig_line); + return; + } + + if (eq == name) + { + error_at_line (0, 0, filename, lineno, + "syntax error, line ignored: `%s' (missing tunable name)\n", + 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)\n", + 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; + } + + 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; + + *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; + } + + /* 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 *) malloc (size); + + if (thc == NULL) + { + error (0, 0, _("Unable to allocate %zu bytes in get_tunable_ext"), size); + return NULL; + } + + /* 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_DEFAULT: + tec->flags |= TUNCONF_OVERRIDE_DEFAULT; + break; + case TOP_ALLOW: + tec->flags |= TUNCONF_OVERRIDE_ALLOW; + break; + case TOP_STRICT: + tec->flags |= TUNCONF_OVERRIDE_STRICTER; + 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..7578605c5a --- /dev/null +++ b/elf/tunconf.h @@ -0,0 +1,40 @@ +#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_DEFAULT 0x00000000 +#define TUNCONF_OVERRIDE_ALLOW 0x00000004 +#define TUNCONF_OVERRIDE_STRICTER 0x00000008 +#define TUNCONF_OVERRIDE_DENY 0x0000000C + +#define TUNCONF_FLAG_FILTER 0x0000ff00 +#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..f438982b6f 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 }; 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); From patchwork Tue Mar 17 01:38:02 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: DJ Delorie X-Patchwork-Id: 131846 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id 9FD5F4C900EB for ; Tue, 17 Mar 2026 01:39:26 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 9FD5F4C900EB Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Z1cpYNcZ X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id E819B4B0A6F4 for ; Tue, 17 Mar 2026 01:38:06 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org E819B4B0A6F4 Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org E819B4B0A6F4 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1773711487; cv=none; b=GSSWg7pIaxUONTAI9vko364df5NZmMu0tcgLbXU6cCJP3bmr3mUDrdfY80s/z+XjEEvrYh4Ygsb2HLeit8c6rtGymVes//qyazz3RHs0L5v6Mb7zQhRVrRTEQpE8V28LrZcnERvy6iWkCO15EAKbjHreR8LNrbXus6tCY2JlMZ4= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1773711487; c=relaxed/simple; bh=8QTeNC5Mrf1isOBUSOzgrLw+/Bm44wpngLRmtflSGV4=; h=DKIM-Signature:Date:Message-Id:From:To:Subject; b=r8KiHhjdZXY6EhuObu2+Z4YSmoR6e56/9lXsm96EOqd/SnfnAEDQWdmcbXg2k9kyseNwv3Z7qp60U632ncQiZI5lfS6A8X/AzR8TfQE81EsxR9jFD/X1jV4xQve51zHNxrbkn22vpUU/TH63/96+i0I2DdsrHOepGbP0TLD3yho= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org E819B4B0A6F4 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1773711486; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:content-type:content-type; bh=cy0tssxtfFbaj2VE5wHAFXd8sRJpEn7lICpoHHurdzg=; b=Z1cpYNcZJH70y/GksxqnR7ImcN0QYXQjJRNfxG1jTmObb+E+4ax2I/WrFlnlhtLL+smTba /RdnbLJ6Ruf1Oz6VUzFLXdw0R59j0m2PZLrPWQJ5eHbXfKy1eV5vu2ordRcDzy9V7aRRRL bLMUMzr0fK88ZE60TmLfAqqtYW3bdMc= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-14-D8_-26plN_u4F-4tDxZg2Q-1; Mon, 16 Mar 2026 21:38:04 -0400 X-MC-Unique: D8_-26plN_u4F-4tDxZg2Q-1 X-Mimecast-MFC-AGG-ID: D8_-26plN_u4F-4tDxZg2Q_1773711483 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id D228618002D7 for ; Tue, 17 Mar 2026 01:38:03 +0000 (UTC) Received: from greed.delorie.com (unknown [10.22.88.83]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 8B23D3000223 for ; Tue, 17 Mar 2026 01:38:03 +0000 (UTC) Received: from greed.delorie.com.redhat.com (localhost [127.0.0.1]) by greed.delorie.com (8.16.1/8.16.1) with ESMTP id 62H1c2wf1583375 for ; Mon, 16 Mar 2026 21:38:02 -0400 Date: Mon, 16 Mar 2026 21:38:02 -0400 Message-Id: From: DJ Delorie To: libc-alpha@sourceware.org Subject: [PATCH v6 2/4] Add system-wide tunables: cache ld.so.cache X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: 8KWBrokT7_kXO3MBya0v2pia9fLbXNboSBL_eyDko7g_1773711483 X-Mimecast-Originator: redhat.com content-type: text/plain; charset="US-ASCII"; x-default=true X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_MAILBOX2, KAM_SHORT, RCVD_IN_DNSWL_BLOCKED, RCVD_IN_MSPIKE_H5, RCVD_IN_MSPIKE_WL, RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED, SPF_HELO_PASS, SPF_NONE, TXREP, URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org 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 | 3 + elf/dl-cache.c | 259 ++++++++++++------ 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, 321 insertions(+), 85 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 6b366df91b..5398fe0d2c 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -559,6 +559,7 @@ endif tests-container += \ tst-dlopen-self-container \ tst-dlopen-tlsmodid-container \ + tst-ldconfig-cache \ tst-pldd \ tst-preload-pthread-libc \ tst-rootdir \ @@ -714,6 +715,8 @@ 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,1 2 3 4 5,tst-tls-manydynamic$(n)mod) 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 9458ffae2a..aa8a2c6a88 100644 --- a/elf/dl-cache.c +++ b/elf/dl-cache.c @@ -25,11 +25,21 @@ #include <_itoa.h> #include #include +#include +#include /* 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 @@ -52,6 +62,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 @@ -83,10 +94,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) @@ -373,6 +380,162 @@ _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))); +} + +/* Attemps 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 && 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) + + 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. */ + 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 @@ -388,81 +551,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, @@ -488,7 +584,7 @@ _dl_load_cache_lookup (const char *name) return NULL; /* The double copy is *required* since malloc may be interposed - and call dlopen itself whose completion would unmap the data + and call dlopen itself whose completion may unmap the data we are accessing. Therefore we must make the copy of the mapping data without using malloc. */ char *temp; @@ -506,14 +602,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 . */ + +/* 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 + +#include +#include + +#include +#include +#include +#include + + +/* 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 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 From patchwork Tue Mar 17 01:39:18 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: DJ Delorie X-Patchwork-Id: 131848 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id 2D5404C3187E for ; Tue, 17 Mar 2026 01:41:02 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 2D5404C3187E Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=EOYCfpSo X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id 3C4664B0A6F8 for ; Tue, 17 Mar 2026 01:39:27 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 3C4664B0A6F8 Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 3C4664B0A6F8 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1773711567; cv=none; b=K3t2u7pLR6Y9x/ERXVEZpvu4gQF1qyiw5n+Bb6yokNDF6Pp5nJOOBQJutFbgldc7ZPL91us0B6GiOT7jlt7i0gU3zlXEXPXouX65gWZ0xf2a6faOKIUJ8Gh5O024khDR8gJiJc5EryYs1CUS4iLneQweYtNKA66gZSWXU4bXp30= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1773711567; c=relaxed/simple; bh=VBqJvG5fOdQzBCp/HF0+JccdeuVOU6oqGdgKaccUIbQ=; h=DKIM-Signature:Date:Message-Id:From:To:Subject; b=JfuLh9ZTNRHl+ILDIil93bxPm2GQC/1FekrYPozRiKXKGPJ/aHcth0CGJIxqxIFuV6LDL6xNDHc7IrOudTPSelblQxINiEdZmPl7lhxRqdqG41JFc1I4JrHyLsuDBJbLEIQEBTDD2Xm42U92Ua56kZAU/+02CN59Aam4KPjMhdE= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 3C4664B0A6F8 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1773711566; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:content-type:content-type; bh=vKoPxA8vIVtnH2ekOlkJuVHBHPZrcW8Tcuj78sjW+fU=; b=EOYCfpSoV+t/Huwz0uMQ9Kx7y67enLHGJxn3TRnf4kCnCH6fqLGzHMTUcuNIZdcC9okQOY 24I3SbAerhZqK/su/zhUpGYLcoEjrL4RHwApWisVqME0v3STrsBlvkc3Z73cq0u/IoUm0z jL2c4Bl5EDW/2C2lacc4HFxH+jALKeM= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-191-eh9QBVglNPCqJEk3hH56Iw-1; Mon, 16 Mar 2026 21:39:21 -0400 X-MC-Unique: eh9QBVglNPCqJEk3hH56Iw-1 X-Mimecast-MFC-AGG-ID: eh9QBVglNPCqJEk3hH56Iw_1773711560 Received: from mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.111]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id B234B19560A6 for ; Tue, 17 Mar 2026 01:39:20 +0000 (UTC) Received: from greed.delorie.com (unknown [10.22.88.83]) by mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 53CEB1800361 for ; Tue, 17 Mar 2026 01:39:20 +0000 (UTC) Received: from greed.delorie.com.redhat.com (localhost [127.0.0.1]) by greed.delorie.com (8.16.1/8.16.1) with ESMTP id 62H1dIxd1583425 for ; Mon, 16 Mar 2026 21:39:18 -0400 Date: Mon, 16 Mar 2026 21:39:18 -0400 Message-Id: From: DJ Delorie To: libc-alpha@sourceware.org Subject: [PATCH v6 3/4] Add system-wide tunables: Apply tunables part X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.111 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: kQc3rwpC6yDzbGR6-cHuvCVD3rzKCkGuy6-pcPpAxAo_1773711560 X-Mimecast-Originator: redhat.com content-type: text/plain; charset="US-ASCII"; x-default=true X-Spam-Status: No, score=-9.7 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_BLOCKED, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED, SPF_HELO_PASS, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org 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 | 25 +++++++++++++++++++ elf/dl-tunables.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++ elf/tunconf.h | 3 +++ 3 files changed, 91 insertions(+) diff --git a/elf/dl-cache.c b/elf/dl-cache.c index aa8a2c6a88..539bf38905 100644 --- a/elf/dl-cache.c +++ b/elf/dl-cache.c @@ -27,6 +27,7 @@ #include #include #include +#include "tunconf.h" /* This is the starting address and the size of the mmap()ed file. */ static struct cache_file *cache; @@ -606,3 +607,27 @@ _dl_unload_cache (void) now. */ } #endif + +const struct tunable_header_cached * +_dl_load_cache_tunables (const char **data) +{ + struct cache_extension_all_loaded ext; + + /* This loads the cache (temporary). */ + 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; + return ext.sections[cache_extension_tag_tunables].base; +} diff --git a/elf/dl-tunables.c b/elf/dl-tunables.c index bdb1de4ceb..29fcd4504b 100644 --- a/elf/dl-tunables.c +++ b/elf/dl-tunables.c @@ -36,6 +36,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) @@ -296,6 +297,68 @@ __tunables_init (char **envp) char *envval = NULL; char **prev_envp = envp; +#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. */ + tunable_list[tid].val.strval.str = __strdup (value); + tunable_list[tid].val.strval.len = strlen (value); + } + 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 7578605c5a..9551119167 100644 --- a/elf/tunconf.h +++ b/elf/tunconf.h @@ -38,3 +38,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); From patchwork Tue Mar 17 01:39:39 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: DJ Delorie X-Patchwork-Id: 131847 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id 379364C91767 for ; Tue, 17 Mar 2026 01:40:31 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 379364C91767 Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=EQdPDhp/ X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id 5C0B64C91764 for ; Tue, 17 Mar 2026 01:39:45 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 5C0B64C91764 Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 5C0B64C91764 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1773711585; cv=none; b=guAeRWEkW2T3xQ/2XkW+pBvGvjZVQ5Yl4J+J0Ae1txWQ68zlNHIklQKHEJyyiFyVnxtd1cyRsCK+rdSQZ2+x+JwUGrl52vQrSpoMbqDeJ6pijpg4dg5IsTB44LFx4wXPr8d9zHRrwzJs18JH0dg6bgZ8dtKuCcLrdtDV9SqxhKs= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1773711585; c=relaxed/simple; bh=Fvqz1egj/MiwtlP7PMMy5cbIVWOpD3VflUmi15sENws=; h=DKIM-Signature:Date:Message-Id:From:To:Subject; b=GUTbZ5U34IKgTY32RzEBG83NsYrKCi6k1xIqp015+73A01oWyHLY4XuBP5BEIXzPlBldlIbTcnxH0xgjJmUirNdzj61IlzTtNoKXHyeKcdcSPnvf5KkQ1uAnC8tSF21emWXwltF6GDfpsZvi4qO8wXEn20kP14aYlz5cskDueGY= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 5C0B64C91764 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1773711585; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:content-type:content-type; bh=JyBQImU7Oge5TRCxLKLNRMS4MagsXqYgcSpoDo3o4Dk=; b=EQdPDhp/3vaeHGpbqStvDJC3LcYb7jSb+QH8eV8jecLr3MavYqM2LnFrjzR3EWdPqjC7fy jFYSS797nXTqcAQXGavQtndRF5cZFfPjVgfFjimAVdWhHjz/KpBapXEMyNoNzKJYK8nY2E Kky6uw7iktx9YAvFP5GftFDllQBlTm4= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-502-XMetceQBNPChKa91PYaG-w-1; Mon, 16 Mar 2026 21:39:43 -0400 X-MC-Unique: XMetceQBNPChKa91PYaG-w-1 X-Mimecast-MFC-AGG-ID: XMetceQBNPChKa91PYaG-w_1773711582 Received: from mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.111]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id B2D38195605E for ; Tue, 17 Mar 2026 01:39:42 +0000 (UTC) Received: from greed.delorie.com (unknown [10.22.88.83]) by mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 6EC251800351 for ; Tue, 17 Mar 2026 01:39:41 +0000 (UTC) Received: from greed.delorie.com.redhat.com (localhost [127.0.0.1]) by greed.delorie.com (8.16.1/8.16.1) with ESMTP id 62H1ddBm1583440 for ; Mon, 16 Mar 2026 21:39:39 -0400 Date: Mon, 16 Mar 2026 21:39:39 -0400 Message-Id: From: DJ Delorie To: libc-alpha@sourceware.org Subject: [PATCH v6 4/4] Add system-wide tunables: Filters X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.111 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: QKkKi1E7crRKmuXRbb_LMXzrK8Jt2HKSngtGhAS3ILc_1773711582 X-Mimecast-Originator: redhat.com content-type: text/plain; charset="US-ASCII"; x-default=true X-Spam-Status: No, score=-9.7 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H5, RCVD_IN_MSPIKE_WL, RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED, SPF_HELO_PASS, SPF_NONE, TXREP, URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org 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.unsecure-binaries=1 @glibc.only-for.secure-binaries=1 --- csu/libc-start.c | 2 +- elf/Makefile | 4 + elf/cache.c | 3 +- elf/dl-tunables.c | 80 +++++++++++++++- 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 | 15 +++ elf/tst-tunconf1.root/ldconfig.run | 0 elf/tst-tunconf1.root/postclean.req | 0 elf/tunconf.c | 122 +++++++++++++++++++++++- elf/tunconf.h | 3 + sysdeps/mach/hurd/dl-sysdep.c | 2 +- sysdeps/unix/sysv/linux/dl-sysdep.c | 2 +- 15 files changed, 265 insertions(+), 15 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 1c58561bce..ae36170045 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 5398fe0d2c..4fbd03cefc 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -323,6 +323,7 @@ tests-internal := \ $(tests-static-internal) \ tst-tls1 \ tst-tls_tp_offset \ + tst-tunconf1 \ # tests-internal tests-static := $(tests-static-normal) $(tests-static-internal) @@ -333,6 +334,8 @@ tests-static += \ tst-tls9-static \ # tests-static +tst-tunconf1-ENV = GLIBC_TUNABLES=glibc.malloc.tcache_count=5 + static-dlopen-environment = \ LD_LIBRARY_PATH=$(ld-library-path):$(common-objpfx)dlfcn tst-tls9-static-ENV = $(static-dlopen-environment) @@ -563,6 +566,7 @@ tests-container += \ tst-pldd \ tst-preload-pthread-libc \ tst-rootdir \ + tst-tunconf1 \ # tests-container test-srcs = \ diff --git a/elf/cache.c b/elf/cache.c index e798f7e2d8..2809f255f5 100644 --- a/elf/cache.c +++ b/elf/cache.c @@ -300,9 +300,10 @@ print_extensions (struct cache_extension_all_loaded *ext, thc->signature, thc->version, thc->num_tunables); for (i = 0; i < count; ++ i) { - printf(" [%d] %s : %s [flags 0x%08x", + printf(" [%d] %s (%d) : %s [flags 0x%08x", i, cache_data + tec[i].name_offset, + tec[i].tunable_id, cache_data + tec[i].value_offset, tec[i].flags); if (tec[i].flag_offset != 0) diff --git a/elf/dl-tunables.c b/elf/dl-tunables.c index 29fcd4504b..acee6eb872 100644 --- a/elf/dl-tunables.c +++ b/elf/dl-tunables.c @@ -291,13 +291,22 @@ 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; char **prev_envp = envp; #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; + int base_name_len = -1; +#ifdef PATH_MAX + char exebuf[PATH_MAX]; +#else + char exebuf[256]; +#endif const struct tunable_header_cached *thc; const char *td; @@ -330,9 +339,70 @@ __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_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); + base_name_len = strlen (base_name); + } + } + /* prog_name and the cached string are both NUL terminated. */ + if (prog_name) + { + if (((const char *)(td + tec->flag_offset))[0] == '/') + { + if (memcmp (prog_name, td + tec->flag_offset, prog_name_len) != 0) + goto skip_due_to_filter; + } + else + { + if (memcmp (base_name, td + tec->flag_offset, base_name_len) != 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: + break; + } + + /* 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 @@ -355,6 +425,8 @@ __tunables_init (char **envp) value, strlen (value)); } } + + skip_due_to_filter:; } } #endif /* defined(SHARED) && defined (USE_LDCONFIG) */ diff --git a/elf/dl-tunables.h b/elf/dl-tunables.h index 45aeed47bc..3f34329614 100644 --- a/elf/dl-tunables.h +++ b/elf/dl-tunables.h @@ -47,7 +47,7 @@ typedef void (*tunable_callback_t) (tunable_val_t *); #include "dl-tunable-list.h" -extern void __tunables_init (char **); +extern void __tunables_init (char **, char **); extern void __tunables_print (void); extern bool __tunable_is_initialized (tunable_id_t); extern void __tunable_get_val (tunable_id_t, void *, tunable_callback_t); diff --git a/elf/ldconfig-parse.c b/elf/ldconfig-parse.c index b7bb664eb5..baddfdbac0 100644 --- a/elf/ldconfig-parse.c +++ b/elf/ldconfig-parse.c @@ -47,8 +47,10 @@ ldconfig_parse_config_1 (const char *filename, bool do_chroot, opt_chroot - If non-NULL, all paths are relative to this. - callback - for each non-blank line in the file, this function is called - with the line and it's location. + callback - for each non-blank line in the file, this function is + called with the line and it's location. Will also be called + with a NULL line at the start and end of each file, for + file-scoped config items. */ void diff --git a/elf/ldconfig.c b/elf/ldconfig.c index 11b063eb5c..1ea55400f3 100644 --- a/elf/ldconfig.c +++ b/elf/ldconfig.c @@ -435,6 +435,9 @@ add_dir_1 (const char *line, const char *from_file, int from_line) static void add_dir_callback (char *line, const char *from_file, int from_line) { + /* Denotes file boundaries. Not needed here. */ + if (line == NULL) + return; if (!strncasecmp (line, "hwcap", 5) && isblank (line[5])) error (0, 0, _("%s:%u: hwcap directive ignored"), from_file, from_line); else diff --git a/elf/tst-tunconf1.c b/elf/tst-tunconf1.c new file mode 100644 index 0000000000..c95a7cb8ba --- /dev/null +++ b/elf/tst-tunconf1.c @@ -0,0 +1,36 @@ +/* Test that the tunables cache can override env vars. + Copyright (C) 2026 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include + +#include "dl-tunables.h" + +static int +do_test (void) +{ + size_t tcache_count = TUNABLE_GET_FULL (glibc, malloc, tcache_count, size_t, NULL); + size_t tcache_max = TUNABLE_GET_FULL (glibc, malloc, tcache_max, size_t, NULL); + printf("tcache count is %ld (should be 5, from env)\n", (long)tcache_count); + TEST_COMPARE ((long)tcache_count, 5); + printf("tcache max is %ld (should be 4, from /etc)\n", (long)tcache_max); + TEST_COMPARE ((long)tcache_max, 4); + return 0; +} + +#include diff --git a/elf/tst-tunconf1.root/etc/tunables.conf b/elf/tst-tunconf1.root/etc/tunables.conf new file mode 100644 index 0000000000..6cd6c8a949 --- /dev/null +++ b/elf/tst-tunconf1.root/etc/tunables.conf @@ -0,0 +1,15 @@ +# These test the parser for both the overridability characters as well as +# tunables that either never exist, or only exist on some platforms. +!glibc.foo=19 +-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 d0fc1b7352..c912abd876 100644 --- a/elf/tunconf.c +++ b/elf/tunconf.c @@ -66,12 +66,16 @@ 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_unsecure: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; }; @@ -79,8 +83,76 @@ struct tunable_entry_int { struct tunable_entry_int *entry_list; struct tunable_entry_int **entry_list_next = &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) + { + printf("%s:%d: syntax error, filter line ignored: `%s' (missing ':')\n", + filename, lineno, line); + return; + } + if (right_bracket == NULL) + { + printf("%s:%d: syntax error, filter line ignored: `%s' (missing ']')\n", + filename, lineno, line); + return; + } + + if (filter_string != NULL) + { + clear_filter (); + } + + if (memcmp ("proc", line + 1, colon - line - 1) == 0) + { + filter_string = (char *) malloc (right_bracket - colon); + memcpy (filter_string, colon + 1, right_bracket - colon - 1); + filter_string [right_bracket - colon] = 0; + filter_flags = TUNCONF_FILTER_PERPROC; + } + + else + printf("%s:%d: unrecognized filter `%.*s', ignored\n", filename, lineno, (int)(colon - line - 1), line + 1); +} + + static void add_tunable (char *line, const char *filename, int lineno) { @@ -91,12 +163,20 @@ add_tunable (char *line, const char *filename, int lineno) char *orig_line; struct tunable_entry_int *entry; int i, id; + bool exclude_secure = 0, exclude_unsecure = 0; + + /* Denotes file boundaries. */ + if (line == NULL) + { + clear_filter(); + return; + } orig_line = line; /* Leading whitespace has already been stripped. */ - if (*line == '!' || *line == '+' || *line == '-') + while (*line) { switch (*line) { @@ -109,11 +189,25 @@ add_tunable (char *line, const char *filename, int lineno) case '-': top = TOP_DENY; break; + case '@': + exclude_unsecure = 1; + break; + case '$': + exclude_secure = 1; + break; + case '[': + parse_filter (line, filename, lineno); + return; + case ' ': + case '\t': + break; + + default: + goto done; } line ++; - while (*line && isspace(*line)) - line ++; } + done: /* NAME now points to the start of the tunable name. */ name = line; @@ -174,6 +268,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_unsecure = exclude_unsecure; + + if (filter_flags) + { + entry->filter_flags = filter_flags; + entry->filter = cache_store_string (filter_string); + } *entry_list_next = entry; entry_list_next = & (entry->next); @@ -248,11 +350,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_unsecure) + 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 9551119167..e880aa574a 100644 --- a/elf/tunconf.h +++ b/elf/tunconf.h @@ -10,6 +10,9 @@ #define TUNCONF_OVERRIDE_STRICTER 0x00000008 #define TUNCONF_OVERRIDE_DENY 0x0000000C +#define TUNCONF_EXCLUDE_SECURE 0x00000010 +#define TUNCONF_EXCLUDE_UNSECURE 0x00000020 + #define TUNCONF_FLAG_FILTER 0x0000ff00 #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 ();