[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
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
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 (©, "%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);
@@ -225,6 +225,7 @@ ldconfig-modules := \
readlib \
static-stubs \
stringtable \
+ tunconf \
xmalloc \
xstrdup \
# ldconfig-modules
@@ -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);
+}
@@ -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);
new file mode 100644
@@ -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 (©, "%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;
+}
new file mode 100644
@@ -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))
@@ -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
};
@@ -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);