diff options
-rw-r--r-- | ChangeLog | 41 | ||||
-rw-r--r-- | INSTALL | 5 | ||||
-rw-r--r-- | Makeconfig | 16 | ||||
-rw-r--r-- | README.tunables | 85 | ||||
-rw-r--r-- | config.h.in | 3 | ||||
-rw-r--r-- | config.make.in | 1 | ||||
-rwxr-xr-x | configure | 16 | ||||
-rw-r--r-- | configure.ac | 10 | ||||
-rw-r--r-- | csu/init-first.c | 2 | ||||
-rw-r--r-- | csu/libc-start.c | 8 | ||||
-rw-r--r-- | elf/Makefile | 5 | ||||
-rw-r--r-- | elf/Versions | 3 | ||||
-rw-r--r-- | elf/dl-support.c | 2 | ||||
-rw-r--r-- | elf/dl-sysdep.c | 4 | ||||
-rw-r--r-- | elf/dl-tunable-types.h | 46 | ||||
-rw-r--r-- | elf/dl-tunables.c | 320 | ||||
-rw-r--r-- | elf/dl-tunables.h | 88 | ||||
-rw-r--r-- | elf/dl-tunables.list | 69 | ||||
-rw-r--r-- | elf/rtld.c | 2 | ||||
-rw-r--r-- | malloc/Makefile | 2 | ||||
-rw-r--r-- | malloc/arena.c | 54 | ||||
-rw-r--r-- | malloc/tst-malloc-usable-static.c | 1 | ||||
-rw-r--r-- | manual/install.texi | 5 | ||||
-rw-r--r-- | scripts/gen-tunables.awk | 157 | ||||
-rw-r--r-- | sysdeps/mach/hurd/dl-sysdep.c | 4 |
25 files changed, 947 insertions, 2 deletions
@@ -1,3 +1,44 @@ +2016-12-31 Siddhesh Poyarekar <siddhesh@sourceware.org> + + * manual/install.texi: Add --enable-tunables option. + * INSTALL: Regenerate. + * README.tunables: New file. + * Makeconfig (CPPFLAGS): Define TOP_NAMESPACE. + (before-compile): Generate dl-tunable-list.h early. + * config.h.in: Add HAVE_TUNABLES. + * config.make.in: Add have-tunables. + * configure.ac: Add --enable-tunables option. + * configure: Regenerate. + * csu/init-first.c (__libc_init_first): Move + __libc_init_secure earlier... + * csu/init-first.c (LIBC_START_MAIN):... to here. + Include dl-tunables.h, libc-internal.h. + (LIBC_START_MAIN) [!SHARED]: Initialize tunables for static + binaries. + * elf/Makefile (dl-routines): Add dl-tunables. + * elf/Versions (ld): Add __tunable_set_val to GLIBC_PRIVATE + namespace. + * elf/dl-support (_dl_nondynamic_init): Unset MALLOC_CHECK_ + only when !HAVE_TUNABLES. + * elf/rtld.c (process_envvars): Likewise. + * elf/dl-sysdep.c [HAVE_TUNABLES]: Include dl-tunables.h + (_dl_sysdep_start): Call __tunables_init. + * elf/dl-tunable-types.h: New file. + * elf/dl-tunables.c: New file. + * elf/dl-tunables.h: New file. + * elf/dl-tunables.list: New file. + * malloc/tst-malloc-usable-static.c: New test case. + * malloc/Makefile (tests-static): Add it. + * malloc/arena.c [HAVE_TUNABLES]: Include dl-tunables.h. + Define TUNABLE_NAMESPACE. + (DL_TUNABLE_CALLBACK (set_mallopt_check)): New function. + (DL_TUNABLE_CALLBACK_FNDECL): New macro. Use it to define + callback functions. + (ptmalloc_init): Set tunable values. + * scripts/gen-tunables.awk: New file. + * sysdeps/mach/hurd/dl-sysdep.c: Include dl-tunables.h. + (_dl_sysdep_start): Call __tunables_init. + 2016-12-31 Florian Weimer <fweimer@redhat.com> * resolv/resolv.h (RES_BLAST): Deprecate. @@ -169,6 +169,11 @@ will be used, and CFLAGS sets optimization options for the compiler. By default for x86_64, the GNU C Library is built with the vector math library. Use this option to disable the vector math library. +'--enable-tunables' + Tunables support allows additional library parameters to be + customized at runtime. This is an experimental feature and affects + startup time and is thus disabled by default. + '--build=BUILD-SYSTEM' '--host=HOST-SYSTEM' These options are for cross-compiling. If you specify both options @@ -934,6 +934,11 @@ CPPFLAGS = $(config-extra-cppflags) $(CPPUNDEFS) $(CPPFLAGS-config) \ $(foreach lib,$(libof-$(basename $(@F))) \ $(libof-$(<F)) $(libof-$(@F)),$(CPPFLAGS-$(lib))) \ $(CPPFLAGS-$(<F)) $(CPPFLAGS-$(@F)) $(CPPFLAGS-$(basename $(@F))) + +ifeq (yes,$(have-tunables)) +CPPFLAGS += -DTOP_NAMESPACE=glibc +endif + override CFLAGS = -std=gnu11 -fgnu89-inline $(config-extra-cflags) \ $(filter-out %frame-pointer,$(+cflags)) $(+gccwarn-c) \ $(sysdep-CFLAGS) $(CFLAGS-$(suffix $@)) $(CFLAGS-$(<F)) \ @@ -1108,6 +1113,17 @@ $(common-objpfx)libc-modules.stmp: $(..)scripts/gen-libc-modules.awk \ endif +# Build the tunables list header early since it could be used by any module in +# glibc. +ifeq (yes,$(have-tunables)) +before-compile += $(common-objpfx)dl-tunable-list.h + +$(common-objpfx)dl-tunable-list.h: $(..)scripts/gen-tunables.awk \ + $(..)elf/dl-tunables.list + $(AWK) -f $^ > $@.tmp + mv $@.tmp $@ +endif + common-generated += libc-modules.h libc-modules.stmp # The name under which the run-time dynamic linker is installed. diff --git a/README.tunables b/README.tunables new file mode 100644 index 0000000..df74f3b --- /dev/null +++ b/README.tunables @@ -0,0 +1,85 @@ + TUNABLE FRAMEWORK + ================= + +Tunables is a feature in the GNU C Library that allows application authors and +distribution maintainers to alter the runtime library behaviour to match their +workload. + +The tunable framework allows modules within glibc to register variables that +may be tweaked through an environment variable. It aims to enforce a strict +namespace rule to bring consistency to naming of these tunable environment +variables across the project. This document is a guide for glibc developers to +add tunables to the framework. + +ADDING A NEW TUNABLE +-------------------- + +The TOP_NAMESPACE macro is defined by default as 'glibc'. If distributions +intend to add their own tunables, they should do so in a different top +namespace by overriding the TOP_NAMESPACE macro for that tunable. Downstream +implementations are discouraged from using the 'glibc' top namespace for +tunables they don't already have consensus to push upstream. + +There are two steps to adding a tunable: + +1. Add a tunable ID: + +Modules that wish to use the tunables interface must define the +TUNABLE_NAMESPACE macro. Following this, for each tunable you want to +add, make an entry in elf/dl-tunables.list. The format of the file is as +follows: + +TOP_NAMESPACE { + NAMESPACE1 { + TUNABLE1 { + # tunable attributes, one per line + } + # A tunable with default attributes, i.e. string variable. + TUNABLE2 + TUNABLE3 { + # its attributes + } + } + NAMESPACE2 { + ... + } +} + +The list of allowed attributes are: + +- type: Data type. Defaults to STRING. Allowed types are: + INT_32, SIZE_T and STRING. + +- minval: Optional minimum acceptable value. For a string type + this is the minimum length of the value. + +- maxval: Optional maximum acceptable value. For a string type + this is the maximum length of the value. + +- env_alias: An alias environment variable + +- is_secure: Specify whether the tunable should be read for setuid + binaries. True allows the tunable to be read for + setuid binaries while false disables it. Note that + even if this is set as true and the value is read, it + may not be used if it does not validate against the + acceptable values or is not considered safe by the + module. + +2. Call either the TUNABLE_SET_VALUE and pass into it the tunable name and a + pointer to the variable that should be set with the tunable value. + If additional work needs to be done after setting the value, use the + TUNABLE_SET_VALUE_WITH_CALLBACK instead and additionally pass a pointer to + the function that should be called if the tunable value has been set. + +FUTURE WORK +----------- + +The framework currently only allows a one-time initialization of variables +through environment variables and in some cases, modification of variables via +an API call. A future goals for this project include: + +- Setting system-wide and user-wide defaults for tunables through some + mechanism like a configuration file. + +- Allow tweaking of some tunables at runtime diff --git a/config.h.in b/config.h.in index 82f95a6..7bfe923 100644 --- a/config.h.in +++ b/config.h.in @@ -256,4 +256,7 @@ /* PowerPC32 uses fctidz for floating point to long long conversions. */ #define HAVE_PPC_FCTIDZ 0 +/* Build glibc with tunables support. */ +#define HAVE_TUNABLES 0 + #endif diff --git a/config.make.in b/config.make.in index 4422025..2f8dae2 100644 --- a/config.make.in +++ b/config.make.in @@ -96,6 +96,7 @@ use-nscd = @use_nscd@ build-hardcoded-path-in-tests= @hardcoded_path_in_tests@ build-pt-chown = @build_pt_chown@ enable-lock-elision = @enable_lock_elision@ +have-tunables = @have_tunables@ # Build tools. CC = @CC@ @@ -666,6 +666,7 @@ libc_cv_ssp base_machine add_on_subdirs add_ons +have_tunables build_pt_chown build_nscd link_obsolete_rpc @@ -782,6 +783,7 @@ enable_systemtap enable_build_nscd enable_nscd enable_pt_chown +enable_tunables enable_mathvec with_cpu ' @@ -1452,6 +1454,7 @@ Optional Features: --disable-build-nscd disable building and installing the nscd daemon --disable-nscd library functions will not contact the nscd daemon --enable-pt_chown Enable building and installing pt_chown + --enable-tunables Enable tunables support --enable-mathvec Enable building and installing mathvec [default depends on architecture] @@ -3698,6 +3701,19 @@ if test "$build_pt_chown" = yes; then fi +# Check whether --enable-tunables was given. +if test "${enable_tunables+set}" = set; then : + enableval=$enable_tunables; have_tunables=$enableval +else + have_tunables=no +fi + + +if test "$have_tunables" = yes; then + $as_echo "#define HAVE_TUNABLES 1" >>confdefs.h + +fi + # The abi-tags file uses a fairly simplistic model for name recognition that # can't distinguish i486-pc-linux-gnu fully from i486-pc-gnu. So we mutate a # $host_os of `gnu*' here to be `gnu-gnu*' just so that it can tell. diff --git a/configure.ac b/configure.ac index 2782bfa..22f5cab 100644 --- a/configure.ac +++ b/configure.ac @@ -421,6 +421,16 @@ if test "$build_pt_chown" = yes; then AC_DEFINE(HAVE_PT_CHOWN) fi +AC_ARG_ENABLE([tunables], + [AS_HELP_STRING([--enable-tunables], + [Enable tunables support])], + [have_tunables=$enableval], + [have_tunables=no]) +AC_SUBST(have_tunables) +if test "$have_tunables" = yes; then + AC_DEFINE(HAVE_TUNABLES) +fi + # The abi-tags file uses a fairly simplistic model for name recognition that # can't distinguish i486-pc-linux-gnu fully from i486-pc-gnu. So we mutate a # $host_os of `gnu*' here to be `gnu-gnu*' just so that it can tell. diff --git a/csu/init-first.c b/csu/init-first.c index 77c6e1c..465f25b 100644 --- a/csu/init-first.c +++ b/csu/init-first.c @@ -72,8 +72,6 @@ _init (int argc, char **argv, char **envp) __environ = envp; #ifndef SHARED - __libc_init_secure (); - /* First the initialization which normally would be done by the dynamic linker. */ _dl_non_dynamic_init (); diff --git a/csu/libc-start.c b/csu/libc-start.c index cc59073..15db9b4 100644 --- a/csu/libc-start.c +++ b/csu/libc-start.c @@ -21,6 +21,9 @@ #include <unistd.h> #include <ldsodefs.h> #include <exit-thread.h> +#include <libc-internal.h> + +#include <elf/dl-tunables.h> extern void __libc_init_first (int argc, char **argv, char **envp); @@ -174,6 +177,11 @@ LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL), } } + /* Initialize very early so that tunables can use it. */ + __libc_init_secure (); + + __tunables_init (__environ); + /* Perform IREL{,A} relocations. */ apply_irel (); diff --git a/elf/Makefile b/elf/Makefile index 8a2ce02..de28d99 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -35,6 +35,11 @@ dl-routines = $(addprefix dl-,load lookup object reloc deps hwcaps \ ifeq (yes,$(use-ldconfig)) dl-routines += dl-cache endif + +ifeq (yes,$(have-tunables)) +dl-routines += dl-tunables +endif + all-dl-routines = $(dl-routines) $(sysdep-dl-routines) # But they are absent from the shared libc, because that code is in ld.so. elide-routines.os = $(all-dl-routines) dl-support enbl-secure dl-origin \ diff --git a/elf/Versions b/elf/Versions index 3d57e36..6abe9db 100644 --- a/elf/Versions +++ b/elf/Versions @@ -70,5 +70,8 @@ ld { # Internal error handling support. Interposed by libc.so. _dl_signal_error; _dl_catch_error; + + # Set value of a tunable. + __tunable_set_val; } } diff --git a/elf/dl-support.c b/elf/dl-support.c index c30194c..d350d6d 100644 --- a/elf/dl-support.c +++ b/elf/dl-support.c @@ -354,8 +354,10 @@ _dl_non_dynamic_init (void) cp = (const char *) __rawmemchr (cp, '\0') + 1; } +#if !HAVE_TUNABLES if (__access ("/etc/suid-debug", F_OK) != 0) __unsetenv ("MALLOC_CHECK_"); +#endif } #ifdef DL_PLATFORM_INIT diff --git a/elf/dl-sysdep.c b/elf/dl-sysdep.c index eaa7155..4283767 100644 --- a/elf/dl-sysdep.c +++ b/elf/dl-sysdep.c @@ -44,6 +44,8 @@ #include <hp-timing.h> #include <tls.h> +#include <dl-tunables.h> + extern char **_environ attribute_hidden; extern char _end[] attribute_hidden; @@ -219,6 +221,8 @@ _dl_sysdep_start (void **start_argptr, } #endif + __tunables_init (_environ); + #ifdef DL_SYSDEP_INIT DL_SYSDEP_INIT; #endif diff --git a/elf/dl-tunable-types.h b/elf/dl-tunable-types.h new file mode 100644 index 0000000..d1591b6 --- /dev/null +++ b/elf/dl-tunable-types.h @@ -0,0 +1,46 @@ +/* Tunable type information. + + Copyright (C) 2016 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 + <http://www.gnu.org/licenses/>. */ + +#ifndef _TUNABLE_TYPES_H_ +# define _TUNABLE_TYPES_H_ +#include <stddef.h> + +typedef void (*tunable_callback_t) (void *); + +typedef enum +{ + TUNABLE_TYPE_INT_32, + TUNABLE_TYPE_SIZE_T, + TUNABLE_TYPE_STRING +} tunable_type_code_t; + +typedef struct +{ + tunable_type_code_t type_code; + int64_t min; + int64_t max; +} tunable_type_t; + +typedef union +{ + int64_t numval; + const char *strval; +} tunable_val_t; + +#endif diff --git a/elf/dl-tunables.c b/elf/dl-tunables.c new file mode 100644 index 0000000..472747b --- /dev/null +++ b/elf/dl-tunables.c @@ -0,0 +1,320 @@ +/* The tunable framework. See the README.tunables to know how to use the + tunable in a glibc module. + + Copyright (C) 2016 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 + <http://www.gnu.org/licenses/>. */ + +#include <stdint.h> +#include <stdbool.h> +#include <unistd.h> +#include <stdlib.h> +#include <libc-internal.h> +#include <sysdep.h> +#include <fcntl.h> +#include <ldsodefs.h> + +#define TUNABLES_INTERNAL 1 +#include "dl-tunables.h" + +/* Compare environment names, bounded by the name hardcoded in glibc. */ +static bool +is_name (const char *orig, const char *envname) +{ + for (;*orig != '\0' && *envname != '\0'; envname++, orig++) + if (*orig != *envname) + break; + + /* The ENVNAME is immediately followed by a value. */ + if (*orig == '\0' && *envname == '=') + return true; + else + return false; +} + +static char ** +get_next_env (char **envp, char **name, size_t *namelen, char **val) +{ + while (envp != NULL && *envp != NULL) + { + char *envline = *envp; + int len = 0; + + while (envline[len] != '\0' && envline[len] != '=') + len++; + + /* Just the name and no value, go to the next one. */ + if (envline[len] == '\0') + continue; + + *name = envline; + *namelen = len; + *val = &envline[len + 1]; + + return ++envp; + } + + return NULL; +} + +static int +tunables_unsetenv (char **ep, const char *name) +{ + while (*ep != NULL) + { + size_t cnt = 0; + + while ((*ep)[cnt] == name[cnt] && name[cnt] != '\0') + ++cnt; + + if (name[cnt] == '\0' && (*ep)[cnt] == '=') + { + /* Found it. Remove this pointer by moving later ones to + the front. */ + char **dp = ep; + + do + dp[0] = dp[1]; + while (*dp++); + /* Continue the loop in case NAME appears again. */ + } + else + ++ep; + } + + return 0; +} + +/* A stripped down strtoul-like implementation for very early use. It does not + set errno if the result is outside bounds because it gets called before + errno may have been set up. */ +static unsigned long int +tunables_strtoul (const char *nptr) +{ + unsigned long int result = 0; + long int sign = 1; + unsigned max_digit; + + while (*nptr == ' ' || *nptr == '\t') + ++nptr; + + if (*nptr == '-') + { + sign = -1; + ++nptr; + } + else if (*nptr == '+') + ++nptr; + + if (*nptr < '0' || *nptr > '9') + return 0UL; + + int base = 10; + max_digit = 9; + if (*nptr == '0') + { + if (nptr[1] == 'x' || nptr[1] == 'X') + { + base = 16; + nptr += 2; + } + else + { + base = 8; + max_digit = 7; + } + } + + while (1) + { + unsigned long int digval; + if (*nptr >= '0' && *nptr <= '0' + max_digit) + digval = *nptr - '0'; + else if (base == 16) + { + if (*nptr >= 'a' && *nptr <= 'f') + digval = *nptr - 'a' + 10; + else if (*nptr >= 'A' && *nptr <= 'F') + digval = *nptr - 'A' + 10; + else + break; + } + else + break; + + if (result > ULONG_MAX / base + || (result == ULONG_MAX / base && digval > ULONG_MAX % base)) + return ULONG_MAX; + result *= base; + result += digval; + ++nptr; + } + + return result * sign; +} + +/* Initialize the internal type if the value validates either using the + explicit constraints of the tunable or with the implicit constraints of its + type. */ +static void +tunable_set_val_if_valid_range (tunable_t *cur, const char *strval, + int64_t default_min, int64_t default_max) +{ + int64_t val = tunables_strtoul (strval); + + int64_t min = cur->type.min; + int64_t max = cur->type.max; + + if (min == max) + { + min = default_min; + max = default_max; + } + + if (val >= min && val <= max) + { + cur->val.numval = val; + cur->strval = strval; + } +} + +/* Validate range of the input value and initialize the tunable CUR if it looks + good. */ +static void +tunable_initialize (tunable_t *cur, const char *strval) +{ + switch (cur->type.type_code) + { + case TUNABLE_TYPE_INT_32: + { + tunable_set_val_if_valid_range (cur, strval, INT32_MIN, INT32_MAX); + break; + } + case TUNABLE_TYPE_SIZE_T: + { + tunable_set_val_if_valid_range (cur, strval, 0, SIZE_MAX); + break; + } + case TUNABLE_TYPE_STRING: + { + cur->val.strval = cur->strval = strval; + break; + } + default: + __builtin_unreachable (); + } +} + +/* Disable a tunable if it is set. */ +static void +disable_tunable (tunable_id_t id, char **envp) +{ + const char *env_alias = tunable_list[id].env_alias; + + if (env_alias != NULL) + tunables_unsetenv (envp, tunable_list[id].env_alias); +} + +/* Disable the glibc.malloc.check tunable in SETUID/SETGID programs unless + the system administrator overrides it by creating the /etc/suid-debug + file. This is a special case where we want to conditionally enable/disable + a tunable even for setuid binaries. We use the special version of access() + to avoid setting ERRNO, which is a TLS variable since TLS has not yet been + set up. */ +static inline void +__always_inline +maybe_disable_malloc_check (void) +{ + if (__libc_enable_secure && __access_noerrno ("/etc/suid-debug", F_OK) != 0) + disable_tunable (TUNABLE_ENUM_NAME(glibc, malloc, check), __environ); +} + +/* Initialize the tunables list from the environment. For now we only use the + ENV_ALIAS to find values. Later we will also use the tunable names to find + values. */ +void +__tunables_init (char **envp) +{ + char *envname = NULL; + char *envval = NULL; + size_t len = 0; + + maybe_disable_malloc_check (); + + while ((envp = get_next_env (envp, &envname, &len, &envval)) != NULL) + { + for (int i = 0; i < sizeof (tunable_list) / sizeof (tunable_t); i++) + { + tunable_t *cur = &tunable_list[i]; + + /* Skip over tunables that have either been set already or should be + skipped. */ + if (cur->strval != NULL || cur->env_alias == NULL + || (__libc_enable_secure && !cur->is_secure)) + continue; + + const char *name = cur->env_alias; + + /* We have a match. Initialize and move on to the next line. */ + if (is_name (name, envname)) + { + tunable_initialize (cur, envval); + break; + } + } + } +} + +/* Set the tunable value. This is called by the module that the tunable exists + in. */ +void +__tunable_set_val (tunable_id_t id, void *valp, tunable_callback_t callback) +{ + tunable_t *cur = &tunable_list[id]; + + /* Don't do anything if our tunable was not set during initialization or if + it failed validation. */ + if (cur->strval == NULL) + return; + + if (valp == NULL) + goto cb; + + switch (cur->type.type_code) + { + case TUNABLE_TYPE_INT_32: + { + *((int32_t *) valp) = (int32_t) cur->val.numval; + break; + } + case TUNABLE_TYPE_SIZE_T: + { + *((size_t *) valp) = (size_t) cur->val.numval; + break; + } + case TUNABLE_TYPE_STRING: + { + *((const char **)valp) = cur->val.strval; + break; + } + default: + __builtin_unreachable (); + } + +cb: + if (callback) + callback (&cur->val); +} diff --git a/elf/dl-tunables.h b/elf/dl-tunables.h new file mode 100644 index 0000000..a3f5472 --- /dev/null +++ b/elf/dl-tunables.h @@ -0,0 +1,88 @@ +/* The tunable framework. See the README to know how to use the tunable in + a glibc module. + + Copyright (C) 2016 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 + <http://www.gnu.org/licenses/>. */ + +#ifndef _TUNABLES_H_ +#define _TUNABLES_H_ + +#if !HAVE_TUNABLES +static inline void +__always_inline +__tunables_init (char **unused __attribute_unused) +{ + /* This is optimized out if tunables are not enabled. */ +} +#else + +# include <stddef.h> +# include "dl-tunable-types.h" + +/* A tunable. */ +struct _tunable +{ + const char *name; /* Internal name of the tunable. */ + tunable_type_t type; /* Data type of the tunable. */ + tunable_val_t val; /* The value. */ + const char *strval; /* The string containing the value, + points into envp. */ + bool is_secure; /* Whether the tunable must be read + even for setuid binaries. Note that + even if the tunable is read, it may + not get used by the target module if + the value is considered unsafe. */ + /* Compatibility elements. */ + const char *env_alias; /* The compatibility environment + variable name. */ +}; + +typedef struct _tunable tunable_t; + +/* Full name for a tunable is top_ns.tunable_ns.id. */ +# define TUNABLE_NAME_S(top,ns,id) #top "." #ns "." #id + +# define TUNABLE_ENUM_NAME(top,ns,id) TUNABLE_ENUM_NAME1 (top,ns,id) +# define TUNABLE_ENUM_NAME1(top,ns,id) top ## _ ## ns ## _ ## id + +# include "dl-tunable-list.h" + +extern void __tunables_init (char **); +extern void __tunable_set_val (tunable_id_t, void *, tunable_callback_t); + +/* Check if the tunable has been set to a non-default value and if it is, copy + it over into __VAL. */ +# define TUNABLE_SET_VAL(__id,__val) \ +({ \ + __tunable_set_val \ + (TUNABLE_ENUM_NAME (TOP_NAMESPACE, TUNABLE_NAMESPACE, __id), (__val), \ + NULL); \ +}) + +/* Same as TUNABLE_SET_VAL, but also call the callback function __CB. */ +# define TUNABLE_SET_VAL_WITH_CALLBACK(__id,__val,__cb) \ +({ \ + __tunable_set_val \ + (TUNABLE_ENUM_NAME (TOP_NAMESPACE, TUNABLE_NAMESPACE, __id), (__val), \ + DL_TUNABLE_CALLBACK (__cb)); \ +}) + +/* Namespace sanity for callback functions. Use this macro to keep the + namespace of the modules clean. */ +# define DL_TUNABLE_CALLBACK(__name) _dl_tunable_ ## __name +#endif +#endif diff --git a/elf/dl-tunables.list b/elf/dl-tunables.list new file mode 100644 index 0000000..11504c4 --- /dev/null +++ b/elf/dl-tunables.list @@ -0,0 +1,69 @@ +# Copyright (C) 2016 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 +# <http://www.gnu.org/licenses/>. + +# Allowed attributes for tunables: +# +# type: Defaults to STRING +# minval: Optional minimum acceptable value +# maxval: Optional maximum acceptable value +# env_alias: An alias environment variable +# is_secure: Specify whether the environment variable should be read for +# setuid binaries. + +glibc { + malloc { + check { + type: INT_32 + minval: 0 + maxval: 3 + env_alias: MALLOC_CHECK_ + is_secure: true + } + top_pad { + type: SIZE_T + env_alias: MALLOC_TOP_PAD_ + } + perturb { + type: INT_32 + minval: 0 + maxval: 0xff + env_alias: MALLOC_PERTURB_ + } + mmap_threshold { + type: SIZE_T + env_alias: MALLOC_MMAP_THRESHOLD_ + } + trim_threshold { + type: SIZE_T + env_alias: MALLOC_TRIM_THRESHOLD_ + } + mmap_max { + type: INT_32 + env_alias: MALLOC_MMAP_MAX_ + } + arena_max { + type: SIZE_T + env_alias: MALLOC_ARENA_MAX + minval: 1 + } + arena_test { + type: SIZE_T + env_alias: MALLOC_ARENA_TEST + minval: 1 + } + } +} @@ -2510,7 +2510,9 @@ process_envvars (enum mode *modep) if (__access ("/etc/suid-debug", F_OK) != 0) { +#if !HAVE_TUNABLES unsetenv ("MALLOC_CHECK_"); +#endif GLRO(dl_debug_mask) = 0; } diff --git a/malloc/Makefile b/malloc/Makefile index b8efcd6..4e4104e 100644 --- a/malloc/Makefile +++ b/malloc/Makefile @@ -37,6 +37,7 @@ tests := mallocbug tst-malloc tst-valloc tst-calloc tst-obstack \ tests-static := \ tst-interpose-static-nothread \ tst-interpose-static-thread \ + tst-malloc-usable-static tests += $(tests-static) test-srcs = tst-mtrace @@ -158,6 +159,7 @@ endif tst-mcheck-ENV = MALLOC_CHECK_=3 tst-malloc-usable-ENV = MALLOC_CHECK_=3 +tst-malloc-usable-static-ENV = $(tst-malloc-usable-ENV) # Uncomment this for test releases. For public releases it is too expensive. #CPPFLAGS-malloc.o += -DMALLOC_DEBUG=1 diff --git a/malloc/arena.c b/malloc/arena.c index eed4247..234035f 100644 --- a/malloc/arena.c +++ b/malloc/arena.c @@ -19,6 +19,11 @@ #include <stdbool.h> +#if HAVE_TUNABLES +# define TUNABLE_NAMESPACE malloc +#endif +#include <elf/dl-tunables.h> + /* Compile-time constants. */ #define HEAP_MIN_SIZE (32 * 1024) @@ -204,6 +209,34 @@ __malloc_fork_unlock_child (void) __libc_lock_init (list_lock); } +#if HAVE_TUNABLES +static inline int do_set_mallopt_check (int32_t value); +void +DL_TUNABLE_CALLBACK (set_mallopt_check) (void *valp) +{ + int32_t value = *(int32_t *) valp; + do_set_mallopt_check (value); + if (check_action != 0) + __malloc_check_init (); +} + +# define DL_TUNABLE_CALLBACK_FNDECL(__name, __type) \ +static inline int do_ ## __name (__type value); \ +void \ +DL_TUNABLE_CALLBACK (__name) (void *valp) \ +{ \ + __type value = *(__type *) valp; \ + do_ ## __name (value); \ +} + +DL_TUNABLE_CALLBACK_FNDECL (set_mmap_threshold, size_t) +DL_TUNABLE_CALLBACK_FNDECL (set_mmaps_max, int32_t) +DL_TUNABLE_CALLBACK_FNDECL (set_top_pad, size_t) +DL_TUNABLE_CALLBACK_FNDECL (set_perturb_byte, int32_t) +DL_TUNABLE_CALLBACK_FNDECL (set_trim_threshold, size_t) +DL_TUNABLE_CALLBACK_FNDECL (set_arena_max, size_t) +DL_TUNABLE_CALLBACK_FNDECL (set_arena_test, size_t) +#else /* Initialization routine. */ #include <string.h> extern char **_environ; @@ -238,6 +271,7 @@ next_env_entry (char ***position) return result; } +#endif #ifdef SHARED @@ -272,6 +306,24 @@ ptmalloc_init (void) #endif thread_arena = &main_arena; + +#if HAVE_TUNABLES + /* Ensure initialization/consolidation and do it under a lock so that a + thread attempting to use the arena in parallel waits on us till we + finish. */ + __libc_lock_lock (main_arena.mutex); + malloc_consolidate (&main_arena); + + TUNABLE_SET_VAL_WITH_CALLBACK (check, NULL, set_mallopt_check); + TUNABLE_SET_VAL_WITH_CALLBACK (top_pad, NULL, set_top_pad); + TUNABLE_SET_VAL_WITH_CALLBACK (perturb, NULL, set_perturb_byte); + TUNABLE_SET_VAL_WITH_CALLBACK (mmap_threshold, NULL, set_mmap_threshold); + TUNABLE_SET_VAL_WITH_CALLBACK (trim_threshold, NULL, set_trim_threshold); + TUNABLE_SET_VAL_WITH_CALLBACK (mmap_max, NULL, set_mmaps_max); + TUNABLE_SET_VAL_WITH_CALLBACK (arena_max, NULL, set_arena_max); + TUNABLE_SET_VAL_WITH_CALLBACK (arena_test, NULL, set_arena_test); + __libc_lock_unlock (main_arena.mutex); +#else const char *s = NULL; if (__glibc_likely (_environ != NULL)) { @@ -340,6 +392,8 @@ ptmalloc_init (void) if (check_action != 0) __malloc_check_init (); } +#endif + #if HAVE_MALLOC_INIT_HOOK void (*hook) (void) = atomic_forced_read (__malloc_initialize_hook); if (hook != NULL) diff --git a/malloc/tst-malloc-usable-static.c b/malloc/tst-malloc-usable-static.c new file mode 100644 index 0000000..8907db0 --- /dev/null +++ b/malloc/tst-malloc-usable-static.c @@ -0,0 +1 @@ +#include <malloc/tst-malloc-usable.c> diff --git a/manual/install.texi b/manual/install.texi index d02e870..d412962 100644 --- a/manual/install.texi +++ b/manual/install.texi @@ -200,6 +200,11 @@ configure with @option{--disable-werror}. By default for x86_64, @theglibc{} is built with the vector math library. Use this option to disable the vector math library. +@item --enable-tunables +Tunables support allows additional library parameters to be customized at +runtime. This is an experimental feature and affects startup time and is thus +disabled by default. + @item --build=@var{build-system} @itemx --host=@var{host-system} These options are for cross-compiling. If you specify both options and diff --git a/scripts/gen-tunables.awk b/scripts/gen-tunables.awk new file mode 100644 index 0000000..b65b5a4 --- /dev/null +++ b/scripts/gen-tunables.awk @@ -0,0 +1,157 @@ +# Generate dl-tunable-list.h from dl-tunables.list + +BEGIN { + tunable="" + ns="" + top_ns="" +} + +# Skip over blank lines and comments. +/^#/ { + next +} + +/^[ \t]*$/ { + next +} + +# Beginning of either a top namespace, tunable namespace or a tunable, decided +# on the current value of TUNABLE, NS or TOP_NS. +$2 == "{" { + if (top_ns == "") { + top_ns = $1 + } + else if (ns == "") { + ns = $1 + } + else if (tunable == "") { + tunable = $1 + } + else { + printf ("Unexpected occurrence of '{': %s:%d\n", FILENAME, FNR) + exit 1 + } + + next +} + +# End of either a top namespace, tunable namespace or a tunable. +$1 == "}" { + if (tunable != "") { + # Tunables definition ended, now fill in default attributes. + if (!types[top_ns][ns][tunable]) { + types[top_ns][ns][tunable] = "STRING" + } + if (!minvals[top_ns][ns][tunable]) { + minvals[top_ns][ns][tunable] = "0" + } + if (!maxvals[top_ns][ns][tunable]) { + maxvals[top_ns][ns][tunable] = "0" + } + if (!env_alias[top_ns][ns][tunable]) { + env_alias[top_ns][ns][tunable] = "NULL" + } + if (!is_secure[top_ns][ns][tunable]) { + is_secure[top_ns][ns][tunable] = "false" + } + + tunable = "" + } + else if (ns != "") { + ns = "" + } + else if (top_ns != "") { + top_ns = "" + } + else { + printf ("syntax error: extra }: %s:%d\n", FILENAME, FNR) + exit 1 + } + next +} + +# Everything else, which could either be a tunable without any attributes or a +# tunable attribute. +{ + if (ns == "") { + printf("Line %d: Invalid tunable outside a namespace: %s\n", NR, $0) + exit 1 + } + + if (tunable == "") { + # We encountered a tunable without any attributes, so note it with a + # default. + types[top_ns][ns][$1] = "STRING" + next + } + + # Otherwise, we have encountered a tunable attribute. + split($0, arr, ":") + attr = gensub(/^[ \t]+|[ \t]+$/, "", "g", arr[1]) + val = gensub(/^[ \t]+|[ \t]+$/, "", "g", arr[2]) + + if (attr == "type") { + types[top_ns][ns][tunable] = val + } + else if (attr == "minval") { + minvals[top_ns][ns][tunable] = val + } + else if (attr == "maxval") { + maxvals[top_ns][ns][tunable] = val + } + else if (attr == "env_alias") { + env_alias[top_ns][ns][tunable] = sprintf("\"%s\"", val) + } + else if (attr == "is_secure") { + if (val == "true" || val == "false") { + is_secure[top_ns][ns][tunable] = val + } + else { + printf("Line %d: Invalid value (%s) for is_secure: %s, ", NR, val, + $0) + print("Allowed values are 'true' or 'false'") + exit 1 + } + } +} + +END { + if (ns != "") { + print "Unterminated namespace. Is a closing brace missing?" + exit 1 + } + + print "/* AUTOGENERATED by gen-tunables.awk. */" + print "#ifndef _TUNABLES_H_" + print "# error \"Do not include this file directly.\"" + print "# error \"Include tunables.h instead.\"" + print "#endif" + + # Now, the enum names + print "\ntypedef enum" + print "{" + for (t in types) { + for (n in types[t]) { + for (m in types[t][n]) { + printf (" TUNABLE_ENUM_NAME(%s, %s, %s),\n", t, n, m); + } + } + } + print "} tunable_id_t;\n" + + # Finally, the tunable list. + print "\n#ifdef TUNABLES_INTERNAL" + print "static tunable_t tunable_list[] = {" + for (t in types) { + for (n in types[t]) { + for (m in types[t][n]) { + printf (" {TUNABLE_NAME_S(%s, %s, %s)", t, n, m) + printf (", {TUNABLE_TYPE_%s, %s, %s}, {.numval = 0}, NULL, %s, %s},\n", + types[t][n][m], minvals[t][n][m], maxvals[t][n][m], + is_secure[t][n][m], env_alias[t][n][m]); + } + } + } + print "};" + print "#endif" +} diff --git a/sysdeps/mach/hurd/dl-sysdep.c b/sysdeps/mach/hurd/dl-sysdep.c index d730f82..42bccda 100644 --- a/sysdeps/mach/hurd/dl-sysdep.c +++ b/sysdeps/mach/hurd/dl-sysdep.c @@ -44,6 +44,8 @@ #include <dl-machine.h> #include <dl-procinfo.h> +#include <dl-tunables.h> + extern void __mach_init (void); extern int _dl_argc; @@ -143,6 +145,8 @@ _dl_sysdep_start (void **start_argptr, __libc_enable_secure = _dl_hurd_data->flags & EXEC_SECURE; + __tunables_init (_environ); + if (_dl_hurd_data->flags & EXEC_STACK_ARGS && _dl_hurd_data->user_entry == 0) _dl_hurd_data->user_entry = (vm_address_t) ENTRY_POINT; |