Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/src/selinux.c
1532 views
1
/*
2
* SPDX-License-Identifier: ISC
3
*
4
* Copyright (c) 2009-2022 Todd C. Miller <[email protected]>
5
* Copyright (c) 2008 Dan Walsh <[email protected]>
6
*
7
* Borrowed heavily from newrole source code
8
* Authors:
9
* Anthony Colatrella
10
* Tim Fraser
11
* Steve Grubb <[email protected]>
12
* Darrel Goeddel <[email protected]>
13
* Michael Thompson <[email protected]>
14
* Dan Walsh <[email protected]>
15
*
16
* Permission to use, copy, modify, and distribute this software for any
17
* purpose with or without fee is hereby granted, provided that the above
18
* copyright notice and this permission notice appear in all copies.
19
*
20
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
21
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
22
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
23
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
24
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
25
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
26
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
27
*/
28
29
#include <config.h>
30
31
#ifdef HAVE_SELINUX
32
33
#include <sys/stat.h>
34
#include <sys/wait.h>
35
#include <stdio.h>
36
#include <stdlib.h>
37
#include <string.h>
38
#include <unistd.h>
39
#include <errno.h>
40
#include <fcntl.h>
41
#include <signal.h>
42
43
#include <selinux/selinux.h> /* for is_selinux_enabled() */
44
#include <selinux/context.h> /* for context-mangling functions */
45
#include <selinux/get_default_type.h>
46
#include <selinux/get_context_list.h>
47
48
#ifdef HAVE_LINUX_AUDIT
49
# include <libaudit.h>
50
#endif
51
52
#include <sudo.h>
53
#include <sudo_exec.h>
54
55
static struct selinux_state {
56
char * old_context;
57
char * new_context;
58
char * tty_con_raw;
59
char * new_tty_con_raw;
60
const char *ttyn;
61
int ttyfd;
62
int enforcing;
63
} se_state;
64
65
int
66
selinux_audit_role_change(void)
67
{
68
#ifdef HAVE_LINUX_AUDIT
69
int au_fd, rc = -1;
70
char *message;
71
debug_decl(selinux_audit_role_change, SUDO_DEBUG_SELINUX);
72
73
au_fd = audit_open();
74
if (au_fd == -1) {
75
/* Kernel may not have audit support. */
76
if (errno != EINVAL && errno != EPROTONOSUPPORT && errno != EAFNOSUPPORT
77
)
78
sudo_fatal("%s", U_("unable to open audit system"));
79
} else {
80
/* audit role change using the same format as newrole(1) */
81
rc = asprintf(&message, "newrole: old-context=%s new-context=%s",
82
se_state.old_context, se_state.new_context ? se_state.new_context : "?");
83
if (rc == -1)
84
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
85
rc = audit_log_user_message(au_fd, AUDIT_USER_ROLE_CHANGE,
86
message, NULL, NULL, se_state.ttyn, se_state.new_context ? 1 : 0);
87
if (rc <= 0)
88
sudo_warn("%s", U_("unable to send audit message"));
89
free(message);
90
close(au_fd);
91
}
92
93
debug_return_int(rc);
94
#else
95
return 0;
96
#endif /* HAVE_LINUX_AUDIT */
97
}
98
99
/*
100
* This function attempts to revert the relabeling done to the tty.
101
* fd - referencing the opened ttyn
102
* ttyn - name of tty to restore
103
*
104
* Returns 0 on success and -1 on failure.
105
*/
106
int
107
selinux_restore_tty(void)
108
{
109
int ret = -1;
110
char * chk_tty_con_raw = NULL;
111
debug_decl(selinux_restore_tty, SUDO_DEBUG_SELINUX);
112
113
if (se_state.ttyfd == -1 || se_state.new_tty_con_raw == NULL) {
114
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: no tty, skip relabel",
115
__func__);
116
debug_return_int(0);
117
}
118
119
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: %s -> %s",
120
__func__, se_state.new_tty_con_raw, se_state.tty_con_raw);
121
122
/* Verify that the tty still has the context set by sudo. */
123
if (fgetfilecon_raw(se_state.ttyfd, &chk_tty_con_raw) == -1) {
124
sudo_warn(U_("unable to fgetfilecon %s"), se_state.ttyn);
125
goto skip_relabel;
126
}
127
128
if (strcmp(chk_tty_con_raw, se_state.new_tty_con_raw) != 0) {
129
sudo_warnx(U_("%s changed labels"), se_state.ttyn);
130
sudo_debug_printf(SUDO_DEBUG_INFO,
131
"%s: not restoring tty label, expected %s, have %s",
132
__func__, se_state.new_tty_con_raw, chk_tty_con_raw);
133
goto skip_relabel;
134
}
135
136
if (fsetfilecon_raw(se_state.ttyfd, se_state.tty_con_raw) == -1) {
137
sudo_warn(U_("unable to restore context for %s"), se_state.ttyn);
138
goto skip_relabel;
139
}
140
141
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: successfully set tty label to %s",
142
__func__, se_state.tty_con_raw);
143
ret = 0;
144
145
skip_relabel:
146
if (se_state.ttyfd != -1) {
147
close(se_state.ttyfd);
148
se_state.ttyfd = -1;
149
}
150
freecon(chk_tty_con_raw);
151
debug_return_int(ret);
152
}
153
154
/*
155
* This function attempts to relabel the tty. If this function fails, then
156
* the contexts are free'd and -1 is returned. On success, 0 is returned
157
* and tty_con_raw and new_tty_con_raw are set.
158
*
159
* This function will not fail if it can not relabel the tty when selinux is
160
* in permissive mode.
161
*/
162
int
163
selinux_relabel_tty(const char *ttyn, int ptyfd)
164
{
165
char * tty_con = NULL;
166
char * new_tty_con = NULL;
167
struct stat sb;
168
int fd;
169
debug_decl(relabel_tty, SUDO_DEBUG_SELINUX);
170
171
se_state.ttyfd = ptyfd;
172
173
/* It is perfectly legal to have no tty. */
174
if (ptyfd == -1 && ttyn == NULL) {
175
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: no tty, skip relabel",
176
__func__);
177
debug_return_int(0);
178
}
179
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: relabeling tty %s", __func__, ttyn);
180
181
/* If sudo is not allocating a pty for the command, open current tty. */
182
if (ptyfd == -1) {
183
se_state.ttyfd = open(ttyn, O_RDWR|O_NOCTTY|O_NONBLOCK);
184
if (se_state.ttyfd == -1 || fstat(se_state.ttyfd, &sb) == -1) {
185
sudo_warn(U_("unable to open %s, not relabeling tty"), ttyn);
186
goto bad;
187
}
188
if (!S_ISCHR(sb.st_mode)) {
189
sudo_warn(U_("%s is not a character device, not relabeling tty"),
190
ttyn);
191
goto bad;
192
}
193
(void)fcntl(se_state.ttyfd, F_SETFL,
194
fcntl(se_state.ttyfd, F_GETFL, 0) & ~O_NONBLOCK);
195
}
196
197
if (fgetfilecon(se_state.ttyfd, &tty_con) == -1) {
198
sudo_warn("%s", U_("unable to get current tty context, not relabeling tty"));
199
goto bad;
200
}
201
202
if (tty_con != NULL) {
203
security_class_t tclass = string_to_security_class("chr_file");
204
if (tclass == 0) {
205
sudo_warn("%s", U_("unknown security class \"chr_file\", not relabeling tty"));
206
goto bad;
207
}
208
if (security_compute_relabel(se_state.new_context, tty_con,
209
tclass, &new_tty_con) == -1) {
210
sudo_warn("%s", U_("unable to get new tty context, not relabeling tty"));
211
goto bad;
212
}
213
}
214
215
if (new_tty_con != NULL) {
216
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: tty context %s -> %s",
217
__func__, tty_con, new_tty_con);
218
if (fsetfilecon(se_state.ttyfd, new_tty_con) == -1) {
219
sudo_warn("%s", U_("unable to set new tty context"));
220
goto bad;
221
}
222
}
223
224
if (ptyfd != -1) {
225
int oflags, flags = 0;
226
227
/* Reopen pty that was relabeled, std{in,out,err} are reset later. */
228
se_state.ttyfd = open(ttyn, O_RDWR|O_NOCTTY, 0);
229
if (se_state.ttyfd == -1 || fstat(se_state.ttyfd, &sb) == -1) {
230
sudo_warn(U_("unable to open %s"), ttyn);
231
goto bad;
232
}
233
if (!S_ISCHR(sb.st_mode)) {
234
sudo_warn(U_("%s is not a character device, not relabeling tty"),
235
ttyn);
236
goto bad;
237
}
238
/* Preserve O_NONBLOCK and the close-on-exec flags. */
239
if ((oflags = fcntl(ptyfd, F_GETFL)) == -1) {
240
sudo_warn("F_GETFL");
241
goto bad;
242
}
243
if (ISSET(oflags, O_NONBLOCK))
244
flags |= O_NONBLOCK;
245
if ((oflags = fcntl(ptyfd, F_GETFD)) == -1) {
246
sudo_warn("F_GETFD");
247
goto bad;
248
}
249
if (ISSET(oflags, FD_CLOEXEC))
250
flags |= O_CLOEXEC;
251
if (dup3(se_state.ttyfd, ptyfd, flags) == -1) {
252
sudo_warn("dup3");
253
goto bad;
254
}
255
} else {
256
/* Re-open tty to get new label and reset std{in,out,err} */
257
close(se_state.ttyfd);
258
se_state.ttyfd = open(ttyn, O_RDWR|O_NOCTTY|O_NONBLOCK);
259
if (se_state.ttyfd == -1 || fstat(se_state.ttyfd, &sb) == -1) {
260
sudo_warn(U_("unable to open %s"), ttyn);
261
goto bad;
262
}
263
if (!S_ISCHR(sb.st_mode)) {
264
sudo_warn(U_("%s is not a character device, not relabeling tty"),
265
ttyn);
266
goto bad;
267
}
268
(void)fcntl(se_state.ttyfd, F_SETFL,
269
fcntl(se_state.ttyfd, F_GETFL, 0) & ~O_NONBLOCK);
270
for (fd = STDIN_FILENO; fd <= STDERR_FILENO; fd++) {
271
if (sudo_isatty(fd, &sb) && dup2(se_state.ttyfd, fd) == -1) {
272
sudo_warn("dup2");
273
goto bad;
274
}
275
}
276
}
277
/* Retain se_state.ttyfd so we can restore label when command finishes. */
278
(void)fcntl(se_state.ttyfd, F_SETFD, FD_CLOEXEC);
279
280
se_state.ttyn = ttyn;
281
if (selinux_trans_to_raw_context(tty_con, &se_state.tty_con_raw) == -1)
282
goto bad;
283
if (selinux_trans_to_raw_context(new_tty_con, &se_state.new_tty_con_raw) == -1)
284
goto bad;
285
freecon(tty_con);
286
freecon(new_tty_con);
287
debug_return_int(0);
288
289
bad:
290
if (se_state.ttyfd != -1 && se_state.ttyfd != ptyfd) {
291
close(se_state.ttyfd);
292
se_state.ttyfd = -1;
293
}
294
freecon(se_state.tty_con_raw);
295
se_state.tty_con_raw = NULL;
296
freecon(se_state.new_tty_con_raw);
297
se_state.new_tty_con_raw = NULL;
298
freecon(tty_con);
299
freecon(new_tty_con);
300
debug_return_int(se_state.enforcing ? -1 : 0);
301
}
302
303
/*
304
* Determine the new security context based on the old context and the
305
* specified role and type.
306
* Returns 0 on success, and -1 on failure.
307
*/
308
static int
309
get_exec_context(const char *role, const char *type)
310
{
311
char *new_context = NULL;
312
context_t context = NULL;
313
char *typebuf = NULL;
314
int ret = -1;
315
debug_decl(get_exec_context, SUDO_DEBUG_SELINUX);
316
317
if (role == NULL) {
318
sudo_warnx(U_("you must specify a role for type %s"), type);
319
errno = EINVAL;
320
goto done;
321
}
322
if (type == NULL) {
323
if (get_default_type(role, &typebuf)) {
324
sudo_warnx(U_("unable to get default type for role %s"), role);
325
errno = EINVAL;
326
goto done;
327
}
328
type = typebuf;
329
}
330
331
/*
332
* Expand old_context into a context_t so that we can extract and modify
333
* its components easily.
334
*/
335
if ((context = context_new(se_state.old_context)) == NULL) {
336
sudo_warn("%s", U_("failed to get new context"));
337
goto done;
338
}
339
340
/*
341
* Replace the role and type in "context" with the role and
342
* type we will be running the command as.
343
*/
344
if (context_role_set(context, role)) {
345
sudo_warn(U_("failed to set new role %s"), role);
346
goto done;
347
}
348
if (context_type_set(context, type)) {
349
sudo_warn(U_("failed to set new type %s"), type);
350
goto done;
351
}
352
353
/*
354
* Convert "context" back into a string and verify it.
355
*/
356
if ((new_context = strdup(context_str(context))) == NULL) {
357
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
358
goto done;
359
}
360
if (security_check_context(new_context) == -1) {
361
sudo_warnx(U_("%s is not a valid context"), new_context);
362
errno = EINVAL;
363
goto done;
364
}
365
366
se_state.new_context = new_context;
367
new_context = NULL;
368
ret = 0;
369
370
done:
371
free(typebuf);
372
context_free(context);
373
freecon(new_context);
374
debug_return_int(ret);
375
}
376
377
/*
378
* Determine the exec and tty contexts the command will run in.
379
* Returns 0 on success and -1 on failure.
380
*/
381
int
382
selinux_getexeccon(const char *role, const char *type)
383
{
384
int ret = -1;
385
debug_decl(selinux_getexeccon, SUDO_DEBUG_SELINUX);
386
387
/* Store the caller's SID in old_context. */
388
if (getprevcon(&se_state.old_context)) {
389
sudo_warn("%s", U_("failed to get old context"));
390
goto done;
391
}
392
393
se_state.enforcing = security_getenforce();
394
if (se_state.enforcing == -1) {
395
sudo_warn("%s", U_("unable to determine enforcing mode."));
396
goto done;
397
}
398
399
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: old context %s", __func__,
400
se_state.old_context);
401
ret = get_exec_context(role, type);
402
if (ret == -1) {
403
/* Audit role change failure (success is logged later). */
404
selinux_audit_role_change();
405
goto done;
406
}
407
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: new context %s", __func__,
408
se_state.new_context);
409
410
done:
411
debug_return_int(ret);
412
}
413
414
int
415
selinux_setexeccon(void)
416
{
417
debug_decl(selinux_setexeccon, SUDO_DEBUG_SELINUX);
418
419
if (setexeccon(se_state.new_context)) {
420
sudo_warn(U_("unable to set exec context to %s"), se_state.new_context);
421
if (se_state.enforcing)
422
debug_return_int(-1);
423
}
424
425
#ifdef HAVE_SETKEYCREATECON
426
if (setkeycreatecon(se_state.new_context)) {
427
sudo_warn(U_("unable to set key creation context to %s"), se_state.new_context);
428
if (se_state.enforcing)
429
debug_return_int(-1);
430
}
431
#endif /* HAVE_SETKEYCREATECON */
432
433
debug_return_int(0);
434
}
435
436
void
437
selinux_execve(int fd, const char *path, char *const argv[], char *envp[],
438
const char *rundir, unsigned int flags)
439
{
440
char **nargv;
441
const char *sesh;
442
int argc, len, nargc, serrno;
443
debug_decl(selinux_execve, SUDO_DEBUG_SELINUX);
444
445
sesh = sudo_conf_sesh_path();
446
if (sesh == NULL) {
447
sudo_warnx("internal error: sesh path not set");
448
errno = EINVAL;
449
debug_return;
450
}
451
452
/* Set SELinux exec and keycreate contexts. */
453
if (selinux_setexeccon() == -1)
454
debug_return;
455
456
/*
457
* Build new argv with sesh as argv[0].
458
*/
459
for (argc = 0; argv[argc] != NULL; argc++)
460
continue;
461
if (argc == 0) {
462
errno = EINVAL;
463
debug_return;
464
}
465
nargv = reallocarray(NULL, 5 + argc + 1, sizeof(char *));
466
if (nargv == NULL) {
467
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
468
debug_return;
469
}
470
if (*argv[0] == '-')
471
nargv[0] = (char *)"-sesh";
472
else
473
nargv[0] = (char *)"sesh";
474
nargc = 1;
475
if (ISSET(flags, CD_RBAC_SET_CWD)) {
476
const char *prefix = ISSET(flags, CD_CWD_OPTIONAL) ? "+" : "";
477
if (rundir == NULL) {
478
sudo_warnx("internal error: sesh rundir not set");
479
errno = EINVAL;
480
debug_return;
481
}
482
len = asprintf(&nargv[nargc++], "--directory=%s%s", prefix, rundir);
483
if (len == -1) {
484
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
485
debug_return;
486
}
487
}
488
if (fd != -1) {
489
len = asprintf(&nargv[nargc++], "--execfd=%d", fd);
490
if (len == -1) {
491
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
492
debug_return;
493
}
494
}
495
if (ISSET(flags, CD_NOEXEC)) {
496
CLR(flags, CD_NOEXEC);
497
nargv[nargc++] = (char *)"--noexec";
498
}
499
nargv[nargc++] = (char *)"--";
500
nargv[nargc++] = (char *)path;
501
memcpy(&nargv[nargc], &argv[1], argc * sizeof(char *)); /* copies NULL */
502
503
sudo_execve(-1, sesh, nargv, envp, -1, flags);
504
serrno = errno;
505
free(nargv);
506
errno = serrno;
507
debug_return;
508
}
509
510
#endif /* HAVE_SELINUX */
511
512