diff options
Diffstat (limited to 'elf/dl-tunables.c')
-rw-r--r-- | elf/dl-tunables.c | 403 |
1 files changed, 403 insertions, 0 deletions
diff --git a/elf/dl-tunables.c b/elf/dl-tunables.c new file mode 100644 index 0000000000..4c9d36e398 --- /dev/null +++ b/elf/dl-tunables.c @@ -0,0 +1,403 @@ +/* The tunable framework. See the README.tunables to know how to use the + tunable in a glibc module. + + Copyright (C) 2016-2018 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 <startup.h> +#include <stdint.h> +#include <stdbool.h> +#include <unistd.h> +#include <stdlib.h> +#include <sysdep.h> +#include <fcntl.h> +#include <ldsodefs.h> + +#define TUNABLES_INTERNAL 1 +#include "dl-tunables.h" + +#include <not-errno.h> + +#if TUNABLES_FRONTEND == TUNABLES_FRONTEND_valstring +# define GLIBC_TUNABLES "GLIBC_TUNABLES" +#endif + +#if TUNABLES_FRONTEND == TUNABLES_FRONTEND_valstring +static char * +tunables_strdup (const char *in) +{ + size_t i = 0; + + while (in[i++] != '\0'); + char *out = __sbrk (i); + + /* FIXME: In reality if the allocation fails, __sbrk will crash attempting to + set the thread-local errno since the TCB has not yet been set up. This + needs to be fixed with an __sbrk implementation that does not set + errno. */ + if (out == (void *)-1) + return NULL; + + i--; + + while (i-- > 0) + out[i] = in[i]; + + return out; +} +#endif + +static char ** +get_next_env (char **envp, char **name, size_t *namelen, char **val, + char ***prev_envp) +{ + while (envp != NULL && *envp != NULL) + { + char **prev = envp; + 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]; + *prev_envp = prev; + + return envp; + } + + return NULL; +} + +#define TUNABLE_SET_VAL_IF_VALID_RANGE(__cur, __val, __type) \ +({ \ + __type min = (__cur)->type.min; \ + __type max = (__cur)->type.max; \ + \ + if ((__type) (__val) >= min && (__type) (val) <= max) \ + { \ + (__cur)->val.numval = val; \ + (__cur)->initialized = true; \ + } \ +}) + +static void +do_tunable_update_val (tunable_t *cur, const void *valp) +{ + uint64_t val; + + if (cur->type.type_code != TUNABLE_TYPE_STRING) + val = *((int64_t *) valp); + + switch (cur->type.type_code) + { + case TUNABLE_TYPE_INT_32: + { + TUNABLE_SET_VAL_IF_VALID_RANGE (cur, val, int64_t); + break; + } + case TUNABLE_TYPE_UINT_64: + { + TUNABLE_SET_VAL_IF_VALID_RANGE (cur, val, uint64_t); + break; + } + case TUNABLE_TYPE_SIZE_T: + { + TUNABLE_SET_VAL_IF_VALID_RANGE (cur, val, uint64_t); + break; + } + case TUNABLE_TYPE_STRING: + { + cur->val.strval = valp; + break; + } + default: + __builtin_unreachable (); + } +} + +/* 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) +{ + uint64_t val; + const void *valp; + + if (cur->type.type_code != TUNABLE_TYPE_STRING) + { + val = _dl_strtoul (strval, NULL); + valp = &val; + } + else + { + cur->initialized = true; + valp = strval; + } + do_tunable_update_val (cur, valp); +} + +void +__tunable_set_val (tunable_id_t id, void *valp) +{ + tunable_t *cur = &tunable_list[id]; + + do_tunable_update_val (cur, valp); +} + +#if TUNABLES_FRONTEND == TUNABLES_FRONTEND_valstring +/* Parse the tunable string TUNESTR and adjust it to drop any tunables that may + be unsafe for AT_SECURE processes so that it can be used as the new + environment variable value for GLIBC_TUNABLES. VALSTRING is the original + environment variable string which we use to make NULL terminated values so + that we don't have to allocate memory again for it. */ +static void +parse_tunables (char *tunestr, char *valstring) +{ + if (tunestr == NULL || *tunestr == '\0') + return; + + char *p = tunestr; + + while (true) + { + char *name = p; + size_t len = 0; + + /* First, find where the name ends. */ + while (p[len] != '=' && p[len] != ':' && p[len] != '\0') + len++; + + /* If we reach the end of the string before getting a valid name-value + pair, bail out. */ + if (p[len] == '\0') + return; + + /* We did not find a valid name-value pair before encountering the + colon. */ + if (p[len]== ':') + { + p += len + 1; + continue; + } + + p += len + 1; + + /* Take the value from the valstring since we need to NULL terminate it. */ + char *value = &valstring[p - tunestr]; + len = 0; + + while (p[len] != ':' && p[len] != '\0') + len++; + + /* Add the tunable if it exists. */ + for (size_t i = 0; i < sizeof (tunable_list) / sizeof (tunable_t); i++) + { + tunable_t *cur = &tunable_list[i]; + + if (tunable_is_name (cur->name, name)) + { + /* If we are in a secure context (AT_SECURE) then ignore the tunable + unless it is explicitly marked as secure. Tunable values take + precendence over their envvar aliases. */ + if (__libc_enable_secure) + { + if (cur->security_level == TUNABLE_SECLEVEL_SXID_ERASE) + { + if (p[len] == '\0') + { + /* Last tunable in the valstring. Null-terminate and + return. */ + *name = '\0'; + return; + } + else + { + /* Remove the current tunable from the string. We do + this by overwriting the string starting from NAME + (which is where the current tunable begins) with + the remainder of the string. We then have P point + to NAME so that we continue in the correct + position in the valstring. */ + char *q = &p[len + 1]; + p = name; + while (*q != '\0') + *name++ = *q++; + name[0] = '\0'; + len = 0; + } + } + + if (cur->security_level != TUNABLE_SECLEVEL_NONE) + break; + } + + value[len] = '\0'; + tunable_initialize (cur, value); + break; + } + } + + if (p[len] == '\0') + return; + else + p += len + 1; + } +} +#endif + +/* Enable the glibc.malloc.check tunable in SETUID/SETGID programs only when + the system administrator has created 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_enable_malloc_check (void) +{ + tunable_id_t id = TUNABLE_ENUM_NAME (glibc, malloc, check); + if (__libc_enable_secure && __access_noerrno ("/etc/suid-debug", F_OK) == 0) + tunable_list[id].security_level = TUNABLE_SECLEVEL_NONE; +} + +/* 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; + char **prev_envp = envp; + + maybe_enable_malloc_check (); + + while ((envp = get_next_env (envp, &envname, &len, &envval, + &prev_envp)) != NULL) + { +#if TUNABLES_FRONTEND == TUNABLES_FRONTEND_valstring + if (tunable_is_name (GLIBC_TUNABLES, envname)) + { + char *new_env = tunables_strdup (envname); + if (new_env != NULL) + parse_tunables (new_env + len + 1, envval); + /* Put in the updated envval. */ + *prev_envp = new_env; + continue; + } +#endif + + 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->initialized || cur->env_alias == NULL) + continue; + + const char *name = cur->env_alias; + + /* We have a match. Initialize and move on to the next line. */ + if (tunable_is_name (name, envname)) + { + /* For AT_SECURE binaries, we need to check the security settings of + the tunable and decide whether we read the value and also whether + we erase the value so that child processes don't inherit them in + the environment. */ + if (__libc_enable_secure) + { + if (cur->security_level == TUNABLE_SECLEVEL_SXID_ERASE) + { + /* Erase the environment variable. */ + char **ep = prev_envp; + + while (*ep != NULL) + { + if (tunable_is_name (name, *ep)) + { + char **dp = ep; + + do + dp[0] = dp[1]; + while (*dp++); + } + else + ++ep; + } + /* Reset the iterator so that we read the environment again + from the point we erased. */ + envp = prev_envp; + } + + if (cur->security_level != TUNABLE_SECLEVEL_NONE) + continue; + } + + tunable_initialize (cur, envval); + break; + } + } + } +} + +/* Set the tunable value. This is called by the module that the tunable exists + in. */ +void +__tunable_get_val (tunable_id_t id, void *valp, tunable_callback_t callback) +{ + tunable_t *cur = &tunable_list[id]; + + switch (cur->type.type_code) + { + case TUNABLE_TYPE_UINT_64: + { + *((uint64_t *) valp) = (uint64_t) cur->val.numval; + break; + } + 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 (); + } + + if (cur->initialized && callback != NULL) + callback (&cur->val); +} + +rtld_hidden_def (__tunable_get_val) |