[1/3] Add system-wide tunables: ldconfig part

Message ID xncyqm2esn.fsf@greed.delorie.com (mailing list archive)
State Superseded
Headers
Series [1/3] Add system-wide tunables: ldconfig part |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Testing passed
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 success Testing passed
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Testing passed
linaro-tcwg-bot/tcwg_glibc_check--master-arm success Testing passed

Commit Message

DJ Delorie April 19, 2024, 3:48 a.m. UTC
  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             |  21 +-
 elf/tunconf.c              | 408 +++++++++++++++++++++++++++++++++++++
 elf/tunconf.h              |  40 ++++
 sysdeps/generic/dl-cache.h |   6 +
 sysdeps/generic/ldconfig.h |   2 +
 7 files changed, 539 insertions(+), 4 deletions(-)
 create mode 100644 elf/tunconf.c
 create mode 100644 elf/tunconf.h
  

Comments

Adhemerval Zanella Netto May 29, 2024, 1:08 p.m. UTC | #1
On 19/04/24 00:48, DJ Delorie wrote:
> 
> 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.

What I have in mind to for system-wide tunable is to add a set of hardening
ones that is in line with recent Florian's recommendation for dynamic linker,
thus allowing an admin to setup a more restrictive environment without to
resort on how the binary was built.

For instance, a /etc/tunables.conf with

-glibc.enforce_bind_now=1
-glibc.enforce_nx_stack=1
-glibc.enforce_full_relro=1
-glibc.enable_audit=0
-glibc.enforce_pie=1
-glibc.enable_dlopen=0

should enforce only binary without lazy binding, non-executable stack, full
RELRO, without any audit module, and with DF_1_PIE should be allowed to
run.  Also, dlopen would return always return NULL.

We can extend it further to disable DF_1_INITFIRST, ignore environment
variables (like LD_PRELOAD, LD_LIBRARY_PATH, GLIBC_TUNABLES, etc.).

The per-file option would be to allow some specific binaries in the system
to run despite not following some security boundary. I am not sure how useful,
but I see on other systems that some transition rule is useful if such
hardening become common.

