1/* Copyright (C) 2012-2024 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3
4 The GNU C Library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 The GNU C Library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with the GNU C Library; if not, see
16 <https://www.gnu.org/licenses/>. */
17
18/* Verify that correctly filter out unsafe environment variables defined
19 in unsecvars.h. */
20
21#include <array_length.h>
22#include <gnu/lib-names.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26#include <unistd.h>
27
28#include <support/check.h>
29#include <support/support.h>
30#include <support/test-driver.h>
31#include <support/capture_subprocess.h>
32
33static char SETGID_CHILD[] = "setgid-child";
34
35#define FILTERED_VALUE "some-filtered-value"
36#define UNFILTERED_VALUE "some-unfiltered-value"
37/* It assumes no other programs is being profile with a library with same
38 SONAME using the default folder. */
39#ifndef PROFILE_LIB
40# define PROFILE_LIB "tst-sonamemove-runmod2.so"
41#endif
42
43struct envvar_t
44{
45 const char *env;
46 const char *value;
47};
48
49/* That is not an extensible list of all filtered out environment
50 variables. */
51static const struct envvar_t filtered_envvars[] =
52{
53 { "GLIBC_TUNABLES", FILTERED_VALUE },
54 { "LD_AUDIT", FILTERED_VALUE },
55 { "LD_HWCAP_MASK", FILTERED_VALUE },
56 { "LD_LIBRARY_PATH", FILTERED_VALUE },
57 { "LD_PRELOAD", FILTERED_VALUE },
58 { "LD_PROFILE", PROFILE_LIB },
59 { "MALLOC_ARENA_MAX", FILTERED_VALUE },
60 { "MALLOC_PERTURB_", FILTERED_VALUE },
61 { "MALLOC_TRACE", FILTERED_VALUE },
62 { "MALLOC_TRIM_THRESHOLD_", FILTERED_VALUE },
63 { "RES_OPTIONS", FILTERED_VALUE },
64 { "LD_DEBUG", "all" },
65 { "LD_DEBUG_OUTPUT", "/tmp/some-file" },
66 { "LD_WARN", FILTERED_VALUE },
67 { "LD_VERBOSE", FILTERED_VALUE },
68 { "LD_BIND_NOW", "0" },
69 { "LD_BIND_NOT", "1" },
70};
71
72static const struct envvar_t unfiltered_envvars[] =
73{
74 /* Non longer supported option. */
75 { "LD_ASSUME_KERNEL", UNFILTERED_VALUE },
76};
77
78static int
79test_child (void)
80{
81 int ret = 0;
82
83 for (const struct envvar_t *e = filtered_envvars;
84 e != array_end (filtered_envvars);
85 e++)
86 {
87 const char *env = getenv (name: e->env);
88 if (env != NULL)
89 {
90 printf (format: "FAIL: filtered environment variable is not NULL: %s=%s\n",
91 e->env, env);
92 ret = 1;
93 }
94 }
95
96 for (const struct envvar_t *e = unfiltered_envvars;
97 e != array_end (unfiltered_envvars);
98 e++)
99 {
100 const char *env = getenv (name: e->env);
101 if (!(env != NULL && strcmp (s1: env, s2: e->value) == 0))
102 {
103 if (env == NULL)
104 printf (format: "FAIL: unfiltered environment variable %s is NULL\n",
105 e->env);
106 else
107 printf (format: "FAIL: unfiltered environment variable %s=%s != %s\n",
108 e->env, env, e->value);
109
110 ret = 1;
111 }
112 }
113
114 /* Also check if no profile file was created.
115 The parent sets LD_DEBUG_OUTPUT="/tmp/some-file"
116 which should be filtered. Then it falls back to "/var/tmp".
117 Note: LD_PROFILE is not supported for static binaries. */
118 {
119 char *profilepath = xasprintf (format: "/var/tmp/%s.profile", PROFILE_LIB);
120 if (!access (name: profilepath, R_OK))
121 {
122 printf (format: "FAIL: LD_PROFILE file at %s was created!\n", profilepath);
123 ret = 1;
124 }
125 free (ptr: profilepath);
126 }
127
128 return ret;
129}
130
131static int
132do_test (int argc, char **argv)
133{
134 /* For dynamic loader, the test requires --enable-hardcoded-path-in-tests so
135 the kernel sets the AT_SECURE on process initialization. */
136 if (argc >= 2 && strstr (haystack: argv[1], LD_SO) != 0)
137 FAIL_UNSUPPORTED ("dynamic test requires --enable-hardcoded-path-in-tests");
138
139 /* Setgid child process. */
140 if (argc == 2 && strcmp (s1: argv[1], s2: SETGID_CHILD) == 0)
141 {
142 if (getgid () == getegid ())
143 /* This can happen if the file system is mounted nosuid. */
144 FAIL_UNSUPPORTED ("SGID failed: GID and EGID match (%jd)\n",
145 (intmax_t) getgid ());
146
147 int ret = test_child ();
148
149 if (ret != 0)
150 exit (status: 1);
151
152 /* Special return code to make sure that the child executed all the way
153 through. */
154 exit (status: 42);
155 }
156 else
157 {
158 for (const struct envvar_t *e = filtered_envvars;
159 e != array_end (filtered_envvars);
160 e++)
161 setenv (name: e->env, value: e->value, replace: 1);
162
163 for (const struct envvar_t *e = unfiltered_envvars;
164 e != array_end (unfiltered_envvars);
165 e++)
166 setenv (name: e->env, value: e->value, replace: 1);
167
168 /* Ensure that the profile output does not exist from a previous run
169 (e.g. if test_dir, which defaults to /tmp, is mounted nosuid.)
170 Note: support_capture_subprogram_self_sgid creates the SGID binary
171 in test_dir. */
172 {
173 char *profilepath = xasprintf (format: "/var/tmp/%s.profile", PROFILE_LIB);
174 unlink (name: profilepath);
175 free (ptr: profilepath);
176 }
177
178 int status = support_capture_subprogram_self_sgid (child_id: SETGID_CHILD);
179
180 if (WEXITSTATUS (status) == EXIT_UNSUPPORTED)
181 exit (status: EXIT_UNSUPPORTED);
182
183 if (WEXITSTATUS (status) != 42)
184 {
185 printf (format: " child failed with status %d\n",
186 WEXITSTATUS (status));
187 support_record_failure ();
188 }
189
190 return 0;
191 }
192}
193
194#define TEST_FUNCTION_ARGV do_test
195#include <support/test-driver.c>
196

source code of glibc/elf/tst-env-setuid.c