Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/src/sudo_edit.c
1532 views
1
/*
2
* SPDX-License-Identifier: ISC
3
*
4
* Copyright (c) 2004-2008, 2010-2023 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 <sys/types.h>
22
#include <sys/stat.h>
23
#include <sys/wait.h>
24
#include <stdio.h>
25
#include <stdlib.h>
26
#include <string.h>
27
#include <time.h>
28
#include <unistd.h>
29
#include <errno.h>
30
#include <grp.h>
31
#include <pwd.h>
32
#include <signal.h>
33
#include <fcntl.h>
34
35
#include <sudo.h>
36
#include <sudo_edit.h>
37
#include <sudo_exec.h>
38
39
#if defined(HAVE_SETRESUID) || defined(HAVE_SETREUID) || defined(HAVE_SETEUID)
40
41
/*
42
* Editor temporary file name along with original name, mtime and size.
43
*/
44
struct tempfile {
45
char *tfile;
46
char *ofile;
47
off_t osize;
48
struct timespec omtim;
49
};
50
51
static char edit_tmpdir[MAX(sizeof(_PATH_VARTMP), sizeof(_PATH_TMP))];
52
53
/*
54
* Find our temporary directory, one of /var/tmp, /usr/tmp, or /tmp
55
* Returns true on success, else false;
56
*/
57
static bool
58
set_tmpdir(const struct sudo_cred *user_cred)
59
{
60
const char *tdir = NULL;
61
const char *tmpdirs[] = {
62
_PATH_VARTMP,
63
#ifdef _PATH_USRTMP
64
_PATH_USRTMP,
65
#endif
66
_PATH_TMP
67
};
68
struct sudo_cred saved_cred;
69
size_t i;
70
size_t len;
71
int dfd;
72
debug_decl(set_tmpdir, SUDO_DEBUG_EDIT);
73
74
/* Stash old credentials. */
75
saved_cred.uid = getuid();
76
saved_cred.euid = geteuid();
77
saved_cred.gid = getgid();
78
saved_cred.egid = getegid();
79
saved_cred.ngroups = getgroups(0, NULL); // -V575
80
if (saved_cred.ngroups > 0) {
81
saved_cred.groups =
82
reallocarray(NULL, (size_t)saved_cred.ngroups, sizeof(GETGROUPS_T));
83
if (saved_cred.groups == NULL) {
84
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
85
debug_return_bool(false);
86
}
87
saved_cred.ngroups = getgroups(saved_cred.ngroups, saved_cred.groups);
88
if (saved_cred.ngroups < 0) {
89
sudo_warn("%s", U_("unable to get group list"));
90
free(saved_cred.groups);
91
debug_return_bool(false);
92
}
93
} else {
94
saved_cred.ngroups = 0;
95
saved_cred.groups = NULL;
96
}
97
98
for (i = 0; tdir == NULL && i < nitems(tmpdirs); i++) {
99
if ((dfd = open(tmpdirs[i], O_RDONLY|O_DIRECTORY)) != -1) {
100
if (dir_is_writable(dfd, user_cred, &saved_cred) == true)
101
tdir = tmpdirs[i];
102
close(dfd);
103
}
104
}
105
free(saved_cred.groups);
106
107
if (tdir == NULL) {
108
sudo_warnx("%s", U_("no writable temporary directory found"));
109
debug_return_bool(false);
110
}
111
112
len = strlcpy(edit_tmpdir, tdir, sizeof(edit_tmpdir));
113
if (len >= sizeof(edit_tmpdir)) {
114
errno = ENAMETOOLONG;
115
sudo_warn("%s", tdir);
116
debug_return_bool(false);
117
}
118
while (len > 0 && edit_tmpdir[--len] == '/')
119
edit_tmpdir[len] = '\0';
120
debug_return_bool(true);
121
}
122
123
/*
124
* Construct a temporary file name for file and return an
125
* open file descriptor. The temporary file name is stored
126
* in tfile which the caller is responsible for freeing.
127
*/
128
static int
129
sudo_edit_mktemp(const char *ofile, char **tfile)
130
{
131
const char *base, *suff;
132
int len, tfd;
133
debug_decl(sudo_edit_mktemp, SUDO_DEBUG_EDIT);
134
135
base = sudo_basename(ofile);
136
suff = strrchr(base, '.');
137
if (suff != NULL) {
138
len = asprintf(tfile, "%s/%.*sXXXXXXXX%s", edit_tmpdir,
139
(int)(suff - base), base, suff);
140
} else {
141
len = asprintf(tfile, "%s/%s.XXXXXXXX", edit_tmpdir, base);
142
}
143
if (len == -1) {
144
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
145
debug_return_int(-1);
146
}
147
tfd = mkstemps(*tfile, suff ? (int)strlen(suff) : 0);
148
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
149
"%s -> %s, fd %d", ofile, *tfile, tfd);
150
debug_return_int(tfd);
151
}
152
153
/*
154
* Create temporary copies of files[] and store the temporary path name
155
* along with the original name, size and mtime in tf.
156
* Returns the number of files copied (which may be less than nfiles)
157
* or -1 if a fatal error occurred.
158
*/
159
static int
160
sudo_edit_create_tfiles(const struct command_details *command_details,
161
const struct sudo_cred *user_cred, struct tempfile *tf, char *files[],
162
int nfiles)
163
{
164
int i, j, tfd, ofd, rc;
165
struct timespec times[2];
166
struct stat sb;
167
debug_decl(sudo_edit_create_tfiles, SUDO_DEBUG_EDIT);
168
169
/*
170
* For each file specified by the user, make a temporary version
171
* and copy the contents of the original to it.
172
*/
173
for (i = 0, j = 0; i < nfiles; i++) {
174
rc = -1;
175
switch_user(command_details->cred.euid, command_details->cred.egid,
176
command_details->cred.ngroups, command_details->cred.groups);
177
ofd = sudo_edit_open(files[i], O_RDONLY,
178
S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, command_details->flags,
179
user_cred, &command_details->cred);
180
if (ofd != -1 || errno == ENOENT) {
181
if (ofd != -1) {
182
rc = fstat(ofd, &sb);
183
} else {
184
/* New file, verify parent dir exists and is not writable. */
185
memset(&sb, 0, sizeof(sb));
186
if (sudo_edit_parent_valid(files[i], command_details->flags, user_cred, &command_details->cred))
187
rc = 0;
188
}
189
}
190
switch_user(ROOT_UID, user_cred->egid, user_cred->ngroups, user_cred->groups);
191
if (ofd != -1 && !S_ISREG(sb.st_mode)) {
192
sudo_warnx(U_("%s: not a regular file"), files[i]);
193
close(ofd);
194
continue;
195
}
196
if (rc == -1) {
197
/* open() or fstat() error. */
198
if (ofd == -1 && errno == ELOOP) {
199
sudo_warnx(U_("%s: editing symbolic links is not permitted"),
200
files[i]);
201
} else if (ofd == -1 && errno == EISDIR) {
202
sudo_warnx(U_("%s: editing files in a writable directory is not permitted"),
203
files[i]);
204
} else {
205
sudo_warn("%s", files[i]);
206
}
207
if (ofd != -1)
208
close(ofd);
209
continue;
210
}
211
tf[j].ofile = files[i];
212
tf[j].osize = sb.st_size; // -V614
213
mtim_get(&sb, tf[j].omtim);
214
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
215
"seteuid(%u)", (unsigned int)user_cred->uid);
216
if (seteuid(user_cred->uid) != 0)
217
sudo_fatal("seteuid(%u)", (unsigned int)user_cred->uid);
218
tfd = sudo_edit_mktemp(tf[j].ofile, &tf[j].tfile);
219
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
220
"seteuid(%u)", ROOT_UID);
221
if (seteuid(ROOT_UID) != 0)
222
sudo_fatal("seteuid(ROOT_UID)");
223
if (tfd == -1) {
224
sudo_warn("mkstemps");
225
if (ofd != -1)
226
close(ofd);
227
debug_return_int(-1);
228
}
229
if (ofd != -1) {
230
if (sudo_copy_file(tf[j].ofile, ofd, tf[j].osize, tf[j].tfile, tfd, -1) == -1) {
231
close(ofd);
232
close(tfd);
233
debug_return_int(-1);
234
}
235
close(ofd);
236
}
237
/*
238
* We always update the stashed mtime because the time
239
* resolution of the filesystem the temporary file is on may
240
* not match that of the filesystem where the file to be edited
241
* resides. It is OK if futimens() fails since we only use the
242
* info to determine whether or not a file has been modified.
243
*/
244
times[0].tv_sec = times[1].tv_sec = tf[j].omtim.tv_sec;
245
times[0].tv_nsec = times[1].tv_nsec = tf[j].omtim.tv_nsec;
246
if (futimens(tfd, times) == -1) {
247
if (utimensat(AT_FDCWD, tf[j].tfile, times, 0) == -1)
248
sudo_warn("%s", tf[j].tfile);
249
}
250
rc = fstat(tfd, &sb);
251
if (!rc)
252
mtim_get(&sb, tf[j].omtim);
253
close(tfd);
254
j++;
255
}
256
debug_return_int(j);
257
}
258
259
/*
260
* Copy the temporary files specified in tf to the originals.
261
* Returns the number of copy errors or 0 if completely successful.
262
*/
263
static int
264
sudo_edit_copy_tfiles(const struct command_details *command_details,
265
const struct sudo_cred *user_cred, struct tempfile *tf,
266
int nfiles, struct timespec *times)
267
{
268
int i, tfd, ofd, errors = 0;
269
struct timespec ts;
270
struct stat sb;
271
mode_t oldmask;
272
debug_decl(sudo_edit_copy_tfiles, SUDO_DEBUG_EDIT);
273
274
/* Copy contents of temp files to real ones. */
275
for (i = 0; i < nfiles; i++) {
276
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
277
"seteuid(%u)", (unsigned int)user_cred->uid);
278
if (seteuid(user_cred->uid) != 0)
279
sudo_fatal("seteuid(%u)", (unsigned int)user_cred->uid);
280
tfd = sudo_edit_open(tf[i].tfile, O_RDONLY,
281
S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, 0, user_cred, NULL);
282
if (seteuid(ROOT_UID) != 0)
283
sudo_fatal("seteuid(ROOT_UID)");
284
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
285
"seteuid(%u)", ROOT_UID);
286
if (tfd == -1 || !sudo_check_temp_file(tfd, tf[i].tfile, user_cred->uid, &sb)) {
287
sudo_warnx(U_("%s left unmodified"), tf[i].ofile);
288
if (tfd != -1)
289
close(tfd);
290
errors++;
291
continue;
292
}
293
mtim_get(&sb, ts);
294
if (tf[i].osize == sb.st_size && sudo_timespeccmp(&tf[i].omtim, &ts, ==)) {
295
/*
296
* If mtime and size match but the user spent no measurable
297
* time in the editor we can't tell if the file was changed.
298
*/
299
if (sudo_timespeccmp(&times[0], &times[1], !=)) {
300
sudo_warnx(U_("%s unchanged"), tf[i].ofile);
301
unlink(tf[i].tfile);
302
close(tfd);
303
continue;
304
}
305
}
306
switch_user(command_details->cred.euid, command_details->cred.egid,
307
command_details->cred.ngroups, command_details->cred.groups);
308
oldmask = umask(command_details->umask);
309
ofd = sudo_edit_open(tf[i].ofile, O_WRONLY|O_CREAT,
310
S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, command_details->flags,
311
user_cred, &command_details->cred);
312
umask(oldmask);
313
switch_user(ROOT_UID, user_cred->egid, user_cred->ngroups, user_cred->groups);
314
if (ofd == -1) {
315
sudo_warn(U_("unable to write to %s"), tf[i].ofile);
316
goto bad;
317
}
318
319
/* Overwrite the old file with the new contents. */
320
if (sudo_copy_file(tf[i].tfile, tfd, sb.st_size, tf[i].ofile, ofd,
321
tf[i].osize) == 0) {
322
/* success, remove temporary file. */
323
unlink(tf[i].tfile);
324
} else {
325
bad:
326
sudo_warnx(U_("contents of edit session left in %s"), tf[i].tfile);
327
errors++;
328
}
329
330
if (ofd != -1)
331
close(ofd);
332
close(tfd);
333
}
334
debug_return_int(errors);
335
}
336
337
#ifdef HAVE_SELINUX
338
static int
339
selinux_run_helper(uid_t uid, gid_t gid, int ngroups, GETGROUPS_T *groups,
340
char *const argv[], char *const envp[])
341
{
342
int status, ret = SESH_ERR_FAILURE;
343
const char *sesh;
344
pid_t child, pid;
345
debug_decl(selinux_run_helper, SUDO_DEBUG_EDIT);
346
347
sesh = sudo_conf_sesh_path();
348
if (sesh == NULL) {
349
sudo_warnx("internal error: sesh path not set");
350
debug_return_int(-1);
351
}
352
353
child = sudo_debug_fork();
354
switch (child) {
355
case -1:
356
sudo_warn("%s", U_("unable to fork"));
357
break;
358
case 0:
359
/* child runs sesh in new context */
360
if (selinux_setexeccon() == 0) {
361
switch_user(uid, gid, ngroups, groups);
362
execve(sesh, argv, envp);
363
}
364
_exit(SESH_ERR_FAILURE);
365
default:
366
/* parent waits */
367
do {
368
pid = waitpid(child, &status, 0);
369
} while (pid == -1 && errno == EINTR);
370
371
ret = WIFSIGNALED(status) ? SESH_ERR_KILLED : WEXITSTATUS(status);
372
}
373
374
debug_return_int(ret);
375
}
376
377
static char *
378
selinux_fmt_sudo_user(const struct sudo_cred *user_cred)
379
{
380
char *cp, *user_str;
381
size_t user_size;
382
int i, len;
383
debug_decl(selinux_fmt_sudo_user, SUDO_DEBUG_EDIT);
384
385
user_size = (STRLEN_MAX_UNSIGNED(uid_t) + 1) * (2 + user_cred->ngroups);
386
if ((user_str = malloc(user_size)) == NULL)
387
debug_return_ptr(NULL);
388
389
/* UID:GID: */
390
len = snprintf(user_str, user_size, "%u:%u:",
391
(unsigned int)user_cred->uid, (unsigned int)user_cred->gid);
392
if (len < 0 || (size_t)len >= user_size)
393
sudo_fatalx(U_("internal error, %s overflow"), __func__);
394
395
/* Supplementary GIDs */
396
cp = user_str + len;
397
for (i = 0; i < user_cred->ngroups; i++) {
398
len = snprintf(cp, user_size - (cp - user_str), "%s%u",
399
i ? "," : "", (unsigned int)user_cred->groups[i]);
400
if (len < 0 || (size_t)len >= user_size - (cp - user_str))
401
sudo_fatalx(U_("internal error, %s overflow"), __func__);
402
cp += len;
403
}
404
405
debug_return_ptr(user_str);
406
}
407
408
static int
409
selinux_edit_create_tfiles(const struct command_details *command_details,
410
const struct sudo_cred *user_cred, struct tempfile *tf,
411
char *files[], int nfiles)
412
{
413
const char **sesh_args, **sesh_ap;
414
char *user_str = NULL;
415
int i, error, sesh_nargs, ret = -1;
416
struct stat sb;
417
debug_decl(selinux_edit_create_tfiles, SUDO_DEBUG_EDIT);
418
419
if (nfiles < 1)
420
debug_return_int(0);
421
422
sesh_nargs = 6 + (nfiles * 2) + 1;
423
sesh_args = sesh_ap = reallocarray(NULL, sesh_nargs, sizeof(char *));
424
if (sesh_args == NULL) {
425
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
426
goto done;
427
}
428
*sesh_ap++ = "sesh";
429
*sesh_ap++ = "--edit-create";
430
if (!ISSET(command_details->flags, CD_SUDOEDIT_FOLLOW))
431
*sesh_ap++ = "--no-dereference";
432
if (ISSET(command_details->flags, CD_SUDOEDIT_CHECKDIR)) {
433
if ((user_str = selinux_fmt_sudo_user(user_cred)) == NULL) {
434
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
435
goto done;
436
}
437
*sesh_ap++ = "--edit-checkdir";
438
*sesh_ap++ = user_str;
439
}
440
*sesh_ap++ = "--";
441
442
for (i = 0; i < nfiles; i++) {
443
char *tfile, *ofile = files[i];
444
int tfd;
445
*sesh_ap++ = ofile;
446
tf[i].ofile = ofile;
447
if (stat(ofile, &sb) == -1)
448
memset(&sb, 0, sizeof(sb)); /* new file */
449
tf[i].osize = sb.st_size;
450
mtim_get(&sb, tf[i].omtim);
451
/*
452
* The temp file must be created by the sesh helper,
453
* which uses O_EXCL | O_NOFOLLOW to make this safe.
454
*/
455
tfd = sudo_edit_mktemp(ofile, &tfile);
456
if (tfd == -1) {
457
sudo_warn("mkstemps");
458
free(tfile);
459
goto done;
460
}
461
/* Helper will re-create temp file with proper security context. */
462
close(tfd);
463
unlink(tfile);
464
*sesh_ap++ = tfile;
465
tf[i].tfile = tfile;
466
}
467
*sesh_ap = NULL;
468
469
/* Run sesh -c [-h] [-w userstr] <o1> <t1> ... <on> <tn> */
470
error = selinux_run_helper(command_details->cred.uid,
471
command_details->cred.gid, command_details->cred.ngroups,
472
command_details->cred.groups, (char **)sesh_args, command_details->envp);
473
switch (error) {
474
case SESH_SUCCESS:
475
break;
476
case SESH_ERR_BAD_PATHS:
477
sudo_fatalx("%s", U_("sesh: internal error: odd number of paths"));
478
case SESH_ERR_NO_FILES:
479
sudo_fatalx("%s", U_("sesh: unable to create temporary files"));
480
case SESH_ERR_KILLED:
481
sudo_fatalx("%s", U_("sesh: killed by a signal"));
482
default:
483
sudo_warnx(U_("sesh: unknown error %d"), error);
484
goto done;
485
}
486
487
for (i = 0; i < nfiles; i++) {
488
int tfd = open(tf[i].tfile, O_RDONLY|O_NONBLOCK|O_NOFOLLOW);
489
if (tfd == -1) {
490
sudo_warn(U_("unable to open %s"), tf[i].tfile);
491
goto done;
492
}
493
if (!sudo_check_temp_file(tfd, tf[i].tfile, command_details->cred.uid, NULL)) {
494
close(tfd);
495
goto done;
496
}
497
if (fchown(tfd, user_cred->uid, user_cred->gid) != 0) {
498
sudo_warn("unable to chown(%s) to %d:%d for editing",
499
tf[i].tfile, user_cred->uid, user_cred->gid);
500
close(tfd);
501
goto done;
502
}
503
close(tfd);
504
}
505
ret = nfiles;
506
507
done:
508
/* Contents of tf will be freed by caller. */
509
free(sesh_args);
510
free(user_str);
511
512
debug_return_int(ret);
513
}
514
515
static int
516
selinux_edit_copy_tfiles(const struct command_details *command_details,
517
const struct sudo_cred *user_cred, struct tempfile *tf,
518
int nfiles, struct timespec *times)
519
{
520
const char **sesh_args, **sesh_ap;
521
char *user_str = NULL;
522
bool run_helper = false;
523
int i, error, sesh_nargs, ret = 1;
524
int tfd = -1;
525
struct timespec ts;
526
struct stat sb;
527
debug_decl(selinux_edit_copy_tfiles, SUDO_DEBUG_EDIT);
528
529
if (nfiles < 1)
530
debug_return_int(0);
531
532
sesh_nargs = 5 + (nfiles * 2) + 1;
533
sesh_args = sesh_ap = reallocarray(NULL, sesh_nargs, sizeof(char *));
534
if (sesh_args == NULL) {
535
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
536
goto done;
537
}
538
*sesh_ap++ = "sesh";
539
*sesh_ap++ = "--edit-install";
540
if (ISSET(command_details->flags, CD_SUDOEDIT_CHECKDIR)) {
541
if ((user_str = selinux_fmt_sudo_user(user_cred)) == NULL) {
542
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
543
goto done;
544
}
545
*sesh_ap++ = "--edit-checkdir";
546
*sesh_ap++ = user_str;
547
}
548
*sesh_ap++ = "--";
549
550
for (i = 0; i < nfiles; i++) {
551
if (tfd != -1)
552
close(tfd);
553
if ((tfd = open(tf[i].tfile, O_RDONLY|O_NONBLOCK|O_NOFOLLOW)) == -1) {
554
sudo_warn(U_("unable to open %s"), tf[i].tfile);
555
continue;
556
}
557
if (!sudo_check_temp_file(tfd, tf[i].tfile, user_cred->uid, &sb))
558
continue;
559
mtim_get(&sb, ts);
560
if (tf[i].osize == sb.st_size && sudo_timespeccmp(&tf[i].omtim, &ts, ==)) {
561
/*
562
* If mtime and size match but the user spent no measurable
563
* time in the editor we can't tell if the file was changed.
564
*/
565
if (sudo_timespeccmp(&times[0], &times[1], !=)) {
566
sudo_warnx(U_("%s unchanged"), tf[i].ofile);
567
unlink(tf[i].tfile);
568
continue;
569
}
570
}
571
run_helper = true;
572
*sesh_ap++ = tf[i].tfile;
573
*sesh_ap++ = tf[i].ofile;
574
if (fchown(tfd, command_details->cred.uid, command_details->cred.gid) != 0) {
575
sudo_warn("unable to chown(%s) back to %d:%d", tf[i].tfile,
576
command_details->cred.uid, command_details->cred.gid);
577
}
578
}
579
*sesh_ap = NULL;
580
581
if (!run_helper)
582
goto done;
583
584
/* Run sesh -i <t1> <o1> ... <tn> <on> */
585
error = selinux_run_helper(command_details->cred.uid,
586
command_details->cred.gid, command_details->cred.ngroups,
587
command_details->cred.groups, (char **)sesh_args, command_details->envp);
588
switch (error) {
589
case SESH_SUCCESS:
590
ret = 0;
591
break;
592
case SESH_ERR_NO_FILES:
593
sudo_warnx("%s",
594
U_("unable to copy temporary files back to their original location"));
595
break;
596
case SESH_ERR_SOME_FILES:
597
sudo_warnx("%s",
598
U_("unable to copy some of the temporary files back to their original location"));
599
break;
600
case SESH_ERR_KILLED:
601
sudo_warnx("%s", U_("sesh: killed by a signal"));
602
break;
603
default:
604
sudo_warnx(U_("sesh: unknown error %d"), error);
605
break;
606
}
607
608
done:
609
if (tfd != -1)
610
close(tfd);
611
/* Contents of tf will be freed by caller. */
612
free(sesh_args);
613
free(user_str);
614
615
debug_return_int(ret);
616
}
617
#endif /* HAVE_SELINUX */
618
619
/*
620
* Wrapper to allow users to edit privileged files with their own uid.
621
* Returns the wait status of the command on success and a wait status
622
* of 1 on failure.
623
*/
624
int
625
sudo_edit(struct command_details *command_details,
626
const struct user_details *user_details)
627
{
628
struct command_details saved_command_details;
629
const struct sudo_cred *user_cred = &user_details->cred;
630
char **nargv = NULL, **files = NULL;
631
int nfiles = command_details->nfiles;
632
int errors, i, ac, nargc, ret;
633
int editor_argc = 0;
634
struct timespec times[2];
635
struct tempfile *tf = NULL;
636
debug_decl(sudo_edit, SUDO_DEBUG_EDIT);
637
638
/*
639
* Set real, effective and saved uids to root.
640
* We will change the euid as needed below.
641
*/
642
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
643
"setuid(%u)", ROOT_UID);
644
if (setuid(ROOT_UID) != 0) {
645
sudo_warn(U_("unable to change uid to root (%u)"), ROOT_UID);
646
goto cleanup;
647
}
648
649
/* Find a temporary directory writable by the user. */
650
if (!set_tmpdir(user_cred))
651
goto cleanup;
652
653
if (nfiles > 0) {
654
/*
655
* The plugin specified the number of files to edit, use it.
656
*/
657
editor_argc = command_details->argc - nfiles;
658
if (editor_argc < 2 || strcmp(command_details->argv[editor_argc - 1], "--") != 0) {
659
sudo_warnx("%s", U_("plugin error: invalid file list for sudoedit"));
660
goto cleanup;
661
}
662
663
/* We don't include the "--" when running the user's editor. */
664
files = &command_details->argv[editor_argc--];
665
} else {
666
/*
667
* Compute the number of files to edit by looking for the "--"
668
* option which separate the editor from the files.
669
*/
670
for (i = 0; command_details->argv[i] != NULL; i++) {
671
if (strcmp(command_details->argv[i], "--") == 0) {
672
editor_argc = i;
673
files = &command_details->argv[i + 1];
674
nfiles = command_details->argc - (i + 1);
675
break;
676
}
677
}
678
}
679
if (nfiles == 0) {
680
sudo_warnx("%s", U_("plugin error: missing file list for sudoedit"));
681
goto cleanup;
682
}
683
684
/* Copy editor files to temporaries. */
685
tf = calloc((size_t)nfiles, sizeof(*tf));
686
if (tf == NULL) {
687
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
688
goto cleanup;
689
}
690
#ifdef HAVE_SELINUX
691
if (ISSET(command_details->flags, CD_RBAC_ENABLED))
692
nfiles = selinux_edit_create_tfiles(command_details, user_cred, tf, files, nfiles);
693
else
694
#endif
695
nfiles = sudo_edit_create_tfiles(command_details, user_cred, tf, files, nfiles);
696
if (nfiles <= 0)
697
goto cleanup;
698
699
/*
700
* Allocate space for the new argument vector and fill it in.
701
* We concatenate the editor with its args and the file list
702
* to create a new argv.
703
*/
704
nargc = editor_argc + nfiles;
705
nargv = reallocarray(NULL, (size_t)nargc + 1, sizeof(char *));
706
if (nargv == NULL) {
707
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
708
goto cleanup;
709
}
710
for (ac = 0; ac < editor_argc; ac++)
711
nargv[ac] = command_details->argv[ac];
712
for (i = 0; i < nfiles && ac < nargc; )
713
nargv[ac++] = tf[i++].tfile;
714
nargv[ac] = NULL;
715
716
/*
717
* Run the editor with the invoking user's creds and drop setuid.
718
* Keep track of the time spent in the editor to distinguish between
719
* a user editing a file and a program doing it.
720
* XXX - should run editor with user's context
721
*/
722
if (sudo_gettime_real(&times[0]) == -1) {
723
sudo_warn("%s", U_("unable to read the clock"));
724
goto cleanup;
725
}
726
#ifdef HAVE_SELINUX
727
if (ISSET(command_details->flags, CD_RBAC_ENABLED))
728
selinux_audit_role_change();
729
#endif
730
memcpy(&saved_command_details, command_details, sizeof(struct command_details));
731
command_details->cred = *user_cred;
732
command_details->cred.euid = user_cred->uid;
733
command_details->cred.egid = user_cred->gid;
734
command_details->argc = nargc;
735
command_details->argv = nargv;
736
ret = run_command(command_details, user_details);
737
if (sudo_gettime_real(&times[1]) == -1) {
738
sudo_warn("%s", U_("unable to read the clock"));
739
goto cleanup;
740
}
741
742
/* Restore saved command_details. */
743
command_details->cred = saved_command_details.cred;
744
command_details->argc = saved_command_details.argc;
745
command_details->argv = saved_command_details.argv;
746
747
/* Copy contents of temp files to real ones. */
748
#ifdef HAVE_SELINUX
749
if (ISSET(command_details->flags, CD_RBAC_ENABLED))
750
errors = selinux_edit_copy_tfiles(command_details, user_cred, tf, nfiles, times);
751
else
752
#endif
753
errors = sudo_edit_copy_tfiles(command_details, user_cred, tf, nfiles, times);
754
if (errors) {
755
/* Preserve the edited temporary files. */
756
ret = W_EXITCODE(1, 0);
757
}
758
759
for (i = 0; i < nfiles; i++)
760
free(tf[i].tfile);
761
free(tf);
762
free(nargv);
763
debug_return_int(ret);
764
765
cleanup:
766
/* Clean up temp files and return. */
767
if (tf != NULL) {
768
for (i = 0; i < nfiles; i++) {
769
if (tf[i].tfile != NULL)
770
unlink(tf[i].tfile);
771
free(tf[i].tfile);
772
}
773
}
774
free(tf);
775
free(nargv);
776
debug_return_int(W_EXITCODE(1, 0));
777
}
778
779
#else /* HAVE_SETRESUID || HAVE_SETREUID || HAVE_SETEUID */
780
781
/*
782
* Must have the ability to change the effective uid to use sudoedit.
783
*/
784
int
785
sudo_edit(const struct command_details *command_details, const struct sudo_cred *user_cred)
786
{
787
debug_decl(sudo_edit, SUDO_DEBUG_EDIT);
788
debug_return_int(W_EXITCODE(1, 0));
789
}
790
791
#endif /* HAVE_SETRESUID || HAVE_SETREUID || HAVE_SETEUID */
792
793