> ---
>  elf/Makefile               |   1 +
>  elf/cache.c                |  65 +++++-
>  elf/ldconfig.c             |  21 +-
>  elf/tunconf.c              | 408 +++++++++++++++++++++++++++++++++++++
>  elf/tunconf.h              |  40 ++++
>  sysdeps/generic/dl-cache.h |   6 +
>  sysdeps/generic/ldconfig.h |   2 +
>  7 files changed, 539 insertions(+), 4 deletions(-)
>  create mode 100644 elf/tunconf.c
>  create mode 100644 elf/tunconf.h
> 
> diff --git a/elf/Makefile b/elf/Makefile
> index ec1516ca3d..ab7839abb3 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 8a618e11fa..1146bba877 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,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 b64c54b53e..b6463b6464 100644
> --- a/elf/ldconfig.c
> +++ b/elf/ldconfig.c
> @@ -43,6 +43,7 @@
>  #include <dl-cache.h>
>  #include <dl-hwcaps.h>
>  #include <dl-is_dso.h>
> +#include "tunconf.h"
>  
>  #include <dl-procinfo.h>
>  
> @@ -50,6 +51,10 @@
>  # 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;
> @@ -1240,6 +1252,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)
> @@ -1315,6 +1330,8 @@ main (int argc, char **argv)
>  
>    search_dirs ();
>  
> +  parse_tunconf (tunconfig_file, true, 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..9ba35326f6
> --- /dev/null
> +++ b/elf/tunconf.c
> @@ -0,0 +1,408 @@
> +/* 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 "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

Afaik this is not used (the cache information is within ld.so.cache).

> +
> +// 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;
> +};

I think we should use the current guidelines for this code.

And we definitely need to add tests for this new tunable parsing.

> +
> +struct tunable_entry_int *entry_list;
> +struct tunable_entry_int **entry_list_next = &entry_list;
> +
> +/*----------------------------------------------------------------------*/
> +
> +static void parse_tunconf_include (const char *tunconfig_file, unsigned int lineno,
> +				   bool do_chroot, const char *pattern, const char *opt_chroot);
> +
> +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;
> +  printf("%s:%d: `%s'\n", filename, lineno, 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;
> +	}
> +      printf("TOP: %d\n", top);
> +      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)
> +    {
> +      printf("%s:%d: syntax error, line ignored: `%s' (missing '=')\n",
> +	     filename, lineno, orig_line);
> +      return;
> +    }
> +
> +  if (eq == name)
> +    {
> +      printf("%s:%d: syntax error, line ignored: `%s' (missing tunable name)\n",
> +	     filename, lineno, orig_line);
> +      return;
> +    }
> +
> +  // At this point, EQ actually points to '='
> +  value = eq + 1;
> +
> +  while (*value && isspace(*value))
> +    value ++;
> +
> +  if (*value == 0)
> +    {
> +      printf("%s:%d: syntax error, line ignored: `%s' (missing value)\n",
> +	     filename, lineno, 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 (name[strlen(value)-1]))
> +    value[strlen(value)-1] = 0;
> +
> +  id = -1;
> +  for (i=0; i<TUNABLE_NAME_MAX; i++)
> +    if (strcmp (tunable_list[i].name, name) == 0)
> +      {
> +	id = i;
> +	break;
> +      }
> +
> +  printf("top %d name `%s' (%d) value `%s'\n", top, name, id, value);
> +  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, int do_chroot, char *opt_chroot)
> +{
> +  FILE *file = NULL;
> +  char *line = NULL;
> +  const char *canon;
> +  size_t len = 0;
> +  unsigned int lineno;
> +
> +  if (do_chroot && opt_chroot)
> +    {
> +      canon = chroot_canon (opt_chroot, filename);
> +      if (canon)
> +	file = fopen (canon, "r");
> +      else
> +	canon = filename;
> +    }
> +  else
> +    {
> +      canon = filename;
> +      file = fopen (filename, "r");
> +    }
> +
> +  if (file == NULL)
> +    {
> +      if (errno != ENOENT)
> +	error (0, errno, _("\
> +Warning: ignoring configuration file that cannot be opened: %s"),
> +	       canon);
> +      if (canon != filename)
> +	free ((char *) canon);
> +      return;
> +    }
> +
> +  /* No threads use this stream.  */
> +  __fsetlocking (file, FSETLOCKING_BYCALLER);
> +
> +  if (canon != filename)
> +    free ((char *) canon);
> +  lineno = 0;
> +  do
> +    {
> +      ssize_t n = getline (&line, &len, file);
> +      if (n < 0)
> +	break;
> +
> +      ++lineno;
> +      if (line[n - 1] == '\n')
> +	line[n - 1] = '\0';
> +
> +      /* Because the file format does not know any form of quoting we
> +	 can search forward for the next '#' character and if found
> +	 make it terminating the line.  */
> +      *strchrnul (line, '#') = '\0';
> +
> +      /* Remove leading whitespace.  NUL is no whitespace character.  */
> +      char *cp = line;
> +      while (isspace (*cp))
> +	++cp;
> +
> +      /* If the line is blank it is ignored.  */
> +      if (cp[0] == '\0')
> +	continue;
> +
> +      if (!strncmp (cp, "include", 7) && isblank (cp[7]))
> +	{
> +	  char *dir;
> +	  cp += 8;
> +	  while ((dir = strsep (&cp, " \t")) != NULL)
> +	    if (dir[0] != '\0')
> +	      parse_tunconf_include (filename, lineno, do_chroot, dir, opt_chroot);
> +	}
> +      else
> +	add_tunable (cp, filename, lineno);
> +    }
> +  while (!feof_unlocked (file));
> +
> +  /* Free buffer and close file.  */
> +  free (line);
> +  fclose (file);
> +}
> +
> +/* Handle one word in an `include' line, a glob pattern of additional
> +   config files to read.  */
> +static void
> +parse_tunconf_include (const char *tunconfig_file, unsigned int lineno,
> +		       bool do_chroot, const char *pattern, const char *opt_chroot)
> +{
> +  if (opt_chroot != NULL && pattern[0] != '/')
> +    error (EXIT_FAILURE, 0,
> +	   _("need absolute file name for configuration file when using -r"));
> +
> +  char *copy = NULL;
> +  if (pattern[0] != '/' && strchr (tunconfig_file, '/') != NULL)
> +    {
> +      if (asprintf (&copy, "%s/%s", dirname (strdupa (tunconfig_file)),
> +		    pattern) < 0)
> +	error (EXIT_FAILURE, 0, _("memory exhausted"));
> +      pattern = copy;
> +    }
> +
> +  glob64_t gl;
> +  int result;
> +  if (do_chroot && opt_chroot)
> +    {
> +      char *canon = chroot_canon (opt_chroot, pattern);
> +      if (canon == NULL)
> +	return;
> +      result = glob64 (canon, 0, NULL, &gl);
> +      free (canon);
> +    }
> +  else
> +    result = glob64 (pattern, 0, NULL, &gl);
> +
> +  switch (result)
> +    {
> +    case 0:
> +      for (size_t i = 0; i < gl.gl_pathc; ++i)
> +	parse_tunconf (gl.gl_pathv[i], false, NULL);
> +      globfree64 (&gl);
> +      break;
> +
> +    case GLOB_NOMATCH:
> +      break;
> +
> +    case GLOB_NOSPACE:
> +      errno = ENOMEM;
> +      /* Fall through.  */
> +    case GLOB_ABORTED:
> +      if (opt_verbose)
> +	error (0, errno, _("%s:%u: cannot read directory %s"),
> +	       tunconfig_file, lineno, pattern);
> +      break;
> +
> +    default:
> +      abort ();
> +      break;
> +    }
> +
> +  free (copy);
> +}
> +
> +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;
> +    }
> +  //  printf("get_tunconf_ext: found %lu entries\n", count);
> +
> +  /* 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;
> +      //      printf("tei->top is %d\n", tei->top);
> +      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;
> +      //      printf("tec->flags is %08x\n", tec->flags);
> +
> +      ++ count;
> +      tei = tei->next;
> +    }
> +
> +  return thc;
> +}
> diff --git a/elf/tunconf.h b/elf/tunconf.h
> new file mode 100644
> index 0000000000..a6c5f0dd9a
> --- /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, int do_chroot, 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 919e49ffc8..41521f5a96 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 7dafa791f2..31ea2ec5d6 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);
  

Patch

diff --git a/elf/Makefile b/elf/Makefile
index ec1516ca3d..ab7839abb3 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 8a618e11fa..1146bba877 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,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 b64c54b53e..b6463b6464 100644
--- a/elf/ldconfig.c
+++ b/elf/ldconfig.c
@@ -43,6 +43,7 @@ 
 #include <dl-cache.h>
 #include <dl-hwcaps.h>
 #include <dl-is_dso.h>
+#include "tunconf.h"
 
 #include <dl-procinfo.h>
 
@@ -50,6 +51,10 @@ 
 # 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;
@@ -1240,6 +1252,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)
@@ -1315,6 +1330,8 @@  main (int argc, char **argv)
 
   search_dirs ();
 
+  parse_tunconf (tunconfig_file, true, 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..9ba35326f6
--- /dev/null
+++ b/elf/tunconf.c
@@ -0,0 +1,408 @@ 
+/* 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 "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 parse_tunconf_include (const char *tunconfig_file, unsigned int lineno,
+				   bool do_chroot, const char *pattern, const char *opt_chroot);
+
+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;
+  printf("%s:%d: `%s'\n", filename, lineno, 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;
+	}
+      printf("TOP: %d\n", top);
+      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)
+    {
+      printf("%s:%d: syntax error, line ignored: `%s' (missing '=')\n",
+	     filename, lineno, orig_line);
+      return;
+    }
+
+  if (eq == name)
+    {
+      printf("%s:%d: syntax error, line ignored: `%s' (missing tunable name)\n",
+	     filename, lineno, orig_line);
+      return;
+    }
+
+  // At this point, EQ actually points to '='
+  value = eq + 1;
+
+  while (*value && isspace(*value))
+    value ++;
+
+  if (*value == 0)
+    {
+      printf("%s:%d: syntax error, line ignored: `%s' (missing value)\n",
+	     filename, lineno, 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 (name[strlen(value)-1]))
+    value[strlen(value)-1] = 0;
+
+  id = -1;
+  for (i=0; i<TUNABLE_NAME_MAX; i++)
+    if (strcmp (tunable_list[i].name, name) == 0)
+      {
+	id = i;
+	break;
+      }
+
+  printf("top %d name `%s' (%d) value `%s'\n", top, name, id, value);
+  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, int do_chroot, char *opt_chroot)
+{
+  FILE *file = NULL;
+  char *line = NULL;
+  const char *canon;
+  size_t len = 0;
+  unsigned int lineno;
+
+  if (do_chroot && opt_chroot)
+    {
+      canon = chroot_canon (opt_chroot, filename);
+      if (canon)
+	file = fopen (canon, "r");
+      else
+	canon = filename;
+    }
+  else
+    {
+      canon = filename;
+      file = fopen (filename, "r");
+    }
+
+  if (file == NULL)
+    {
+      if (errno != ENOENT)
+	error (0, errno, _("\
+Warning: ignoring configuration file that cannot be opened: %s"),
+	       canon);
+      if (canon != filename)
+	free ((char *) canon);
+      return;
+    }
+
+  /* No threads use this stream.  */
+  __fsetlocking (file, FSETLOCKING_BYCALLER);
+
+  if (canon != filename)
+    free ((char *) canon);
+  lineno = 0;
+  do
+    {
+      ssize_t n = getline (&line, &len, file);
+      if (n < 0)
+	break;
+
+      ++lineno;
+      if (line[n - 1] == '\n')
+	line[n - 1] = '\0';
+
+      /* Because the file format does not know any form of quoting we
+	 can search forward for the next '#' character and if found
+	 make it terminating the line.  */
+      *strchrnul (line, '#') = '\0';
+
+      /* Remove leading whitespace.  NUL is no whitespace character.  */
+      char *cp = line;
+      while (isspace (*cp))
+	++cp;
+
+      /* If the line is blank it is ignored.  */
+      if (cp[0] == '\0')
+	continue;
+
+      if (!strncmp (cp, "include", 7) && isblank (cp[7]))
+	{
+	  char *dir;
+	  cp += 8;
+	  while ((dir = strsep (&cp, " \t")) != NULL)
+	    if (dir[0] != '\0')
+	      parse_tunconf_include (filename, lineno, do_chroot, dir, opt_chroot);
+	}
+      else
+	add_tunable (cp, filename, lineno);
+    }
+  while (!feof_unlocked (file));
+
+  /* Free buffer and close file.  */
+  free (line);
+  fclose (file);
+}
+
+/* Handle one word in an `include' line, a glob pattern of additional
+   config files to read.  */
+static void
+parse_tunconf_include (const char *tunconfig_file, unsigned int lineno,
+		       bool do_chroot, const char *pattern, const char *opt_chroot)
+{
+  if (opt_chroot != NULL && pattern[0] != '/')
+    error (EXIT_FAILURE, 0,
+	   _("need absolute file name for configuration file when using -r"));
+
+  char *copy = NULL;
+  if (pattern[0] != '/' && strchr (tunconfig_file, '/') != NULL)
+    {
+      if (asprintf (&copy, "%s/%s", dirname (strdupa (tunconfig_file)),
+		    pattern) < 0)
+	error (EXIT_FAILURE, 0, _("memory exhausted"));
+      pattern = copy;
+    }
+
+  glob64_t gl;
+  int result;
+  if (do_chroot && opt_chroot)
+    {
+      char *canon = chroot_canon (opt_chroot, pattern);
+      if (canon == NULL)
+	return;
+      result = glob64 (canon, 0, NULL, &gl);
+      free (canon);
+    }
+  else
+    result = glob64 (pattern, 0, NULL, &gl);
+
+  switch (result)
+    {
+    case 0:
+      for (size_t i = 0; i < gl.gl_pathc; ++i)
+	parse_tunconf (gl.gl_pathv[i], false, NULL);
+      globfree64 (&gl);
+      break;
+
+    case GLOB_NOMATCH:
+      break;
+
+    case GLOB_NOSPACE:
+      errno = ENOMEM;
+      /* Fall through.  */
+    case GLOB_ABORTED:
+      if (opt_verbose)
+	error (0, errno, _("%s:%u: cannot read directory %s"),
+	       tunconfig_file, lineno, pattern);
+      break;
+
+    default:
+      abort ();
+      break;
+    }
+
+  free (copy);
+}
+
+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;
+    }
+  //  printf("get_tunconf_ext: found %lu entries\n", count);
+
+  /* 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;
+      //      printf("tei->top is %d\n", tei->top);
+      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;
+      //      printf("tec->flags is %08x\n", tec->flags);
+
+      ++ count;
+      tei = tei->next;
+    }
+
+  return thc;
+}
diff --git a/elf/tunconf.h b/elf/tunconf.h
new file mode 100644
index 0000000000..a6c5f0dd9a
--- /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, int do_chroot, 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 919e49ffc8..41521f5a96 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 7dafa791f2..31ea2ec5d6 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);