]> git.ipfire.org Git - thirdparty/glibc.git/blame - elf/dl-tunables.c
Initialize tunable list with the GLIBC_TUNABLES environment variable
[thirdparty/glibc.git] / elf / dl-tunables.c
CommitLineData
67e58f39
SP
1/* The tunable framework. See the README.tunables to know how to use the
2 tunable in a glibc module.
3
4 Copyright (C) 2016 Free Software Foundation, Inc.
5 This file is part of the GNU C Library.
6
7 The GNU C Library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
11
12 The GNU C Library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public
18 License along with the GNU C Library; if not, see
19 <http://www.gnu.org/licenses/>. */
20
21#include <stdint.h>
22#include <stdbool.h>
23#include <unistd.h>
24#include <stdlib.h>
25#include <libc-internal.h>
26#include <sysdep.h>
27#include <fcntl.h>
28#include <ldsodefs.h>
29
30#define TUNABLES_INTERNAL 1
31#include "dl-tunables.h"
32
9dd409a5
SP
33#define GLIBC_TUNABLES "GLIBC_TUNABLES"
34
35/* Compare environment or tunable names, bounded by the name hardcoded in
36 glibc. */
67e58f39
SP
37static bool
38is_name (const char *orig, const char *envname)
39{
40 for (;*orig != '\0' && *envname != '\0'; envname++, orig++)
41 if (*orig != *envname)
42 break;
43
44 /* The ENVNAME is immediately followed by a value. */
45 if (*orig == '\0' && *envname == '=')
46 return true;
47 else
48 return false;
49}
50
9dd409a5
SP
51static char *
52tunables_strdup (const char *in)
53{
54 size_t i = 0;
55
56 while (in[i++] != '\0');
57 char *out = __sbrk (i);
58
59 /* FIXME: In reality if the allocation fails, __sbrk will crash attempting to
60 set the thread-local errno since the TCB has not yet been set up. This
61 needs to be fixed with an __sbrk implementation that does not set
62 errno. */
63 if (out == (void *)-1)
64 return NULL;
65
66 i--;
67
68 while (i-- > 0)
69 out[i] = in[i];
70
71 return out;
72}
73
67e58f39
SP
74static char **
75get_next_env (char **envp, char **name, size_t *namelen, char **val)
76{
77 while (envp != NULL && *envp != NULL)
78 {
79 char *envline = *envp;
80 int len = 0;
81
82 while (envline[len] != '\0' && envline[len] != '=')
83 len++;
84
85 /* Just the name and no value, go to the next one. */
86 if (envline[len] == '\0')
87 continue;
88
89 *name = envline;
90 *namelen = len;
91 *val = &envline[len + 1];
92
93 return ++envp;
94 }
95
96 return NULL;
97}
98
99static int
100tunables_unsetenv (char **ep, const char *name)
101{
102 while (*ep != NULL)
103 {
104 size_t cnt = 0;
105
106 while ((*ep)[cnt] == name[cnt] && name[cnt] != '\0')
107 ++cnt;
108
109 if (name[cnt] == '\0' && (*ep)[cnt] == '=')
110 {
111 /* Found it. Remove this pointer by moving later ones to
112 the front. */
113 char **dp = ep;
114
115 do
116 dp[0] = dp[1];
117 while (*dp++);
118 /* Continue the loop in case NAME appears again. */
119 }
120 else
121 ++ep;
122 }
123
124 return 0;
125}
126
127/* A stripped down strtoul-like implementation for very early use. It does not
128 set errno if the result is outside bounds because it gets called before
129 errno may have been set up. */
130static unsigned long int
131tunables_strtoul (const char *nptr)
132{
133 unsigned long int result = 0;
134 long int sign = 1;
135 unsigned max_digit;
136
137 while (*nptr == ' ' || *nptr == '\t')
138 ++nptr;
139
140 if (*nptr == '-')
141 {
142 sign = -1;
143 ++nptr;
144 }
145 else if (*nptr == '+')
146 ++nptr;
147
148 if (*nptr < '0' || *nptr > '9')
149 return 0UL;
150
151 int base = 10;
152 max_digit = 9;
153 if (*nptr == '0')
154 {
155 if (nptr[1] == 'x' || nptr[1] == 'X')
156 {
157 base = 16;
158 nptr += 2;
159 }
160 else
161 {
162 base = 8;
163 max_digit = 7;
164 }
165 }
166
167 while (1)
168 {
169 unsigned long int digval;
170 if (*nptr >= '0' && *nptr <= '0' + max_digit)
171 digval = *nptr - '0';
172 else if (base == 16)
173 {
174 if (*nptr >= 'a' && *nptr <= 'f')
175 digval = *nptr - 'a' + 10;
176 else if (*nptr >= 'A' && *nptr <= 'F')
177 digval = *nptr - 'A' + 10;
178 else
179 break;
180 }
181 else
182 break;
183
184 if (result > ULONG_MAX / base
185 || (result == ULONG_MAX / base && digval > ULONG_MAX % base))
186 return ULONG_MAX;
187 result *= base;
188 result += digval;
189 ++nptr;
190 }
191
192 return result * sign;
193}
194
195/* Initialize the internal type if the value validates either using the
196 explicit constraints of the tunable or with the implicit constraints of its
197 type. */
198static void
199tunable_set_val_if_valid_range (tunable_t *cur, const char *strval,
200 int64_t default_min, int64_t default_max)
201{
202 int64_t val = tunables_strtoul (strval);
203
204 int64_t min = cur->type.min;
205 int64_t max = cur->type.max;
206
207 if (min == max)
208 {
209 min = default_min;
210 max = default_max;
211 }
212
213 if (val >= min && val <= max)
214 {
215 cur->val.numval = val;
216 cur->strval = strval;
217 }
218}
219
220/* Validate range of the input value and initialize the tunable CUR if it looks
221 good. */
222static void
223tunable_initialize (tunable_t *cur, const char *strval)
224{
225 switch (cur->type.type_code)
226 {
227 case TUNABLE_TYPE_INT_32:
228 {
229 tunable_set_val_if_valid_range (cur, strval, INT32_MIN, INT32_MAX);
230 break;
231 }
232 case TUNABLE_TYPE_SIZE_T:
233 {
234 tunable_set_val_if_valid_range (cur, strval, 0, SIZE_MAX);
235 break;
236 }
237 case TUNABLE_TYPE_STRING:
238 {
239 cur->val.strval = cur->strval = strval;
240 break;
241 }
242 default:
243 __builtin_unreachable ();
244 }
245}
246
9dd409a5
SP
247static void
248parse_tunables (char *tunestr)
249{
250 if (tunestr == NULL || *tunestr == '\0')
251 return;
252
253 char *p = tunestr;
254
255 while (true)
256 {
257 char *name = p;
258 size_t len = 0;
259
260 /* First, find where the name ends. */
261 while (p[len] != '=' && p[len] != ':' && p[len] != '\0')
262 len++;
263
264 /* If we reach the end of the string before getting a valid name-value
265 pair, bail out. */
266 if (p[len] == '\0')
267 return;
268
269 /* We did not find a valid name-value pair before encountering the
270 colon. */
271 if (p[len]== ':')
272 {
273 p += len + 1;
274 continue;
275 }
276
277 p += len + 1;
278
279 char *value = p;
280 len = 0;
281
282 while (p[len] != ':' && p[len] != '\0')
283 len++;
284
285 char end = p[len];
286 p[len] = '\0';
287
288 /* Add the tunable if it exists. */
289 for (size_t i = 0; i < sizeof (tunable_list) / sizeof (tunable_t); i++)
290 {
291 tunable_t *cur = &tunable_list[i];
292
293 /* If we are in a secure context (AT_SECURE) then ignore the tunable
294 unless it is explicitly marked as secure. Tunable values take
295 precendence over their envvar aliases. */
296 if (__libc_enable_secure && !cur->is_secure)
297 continue;
298
299 if (is_name (cur->name, name))
300 {
301 tunable_initialize (cur, value);
302 break;
303 }
304 }
305
306 if (end == ':')
307 p += len + 1;
308 else
309 return;
310 }
311}
312
313static size_t
314min_strlen (const char *s)
315{
316 size_t i = 0;
317 while (*s++ != '\0')
318 i++;
319
320 return i;
321}
322
67e58f39
SP
323/* Disable a tunable if it is set. */
324static void
325disable_tunable (tunable_id_t id, char **envp)
326{
327 const char *env_alias = tunable_list[id].env_alias;
328
329 if (env_alias != NULL)
330 tunables_unsetenv (envp, tunable_list[id].env_alias);
9dd409a5
SP
331
332 char *tunable = getenv (GLIBC_TUNABLES);
333 const char *cmp = tunable_list[id].name;
334 const size_t len = min_strlen (cmp);
335
336 while (tunable && *tunable != '\0' && *tunable != ':')
337 {
338 if (is_name (tunable, cmp))
339 {
340 tunable += len;
341 /* Overwrite the = and the value with colons. */
342 while (*tunable != '\0' && *tunable != ':')
343 *tunable++ = ':';
344 break;
345 }
346 tunable++;
347 }
67e58f39
SP
348}
349
350/* Disable the glibc.malloc.check tunable in SETUID/SETGID programs unless
351 the system administrator overrides it by creating the /etc/suid-debug
352 file. This is a special case where we want to conditionally enable/disable
353 a tunable even for setuid binaries. We use the special version of access()
354 to avoid setting ERRNO, which is a TLS variable since TLS has not yet been
355 set up. */
356static inline void
357__always_inline
358maybe_disable_malloc_check (void)
359{
360 if (__libc_enable_secure && __access_noerrno ("/etc/suid-debug", F_OK) != 0)
361 disable_tunable (TUNABLE_ENUM_NAME(glibc, malloc, check), __environ);
362}
363
364/* Initialize the tunables list from the environment. For now we only use the
365 ENV_ALIAS to find values. Later we will also use the tunable names to find
366 values. */
367void
368__tunables_init (char **envp)
369{
370 char *envname = NULL;
371 char *envval = NULL;
372 size_t len = 0;
373
374 maybe_disable_malloc_check ();
375
376 while ((envp = get_next_env (envp, &envname, &len, &envval)) != NULL)
377 {
9dd409a5
SP
378 if (is_name (GLIBC_TUNABLES, envname))
379 {
380 char *val = tunables_strdup (envval);
381 if (val != NULL)
382 parse_tunables (val);
383 continue;
384 }
385
67e58f39
SP
386 for (int i = 0; i < sizeof (tunable_list) / sizeof (tunable_t); i++)
387 {
388 tunable_t *cur = &tunable_list[i];
389
390 /* Skip over tunables that have either been set already or should be
391 skipped. */
392 if (cur->strval != NULL || cur->env_alias == NULL
393 || (__libc_enable_secure && !cur->is_secure))
394 continue;
395
396 const char *name = cur->env_alias;
397
398 /* We have a match. Initialize and move on to the next line. */
399 if (is_name (name, envname))
400 {
401 tunable_initialize (cur, envval);
402 break;
403 }
404 }
405 }
406}
407
408/* Set the tunable value. This is called by the module that the tunable exists
409 in. */
410void
411__tunable_set_val (tunable_id_t id, void *valp, tunable_callback_t callback)
412{
413 tunable_t *cur = &tunable_list[id];
414
415 /* Don't do anything if our tunable was not set during initialization or if
416 it failed validation. */
417 if (cur->strval == NULL)
418 return;
419
420 if (valp == NULL)
421 goto cb;
422
423 switch (cur->type.type_code)
424 {
425 case TUNABLE_TYPE_INT_32:
426 {
427 *((int32_t *) valp) = (int32_t) cur->val.numval;
428 break;
429 }
430 case TUNABLE_TYPE_SIZE_T:
431 {
432 *((size_t *) valp) = (size_t) cur->val.numval;
433 break;
434 }
435 case TUNABLE_TYPE_STRING:
436 {
437 *((const char **)valp) = cur->val.strval;
438 break;
439 }
440 default:
441 __builtin_unreachable ();
442 }
443
444cb:
445 if (callback)
446 callback (&cur->val);
447}