]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/journald-audit.c
license: LGPL-2.1+ -> LGPL-2.1-or-later
[thirdparty/systemd.git] / src / journal / journald-audit.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
875c2e22 2
b5efdb8a 3#include "alloc-util.h"
07630cea 4#include "audit-type.h"
0648f9be 5#include "errno-util.h"
3ffd4af2 6#include "fd-util.h"
afc5dbf3
LP
7#include "hexdecoct.h"
8#include "io-util.h"
3ffd4af2 9#include "journald-audit.h"
f5947a5e 10#include "missing_audit.h"
07630cea 11#include "string-util.h"
875c2e22
LP
12
13typedef struct MapField {
14 const char *audit_field;
15 const char *journal_field;
d3070fbd 16 int (*map)(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, size_t *n_iov);
875c2e22
LP
17} MapField;
18
d3070fbd 19static int map_simple_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, size_t *n_iov) {
875c2e22
LP
20 _cleanup_free_ char *c = NULL;
21 size_t l = 0, allocated = 0;
22 const char *e;
23
24 assert(field);
25 assert(p);
26 assert(iov);
27 assert(n_iov);
28
29 l = strlen(field);
30 allocated = l + 1;
31 c = malloc(allocated);
32 if (!c)
33 return -ENOMEM;
34
35 memcpy(c, field, l);
4c701096 36 for (e = *p; !IN_SET(*e, 0, ' '); e++) {
875c2e22
LP
37 if (!GREEDY_REALLOC(c, allocated, l+2))
38 return -ENOMEM;
39
40 c[l++] = *e;
41 }
42
43 c[l] = 0;
44
45 if (!GREEDY_REALLOC(*iov, *n_iov_allocated, *n_iov + 1))
46 return -ENOMEM;
47
dde26374 48 (*iov)[(*n_iov)++] = IOVEC_MAKE(c, l);
875c2e22
LP
49
50 *p = e;
51 c = NULL;
52
53 return 1;
54}
55
d3070fbd 56static int map_string_field_internal(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, size_t *n_iov, bool filter_printable) {
875c2e22
LP
57 _cleanup_free_ char *c = NULL;
58 const char *s, *e;
59 size_t l;
60
61 assert(field);
62 assert(p);
63 assert(iov);
64 assert(n_iov);
65
66 /* The kernel formats string fields in one of two formats. */
67
68 if (**p == '"') {
69 /* Normal quoted syntax */
70 s = *p + 1;
71 e = strchr(s, '"');
72 if (!e)
73 return 0;
74
75 l = strlen(field) + (e - s);
76 c = malloc(l+1);
77 if (!c)
78 return -ENOMEM;
79
80 *((char*) mempcpy(stpcpy(c, field), s, e - s)) = 0;
81
82 e += 1;
83
84 } else if (unhexchar(**p) >= 0) {
85 /* Hexadecimal escaping */
86 size_t allocated = 0;
87
88 l = strlen(field);
89 allocated = l + 2;
90 c = malloc(allocated);
91 if (!c)
92 return -ENOMEM;
93
94 memcpy(c, field, l);
4c701096 95 for (e = *p; !IN_SET(*e, 0, ' '); e += 2) {
875c2e22 96 int a, b;
78fe420f 97 uint8_t x;
875c2e22
LP
98
99 a = unhexchar(e[0]);
100 if (a < 0)
101 return 0;
102
103 b = unhexchar(e[1]);
104 if (b < 0)
105 return 0;
106
78fe420f
LP
107 x = ((uint8_t) a << 4 | (uint8_t) b);
108
109 if (filter_printable && x < (uint8_t) ' ')
110 x = (uint8_t) ' ';
111
875c2e22
LP
112 if (!GREEDY_REALLOC(c, allocated, l+2))
113 return -ENOMEM;
114
78fe420f 115 c[l++] = (char) x;
875c2e22
LP
116 }
117
118 c[l] = 0;
119 } else
120 return 0;
121
122 if (!GREEDY_REALLOC(*iov, *n_iov_allocated, *n_iov + 1))
123 return -ENOMEM;
124
dde26374 125 (*iov)[(*n_iov)++] = IOVEC_MAKE(c, l);
875c2e22
LP
126
127 *p = e;
128 c = NULL;
129
130 return 1;
131}
132
d3070fbd 133static int map_string_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, size_t *n_iov) {
78fe420f
LP
134 return map_string_field_internal(field, p, iov, n_iov_allocated, n_iov, false);
135}
136
d3070fbd 137static int map_string_field_printable(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, size_t *n_iov) {
78fe420f
LP
138 return map_string_field_internal(field, p, iov, n_iov_allocated, n_iov, true);
139}
140
d3070fbd 141static int map_generic_field(const char *prefix, const char **p, struct iovec **iov, size_t *n_iov_allocated, size_t *n_iov) {
875c2e22
LP
142 const char *e, *f;
143 char *c, *t;
144 int r;
145
146 /* Implements fallback mappings for all fields we don't know */
147
148 for (e = *p; e < *p + 16; e++) {
149
4c701096 150 if (IN_SET(*e, 0, ' '))
875c2e22
LP
151 return 0;
152
153 if (*e == '=')
154 break;
155
156 if (!((*e >= 'a' && *e <= 'z') ||
157 (*e >= 'A' && *e <= 'Z') ||
158 (*e >= '0' && *e <= '9') ||
4c701096 159 IN_SET(*e, '_', '-')))
875c2e22
LP
160 return 0;
161 }
162
163 if (e <= *p || e >= *p + 16)
164 return 0;
165
6e9417f5 166 c = newa(char, strlen(prefix) + (e - *p) + 2);
875c2e22
LP
167
168 t = stpcpy(c, prefix);
9833a66c
LP
169 for (f = *p; f < e; f++) {
170 char x;
171
172 if (*f >= 'a' && *f <= 'z')
173 x = (*f - 'a') + 'A'; /* uppercase */
174 else if (*f == '-')
175 x = '_'; /* dashes → underscores */
176 else
177 x = *f;
178
179 *(t++) = x;
180 }
875c2e22
LP
181 strcpy(t, "=");
182
313cefa1 183 e++;
875c2e22
LP
184
185 r = map_simple_field(c, &e, iov, n_iov_allocated, n_iov);
186 if (r < 0)
187 return r;
188
189 *p = e;
190 return r;
191}
192
f131770b 193/* Kernel fields are those occurring in the audit string before
875c2e22
LP
194 * msg='. All of these fields are trusted, hence carry the "_" prefix.
195 * We try to translate the fields we know into our native names. The
196 * other's are generically mapped to _AUDIT_FIELD_XYZ= */
197static const MapField map_fields_kernel[] = {
198
199 /* First, we map certain well-known audit fields into native
200 * well-known fields */
500cbc4e
LP
201 { "pid=", "_PID=", map_simple_field },
202 { "ppid=", "_PPID=", map_simple_field },
203 { "uid=", "_UID=", map_simple_field },
204 { "euid=", "_EUID=", map_simple_field },
205 { "fsuid=", "_FSUID=", map_simple_field },
206 { "gid=", "_GID=", map_simple_field },
207 { "egid=", "_EGID=", map_simple_field },
208 { "fsgid=", "_FSGID=", map_simple_field },
209 { "tty=", "_TTY=", map_simple_field },
210 { "ses=", "_AUDIT_SESSION=", map_simple_field },
211 { "auid=", "_AUDIT_LOGINUID=", map_simple_field },
212 { "subj=", "_SELINUX_CONTEXT=", map_simple_field },
213 { "comm=", "_COMM=", map_string_field },
214 { "exe=", "_EXE=", map_string_field },
215 { "proctitle=", "_CMDLINE=", map_string_field_printable },
875c2e22
LP
216
217 /* Some fields don't map to native well-known fields. However,
218 * we know that they are string fields, hence let's undo
219 * string field escaping for them, though we stick to the
220 * generic field names. */
500cbc4e
LP
221 { "path=", "_AUDIT_FIELD_PATH=", map_string_field },
222 { "dev=", "_AUDIT_FIELD_DEV=", map_string_field },
223 { "name=", "_AUDIT_FIELD_NAME=", map_string_field },
875c2e22
LP
224 {}
225};
226
f131770b 227/* Userspace fields are those occurring in the audit string after
875c2e22
LP
228 * msg='. All of these fields are untrusted, hence carry no "_"
229 * prefix. We map the fields we don't know to AUDIT_FIELD_XYZ= */
230static const MapField map_fields_userspace[] = {
500cbc4e
LP
231 { "cwd=", "AUDIT_FIELD_CWD=", map_string_field },
232 { "cmd=", "AUDIT_FIELD_CMD=", map_string_field },
233 { "acct=", "AUDIT_FIELD_ACCT=", map_string_field },
234 { "exe=", "AUDIT_FIELD_EXE=", map_string_field },
235 { "comm=", "AUDIT_FIELD_COMM=", map_string_field },
875c2e22
LP
236 {}
237};
238
239static int map_all_fields(
240 const char *p,
241 const MapField map_fields[],
242 const char *prefix,
243 bool handle_msg,
244 struct iovec **iov,
245 size_t *n_iov_allocated,
d3070fbd 246 size_t *n_iov) {
875c2e22
LP
247
248 int r;
249
250 assert(p);
251 assert(iov);
252 assert(n_iov_allocated);
253 assert(n_iov);
254
255 for (;;) {
256 bool mapped = false;
257 const MapField *m;
258 const char *v;
259
260 p += strspn(p, WHITESPACE);
261
262 if (*p == 0)
263 return 0;
264
265 if (handle_msg) {
266 v = startswith(p, "msg='");
267 if (v) {
6d946490 268 _cleanup_free_ char *c = NULL;
875c2e22 269 const char *e;
875c2e22
LP
270
271 /* Userspace message. It's enclosed in
272 simple quotation marks, is not
273 escaped, but the last field in the
274 line, hence let's remove the
275 quotation mark, and apply the
276 userspace mapping instead of the
277 kernel mapping. */
278
279 e = endswith(v, "'");
280 if (!e)
281 return 0; /* don't continue splitting up if the final quotation mark is missing */
282
6d946490
YW
283 c = strndup(v, e - v);
284 if (!c)
285 return -ENOMEM;
286
875c2e22
LP
287 return map_all_fields(c, map_fields_userspace, "AUDIT_FIELD_", false, iov, n_iov_allocated, n_iov);
288 }
289 }
290
291 /* Try to map the kernel fields to our own names */
292 for (m = map_fields; m->audit_field; m++) {
293 v = startswith(p, m->audit_field);
294 if (!v)
295 continue;
296
297 r = m->map(m->journal_field, &v, iov, n_iov_allocated, n_iov);
23bbb0de
MS
298 if (r < 0)
299 return log_debug_errno(r, "Failed to parse audit array: %m");
875c2e22
LP
300
301 if (r > 0) {
302 mapped = true;
303 p = v;
304 break;
305 }
306 }
307
308 if (!mapped) {
309 r = map_generic_field(prefix, &p, iov, n_iov_allocated, n_iov);
23bbb0de
MS
310 if (r < 0)
311 return log_debug_errno(r, "Failed to parse audit array: %m");
875c2e22 312
ece174c5 313 if (r == 0)
875c2e22
LP
314 /* Couldn't process as generic field, let's just skip over it */
315 p += strcspn(p, WHITESPACE);
875c2e22
LP
316 }
317 }
318}
319
090a20cf 320void process_audit_string(Server *s, int type, const char *data, size_t size) {
d3070fbd 321 size_t n_iov_allocated = 0, n_iov = 0, z;
875c2e22 322 _cleanup_free_ struct iovec *iov = NULL;
875c2e22 323 uint64_t seconds, msec, id;
8bb3626d 324 const char *p, *type_name;
875c2e22
LP
325 char id_field[sizeof("_AUDIT_ID=") + DECIMAL_STR_MAX(uint64_t)],
326 type_field[sizeof("_AUDIT_TYPE=") + DECIMAL_STR_MAX(int)],
327 source_time_field[sizeof("_SOURCE_REALTIME_TIMESTAMP=") + DECIMAL_STR_MAX(usec_t)];
14c1abcc 328 char *m, *type_field_name;
d3070fbd 329 int k;
875c2e22
LP
330
331 assert(s);
332
333 if (size <= 0)
334 return;
335
336 if (!data)
337 return;
338
339 /* Note that the input buffer is NUL terminated, but let's
340 * check whether there is a spurious NUL byte */
341 if (memchr(data, 0, size))
342 return;
343
344 p = startswith(data, "audit");
345 if (!p)
346 return;
347
1dab14ab 348 k = 0;
1fa2f38f 349 if (sscanf(p, "(%" PRIu64 ".%" PRIu64 ":%" PRIu64 "):%n",
875c2e22
LP
350 &seconds,
351 &msec,
352 &id,
1dab14ab 353 &k) != 3 || k == 0)
875c2e22
LP
354 return;
355
356 p += k;
5034c7bc
LP
357 p += strspn(p, WHITESPACE);
358
359 if (isempty(p))
360 return;
875c2e22 361
14c1abcc 362 n_iov_allocated = N_IOVEC_META_FIELDS + 8;
875c2e22
LP
363 iov = new(struct iovec, n_iov_allocated);
364 if (!iov) {
365 log_oom();
366 return;
367 }
368
e6a7ec4b 369 iov[n_iov++] = IOVEC_MAKE_STRING("_TRANSPORT=audit");
875c2e22
LP
370
371 sprintf(source_time_field, "_SOURCE_REALTIME_TIMESTAMP=%" PRIu64,
372 (usec_t) seconds * USEC_PER_SEC + (usec_t) msec * USEC_PER_MSEC);
e6a7ec4b 373 iov[n_iov++] = IOVEC_MAKE_STRING(source_time_field);
875c2e22
LP
374
375 sprintf(type_field, "_AUDIT_TYPE=%i", type);
e6a7ec4b 376 iov[n_iov++] = IOVEC_MAKE_STRING(type_field);
875c2e22
LP
377
378 sprintf(id_field, "_AUDIT_ID=%" PRIu64, id);
e6a7ec4b 379 iov[n_iov++] = IOVEC_MAKE_STRING(id_field);
875c2e22 380
d6f4302b 381 assert_cc(4 == LOG_FAC(LOG_AUTH));
e6a7ec4b
LP
382 iov[n_iov++] = IOVEC_MAKE_STRING("SYSLOG_FACILITY=4");
383 iov[n_iov++] = IOVEC_MAKE_STRING("SYSLOG_IDENTIFIER=audit");
cd556b6c 384
8bb3626d
ZJS
385 type_name = audit_type_name_alloca(type);
386
14c1abcc
JR
387 type_field_name = strjoina("_AUDIT_TYPE_NAME=", type_name);
388 iov[n_iov++] = IOVEC_MAKE_STRING(type_field_name);
389
8bb3626d 390 m = strjoina("MESSAGE=", type_name, " ", p);
e6a7ec4b 391 iov[n_iov++] = IOVEC_MAKE_STRING(m);
875c2e22
LP
392
393 z = n_iov;
394
395 map_all_fields(p, map_fields_kernel, "_AUDIT_FIELD_", true, &iov, &n_iov_allocated, &n_iov);
396
397 if (!GREEDY_REALLOC(iov, n_iov_allocated, n_iov + N_IOVEC_META_FIELDS)) {
398 log_oom();
399 goto finish;
400 }
401
22e3a02b 402 server_dispatch_message(s, iov, n_iov, n_iov_allocated, NULL, NULL, LOG_NOTICE, 0);
875c2e22
LP
403
404finish:
405 /* free() all entries that map_all_fields() added. All others
406 * are allocated on the stack or are constant. */
407
408 for (; z < n_iov; z++)
409 free(iov[z].iov_base);
410}
411
412void server_process_audit_message(
413 Server *s,
414 const void *buffer,
415 size_t buffer_size,
416 const struct ucred *ucred,
875c2e22
LP
417 const union sockaddr_union *sa,
418 socklen_t salen) {
419
420 const struct nlmsghdr *nl = buffer;
421
422 assert(s);
423
424 if (buffer_size < ALIGN(sizeof(struct nlmsghdr)))
425 return;
426
427 assert(buffer);
428
429 /* Filter out fake data */
430 if (!sa ||
431 salen != sizeof(struct sockaddr_nl) ||
432 sa->nl.nl_family != AF_NETLINK ||
433 sa->nl.nl_pid != 0) {
434 log_debug("Audit netlink message from invalid sender.");
435 return;
436 }
437
438 if (!ucred || ucred->pid != 0) {
439 log_debug("Audit netlink message with invalid credentials.");
440 return;
441 }
442
443 if (!NLMSG_OK(nl, buffer_size)) {
444 log_error("Audit netlink message truncated.");
445 return;
446 }
447
448 /* Ignore special Netlink messages */
449 if (IN_SET(nl->nlmsg_type, NLMSG_NOOP, NLMSG_ERROR))
450 return;
451
5238e957 452 /* Except AUDIT_USER, all messages below AUDIT_FIRST_USER_MSG are control messages, let's ignore those */
ed563b60 453 if (nl->nlmsg_type < AUDIT_FIRST_USER_MSG && nl->nlmsg_type != AUDIT_USER)
875c2e22
LP
454 return;
455
0b97208d 456 process_audit_string(s, nl->nlmsg_type, NLMSG_DATA(nl), nl->nlmsg_len - ALIGN(sizeof(struct nlmsghdr)));
875c2e22
LP
457}
458
4d9ced99
LP
459static int enable_audit(int fd, bool b) {
460 struct {
461 union {
462 struct nlmsghdr header;
463 uint8_t header_space[NLMSG_HDRLEN];
464 };
465 struct audit_status body;
466 } _packed_ request = {
467 .header.nlmsg_len = NLMSG_LENGTH(sizeof(struct audit_status)),
468 .header.nlmsg_type = AUDIT_SET,
469 .header.nlmsg_flags = NLM_F_REQUEST,
470 .header.nlmsg_seq = 1,
471 .header.nlmsg_pid = 0,
472 .body.mask = AUDIT_STATUS_ENABLED,
473 .body.enabled = b,
474 };
475 union sockaddr_union sa = {
476 .nl.nl_family = AF_NETLINK,
477 .nl.nl_pid = 0,
478 };
479 struct iovec iovec = {
480 .iov_base = &request,
481 .iov_len = NLMSG_LENGTH(sizeof(struct audit_status)),
482 };
483 struct msghdr mh = {
484 .msg_iov = &iovec,
485 .msg_iovlen = 1,
486 .msg_name = &sa.sa,
487 .msg_namelen = sizeof(sa.nl),
488 };
489
490 ssize_t n;
491
492 n = sendmsg(fd, &mh, MSG_NOSIGNAL);
493 if (n < 0)
494 return -errno;
495 if (n != NLMSG_LENGTH(sizeof(struct audit_status)))
496 return -EIO;
497
498 /* We don't wait for the result here, we can't do anything
499 * about it anyway */
500
501 return 0;
502}
503
875c2e22 504int server_open_audit(Server *s) {
875c2e22
LP
505 int r;
506
507 if (s->audit_fd < 0) {
508 static const union sockaddr_union sa = {
509 .nl.nl_family = AF_NETLINK,
510 .nl.nl_pid = 0,
511 .nl.nl_groups = AUDIT_NLGRP_READLOG,
512 };
513
514 s->audit_fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_AUDIT);
515 if (s->audit_fd < 0) {
0648f9be 516 if (ERRNO_IS_NOT_SUPPORTED(errno))
875c2e22
LP
517 log_debug("Audit not supported in the kernel.");
518 else
56f64d95 519 log_warning_errno(errno, "Failed to create audit socket, ignoring: %m");
875c2e22
LP
520
521 return 0;
522 }
523
417a7fdc
LP
524 if (bind(s->audit_fd, &sa.sa, sizeof(sa.nl)) < 0) {
525 log_warning_errno(errno,
526 "Failed to join audit multicast group. "
527 "The kernel is probably too old or multicast reading is not supported. "
528 "Ignoring: %m");
529 s->audit_fd = safe_close(s->audit_fd);
530 return 0;
531 }
875c2e22 532 } else
48440643 533 (void) fd_nonblock(s->audit_fd, true);
875c2e22 534
2ff48e98 535 r = setsockopt_int(s->audit_fd, SOL_SOCKET, SO_PASSCRED, true);
4a62c710 536 if (r < 0)
2ff48e98 537 return log_error_errno(r, "Failed to set SO_PASSCRED on audit socket: %m");
875c2e22 538
8531ae70 539 r = sd_event_add_io(s->event, &s->audit_event_source, s->audit_fd, EPOLLIN, server_process_datagram, s);
23bbb0de
MS
540 if (r < 0)
541 return log_error_errno(r, "Failed to add audit fd to event loop: %m");
875c2e22 542
511e03a3
LP
543 if (s->set_audit >= 0) {
544 /* We are listening now, try to enable audit if configured so */
545 r = enable_audit(s->audit_fd, s->set_audit);
546 if (r < 0)
547 log_warning_errno(r, "Failed to issue audit enable call: %m");
548 else if (s->set_audit > 0)
549 log_debug("Auditing in kernel turned on.");
550 else
551 log_debug("Auditing in kernel turned off.");
552 }
4d9ced99 553
875c2e22
LP
554 return 0;
555}