Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/src/exec_preload.c
1532 views
1
/*
2
* SPDX-License-Identifier: ISC
3
*
4
* Copyright (c) 2009-2022 Todd C. Miller <[email protected]>
5
*
6
* Permission to use, copy, modify, and distribute this software for any
7
* purpose with or without fee is hereby granted, provided that the above
8
* copyright notice and this permission notice appear in all copies.
9
*
10
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
*/
18
19
#include <config.h>
20
21
#include <fcntl.h>
22
#include <stdarg.h>
23
#include <stdio.h>
24
#include <stdlib.h>
25
#include <string.h>
26
#include <unistd.h>
27
28
#include <sudo.h>
29
#include <sudo_exec.h>
30
#include <sudo_util.h>
31
32
#ifdef RTLD_PRELOAD_VAR
33
typedef void * (*sudo_alloc_fn_t)(size_t, size_t);
34
typedef void (*sudo_free_fn_t)(void *);
35
36
static void *
37
sudo_allocarray(size_t nmemb, size_t size)
38
{
39
return reallocarray(NULL, nmemb, size);
40
}
41
42
/*
43
* Allocate space for the string described by fmt and return it,
44
* or NULL on error.
45
* Currently only supports %%, %c, %d, and %s escapes.
46
*/
47
static char *
48
fmtstr(sudo_alloc_fn_t alloc_fn, sudo_free_fn_t free_fn, const char * restrict ofmt, ...)
49
{
50
char *cp, *cur, *newstr = NULL;
51
size_t len, size = 1;
52
const char *fmt;
53
va_list ap;
54
debug_decl(fmtstr, SUDO_DEBUG_UTIL);
55
56
/* Determine size. */
57
va_start(ap, ofmt);
58
for (fmt = ofmt; *fmt != '\0'; ) {
59
if (fmt[0] == '%') {
60
switch (fmt[1]) {
61
case 'c':
62
(void)va_arg(ap, int);
63
FALLTHROUGH;
64
case '%':
65
size++;
66
fmt += 2;
67
continue;
68
case 's':
69
cp = va_arg(ap, char *);
70
size += strlen(cp ? cp : "(NULL)");
71
fmt += 2;
72
continue;
73
case 'd': {
74
char numbuf[STRLEN_MAX_SIGNED(int) + 1];
75
len = (size_t)snprintf(numbuf, sizeof(numbuf), "%d",
76
va_arg(ap, int));
77
if (len >= sizeof(numbuf)) {
78
goto oflow;
79
}
80
size += len;
81
fmt += 2;
82
continue;
83
}
84
default:
85
/* Treat as literal. */
86
break;
87
}
88
}
89
size++;
90
fmt++;
91
}
92
va_end(ap);
93
94
newstr = alloc_fn(1, size);
95
if (newstr == NULL)
96
debug_return_str(NULL);
97
98
/* Format/copy data. */
99
cur = newstr;
100
va_start(ap, ofmt);
101
for (fmt = ofmt; *fmt != '\0'; ) {
102
if (fmt[0] == '%') {
103
switch (fmt[1]) {
104
case '%':
105
if (size < 2) {
106
goto oflow;
107
}
108
*cur++ = '%';
109
size--;
110
fmt += 2;
111
continue;
112
case 'c':
113
if (size < 2) {
114
goto oflow;
115
}
116
*cur++ = (char )va_arg(ap, int);
117
size--;
118
fmt += 2;
119
continue;
120
case 's':
121
cp = va_arg(ap, char *);
122
len = strlcpy(cur, cp ? cp : "(NULL)", size);
123
if (len >= size) {
124
goto oflow;
125
}
126
cur += len;
127
size -= len;
128
fmt += 2;
129
continue;
130
case 'd':
131
len = (size_t)snprintf(cur, size, "%d", va_arg(ap, int));
132
if (len >= size) {
133
goto oflow;
134
}
135
cur += len;
136
size -= len;
137
fmt += 2;
138
continue;
139
default:
140
/* Treat as literal. */
141
break;
142
}
143
}
144
if (size < 2) {
145
goto oflow;
146
}
147
*cur++ = *fmt++;
148
size++;
149
}
150
151
if (size < 1) {
152
goto oflow;
153
}
154
*cur = '\0';
155
va_end(ap);
156
157
debug_return_str(newstr);
158
159
oflow:
160
/* We pre-allocate enough space, so this should never happen. */
161
va_end(ap);
162
free_fn(newstr);
163
sudo_warnx(U_("internal error, %s overflow"), __func__);
164
debug_return_str(NULL);
165
}
166
167
/*
168
* Add a DSO file to LD_PRELOAD or the system equivalent.
169
*/
170
static char **
171
sudo_preload_dso_alloc(char *const envp[], const char *preload_var,
172
const char *dso_file, int intercept_fd,
173
sudo_alloc_fn_t alloc_fn, sudo_free_fn_t free_fn)
174
{
175
const size_t preload_var_len = strlen(preload_var);
176
char *preload = NULL;
177
char **nep, **nenvp = NULL;
178
char *const *ep;
179
char **preload_ptr = NULL;
180
char **intercept_ptr = NULL;
181
char *const empty[1] = { NULL };
182
bool fd_present = false;
183
bool dso_present = false;
184
# ifdef RTLD_PRELOAD_ENABLE_VAR
185
bool dso_enabled = false;
186
# else
187
const bool dso_enabled = true;
188
# endif
189
# ifdef _PATH_ASAN_LIB
190
char *dso_buf = NULL;
191
# endif
192
size_t env_size;
193
debug_decl(sudo_preload_dso_alloc, SUDO_DEBUG_UTIL);
194
195
# ifdef _PATH_ASAN_LIB
196
/*
197
* The address sanitizer DSO needs to be first in the list.
198
*/
199
dso_buf = fmtstr(alloc_fn, free_fn, "%s%c%s", _PATH_ASAN_LIB,
200
RTLD_PRELOAD_DELIM, dso_file);
201
if (dso_buf == NULL) {
202
goto oom;
203
}
204
dso_file = dso_buf;
205
# endif
206
207
/*
208
* Preload a DSO file. For a list of LD_PRELOAD-alikes, see
209
* http://www.fortran-2000.com/ArnaudRecipes/sharedlib.html
210
* XXX - need to support 32-bit and 64-bit variants
211
*/
212
213
/* Treat a NULL envp as empty, thanks Linux. */
214
if (envp == NULL)
215
envp = empty;
216
217
/* Determine max size for new envp. */
218
for (env_size = 0; envp[env_size] != NULL; env_size++)
219
continue;
220
if (!dso_enabled)
221
env_size++;
222
if (intercept_fd != -1)
223
env_size++;
224
env_size += 2; /* dso_file + terminating NULL */
225
226
/* Allocate new envp. */
227
nenvp = alloc_fn(env_size, sizeof(*nenvp));
228
if (nenvp == NULL)
229
goto oom;
230
231
/*
232
* Shallow copy envp, with special handling for preload_var,
233
* RTLD_PRELOAD_ENABLE_VAR and SUDO_INTERCEPT_FD.
234
*/
235
for (ep = envp, nep = nenvp; *ep != NULL; ep++) {
236
if (strncmp(*ep, preload_var, preload_var_len) == 0 &&
237
(*ep)[preload_var_len] == '=') {
238
const char *cp = *ep + preload_var_len + 1;
239
const size_t dso_len = strlen(dso_file);
240
241
/* Skip duplicates. */
242
if (preload_ptr != NULL)
243
continue;
244
245
/*
246
* Check to see if dso_file is already first in the list.
247
* We don't bother checking for it later in the list.
248
*/
249
if (strncmp(cp, dso_file, dso_len) == 0) {
250
if (cp[dso_len] == '\0' || cp[dso_len] == RTLD_PRELOAD_DELIM)
251
dso_present = true;
252
}
253
254
/* Save pointer to LD_PRELOAD variable. */
255
preload_ptr = nep;
256
257
goto copy;
258
}
259
if (intercept_fd != -1 && strncmp(*ep, "SUDO_INTERCEPT_FD=",
260
sizeof("SUDO_INTERCEPT_FD=") - 1) == 0) {
261
const char *cp = *ep + sizeof("SUDO_INTERCEPT_FD=") - 1;
262
const char *errstr;
263
int fd;
264
265
/* Skip duplicates. */
266
if (intercept_ptr != NULL)
267
continue;
268
269
fd = (int)sudo_strtonum(cp, 0, INT_MAX, &errstr);
270
if (fd == intercept_fd && errstr == NULL)
271
fd_present = true;
272
273
/* Save pointer to SUDO_INTERCEPT_FD variable. */
274
intercept_ptr = nep;
275
276
goto copy;
277
}
278
# ifdef RTLD_PRELOAD_ENABLE_VAR
279
if (strncmp(*ep, RTLD_PRELOAD_ENABLE_VAR "=",
280
sizeof(RTLD_PRELOAD_ENABLE_VAR)) == 0) {
281
dso_enabled = true;
282
}
283
# endif
284
copy:
285
*nep++ = *ep; /* shallow copy */
286
}
287
288
/* Prepend our LD_PRELOAD to existing value or add new entry at the end. */
289
if (!dso_present) {
290
if (preload_ptr == NULL) {
291
# ifdef RTLD_PRELOAD_DEFAULT
292
preload = fmtstr(alloc_fn, free_fn, "%s=%s%c%s", preload_var,
293
dso_file, RTLD_PRELOAD_DELIM, RTLD_PRELOAD_DEFAULT);
294
if (preload == NULL) {
295
goto oom;
296
}
297
# else
298
preload = fmtstr(alloc_fn, free_fn, "%s=%s", preload_var,
299
dso_file);
300
if (preload == NULL) {
301
goto oom;
302
}
303
# endif
304
*nep++ = preload;
305
} else {
306
const char *old_val = *preload_ptr + preload_var_len + 1;
307
preload = fmtstr(alloc_fn, free_fn, "%s=%s%c%s", preload_var,
308
dso_file, RTLD_PRELOAD_DELIM, old_val);
309
if (preload == NULL) {
310
goto oom;
311
}
312
*preload_ptr = preload;
313
}
314
}
315
# ifdef RTLD_PRELOAD_ENABLE_VAR
316
if (!dso_enabled) {
317
*nenvp++ = RTLD_PRELOAD_ENABLE_VAR "=";
318
}
319
# endif
320
if (!fd_present && intercept_fd != -1) {
321
char *fdstr = fmtstr(alloc_fn, free_fn, "SUDO_INTERCEPT_FD=%d",
322
intercept_fd);
323
if (fdstr == NULL) {
324
goto oom;
325
}
326
if (intercept_ptr != NULL) {
327
*intercept_ptr = fdstr;
328
} else {
329
*nep++ = fdstr;
330
}
331
}
332
333
/* NULL terminate nenvp at last. */
334
*nep = NULL;
335
336
# ifdef _PATH_ASAN_LIB
337
free_fn(dso_buf);
338
# endif
339
340
debug_return_ptr(nenvp);
341
oom:
342
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
343
# ifdef _PATH_ASAN_LIB
344
free_fn(dso_buf);
345
# endif
346
free_fn(preload);
347
free_fn(nenvp);
348
debug_return_ptr(NULL);
349
}
350
351
static char **
352
sudo_preload_dso_path(char *const envp[], const char *dso_file,
353
int intercept_fd, sudo_alloc_fn_t alloc_fn, sudo_free_fn_t free_fn)
354
{
355
char **ret = NULL;
356
const char *ep;
357
debug_decl(sudo_preload_dso_path, SUDO_DEBUG_UTIL);
358
359
ep = strchr(dso_file, ':');
360
if (ep == NULL) {
361
/* Use default LD_PRELOAD */
362
return sudo_preload_dso_alloc(envp, RTLD_PRELOAD_VAR, dso_file,
363
intercept_fd, alloc_fn, free_fn);
364
}
365
366
/* Add 32-bit LD_PRELOAD if present. */
367
if (ep != dso_file) {
368
#ifdef RTLD_PRELOAD_VAR_32
369
const size_t len = (size_t)(ep - dso_file);
370
char name[PATH_MAX];
371
372
if (len >= sizeof(name)) {
373
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
374
"%.*s: path too long", (int)len, dso_file);
375
} else {
376
memcpy(name, dso_file, len);
377
name[len] = '\0';
378
ret = sudo_preload_dso_alloc(envp, RTLD_PRELOAD_VAR_32, name,
379
intercept_fd, alloc_fn, free_fn);
380
envp = ret;
381
}
382
#endif /* RTLD_PRELOAD_VAR_32 */
383
dso_file = ep + 1;
384
}
385
386
#ifdef RTLD_PRELOAD_VAR_64
387
/* Add 64-bit LD_PRELOAD if present. */
388
if (*dso_file != '\0') {
389
char **new_envp = sudo_preload_dso_alloc(envp, RTLD_PRELOAD_VAR_64,
390
dso_file, intercept_fd, alloc_fn, free_fn);
391
free_fn(ret);
392
ret = new_envp;
393
}
394
#endif /* RTLD_PRELOAD_VAR_64 */
395
396
debug_return_ptr(ret);
397
}
398
399
char **
400
sudo_preload_dso_mmap(char *const envp[], const char *dso_file,
401
int intercept_fd)
402
{
403
return sudo_preload_dso_path(envp, dso_file, intercept_fd,
404
sudo_mmap_allocarray_v1, sudo_mmap_free_v1);
405
}
406
407
char **
408
sudo_preload_dso(char *const envp[], const char *dso_file,
409
int intercept_fd)
410
{
411
return sudo_preload_dso_path(envp, dso_file, intercept_fd,
412
sudo_allocarray, free);
413
}
414
#endif /* RTLD_PRELOAD_VAR */
415
416