Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/plugins/sudoers/audit.c
1532 views
1
/*
2
* SPDX-License-Identifier: ISC
3
*
4
* Copyright (c) 2009-2015, 2019-2025 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/wait.h>
22
#include <stdarg.h>
23
#include <stdio.h>
24
#include <stdlib.h>
25
#include <string.h>
26
27
#include <sudoers.h>
28
#ifdef SUDOERS_LOG_CLIENT
29
# include <log_client.h>
30
# include <strlist.h>
31
#endif
32
33
#ifdef HAVE_BSM_AUDIT
34
# include <bsm_audit.h>
35
#endif
36
#ifdef HAVE_LINUX_AUDIT
37
# include <linux_audit.h>
38
#endif
39
#ifdef HAVE_SOLARIS_AUDIT
40
# include <solaris_audit.h>
41
#endif
42
43
char *audit_msg = NULL;
44
45
/* sudoers_audit is declared at the end of this file. */
46
extern sudo_dso_public struct audit_plugin sudoers_audit;
47
48
static int
49
audit_success(const struct sudoers_context *ctx, char *const argv[])
50
{
51
int rc = 0;
52
debug_decl(audit_success, SUDOERS_DEBUG_AUDIT);
53
54
if (argv != NULL) {
55
#ifdef HAVE_BSM_AUDIT
56
if (bsm_audit_success(ctx, argv) == -1)
57
rc = -1;
58
#endif
59
#ifdef HAVE_LINUX_AUDIT
60
if (linux_audit_command(argv, 1) == -1)
61
rc = -1;
62
#endif
63
#ifdef HAVE_SOLARIS_AUDIT
64
if (solaris_audit_success(ctx, argv) == -1)
65
rc = -1;
66
#endif
67
}
68
69
debug_return_int(rc);
70
}
71
72
static int
73
audit_failure_int(const struct sudoers_context *ctx, char *const argv[],
74
const char *message)
75
{
76
int ret = 0;
77
debug_decl(audit_failure_int, SUDOERS_DEBUG_AUDIT);
78
79
#if defined(HAVE_BSM_AUDIT) || defined(HAVE_LINUX_AUDIT)
80
if (def_log_denied && argv != NULL) {
81
#ifdef HAVE_BSM_AUDIT
82
if (bsm_audit_failure(ctx, argv, message) == -1)
83
ret = -1;
84
#endif
85
#ifdef HAVE_LINUX_AUDIT
86
if (linux_audit_command(argv, 0) == -1)
87
ret = -1;
88
#endif
89
#ifdef HAVE_SOLARIS_AUDIT
90
if (solaris_audit_failure(ctx, argv, message) == -1)
91
ret = -1;
92
#endif
93
}
94
#endif /* HAVE_BSM_AUDIT || HAVE_LINUX_AUDIT */
95
96
debug_return_int(ret);
97
}
98
99
int
100
vaudit_failure(const struct sudoers_context *ctx, char *const argv[],
101
char const * restrict const fmt, va_list ap)
102
{
103
int oldlocale, ret;
104
char *message;
105
debug_decl(vaudit_failure, SUDOERS_DEBUG_AUDIT);
106
107
/* Audit messages should be in the sudoers locale. */
108
sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, &oldlocale);
109
110
if ((ret = vasprintf(&message, _(fmt), ap)) == -1)
111
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
112
113
if (ret != -1) {
114
/* Set audit_msg for audit plugins. */
115
free(audit_msg);
116
audit_msg = message;
117
118
ret = audit_failure_int(ctx, argv, audit_msg);
119
}
120
121
sudoers_setlocale(oldlocale, NULL);
122
123
debug_return_int(ret);
124
}
125
126
int
127
audit_failure(const struct sudoers_context *ctx, char *const argv[],
128
char const * restrict const fmt, ...)
129
{
130
va_list ap;
131
int ret;
132
debug_decl(audit_failure, SUDOERS_DEBUG_AUDIT);
133
134
va_start(ap, fmt);
135
ret = vaudit_failure(ctx, argv, fmt, ap);
136
va_end(ap);
137
138
debug_return_int(ret);
139
}
140
141
static int
142
sudoers_audit_open(unsigned int version, sudo_conv_t conversation,
143
sudo_printf_t plugin_printf, char * const settings[],
144
char * const user_info[], int submit_optind, char * const submit_argv[],
145
char * const submit_envp[], char * const plugin_options[],
146
const char **errstr)
147
{
148
struct sudo_conf_debug_file_list debug_files = TAILQ_HEAD_INITIALIZER(debug_files);
149
struct sudoers_open_info info;
150
const char *cp, *plugin_path = NULL;
151
char * const *cur;
152
int ret;
153
debug_decl(sudoers_audit_open, SUDOERS_DEBUG_PLUGIN);
154
155
sudo_conv = conversation;
156
sudo_printf = plugin_printf;
157
if (sudoers_audit.event_alloc != NULL)
158
plugin_event_alloc = sudoers_audit.event_alloc;
159
160
bindtextdomain("sudoers", LOCALEDIR);
161
162
/* Initialize the debug subsystem. */
163
for (cur = settings; (cp = *cur) != NULL; cur++) {
164
if (strncmp(cp, "debug_flags=", sizeof("debug_flags=") - 1) == 0) {
165
cp += sizeof("debug_flags=") - 1;
166
if (!sudoers_debug_parse_flags(&debug_files, cp))
167
debug_return_int(-1);
168
continue;
169
}
170
if (strncmp(cp, "plugin_path=", sizeof("plugin_path=") - 1) == 0) {
171
plugin_path = cp + sizeof("plugin_path=") - 1;
172
continue;
173
}
174
}
175
if (!sudoers_debug_register(plugin_path, &debug_files))
176
debug_return_int(-1);
177
178
/* Call the sudoers init function. */
179
info.settings = settings;
180
info.user_info = user_info;
181
info.plugin_args = plugin_options;
182
ret = sudoers_init(&info, log_parse_error, submit_envp);
183
184
if (ret == 1) {
185
/* Unset close function if we don't need it to avoid extra process. */
186
#ifdef SUDOERS_LOG_CLIENT
187
if (SLIST_EMPTY(&def_log_servers))
188
#endif
189
sudoers_audit.close = NULL;
190
} else {
191
/* The audit functions set audit_msg on failure. */
192
if (audit_msg != NULL)
193
*errstr = audit_msg;
194
}
195
196
debug_return_int(ret);
197
}
198
199
static void
200
audit_to_eventlog(const struct sudoers_context *ctx, struct eventlog *evlog,
201
char * const command_info[], char * const run_argv[],
202
char * const run_envp[], const char *uuid_str)
203
{
204
char * const *cur;
205
debug_decl(audit_to_eventlog, SUDOERS_DEBUG_PLUGIN);
206
207
/* Fill in evlog from sudoers Defaults, run_argv and run_envp. */
208
sudoers_to_eventlog(ctx, evlog, NULL, run_argv, run_envp, uuid_str);
209
210
/* Update iolog and execution environment from command_info[]. */
211
if (command_info != NULL) {
212
for (cur = command_info; *cur != NULL; cur++) {
213
switch (**cur) {
214
case 'c':
215
if (strncmp(*cur, "command=", sizeof("command=") - 1) == 0) {
216
evlog->command = *cur + sizeof("command=") - 1;
217
continue;
218
}
219
if (strncmp(*cur, "chroot=", sizeof("chroot=") - 1) == 0) {
220
evlog->runchroot = *cur + sizeof("chroot=") - 1;
221
continue;
222
}
223
break;
224
case 'i':
225
if (strncmp(*cur, "iolog_path=", sizeof("iolog_path=") - 1) == 0) {
226
evlog->iolog_path = *cur + sizeof("iolog_path=") - 1;
227
continue;
228
}
229
break;
230
case 'r':
231
if (strncmp(*cur, "runcwd=", sizeof("runcwd=") - 1) == 0) {
232
evlog->runcwd = *cur + sizeof("runcwd=") - 1;
233
continue;
234
}
235
break;
236
}
237
}
238
}
239
240
debug_return;
241
}
242
243
#ifdef SUDOERS_LOG_CLIENT
244
/*
245
* Persistent audit details and associated event log data used
246
* when opening a new connection to the log server.
247
*/
248
static struct log_details audit_details;
249
250
static void
251
free_audit_details(void)
252
{
253
debug_decl(free_audit_details, SUDOERS_DEBUG_PLUGIN);
254
255
/* Only the log_servers string list is dynamically allocated. */
256
str_list_free(audit_details.log_servers);
257
258
debug_return;
259
}
260
261
static bool
262
log_server_accept(const struct sudoers_context *ctx, struct eventlog *evlog)
263
{
264
struct timespec start_time;
265
bool ret = false;
266
debug_decl(log_server_accept, SUDOERS_DEBUG_PLUGIN);
267
268
if (SLIST_EMPTY(&def_log_servers))
269
debug_return_bool(true);
270
271
if (client_closure != NULL && ISSET(ctx->mode, MODE_POLICY_INTERCEPTED)) {
272
/* Older servers don't support multiple commands per session. */
273
if (!client_closure->subcommands)
274
debug_return_bool(true);
275
} else {
276
/* Only send accept event to log server if I/O log plugin did not. */
277
if (iolog_enabled)
278
debug_return_bool(true);
279
}
280
281
if (client_closure != NULL) {
282
/* Use existing client closure. */
283
if (fmt_accept_message(client_closure, evlog)) {
284
if (client_closure->write_ev->add(client_closure->write_ev,
285
&client_closure->log_details->server_timeout) == -1) {
286
sudo_warn("%s", U_("unable to add event to queue"));
287
goto done;
288
}
289
ret = true;
290
}
291
} else {
292
/* No existing client closure, I/O logging not enabled. */
293
if (sudo_gettime_awake(&start_time) == -1) {
294
sudo_warn("%s", U_("unable to get time of day"));
295
goto done;
296
}
297
298
/*
299
* audit_details is stored in client_closure->log_details
300
* and must remain in scope until the command exits.
301
*/
302
if (!init_log_details(&audit_details, evlog))
303
goto done;
304
305
/* Open connection to log server, send hello and accept messages. */
306
client_closure = log_server_open(&audit_details, &start_time, false,
307
SEND_ACCEPT, NULL);
308
if (client_closure != NULL)
309
ret = true;
310
}
311
312
done:
313
debug_return_bool(ret);
314
}
315
316
static void
317
log_server_exit(int status_type, int status)
318
{
319
debug_decl(log_server_exit, SUDOERS_DEBUG_PLUGIN);
320
321
/*
322
* I/O log plugin clears client_closure on close so we don't log
323
* the exit status twice.
324
*/
325
if (client_closure != NULL) {
326
int exit_status = 0, error = 0;
327
328
if (status_type == SUDO_PLUGIN_WAIT_STATUS) {
329
if (WIFEXITED(status))
330
exit_status = WEXITSTATUS(status);
331
else
332
exit_status = WTERMSIG(status) | 128;
333
} else {
334
/* Must be errno. */
335
error = status;
336
}
337
log_server_close(client_closure, exit_status, error);
338
client_closure = NULL;
339
}
340
free_audit_details();
341
342
debug_return;
343
}
344
#else
345
static bool
346
log_server_accept(const struct sudoers_context *ctx, struct eventlog *evlog)
347
{
348
return true;
349
}
350
351
static void
352
log_server_exit(int status_type, int status)
353
{
354
return;
355
}
356
#endif /* SUDOERS_LOG_CLIENT */
357
358
static int
359
sudoers_audit_accept(const char *plugin_name, unsigned int plugin_type,
360
char * const command_info[], char * const run_argv[],
361
char * const run_envp[], const char **errstr)
362
{
363
const struct sudoers_context *ctx = sudoers_get_context();
364
const char *uuid_str = NULL;
365
struct eventlog evlog;
366
static bool first = true;
367
int ret = true;
368
debug_decl(sudoers_audit_accept, SUDOERS_DEBUG_PLUGIN);
369
370
/* Only log the accept event from the sudo front-end */
371
if (plugin_type != SUDO_FRONT_END)
372
debug_return_int(true);
373
374
/* Log sub-commands with the uuid of the original command. */
375
if (!ISSET(ctx->mode, MODE_POLICY_INTERCEPTED))
376
uuid_str = ctx->uuid_str;
377
378
/*
379
* We must always call log_allowed() even if def_log_allowed is disabled
380
* since it will send mail if def_mail_always or def_mail_all_cmnds are
381
* set (it has its own checks for def_log_allowed).
382
*/
383
audit_to_eventlog(ctx, &evlog, command_info, run_argv, run_envp, uuid_str);
384
if (!log_allowed(ctx, &evlog) && !def_ignore_logfile_errors)
385
ret = false;
386
387
/*
388
* Skip auditing and log server logging if "log_allowed" is disabled.
389
*/
390
if (!def_log_allowed)
391
goto done;
392
393
if (audit_success(ctx, run_argv) != 0) {
394
if (!def_ignore_logfile_errors)
395
ret = false;
396
}
397
398
if (!log_server_accept(ctx, &evlog)) {
399
if (!def_ignore_logfile_errors)
400
ret = false;
401
}
402
403
if (first) {
404
/* log_subcmds doesn't go through sudo_policy_main again to set this. */
405
if (def_log_subcmds) {
406
if (!sudoers_set_mode(MODE_POLICY_INTERCEPTED, UINT_MAX)) {
407
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
408
"unable to set 0x%x in ctx->mode", MODE_POLICY_INTERCEPTED);
409
}
410
}
411
first = false;
412
}
413
414
done:
415
debug_return_int(ret);
416
}
417
418
static int
419
sudoers_audit_reject(const char *plugin_name, unsigned int plugin_type,
420
const char *message, char * const command_info[], const char **errstr)
421
{
422
const struct sudoers_context *ctx = sudoers_get_context();
423
struct eventlog evlog;
424
int ret = true;
425
debug_decl(sudoers_audit_reject, SUDOERS_DEBUG_PLUGIN);
426
427
/* Skip reject events that sudoers generated itself. */
428
if (strncmp(plugin_name, "sudoers_", 8) == 0)
429
debug_return_int(true);
430
431
if (!def_log_denied)
432
debug_return_int(true);
433
434
if (audit_failure_int(ctx, ctx->runas.argv, message) != 0) {
435
if (!def_ignore_audit_errors)
436
ret = false;
437
}
438
439
audit_to_eventlog(ctx, &evlog, command_info, ctx->runas.argv, NULL, NULL);
440
if (!eventlog_reject(&evlog, 0, message, NULL, NULL))
441
ret = false;
442
443
if (!log_server_reject(ctx, &evlog, message))
444
ret = false;
445
446
debug_return_int(ret);
447
}
448
449
static int
450
sudoers_audit_error(const char *plugin_name, unsigned int plugin_type,
451
const char *message, char * const command_info[], const char **errstr)
452
{
453
const struct sudoers_context *ctx = sudoers_get_context();
454
struct eventlog evlog;
455
int ret = true;
456
debug_decl(sudoers_audit_error, SUDOERS_DEBUG_PLUGIN);
457
458
/* Skip error events that sudoers generated itself. */
459
if (strncmp(plugin_name, "sudoers_", 8) == 0)
460
debug_return_int(true);
461
462
if (audit_failure_int(ctx, ctx->runas.argv, message) != 0) {
463
if (!def_ignore_audit_errors)
464
ret = false;
465
}
466
467
audit_to_eventlog(ctx, &evlog, command_info, ctx->runas.argv, NULL, NULL);
468
if (!eventlog_alert(&evlog, 0, &evlog.event_time, message, NULL))
469
ret = false;
470
471
if (!log_server_alert(ctx, &evlog, message, NULL))
472
ret = false;
473
474
debug_return_int(ret);
475
}
476
477
static void
478
sudoers_audit_close(int status_type, int status)
479
{
480
log_server_exit(status_type, status);
481
}
482
483
static int
484
sudoers_audit_version(int verbose)
485
{
486
debug_decl(sudoers_audit_version, SUDOERS_DEBUG_PLUGIN);
487
488
sudo_printf(SUDO_CONV_INFO_MSG, "Sudoers audit plugin version %s\n",
489
PACKAGE_VERSION);
490
491
debug_return_int(true);
492
}
493
494
sudo_dso_public struct audit_plugin sudoers_audit = {
495
SUDO_AUDIT_PLUGIN,
496
SUDO_API_VERSION,
497
sudoers_audit_open,
498
sudoers_audit_close,
499
sudoers_audit_accept,
500
sudoers_audit_reject,
501
sudoers_audit_error,
502
sudoers_audit_version,
503
NULL, /* register_hooks */
504
NULL, /* deregister_hooks */
505
NULL /* event_alloc() filled in by sudo */
506
};
507
508