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-2022 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 <https://www.gnu.org/licenses/>. */
20
21/* Mark symbols hidden in static PIE for early self relocation to work. */
22#if BUILD_PIE_DEFAULT
23# pragma GCC visibility push(hidden)
24#endif
25#include <startup.h>
26#include <stdint.h>
27#include <stdbool.h>
28#include <unistd.h>
29#include <stdlib.h>
30#include <sysdep.h>
31#include <fcntl.h>
32#include <ldsodefs.h>
33#include <array_length.h>
34#include <dl-minimal-malloc.h>
35
36#define TUNABLES_INTERNAL 1
37#include "dl-tunables.h"
38
39#include <not-errno.h>
40
41#if TUNABLES_FRONTEND == TUNABLES_FRONTEND_valstring
42# define GLIBC_TUNABLES "GLIBC_TUNABLES"
43#endif
44
45#if TUNABLES_FRONTEND == TUNABLES_FRONTEND_valstring
46static char *
47tunables_strdup (const char *in)
48{
49 size_t i = 0;
50
51 while (in[i++] != '\0');
52 char *out = __minimal_malloc (n: i + 1);
53
54 /* For most of the tunables code, we ignore user errors. However,
55 this is a system error - and running out of memory at program
56 startup should be reported, so we do. */
57 if (out == NULL)
58 _dl_fatal_printf (fmt: "failed to allocate memory to process tunables\n");
59
60 while (i-- > 0)
61 out[i] = in[i];
62
63 return out;
64}
65#endif
66
67static char **
68get_next_env (char **envp, char **name, size_t *namelen, char **val,
69 char ***prev_envp)
70{
71 while (envp != NULL && *envp != NULL)
72 {
73 char **prev = envp;
74 char *envline = *envp++;
75 int len = 0;
76
77 while (envline[len] != '\0' && envline[len] != '=')
78 len++;
79
80 /* Just the name and no value, go to the next one. */
81 if (envline[len] == '\0')
82 continue;
83
84 *name = envline;
85 *namelen = len;
86 *val = &envline[len + 1];
87 *prev_envp = prev;
88
89 return envp;
90 }
91
92 return NULL;
93}
94
95static void
96do_tunable_update_val (tunable_t *cur, const tunable_val_t *valp,
97 const tunable_num_t *minp,
98 const tunable_num_t *maxp)
99{
100 tunable_num_t val, min, max;
101
102 if (cur->type.type_code == TUNABLE_TYPE_STRING)
103 {
104 cur->val.strval = valp->strval;
105 cur->initialized = true;
106 return;
107 }
108
109 bool unsigned_cmp = unsigned_tunable_type (cur->type.type_code);
110
111 val = valp->numval;
112 min = minp != NULL ? *minp : cur->type.min;
113 max = maxp != NULL ? *maxp : cur->type.max;
114
115 /* We allow only increasingly restrictive bounds. */
116 if (tunable_val_lt (lhs: min, rhs: cur->type.min, unsigned_cmp))
117 min = cur->type.min;
118
119 if (tunable_val_gt (lhs: max, rhs: cur->type.max, unsigned_cmp))
120 max = cur->type.max;
121
122 /* Skip both bounds if they're inconsistent. */
123 if (tunable_val_gt (lhs: min, rhs: max, unsigned_cmp))
124 {
125 min = cur->type.min;
126 max = cur->type.max;
127 }
128
129 /* Bail out if the bounds are not valid. */
130 if (tunable_val_lt (lhs: val, rhs: min, unsigned_cmp)
131 || tunable_val_lt (lhs: max, rhs: val, unsigned_cmp))
132 return;
133
134 cur->val.numval = val;
135 cur->type.min = min;
136 cur->type.max = max;
137 cur->initialized = true;
138}
139
140/* Validate range of the input value and initialize the tunable CUR if it looks
141 good. */
142static void
143tunable_initialize (tunable_t *cur, const char *strval)
144{
145 tunable_val_t val;
146
147 if (cur->type.type_code != TUNABLE_TYPE_STRING)
148 val.numval = (tunable_num_t) _dl_strtoul (strval, NULL);
149 else
150 val.strval = strval;
151 do_tunable_update_val (cur, &val, NULL, NULL);
152}
153
154void
155__tunable_set_val (tunable_id_t id, tunable_val_t *valp, tunable_num_t *minp,
156 tunable_num_t *maxp)
157{
158 tunable_t *cur = &tunable_list[id];
159
160 do_tunable_update_val (cur, valp, minp, maxp);
161}
162
163#if TUNABLES_FRONTEND == TUNABLES_FRONTEND_valstring
164/* Parse the tunable string TUNESTR and adjust it to drop any tunables that may
165 be unsafe for AT_SECURE processes so that it can be used as the new
166 environment variable value for GLIBC_TUNABLES. VALSTRING is the original
167 environment variable string which we use to make NULL terminated values so
168 that we don't have to allocate memory again for it. */
169static void
170parse_tunables (char *tunestr, char *valstring)
171{
172 if (tunestr == NULL || *tunestr == '\0')
173 return;
174
175 char *p = tunestr;
176 size_t off = 0;
177
178 while (true)
179 {
180 char *name = p;
181 size_t len = 0;
182
183 /* First, find where the name ends. */
184 while (p[len] != '=' && p[len] != ':' && p[len] != '\0')
185 len++;
186
187 /* If we reach the end of the string before getting a valid name-value
188 pair, bail out. */
189 if (p[len] == '\0')
190 break;
191
192 /* We did not find a valid name-value pair before encountering the
193 colon. */
194 if (p[len]== ':')
195 {
196 p += len + 1;
197 continue;
198 }
199
200 p += len + 1;
201
202 /* Take the value from the valstring since we need to NULL terminate it. */
203 char *value = &valstring[p - tunestr];
204 len = 0;
205
206 while (p[len] != ':' && p[len] != '\0')
207 len++;
208
209 /* Add the tunable if it exists. */
210 for (size_t i = 0; i < sizeof (tunable_list) / sizeof (tunable_t); i++)
211 {
212 tunable_t *cur = &tunable_list[i];
213
214 if (tunable_is_name (cur->name, name))
215 {
216 /* If we are in a secure context (AT_SECURE) then ignore the
217 tunable unless it is explicitly marked as secure. Tunable
218 values take precedence over their envvar aliases. We write
219 the tunables that are not SXID_ERASE back to TUNESTR, thus
220 dropping all SXID_ERASE tunables and any invalid or
221 unrecognized tunables. */
222 if (__libc_enable_secure)
223 {
224 if (cur->security_level != TUNABLE_SECLEVEL_SXID_ERASE)
225 {
226 if (off > 0)
227 tunestr[off++] = ':';
228
229 const char *n = cur->name;
230
231 while (*n != '\0')
232 tunestr[off++] = *n++;
233
234 tunestr[off++] = '=';
235
236 for (size_t j = 0; j < len; j++)
237 tunestr[off++] = value[j];
238 }
239
240 if (cur->security_level != TUNABLE_SECLEVEL_NONE)
241 break;
242 }
243
244 value[len] = '\0';
245 tunable_initialize (cur, value);
246 break;
247 }
248 }
249
250 /* We reached the end while processing the tunable string. */
251 if (p[len] == '\0')
252 break;
253
254 p += len + 1;
255 }
256
257 /* Terminate tunestr before we leave. */
258 if (__libc_enable_secure)
259 tunestr[off] = '\0';
260}
261#endif
262
263/* Enable the glibc.malloc.check tunable in SETUID/SETGID programs only when
264 the system administrator has created the /etc/suid-debug file. This is a
265 special case where we want to conditionally enable/disable a tunable even
266 for setuid binaries. We use the special version of access() to avoid
267 setting ERRNO, which is a TLS variable since TLS has not yet been set
268 up. */
269static __always_inline void
270maybe_enable_malloc_check (void)
271{
272 tunable_id_t id = TUNABLE_ENUM_NAME (glibc, malloc, check);
273 if (__libc_enable_secure && __access_noerrno ("/etc/suid-debug", F_OK) == 0)
274 tunable_list[id].security_level = TUNABLE_SECLEVEL_NONE;
275}
276
277/* Initialize the tunables list from the environment. For now we only use the
278 ENV_ALIAS to find values. Later we will also use the tunable names to find
279 values. */
280void
281__tunables_init (char **envp)
282{
283 char *envname = NULL;
284 char *envval = NULL;
285 size_t len = 0;
286 char **prev_envp = envp;
287
288 maybe_enable_malloc_check ();
289
290 while ((envp = get_next_env (envp, name: &envname, namelen: &len, val: &envval,
291 prev_envp: &prev_envp)) != NULL)
292 {
293#if TUNABLES_FRONTEND == TUNABLES_FRONTEND_valstring
294 if (tunable_is_name (GLIBC_TUNABLES, envname))
295 {
296 char *new_env = tunables_strdup (in: envname);
297 if (new_env != NULL)
298 parse_tunables (tunestr: new_env + len + 1, valstring: envval);
299 /* Put in the updated envval. */
300 *prev_envp = new_env;
301 continue;
302 }
303#endif
304
305 for (int i = 0; i < sizeof (tunable_list) / sizeof (tunable_t); i++)
306 {
307 tunable_t *cur = &tunable_list[i];
308
309 /* Skip over tunables that have either been set already or should be
310 skipped. */
311 if (cur->initialized || cur->env_alias[0] == '\0')
312 continue;
313
314 const char *name = cur->env_alias;
315
316 /* We have a match. Initialize and move on to the next line. */
317 if (tunable_is_name (name, envname))
318 {
319 /* For AT_SECURE binaries, we need to check the security settings of
320 the tunable and decide whether we read the value and also whether
321 we erase the value so that child processes don't inherit them in
322 the environment. */
323 if (__libc_enable_secure)
324 {
325 if (cur->security_level == TUNABLE_SECLEVEL_SXID_ERASE)
326 {
327 /* Erase the environment variable. */
328 char **ep = prev_envp;
329
330 while (*ep != NULL)
331 {
332 if (tunable_is_name (name, *ep))
333 {
334 char **dp = ep;
335
336 do
337 dp[0] = dp[1];
338 while (*dp++);
339 }
340 else
341 ++ep;
342 }
343 /* Reset the iterator so that we read the environment again
344 from the point we erased. */
345 envp = prev_envp;
346 }
347
348 if (cur->security_level != TUNABLE_SECLEVEL_NONE)
349 continue;
350 }
351
352 tunable_initialize (cur, envval);
353 break;
354 }
355 }
356 }
357}
358
359void
360__tunables_print (void)
361{
362 for (int i = 0; i < array_length (tunable_list); i++)
363 {
364 const tunable_t *cur = &tunable_list[i];
365 if (cur->type.type_code == TUNABLE_TYPE_STRING
366 && cur->val.strval == NULL)
367 _dl_printf ("%s:\n", cur->name);
368 else
369 {
370 _dl_printf ("%s: ", cur->name);
371 switch (cur->type.type_code)
372 {
373 case TUNABLE_TYPE_INT_32:
374 _dl_printf ("%d (min: %d, max: %d)\n",
375 (int) cur->val.numval,
376 (int) cur->type.min,
377 (int) cur->type.max);
378 break;
379 case TUNABLE_TYPE_UINT_64:
380 _dl_printf ("0x%lx (min: 0x%lx, max: 0x%lx)\n",
381 (long int) cur->val.numval,
382 (long int) cur->type.min,
383 (long int) cur->type.max);
384 break;
385 case TUNABLE_TYPE_SIZE_T:
386 _dl_printf ("0x%Zx (min: 0x%Zx, max: 0x%Zx)\n",
387 (size_t) cur->val.numval,
388 (size_t) cur->type.min,
389 (size_t) cur->type.max);
390 break;
391 case TUNABLE_TYPE_STRING:
392 _dl_printf ("%s\n", cur->val.strval);
393 break;
394 default:
395 __builtin_unreachable ();
396 }
397 }
398 }
399}
400
401/* Set the tunable value. This is called by the module that the tunable exists
402 in. */
403void
404__tunable_get_val (tunable_id_t id, void *valp, tunable_callback_t callback)
405{
406 tunable_t *cur = &tunable_list[id];
407
408 switch (cur->type.type_code)
409 {
410 case TUNABLE_TYPE_UINT_64:
411 {
412 *((uint64_t *) valp) = (uint64_t) cur->val.numval;
413 break;
414 }
415 case TUNABLE_TYPE_INT_32:
416 {
417 *((int32_t *) valp) = (int32_t) cur->val.numval;
418 break;
419 }
420 case TUNABLE_TYPE_SIZE_T:
421 {
422 *((size_t *) valp) = (size_t) cur->val.numval;
423 break;
424 }
425 case TUNABLE_TYPE_STRING:
426 {
427 *((const char **)valp) = cur->val.strval;
428 break;
429 }
430 default:
431 __builtin_unreachable ();
432 }
433
434 if (cur->initialized && callback != NULL)
435 callback (&cur->val);
436}
437
438rtld_hidden_def (__tunable_get_val)
439

source code of glibc/elf/dl-tunables.c