Gentoo Archives: gentoo-commits

From: Sergei Trofimovich <slyfox@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] proj/sandbox:master commit in: libsandbox/, tests/, libsandbox/wrapper-funcs/
Date: Tue, 08 Jan 2019 22:58:18
Message-Id: 1546987724.f3e51a930312422cc78b693a247b7c5704ac90a2.slyfox@gentoo
1 commit: f3e51a930312422cc78b693a247b7c5704ac90a2
2 Author: Sergei Trofimovich <slyfox <AT> gentoo <DOT> org>
3 AuthorDate: Sun Jan 6 09:32:55 2019 +0000
4 Commit: Sergei Trofimovich <slyfox <AT> gentoo <DOT> org>
5 CommitDate: Tue Jan 8 22:48:44 2019 +0000
6 URL: https://gitweb.gentoo.org/proj/sandbox.git/commit/?id=f3e51a93
7
8 exec*() wrappers: never mutate 'environ' of host process
9
10 In bug #669702 gcc exposed sandbox bug where execv()
11 wrapper changed 'environ' global variable underneath.
12
13 A few GNU projects (pex_unix_exec_child in gcc and gdb)
14 use the following idiom:
15
16 for (;;) {
17 vfork();
18 char ** save_environ = environ; // [1]
19 if (child) {
20 environ = child_environ; // [2]
21 execv(payload); // [3]
22 }
23 if (parent) {
24 environ = save_environ; // [4]
25 ...
26 waitpid(child, ...);
27 }
28 }
29
30 Code above assumes that execv() does not mutate 'environ'.
31
32 In case of #669702 sandbox's execv() wrapper at '[3]' mutated
33 'environ' and relocated it (via maloc()/free() internally).
34 This caused '[4]' to point 'environ' fo freed location.
35
36 The change fixes it in a following way:
37 - execv() call now works more like execve() call by mutating
38 external array and substitutes 'environ' only for a period
39 of 'execv()' execution.
40 - add basic execv()/'environ' corruption test
41
42 Tested on:
43 - linux/glibc-2.28
44 - linux/uclibc-ng-1.0.31
45
46 Reported-and-tested-by: Walther
47 Reported-by: 0x6d6174 <AT> posteo.de
48 Reported-by: Andrey Korolyov
49 Bug: https://bugs.gentoo.org/669702
50 Signed-off-by: Sergei Trofimovich <slyfox <AT> gentoo.org>
51
52 libsandbox/libsandbox.c | 92 +++++++++++++++----------------
53 libsandbox/libsandbox.h | 17 +++++-
54 libsandbox/wrapper-funcs/__wrapper_exec.c | 15 +++--
55 tests/Makefile.am | 1 +
56 tests/execv-0.c | 21 +++++++
57 tests/execv-1.sh | 4 ++
58 tests/execv.at | 1 +
59 7 files changed, 96 insertions(+), 55 deletions(-)
60
61 diff --git a/libsandbox/libsandbox.c b/libsandbox/libsandbox.c
62 index e0c9d1a..77e9959 100644
63 --- a/libsandbox/libsandbox.c
64 +++ b/libsandbox/libsandbox.c
65 @@ -1122,10 +1122,18 @@ typedef struct {
66 *
67 * XXX: Might be much nicer if we could serialize these vars behind the back of
68 * the program. Might be hard to handle LD_PRELOAD though ...
69 + *
70 + * execv*() must never modify environment inplace with
71 + * setenv/putenv/unsetenv as it can relocate 'environ' and break
72 + * vfork()/execv() users: https://bugs.gentoo.org/669702
73 */
74 -char **sb_check_envp(char **envp, size_t *mod_cnt, bool insert)
75 +struct sb_envp_ctx sb_new_envp(char **envp, bool insert)
76 {
77 - char **my_env;
78 + struct sb_envp_ctx r = {
79 + .sb_envp = envp,
80 + .orig_envp = envp,
81 + .__mod_cnt = 0,
82 + };
83 char *entry;
84 size_t count, i;
85 env_pair vars[] = {
86 @@ -1152,7 +1160,7 @@ char **sb_check_envp(char **envp, size_t *mod_cnt, bool insert)
87 /* If sandbox is explicitly disabled, do not propagate the vars
88 * and just return user's envp */
89 if (!sbcontext.on)
90 - return envp;
91 + return r;
92
93 /* First figure out how many vars are already in the env */
94 found_var_cnt = 0;
95 @@ -1188,7 +1196,7 @@ char **sb_check_envp(char **envp, size_t *mod_cnt, bool insert)
96 if ((insert && num_vars == found_var_cnt) ||
97 (!insert && found_var_cnt == 0))
98 /* Use the user's envp */
99 - return envp;
100 + return r;
101
102 /* Ok, we need to create our own envp, as we need to restore stuff
103 * and we should not touch the user's envp. First we add our vars,
104 @@ -1208,61 +1216,50 @@ char **sb_check_envp(char **envp, size_t *mod_cnt, bool insert)
105 vars[13].value = "1";
106 }
107
108 - my_env = NULL;
109 + char ** my_env = NULL;
110 if (!insert) {
111 - if (mod_cnt) {
112 - str_list_for_each_item(envp, entry, count) {
113 - for (i = 0; i < num_vars; ++i)
114 - if (i != 12 && is_env_var(entry, vars[i].name, vars[i].len)) {
115 - (*mod_cnt)++;
116 - goto skip;
117 - }
118 - str_list_add_item(my_env, entry, error);
119 - skip: ;
120 - }
121 - } else {
122 + str_list_for_each_item(envp, entry, count) {
123 for (i = 0; i < num_vars; ++i)
124 - if (i != 12) unsetenv(vars[i].name);
125 + if (i != 12 /* LD_LIBRARY_PATH index */
126 + && is_env_var(entry, vars[i].name, vars[i].len)) {
127 + r.__mod_cnt++;
128 + goto skip;
129 + }
130 + str_list_add_item(my_env, entry, error);
131 + skip: ;
132 }
133 } else {
134 - if (mod_cnt) {
135 - /* Count directly due to variability with LD_PRELOAD and the value
136 - * logic below. Getting out of sync can mean memory corruption. */
137 - *mod_cnt = 0;
138 - if (unlikely(merge_ld_preload)) {
139 - str_list_add_item(my_env, ld_preload, error);
140 - (*mod_cnt)++;
141 - }
142 - for (i = 0; i < num_vars; ++i) {
143 - if (found_vars[i] || !vars[i].value)
144 - continue;
145 - str_list_add_item_env(my_env, vars[i].name, vars[i].value, error);
146 - (*mod_cnt)++;
147 - }
148 + /* Count directly due to variability with LD_PRELOAD and the value
149 + * logic below. Getting out of sync can mean memory corruption. */
150 + r.__mod_cnt = 0;
151 + if (unlikely(merge_ld_preload)) {
152 + str_list_add_item(my_env, ld_preload, error);
153 + r.__mod_cnt++;
154 + }
155 + for (i = 0; i < num_vars; ++i) {
156 + if (found_vars[i] || !vars[i].value)
157 + continue;
158 + str_list_add_item_env(my_env, vars[i].name, vars[i].value, error);
159 + r.__mod_cnt++;
160 + }
161
162 - str_list_for_each_item(envp, entry, count) {
163 - if (unlikely(merge_ld_preload && is_env_var(entry, vars[0].name, vars[0].len)))
164 - continue;
165 - str_list_add_item(my_env, entry, error);
166 - }
167 - } else {
168 - if (unlikely(merge_ld_preload))
169 - putenv(ld_preload);
170 - for (i = 0; i < num_vars; ++i) {
171 - if (found_vars[i] || !vars[i].value)
172 - continue;
173 - setenv(vars[i].name, vars[i].value, 1);
174 - }
175 + str_list_for_each_item(envp, entry, count) {
176 + if (unlikely(merge_ld_preload && is_env_var(entry, vars[0].name, vars[0].len)))
177 + continue;
178 + str_list_add_item(my_env, entry, error);
179 }
180 }
181
182 error:
183 - return my_env;
184 + r.sb_envp = my_env;
185 + return r;
186 }
187
188 -void sb_cleanup_envp(char **envp, size_t mod_cnt)
189 +void sb_free_envp(struct sb_envp_ctx * envp_ctx)
190 {
191 /* We assume all the stuffed vars are at the start */
192 + size_t mod_cnt = envp_ctx->__mod_cnt;
193 + char ** envp = envp_ctx->sb_envp;
194 size_t i;
195 for (i = 0; i < mod_cnt; ++i)
196 free(envp[i]);
197 @@ -1271,5 +1268,6 @@ void sb_cleanup_envp(char **envp, size_t mod_cnt)
198 * entries except for LD_PRELOAD. All the other entries are
199 * pointers to existing envp memory.
200 */
201 - free(envp);
202 + if (envp != envp_ctx->orig_envp)
203 + free(envp);
204 }
205
206 diff --git a/libsandbox/libsandbox.h b/libsandbox/libsandbox.h
207 index 63882e7..70fc422 100644
208 --- a/libsandbox/libsandbox.h
209 +++ b/libsandbox/libsandbox.h
210 @@ -56,8 +56,21 @@ void *get_dlsym(const char *symname, const char *symver);
211
212 extern char sandbox_lib[SB_PATH_MAX];
213 extern bool sandbox_on;
214 -char **sb_check_envp(char **envp, size_t *mod_cnt, bool insert);
215 -void sb_cleanup_envp(char **envp, size_t mod_cnt);
216 +
217 +struct sb_envp_ctx {
218 + /* Sandboxified environment with sandbox variables injected.
219 + * Allocated by 'sb_new_envp', freed by 'sb_free_envp'. */
220 + char ** sb_envp;
221 + /* Original environment. Passed from outside. */
222 + char ** orig_envp;
223 +
224 + /* Internal counter to free.
225 + * Not to be used outside sb_{new,free}_envp. */
226 + size_t __mod_cnt;
227 +};
228 +struct sb_envp_ctx sb_new_envp(char **envp, bool insert);
229 +void sb_free_envp(struct sb_envp_ctx * envp_ctx);
230 +
231 extern pid_t trace_pid;
232
233 extern void sb_lock(void);
234
235 diff --git a/libsandbox/wrapper-funcs/__wrapper_exec.c b/libsandbox/wrapper-funcs/__wrapper_exec.c
236 index 226c0c0..974156e 100644
237 --- a/libsandbox/wrapper-funcs/__wrapper_exec.c
238 +++ b/libsandbox/wrapper-funcs/__wrapper_exec.c
239 @@ -275,10 +275,11 @@ WRAPPER_RET_TYPE WRAPPER_NAME(WRAPPER_ARGS_PROTO)
240 #endif
241
242 #ifdef EXEC_MY_ENV
243 - size_t mod_cnt;
244 - char **my_env = sb_check_envp((char **)envp, &mod_cnt, run_in_process);
245 + struct sb_envp_ctx ec = sb_new_envp((char**)envp, run_in_process);
246 + char **my_env = ec.sb_envp;
247 #else
248 - sb_check_envp(environ, NULL, run_in_process);
249 + struct sb_envp_ctx ec = sb_new_envp(environ, run_in_process);
250 + environ = ec.sb_envp;
251 #endif
252
253 restore_errno();
254 @@ -287,10 +288,12 @@ WRAPPER_RET_TYPE WRAPPER_NAME(WRAPPER_ARGS_PROTO)
255 #endif
256 result = SB_HIDDEN_FUNC(WRAPPER_NAME)(EXEC_ARGS);
257
258 -#ifdef EXEC_MY_ENV
259 - if (my_env != envp)
260 - sb_cleanup_envp(my_env, mod_cnt);
261 +#ifndef EXEC_MY_ENV
262 + /* https://bugs.gentoo.org/669702: maintain illusion
263 + or unmodified 'environ'. */
264 + environ = ec.orig_envp;
265 #endif
266 + sb_free_envp(&ec);
267
268 #ifdef EXEC_RECUR_CHECK
269 --recursive;
270
271 diff --git a/tests/Makefile.am b/tests/Makefile.am
272 index d98898f..3baf5b1 100644
273 --- a/tests/Makefile.am
274 +++ b/tests/Makefile.am
275 @@ -18,6 +18,7 @@ check_PROGRAMS = \
276 chown-0 \
277 creat-0 \
278 creat64-0 \
279 + execv-0 \
280 execvp-0 \
281 faccessat-0 \
282 fchmodat-0 \
283
284 diff --git a/tests/execv-0.c b/tests/execv-0.c
285 new file mode 100644
286 index 0000000..bea0aaa
287 --- /dev/null
288 +++ b/tests/execv-0.c
289 @@ -0,0 +1,21 @@
290 +/*
291 + * A simple wrapper for execv that validates environment
292 + * is not corrupted by wrapper: https://bugs.gentoo.org/669702
293 + */
294 +
295 +#define _GNU_SOURCE /* for environ */
296 +#include <stdio.h>
297 +#include "tests.h"
298 +
299 +int main(int argc, char *argv[])
300 +{
301 + char* execv_argv[] = {"nope", (char*)NULL,};
302 + char* execv_environ[] = {"FOO=1", (char*)NULL,};
303 + environ = execv_environ;
304 + execv("./does/not/exist", execv_argv);
305 + if (environ != execv_environ) {
306 + fprintf(stderr, "environ was changed unexpectedly by execv wrapper\n");
307 + return 1;
308 + }
309 + return 0;
310 +}
311
312 diff --git a/tests/execv-1.sh b/tests/execv-1.sh
313 new file mode 100755
314 index 0000000..ab816b0
315 --- /dev/null
316 +++ b/tests/execv-1.sh
317 @@ -0,0 +1,4 @@
318 +#!/bin/sh
319 +# Make sure execv run does not corrup 'environ' of caller process:
320 +# https://bugs.gentoo.org/669702
321 +timeout -s KILL 10 execv-0
322
323 diff --git a/tests/execv.at b/tests/execv.at
324 new file mode 100644
325 index 0000000..081d7d2
326 --- /dev/null
327 +++ b/tests/execv.at
328 @@ -0,0 +1 @@
329 +SB_CHECK(1)