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 | |
33 | static 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 | |
43 | struct 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. */ |
51 | static 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 | |
72 | static const struct envvar_t unfiltered_envvars[] = |
73 | { |
74 | /* Non longer supported option. */ |
75 | { "LD_ASSUME_KERNEL" , UNFILTERED_VALUE }, |
76 | }; |
77 | |
78 | static int |
79 | test_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 | |
131 | static int |
132 | do_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 | |