Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/src/exec_iolog.c
1532 views
1
/*
2
* SPDX-License-Identifier: ISC
3
*
4
* Copyright (c) 2009-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 <stdlib.h>
23
#include <string.h>
24
#include <unistd.h>
25
#include <errno.h>
26
#include <fcntl.h>
27
#include <signal.h>
28
29
#include <sudo.h>
30
#include <sudo_exec.h>
31
#include <sudo_plugin.h>
32
#include <sudo_plugin_int.h>
33
34
int io_fds[6] = { -1, -1, -1, -1, -1, -1 };
35
36
static struct io_buffer_list iobufs = SLIST_HEAD_INITIALIZER(&iobufs);
37
38
static sigset_t ttyblock;
39
40
/*
41
* Remove and free any events associated with the specified
42
* file descriptor present in the I/O buffers list.
43
*/
44
void
45
ev_free_by_fd(struct sudo_event_base *evbase, int fd)
46
{
47
struct io_buffer *iob;
48
debug_decl(ev_free_by_fd, SUDO_DEBUG_EXEC);
49
50
/* Deschedule any users of the fd and free up the events. */
51
SLIST_FOREACH(iob, &iobufs, entries) {
52
if (iob->revent != NULL) {
53
if (sudo_ev_get_fd(iob->revent) == fd) {
54
sudo_debug_printf(SUDO_DEBUG_INFO,
55
"%s: deleting and freeing revent %p with fd %d",
56
__func__, iob->revent, fd);
57
sudo_ev_free(iob->revent);
58
iob->revent = NULL;
59
}
60
}
61
if (iob->wevent != NULL) {
62
if (sudo_ev_get_fd(iob->wevent) == fd) {
63
sudo_debug_printf(SUDO_DEBUG_INFO,
64
"%s: deleting and freeing wevent %p with fd %d",
65
__func__, iob->wevent, fd);
66
sudo_ev_free(iob->wevent);
67
iob->wevent = NULL;
68
}
69
}
70
}
71
debug_return;
72
}
73
74
/*
75
* Only close the fd if it is not /dev/tty or std{in,out,err}.
76
* Return value is the same as close(2).
77
*/
78
int
79
safe_close(int fd)
80
{
81
debug_decl(safe_close, SUDO_DEBUG_EXEC);
82
83
/* Avoid closing /dev/tty or std{in,out,err}. */
84
if (fd < 3 || fd == io_fds[SFD_USERTTY]) {
85
sudo_debug_printf(SUDO_DEBUG_INFO,
86
"%s: not closing fd %d (%s)", __func__, fd, _PATH_TTY);
87
errno = EINVAL;
88
debug_return_int(-1);
89
}
90
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: closing fd %d", __func__, fd);
91
debug_return_int(close(fd));
92
}
93
94
/*
95
* Allocate a new I/O buffer and associated read/write events.
96
*/
97
void
98
io_buf_new(int rfd, int wfd,
99
bool (*action)(const char *, unsigned int, struct io_buffer *),
100
void (*read_cb)(int fd, int what, void *v),
101
void (*write_cb)(int fd, int what, void *v), struct exec_closure *ec)
102
{
103
int n;
104
struct io_buffer *iob;
105
debug_decl(io_buf_new, SUDO_DEBUG_EXEC);
106
107
/* Set non-blocking mode. */
108
n = fcntl(rfd, F_GETFL, 0);
109
if (n != -1 && !ISSET(n, O_NONBLOCK))
110
(void) fcntl(rfd, F_SETFL, n | O_NONBLOCK);
111
n = fcntl(wfd, F_GETFL, 0);
112
if (n != -1 && !ISSET(n, O_NONBLOCK))
113
(void) fcntl(wfd, F_SETFL, n | O_NONBLOCK);
114
115
/* Allocate and add to head of list. */
116
if ((iob = malloc(sizeof(*iob))) == NULL)
117
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
118
iob->ec = ec;
119
iob->revent = sudo_ev_alloc(rfd, SUDO_EV_READ|SUDO_EV_PERSIST,
120
read_cb, iob);
121
iob->wevent = sudo_ev_alloc(wfd, SUDO_EV_WRITE|SUDO_EV_PERSIST,
122
write_cb, iob);
123
iob->len = 0;
124
iob->off = 0;
125
iob->action = action;
126
iob->buf[0] = '\0';
127
if (iob->revent == NULL || iob->wevent == NULL)
128
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
129
SLIST_INSERT_HEAD(&iobufs, iob, entries);
130
131
debug_return;
132
}
133
134
/*
135
* Schedule I/O events before starting the main event loop or
136
* resuming from suspend.
137
*/
138
void
139
add_io_events(struct exec_closure *ec)
140
{
141
struct io_buffer *iob;
142
debug_decl(add_io_events, SUDO_DEBUG_EXEC);
143
144
/*
145
* Schedule all readers as long as the buffer is not full.
146
* Schedule writers that contain buffered data.
147
* Normally, write buffers are added on demand when data is read.
148
*/
149
SLIST_FOREACH(iob, &iobufs, entries) {
150
/* Don't read from /dev/tty if we are not in the foreground. */
151
if (iob->revent != NULL &&
152
(ec->term_raw || !USERTTY_EVENT(iob->revent))) {
153
if (iob->len != sizeof(iob->buf)) {
154
sudo_debug_printf(SUDO_DEBUG_INFO,
155
"added I/O revent %p, fd %d, events %d",
156
iob->revent, iob->revent->fd, iob->revent->events);
157
if (sudo_ev_add(ec->evbase, iob->revent, NULL, false) == -1)
158
sudo_fatal("%s", U_("unable to add event to queue"));
159
}
160
}
161
if (iob->wevent != NULL) {
162
/* Enable writer if buffer is not empty. */
163
if (iob->len > iob->off) {
164
sudo_debug_printf(SUDO_DEBUG_INFO,
165
"added I/O wevent %p, fd %d, events %d",
166
iob->wevent, iob->wevent->fd, iob->wevent->events);
167
if (sudo_ev_add(ec->evbase, iob->wevent, NULL, false) == -1)
168
sudo_fatal("%s", U_("unable to add event to queue"));
169
}
170
}
171
}
172
debug_return;
173
}
174
175
/*
176
* Flush any output buffered in iobufs or readable from fds other
177
* than /dev/tty. Removes I/O events from the event base when done.
178
*/
179
void
180
del_io_events(bool nonblocking)
181
{
182
struct io_buffer *iob;
183
struct sudo_event_base *evbase;
184
debug_decl(del_io_events, SUDO_DEBUG_EXEC);
185
186
/* Remove iobufs from existing event base. */
187
SLIST_FOREACH(iob, &iobufs, entries) {
188
if (iob->revent != NULL) {
189
sudo_debug_printf(SUDO_DEBUG_INFO,
190
"deleted I/O revent %p, fd %d, events %d",
191
iob->revent, iob->revent->fd, iob->revent->events);
192
sudo_ev_del(NULL, iob->revent);
193
}
194
if (iob->wevent != NULL) {
195
sudo_debug_printf(SUDO_DEBUG_INFO,
196
"deleted I/O wevent %p, fd %d, events %d",
197
iob->wevent, iob->wevent->fd, iob->wevent->events);
198
sudo_ev_del(NULL, iob->wevent);
199
}
200
}
201
202
/* Create temporary event base for flushing. */
203
evbase = sudo_ev_base_alloc();
204
if (evbase == NULL)
205
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
206
207
/* Avoid reading from /dev/tty, just flush existing data. */
208
SLIST_FOREACH(iob, &iobufs, entries) {
209
/* Don't read from /dev/tty while flushing. */
210
if (iob->revent != NULL && !USERTTY_EVENT(iob->revent)) {
211
if (iob->len != sizeof(iob->buf)) {
212
if (sudo_ev_add(evbase, iob->revent, NULL, false) == -1)
213
sudo_fatal("%s", U_("unable to add event to queue"));
214
}
215
}
216
/* Flush any write buffers with data in them. */
217
if (iob->wevent != NULL) {
218
if (iob->len > iob->off) {
219
if (sudo_ev_add(evbase, iob->wevent, NULL, false) == -1)
220
sudo_fatal("%s", U_("unable to add event to queue"));
221
}
222
}
223
}
224
sudo_debug_printf(SUDO_DEBUG_INFO,
225
"%s: flushing remaining I/O buffers (nonblocking)", __func__);
226
(void) sudo_ev_loop(evbase, SUDO_EVLOOP_NONBLOCK);
227
228
/*
229
* If not in non-blocking mode, make sure we flush write buffers.
230
* We don't want to read from the pty or stdin since that might block
231
* and the command is no longer running anyway.
232
*/
233
if (!nonblocking) {
234
/* Clear out iobufs from event base. */
235
SLIST_FOREACH(iob, &iobufs, entries) {
236
if (iob->revent != NULL && !USERTTY_EVENT(iob->revent))
237
sudo_ev_del(evbase, iob->revent);
238
if (iob->wevent != NULL)
239
sudo_ev_del(evbase, iob->wevent);
240
}
241
242
SLIST_FOREACH(iob, &iobufs, entries) {
243
/* Flush any write buffers with data in them. */
244
if (iob->wevent != NULL) {
245
if (iob->len > iob->off) {
246
if (sudo_ev_add(evbase, iob->wevent, NULL, false) == -1)
247
sudo_fatal("%s", U_("unable to add event to queue"));
248
}
249
}
250
}
251
sudo_debug_printf(SUDO_DEBUG_INFO,
252
"%s: flushing remaining write buffers (blocking)", __func__);
253
(void) sudo_ev_dispatch(evbase);
254
255
/* We should now have flushed all write buffers. */
256
SLIST_FOREACH(iob, &iobufs, entries) {
257
if (iob->wevent != NULL) {
258
if (iob->len > iob->off) {
259
sudo_debug_printf(SUDO_DEBUG_ERROR,
260
"unflushed data: wevent %p, fd %d, events %d",
261
iob->wevent, iob->wevent->fd, iob->wevent->events);
262
}
263
}
264
}
265
}
266
267
/* Free temporary event base, removing its events. */
268
sudo_ev_base_free(evbase);
269
270
debug_return;
271
}
272
273
/*
274
* Free the contents of the I/O buffers queue.
275
*/
276
void
277
free_io_bufs(void)
278
{
279
struct io_buffer *iob;
280
debug_decl(free_io_bufs, SUDO_DEBUG_EXEC);
281
282
while ((iob = SLIST_FIRST(&iobufs)) != NULL) {
283
SLIST_REMOVE_HEAD(&iobufs, entries);
284
if (iob->revent != NULL)
285
sudo_ev_free(iob->revent);
286
if (iob->wevent != NULL)
287
sudo_ev_free(iob->wevent);
288
free(iob);
289
}
290
291
debug_return;
292
}
293
294
/* Call I/O plugin tty input log method. */
295
bool
296
log_ttyin(const char *buf, unsigned int n, struct io_buffer *iob)
297
{
298
struct plugin_container *plugin;
299
const char *errstr = NULL;
300
sigset_t omask;
301
bool ret = true;
302
debug_decl(log_ttyin, SUDO_DEBUG_EXEC);
303
304
sigprocmask(SIG_BLOCK, &ttyblock, &omask);
305
TAILQ_FOREACH(plugin, &io_plugins, entries) {
306
if (plugin->u.io->log_ttyin) {
307
int rc;
308
309
sudo_debug_set_active_instance(plugin->debug_instance);
310
rc = plugin->u.io->log_ttyin(buf, n, &errstr);
311
if (rc <= 0) {
312
if (rc < 0) {
313
/* Error: disable plugin's I/O function. */
314
plugin->u.io->log_ttyin = NULL;
315
audit_error(plugin->name, SUDO_IO_PLUGIN,
316
errstr ? errstr : _("I/O plugin error"),
317
iob->ec->details->info);
318
} else {
319
audit_reject(plugin->name, SUDO_IO_PLUGIN,
320
errstr ? errstr : _("command rejected by I/O plugin"),
321
iob->ec->details->info);
322
}
323
ret = false;
324
break;
325
}
326
}
327
}
328
sudo_debug_set_active_instance(sudo_debug_instance);
329
sigprocmask(SIG_SETMASK, &omask, NULL);
330
331
debug_return_bool(ret);
332
}
333
334
/* Call I/O plugin stdin log method. */
335
bool
336
log_stdin(const char *buf, unsigned int n, struct io_buffer *iob)
337
{
338
struct plugin_container *plugin;
339
const char *errstr = NULL;
340
sigset_t omask;
341
bool ret = true;
342
debug_decl(log_stdin, SUDO_DEBUG_EXEC);
343
344
sigprocmask(SIG_BLOCK, &ttyblock, &omask);
345
TAILQ_FOREACH(plugin, &io_plugins, entries) {
346
if (plugin->u.io->log_stdin) {
347
int rc;
348
349
sudo_debug_set_active_instance(plugin->debug_instance);
350
rc = plugin->u.io->log_stdin(buf, n, &errstr);
351
if (rc <= 0) {
352
if (rc < 0) {
353
/* Error: disable plugin's I/O function. */
354
plugin->u.io->log_stdin = NULL;
355
audit_error(plugin->name, SUDO_IO_PLUGIN,
356
errstr ? errstr : _("I/O plugin error"),
357
iob->ec->details->info);
358
} else {
359
audit_reject(plugin->name, SUDO_IO_PLUGIN,
360
errstr ? errstr : _("command rejected by I/O plugin"),
361
iob->ec->details->info);
362
}
363
ret = false;
364
break;
365
}
366
}
367
}
368
sudo_debug_set_active_instance(sudo_debug_instance);
369
sigprocmask(SIG_SETMASK, &omask, NULL);
370
371
debug_return_bool(ret);
372
}
373
374
/* Call I/O plugin tty output log method. */
375
bool
376
log_ttyout(const char *buf, unsigned int n, struct io_buffer *iob)
377
{
378
struct plugin_container *plugin;
379
const char *errstr = NULL;
380
sigset_t omask;
381
bool ret = true;
382
debug_decl(log_ttyout, SUDO_DEBUG_EXEC);
383
384
sigprocmask(SIG_BLOCK, &ttyblock, &omask);
385
TAILQ_FOREACH(plugin, &io_plugins, entries) {
386
if (plugin->u.io->log_ttyout) {
387
int rc;
388
389
sudo_debug_set_active_instance(plugin->debug_instance);
390
rc = plugin->u.io->log_ttyout(buf, n, &errstr);
391
if (rc <= 0) {
392
if (rc < 0) {
393
/* Error: disable plugin's I/O function. */
394
plugin->u.io->log_ttyout = NULL;
395
audit_error(plugin->name, SUDO_IO_PLUGIN,
396
errstr ? errstr : _("I/O plugin error"),
397
iob->ec->details->info);
398
} else {
399
audit_reject(plugin->name, SUDO_IO_PLUGIN,
400
errstr ? errstr : _("command rejected by I/O plugin"),
401
iob->ec->details->info);
402
}
403
ret = false;
404
break;
405
}
406
}
407
}
408
sudo_debug_set_active_instance(sudo_debug_instance);
409
if (!ret) {
410
/*
411
* I/O plugin rejected the output, delete the write event
412
* (user's tty) so we do not display the rejected output.
413
*/
414
sudo_debug_printf(SUDO_DEBUG_INFO,
415
"%s: deleting and freeing devtty wevent %p", __func__, iob->wevent);
416
sudo_ev_free(iob->wevent);
417
iob->wevent = NULL;
418
iob->off = iob->len = 0;
419
}
420
sigprocmask(SIG_SETMASK, &omask, NULL);
421
422
debug_return_bool(ret);
423
}
424
425
/* Call I/O plugin stdout log method. */
426
bool
427
log_stdout(const char *buf, unsigned int n, struct io_buffer *iob)
428
{
429
struct plugin_container *plugin;
430
const char *errstr = NULL;
431
sigset_t omask;
432
bool ret = true;
433
debug_decl(log_stdout, SUDO_DEBUG_EXEC);
434
435
sigprocmask(SIG_BLOCK, &ttyblock, &omask);
436
TAILQ_FOREACH(plugin, &io_plugins, entries) {
437
if (plugin->u.io->log_stdout) {
438
int rc;
439
440
sudo_debug_set_active_instance(plugin->debug_instance);
441
rc = plugin->u.io->log_stdout(buf, n, &errstr);
442
if (rc <= 0) {
443
if (rc < 0) {
444
/* Error: disable plugin's I/O function. */
445
plugin->u.io->log_stdout = NULL;
446
audit_error(plugin->name, SUDO_IO_PLUGIN,
447
errstr ? errstr : _("I/O plugin error"),
448
iob->ec->details->info);
449
} else {
450
audit_reject(plugin->name, SUDO_IO_PLUGIN,
451
errstr ? errstr : _("command rejected by I/O plugin"),
452
iob->ec->details->info);
453
}
454
ret = false;
455
break;
456
}
457
}
458
}
459
sudo_debug_set_active_instance(sudo_debug_instance);
460
if (!ret) {
461
/*
462
* I/O plugin rejected the output, delete the write event
463
* (user's stdout) so we do not display the rejected output.
464
*/
465
sudo_debug_printf(SUDO_DEBUG_INFO,
466
"%s: deleting and freeing stdout wevent %p", __func__, iob->wevent);
467
sudo_ev_free(iob->wevent);
468
iob->wevent = NULL;
469
iob->off = iob->len = 0;
470
}
471
sigprocmask(SIG_SETMASK, &omask, NULL);
472
473
debug_return_bool(ret);
474
}
475
476
/* Call I/O plugin stderr log method. */
477
bool
478
log_stderr(const char *buf, unsigned int n, struct io_buffer *iob)
479
{
480
struct plugin_container *plugin;
481
const char *errstr = NULL;
482
sigset_t omask;
483
bool ret = true;
484
debug_decl(log_stderr, SUDO_DEBUG_EXEC);
485
486
sigprocmask(SIG_BLOCK, &ttyblock, &omask);
487
TAILQ_FOREACH(plugin, &io_plugins, entries) {
488
if (plugin->u.io->log_stderr) {
489
int rc;
490
491
sudo_debug_set_active_instance(plugin->debug_instance);
492
rc = plugin->u.io->log_stderr(buf, n, &errstr);
493
if (rc <= 0) {
494
if (rc < 0) {
495
/* Error: disable plugin's I/O function. */
496
plugin->u.io->log_stderr = NULL;
497
audit_error(plugin->name, SUDO_IO_PLUGIN,
498
errstr ? errstr : _("I/O plugin error"),
499
iob->ec->details->info);
500
} else {
501
audit_reject(plugin->name, SUDO_IO_PLUGIN,
502
errstr ? errstr : _("command rejected by I/O plugin"),
503
iob->ec->details->info);
504
}
505
ret = false;
506
break;
507
}
508
}
509
}
510
sudo_debug_set_active_instance(sudo_debug_instance);
511
if (!ret) {
512
/*
513
* I/O plugin rejected the output, delete the write event
514
* (user's stderr) so we do not display the rejected output.
515
*/
516
sudo_debug_printf(SUDO_DEBUG_INFO,
517
"%s: deleting and freeing stderr wevent %p", __func__, iob->wevent);
518
sudo_ev_free(iob->wevent);
519
iob->wevent = NULL;
520
iob->off = iob->len = 0;
521
}
522
sigprocmask(SIG_SETMASK, &omask, NULL);
523
524
debug_return_bool(ret);
525
}
526
527
/* Call I/O plugin suspend log method. */
528
void
529
log_suspend(void *v, int signo)
530
{
531
struct exec_closure *ec = v;
532
struct plugin_container *plugin;
533
const char *errstr = NULL;
534
sigset_t omask;
535
debug_decl(log_suspend, SUDO_DEBUG_EXEC);
536
537
sigprocmask(SIG_BLOCK, &ttyblock, &omask);
538
TAILQ_FOREACH(plugin, &io_plugins, entries) {
539
if (plugin->u.io->version < SUDO_API_MKVERSION(1, 13))
540
continue;
541
if (plugin->u.io->log_suspend) {
542
int rc;
543
544
sudo_debug_set_active_instance(plugin->debug_instance);
545
rc = plugin->u.io->log_suspend(signo, &errstr);
546
if (rc <= 0) {
547
/* Error: disable plugin's I/O function. */
548
plugin->u.io->log_suspend = NULL;
549
audit_error(plugin->name, SUDO_IO_PLUGIN,
550
errstr ? errstr : _("error logging suspend"),
551
ec->details->info);
552
break;
553
}
554
}
555
}
556
sudo_debug_set_active_instance(sudo_debug_instance);
557
sigprocmask(SIG_SETMASK, &omask, NULL);
558
559
debug_return;
560
}
561
562
/* Call I/O plugin window change log method. */
563
void
564
log_winchange(struct exec_closure *ec, unsigned int rows,
565
unsigned int cols)
566
{
567
struct plugin_container *plugin;
568
const char *errstr = NULL;
569
sigset_t omask;
570
debug_decl(log_winchange, SUDO_DEBUG_EXEC);
571
572
sigprocmask(SIG_BLOCK, &ttyblock, &omask);
573
TAILQ_FOREACH(plugin, &io_plugins, entries) {
574
if (plugin->u.io->version < SUDO_API_MKVERSION(1, 12))
575
continue;
576
if (plugin->u.io->change_winsize) {
577
int rc;
578
579
sudo_debug_set_active_instance(plugin->debug_instance);
580
rc = plugin->u.io->change_winsize(rows, cols, &errstr);
581
if (rc <= 0) {
582
/* Error: disable plugin's I/O function. */
583
plugin->u.io->change_winsize = NULL;
584
audit_error(plugin->name, SUDO_IO_PLUGIN,
585
errstr ? errstr : _("error changing window size"),
586
ec->details->info);
587
break;
588
}
589
}
590
}
591
sudo_debug_set_active_instance(sudo_debug_instance);
592
sigprocmask(SIG_SETMASK, &omask, NULL);
593
594
debug_return;
595
}
596
597
void
598
init_ttyblock(void)
599
{
600
/* So we can block tty-generated signals */
601
sigemptyset(&ttyblock);
602
sigaddset(&ttyblock, SIGINT);
603
sigaddset(&ttyblock, SIGQUIT);
604
sigaddset(&ttyblock, SIGTSTP);
605
sigaddset(&ttyblock, SIGTTIN);
606
sigaddset(&ttyblock, SIGTTOU);
607
}
608
609