mirror of
https://github.com/systemd/systemd.git
synced 2024-12-22 17:35:35 +03:00
journald: if available pull audit messages from the kernel into journal logs
This commit is contained in:
parent
8457f8d6ac
commit
875c2e220e
@ -4073,6 +4073,8 @@ libsystemd_journal_core_la_SOURCES = \
|
||||
src/journal/journald-wall.h \
|
||||
src/journal/journald-native.c \
|
||||
src/journal/journald-native.h \
|
||||
src/journal/journald-audit.c \
|
||||
src/journal/journald-audit.h \
|
||||
src/journal/journald-rate-limit.c \
|
||||
src/journal/journald-rate-limit.h \
|
||||
src/journal/journal-internal.h
|
||||
@ -4224,7 +4226,8 @@ bin_PROGRAMS += \
|
||||
|
||||
dist_systemunit_DATA += \
|
||||
units/systemd-journald.socket \
|
||||
units/systemd-journald-dev-log.socket
|
||||
units/systemd-journald-dev-log.socket \
|
||||
units/systemd-journald-audit.socket
|
||||
|
||||
nodist_systemunit_DATA += \
|
||||
units/systemd-journald.service \
|
||||
@ -4243,7 +4246,8 @@ dist_catalog_DATA = \
|
||||
|
||||
SOCKETS_TARGET_WANTS += \
|
||||
systemd-journald.socket \
|
||||
systemd-journald-dev-log.socket
|
||||
systemd-journald-dev-log.socket \
|
||||
systemd-journald-audit.socket
|
||||
|
||||
SYSINIT_TARGET_WANTS += \
|
||||
systemd-journald.service \
|
||||
|
489
src/journal/journald-audit.c
Normal file
489
src/journal/journald-audit.c
Normal file
@ -0,0 +1,489 @@
|
||||
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
|
||||
|
||||
/***
|
||||
This file is part of systemd.
|
||||
|
||||
Copyright 2014 Lennart Poettering
|
||||
|
||||
systemd is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
systemd is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with systemd; If not, see <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#include "missing.h"
|
||||
#include "journald-audit.h"
|
||||
|
||||
typedef struct MapField {
|
||||
const char *audit_field;
|
||||
const char *journal_field;
|
||||
int (*map)(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov);
|
||||
} MapField;
|
||||
|
||||
static int map_simple_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) {
|
||||
_cleanup_free_ char *c = NULL;
|
||||
size_t l = 0, allocated = 0;
|
||||
const char *e;
|
||||
|
||||
assert(field);
|
||||
assert(p);
|
||||
assert(iov);
|
||||
assert(n_iov);
|
||||
|
||||
l = strlen(field);
|
||||
allocated = l + 1;
|
||||
c = malloc(allocated);
|
||||
if (!c)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy(c, field, l);
|
||||
for (e = *p; *e != ' ' && *e != 0; e++) {
|
||||
if (!GREEDY_REALLOC(c, allocated, l+2))
|
||||
return -ENOMEM;
|
||||
|
||||
c[l++] = *e;
|
||||
}
|
||||
|
||||
c[l] = 0;
|
||||
|
||||
if (!GREEDY_REALLOC(*iov, *n_iov_allocated, *n_iov + 1))
|
||||
return -ENOMEM;
|
||||
|
||||
(*iov)[*n_iov].iov_base = c;
|
||||
(*iov)[*n_iov].iov_len = l;
|
||||
(*n_iov) ++;
|
||||
|
||||
*p = e;
|
||||
c = NULL;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int map_string_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) {
|
||||
_cleanup_free_ char *c = NULL;
|
||||
const char *s, *e;
|
||||
size_t l;
|
||||
|
||||
assert(field);
|
||||
assert(p);
|
||||
assert(iov);
|
||||
assert(n_iov);
|
||||
|
||||
/* The kernel formats string fields in one of two formats. */
|
||||
|
||||
if (**p == '"') {
|
||||
/* Normal quoted syntax */
|
||||
s = *p + 1;
|
||||
e = strchr(s, '"');
|
||||
if (!e)
|
||||
return 0;
|
||||
|
||||
l = strlen(field) + (e - s);
|
||||
c = malloc(l+1);
|
||||
if (!c)
|
||||
return -ENOMEM;
|
||||
|
||||
*((char*) mempcpy(stpcpy(c, field), s, e - s)) = 0;
|
||||
|
||||
e += 1;
|
||||
|
||||
} else if (unhexchar(**p) >= 0) {
|
||||
/* Hexadecimal escaping */
|
||||
size_t allocated = 0;
|
||||
|
||||
l = strlen(field);
|
||||
allocated = l + 2;
|
||||
c = malloc(allocated);
|
||||
if (!c)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy(c, field, l);
|
||||
for (e = *p; *e != ' ' && *e != 0; e += 2) {
|
||||
int a, b;
|
||||
|
||||
a = unhexchar(e[0]);
|
||||
if (a < 0)
|
||||
return 0;
|
||||
|
||||
b = unhexchar(e[1]);
|
||||
if (b < 0)
|
||||
return 0;
|
||||
|
||||
if (!GREEDY_REALLOC(c, allocated, l+2))
|
||||
return -ENOMEM;
|
||||
|
||||
c[l++] = (char) ((uint8_t) a << 4 | (uint8_t) b);
|
||||
}
|
||||
|
||||
c[l] = 0;
|
||||
} else
|
||||
return 0;
|
||||
|
||||
if (!GREEDY_REALLOC(*iov, *n_iov_allocated, *n_iov + 1))
|
||||
return -ENOMEM;
|
||||
|
||||
(*iov)[*n_iov].iov_base = c;
|
||||
(*iov)[*n_iov].iov_len = l;
|
||||
(*n_iov) ++;
|
||||
|
||||
*p = e;
|
||||
c = NULL;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int map_generic_field(const char *prefix, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) {
|
||||
const char *e, *f;
|
||||
char *c, *t;
|
||||
int r;
|
||||
|
||||
/* Implements fallback mappings for all fields we don't know */
|
||||
|
||||
for (e = *p; e < *p + 16; e++) {
|
||||
|
||||
if (*e == 0 || *e == ' ')
|
||||
return 0;
|
||||
|
||||
if (*e == '=')
|
||||
break;
|
||||
|
||||
if (!((*e >= 'a' && *e <= 'z') ||
|
||||
(*e >= 'A' && *e <= 'Z') ||
|
||||
(*e >= '0' && *e <= '9') ||
|
||||
(*e == '_')))
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (e <= *p || e >= *p + 16)
|
||||
return 0;
|
||||
|
||||
c = alloca(strlen(prefix) + (e - *p) + 2);
|
||||
|
||||
t = stpcpy(c, prefix);
|
||||
for (f = *p; f < e; f++)
|
||||
*(t++) = *f >= 'a' && *f <= 'z' ? ((*f - 'a') + 'A') : *f;
|
||||
strcpy(t, "=");
|
||||
|
||||
e ++;
|
||||
|
||||
r = map_simple_field(c, &e, iov, n_iov_allocated, n_iov);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*p = e;
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Kernel fields are those occuring in the audit string before
|
||||
* msg='. All of these fields are trusted, hence carry the "_" prefix.
|
||||
* We try to translate the fields we know into our native names. The
|
||||
* other's are generically mapped to _AUDIT_FIELD_XYZ= */
|
||||
static const MapField map_fields_kernel[] = {
|
||||
|
||||
/* First, we map certain well-known audit fields into native
|
||||
* well-known fields */
|
||||
{ "pid=", "_PID=", map_simple_field },
|
||||
{ "ppid=", "_PPID=", map_simple_field },
|
||||
{ "uid=", "_UID=", map_simple_field },
|
||||
{ "euid=", "_EUID=", map_simple_field },
|
||||
{ "fsuid=", "_FSUID=", map_simple_field },
|
||||
{ "gid=", "_GID=", map_simple_field },
|
||||
{ "egid=", "_EGID=", map_simple_field },
|
||||
{ "fsgid=", "_FSGID=", map_simple_field },
|
||||
{ "tty=", "_TTY=", map_simple_field },
|
||||
{ "ses=", "_AUDIT_SESSION=", map_simple_field },
|
||||
{ "auid=", "_AUDIT_LOGINUID=", map_simple_field },
|
||||
{ "subj=", "_SELINUX_CONTEXT=", map_simple_field },
|
||||
{ "comm=", "_COMM=", map_string_field },
|
||||
{ "exe=", "_EXE=", map_string_field },
|
||||
{ "proctitle=", "_CMDLINE=", map_string_field },
|
||||
|
||||
/* Some fields don't map to native well-known fields. However,
|
||||
* we know that they are string fields, hence let's undo
|
||||
* string field escaping for them, though we stick to the
|
||||
* generic field names. */
|
||||
{ "path=", "_AUDIT_FIELD_PATH=", map_string_field },
|
||||
{ "dev=", "_AUDIT_FIELD_DEV=", map_string_field },
|
||||
{ "name=", "_AUDIT_FIELD_NAME=", map_string_field },
|
||||
{}
|
||||
};
|
||||
|
||||
/* Userspace fields are thos occuring in the audit string after
|
||||
* msg='. All of these fields are untrusted, hence carry no "_"
|
||||
* prefix. We map the fields we don't know to AUDIT_FIELD_XYZ= */
|
||||
static const MapField map_fields_userspace[] = {
|
||||
{ "cwd=", "AUDIT_FIELD_CWD=", map_string_field },
|
||||
{ "cmd=", "AUDIT_FIELD_CMD=", map_string_field },
|
||||
{ "acct=", "AUDIT_FIELD_ACCT=", map_string_field },
|
||||
{ "exe=", "AUDIT_FIELD_EXE=", map_string_field },
|
||||
{ "comm=", "AUDIT_FIELD_COMM=", map_string_field },
|
||||
{}
|
||||
};
|
||||
|
||||
static int map_all_fields(
|
||||
const char *p,
|
||||
const MapField map_fields[],
|
||||
const char *prefix,
|
||||
bool handle_msg,
|
||||
struct iovec **iov,
|
||||
size_t *n_iov_allocated,
|
||||
unsigned *n_iov) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(p);
|
||||
assert(iov);
|
||||
assert(n_iov_allocated);
|
||||
assert(n_iov);
|
||||
|
||||
for (;;) {
|
||||
bool mapped = false;
|
||||
const MapField *m;
|
||||
const char *v;
|
||||
|
||||
p += strspn(p, WHITESPACE);
|
||||
|
||||
if (*p == 0)
|
||||
return 0;
|
||||
|
||||
if (handle_msg) {
|
||||
v = startswith(p, "msg='");
|
||||
if (v) {
|
||||
const char *e;
|
||||
char *c;
|
||||
|
||||
/* Userspace message. It's enclosed in
|
||||
simple quotation marks, is not
|
||||
escaped, but the last field in the
|
||||
line, hence let's remove the
|
||||
quotation mark, and apply the
|
||||
userspace mapping instead of the
|
||||
kernel mapping. */
|
||||
|
||||
e = endswith(v, "'");
|
||||
if (!e)
|
||||
return 0; /* don't continue splitting up if the final quotation mark is missing */
|
||||
|
||||
c = strndupa(v, e - v);
|
||||
return map_all_fields(c, map_fields_userspace, "AUDIT_FIELD_", false, iov, n_iov_allocated, n_iov);
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to map the kernel fields to our own names */
|
||||
for (m = map_fields; m->audit_field; m++) {
|
||||
v = startswith(p, m->audit_field);
|
||||
if (!v)
|
||||
continue;
|
||||
|
||||
r = m->map(m->journal_field, &v, iov, n_iov_allocated, n_iov);
|
||||
if (r < 0) {
|
||||
log_debug("Failed to parse audit array: %s", strerror(-r));
|
||||
return r;
|
||||
}
|
||||
|
||||
if (r > 0) {
|
||||
mapped = true;
|
||||
p = v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mapped) {
|
||||
r = map_generic_field(prefix, &p, iov, n_iov_allocated, n_iov);
|
||||
if (r < 0) {
|
||||
log_debug("Failed to parse audit array: %s", strerror(-r));
|
||||
return r;
|
||||
}
|
||||
|
||||
if (r == 0) {
|
||||
/* Couldn't process as generic field, let's just skip over it */
|
||||
p += strcspn(p, WHITESPACE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void process_audit_string(Server *s, int type, const char *data, size_t size, const struct timeval *tv) {
|
||||
_cleanup_free_ struct iovec *iov = NULL;
|
||||
size_t n_iov_allocated = 0;
|
||||
unsigned n_iov = 0, k;
|
||||
uint64_t seconds, msec, id;
|
||||
const char *p;
|
||||
unsigned z;
|
||||
char id_field[sizeof("_AUDIT_ID=") + DECIMAL_STR_MAX(uint64_t)],
|
||||
type_field[sizeof("_AUDIT_TYPE=") + DECIMAL_STR_MAX(int)],
|
||||
source_time_field[sizeof("_SOURCE_REALTIME_TIMESTAMP=") + DECIMAL_STR_MAX(usec_t)];
|
||||
const char *m;
|
||||
|
||||
assert(s);
|
||||
|
||||
if (size <= 0)
|
||||
return;
|
||||
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
/* Note that the input buffer is NUL terminated, but let's
|
||||
* check whether there is a spurious NUL byte */
|
||||
if (memchr(data, 0, size))
|
||||
return;
|
||||
|
||||
p = startswith(data, "audit");
|
||||
if (!p)
|
||||
return;
|
||||
|
||||
if (sscanf(p, "(%" PRIi64 ".%" PRIi64 ":%" PRIi64 "): %n",
|
||||
&seconds,
|
||||
&msec,
|
||||
&id,
|
||||
&k) != 3)
|
||||
return;
|
||||
|
||||
p += k;
|
||||
|
||||
n_iov_allocated = N_IOVEC_META_FIELDS + 5;
|
||||
iov = new(struct iovec, n_iov_allocated);
|
||||
if (!iov) {
|
||||
log_oom();
|
||||
return;
|
||||
}
|
||||
|
||||
IOVEC_SET_STRING(iov[n_iov++], "_TRANSPORT=audit");
|
||||
|
||||
sprintf(source_time_field, "_SOURCE_REALTIME_TIMESTAMP=%" PRIu64,
|
||||
(usec_t) seconds * USEC_PER_SEC + (usec_t) msec * USEC_PER_MSEC);
|
||||
IOVEC_SET_STRING(iov[n_iov++], source_time_field);
|
||||
|
||||
sprintf(type_field, "_AUDIT_TYPE=%i", type);
|
||||
IOVEC_SET_STRING(iov[n_iov++], type_field);
|
||||
|
||||
sprintf(id_field, "_AUDIT_ID=%" PRIu64, id);
|
||||
IOVEC_SET_STRING(iov[n_iov++], id_field);
|
||||
|
||||
m = strappenda("MESSAGE=", data);
|
||||
IOVEC_SET_STRING(iov[n_iov++], m);
|
||||
|
||||
z = n_iov;
|
||||
|
||||
map_all_fields(p, map_fields_kernel, "_AUDIT_FIELD_", true, &iov, &n_iov_allocated, &n_iov);
|
||||
|
||||
if (!GREEDY_REALLOC(iov, n_iov_allocated, n_iov + N_IOVEC_META_FIELDS)) {
|
||||
log_oom();
|
||||
goto finish;
|
||||
}
|
||||
|
||||
server_dispatch_message(s, iov, n_iov, n_iov_allocated, NULL, tv, NULL, 0, NULL, LOG_NOTICE, 0);
|
||||
|
||||
finish:
|
||||
/* free() all entries that map_all_fields() added. All others
|
||||
* are allocated on the stack or are constant. */
|
||||
|
||||
for (; z < n_iov; z++)
|
||||
free(iov[z].iov_base);
|
||||
}
|
||||
|
||||
void server_process_audit_message(
|
||||
Server *s,
|
||||
const void *buffer,
|
||||
size_t buffer_size,
|
||||
const struct ucred *ucred,
|
||||
const struct timeval *tv,
|
||||
const union sockaddr_union *sa,
|
||||
socklen_t salen) {
|
||||
|
||||
const struct nlmsghdr *nl = buffer;
|
||||
|
||||
assert(s);
|
||||
|
||||
if (buffer_size < ALIGN(sizeof(struct nlmsghdr)))
|
||||
return;
|
||||
|
||||
assert(buffer);
|
||||
|
||||
/* Filter out fake data */
|
||||
if (!sa ||
|
||||
salen != sizeof(struct sockaddr_nl) ||
|
||||
sa->nl.nl_family != AF_NETLINK ||
|
||||
sa->nl.nl_pid != 0) {
|
||||
log_debug("Audit netlink message from invalid sender.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ucred || ucred->pid != 0) {
|
||||
log_debug("Audit netlink message with invalid credentials.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NLMSG_OK(nl, buffer_size)) {
|
||||
log_error("Audit netlink message truncated.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Ignore special Netlink messages */
|
||||
if (IN_SET(nl->nlmsg_type, NLMSG_NOOP, NLMSG_ERROR))
|
||||
return;
|
||||
|
||||
/* Below AUDIT_FIRST_USER_MSG theer are only control messages, let's ignore those */
|
||||
if (nl->nlmsg_type < AUDIT_FIRST_USER_MSG)
|
||||
return;
|
||||
|
||||
process_audit_string(s, nl->nlmsg_type, NLMSG_DATA(nl), nl->nlmsg_len - ALIGN(sizeof(struct nlmsghdr)), tv);
|
||||
}
|
||||
|
||||
int server_open_audit(Server *s) {
|
||||
static const int one = 1;
|
||||
int r;
|
||||
|
||||
if (s->audit_fd < 0) {
|
||||
static const union sockaddr_union sa = {
|
||||
.nl.nl_family = AF_NETLINK,
|
||||
.nl.nl_pid = 0,
|
||||
.nl.nl_groups = AUDIT_NLGRP_READLOG,
|
||||
};
|
||||
|
||||
s->audit_fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_AUDIT);
|
||||
if (s->audit_fd < 0) {
|
||||
if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT)
|
||||
log_debug("Audit not supported in the kernel.");
|
||||
else
|
||||
log_warning("Failed to create audit socket, ignoring: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = bind(s->audit_fd, &sa.sa, sizeof(sa.nl));
|
||||
if (r < 0) {
|
||||
log_error("Failed to join audit multicast group: %m");
|
||||
return -errno;
|
||||
}
|
||||
} else
|
||||
fd_nonblock(s->audit_fd, 1);
|
||||
|
||||
r = setsockopt(s->audit_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one));
|
||||
if (r < 0) {
|
||||
log_error("Failed to set SO_PASSCRED on audit socket: %m");
|
||||
return -errno;
|
||||
}
|
||||
|
||||
r = setsockopt(s->audit_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one));
|
||||
if (r < 0) {
|
||||
log_error("Failed to set SO_TIMESTAMP on audit socket: %m");
|
||||
return -errno;
|
||||
}
|
||||
|
||||
r = sd_event_add_io(s->event, &s->audit_event_source, s->audit_fd, EPOLLIN, process_datagram, s);
|
||||
if (r < 0) {
|
||||
log_error("Failed to add audit fd to event loop: %s", strerror(-r));
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
29
src/journal/journald-audit.h
Normal file
29
src/journal/journald-audit.h
Normal file
@ -0,0 +1,29 @@
|
||||
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/***
|
||||
This file is part of systemd.
|
||||
|
||||
Copyright 2014 Lennart Poettering
|
||||
|
||||
systemd is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
systemd is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with systemd; If not, see <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#include "socket-util.h"
|
||||
#include "journald-server.h"
|
||||
|
||||
void server_process_audit_message(Server *s, const void *buffer, size_t buffer_size, const struct ucred *ucred, const struct timeval *tv, const union sockaddr_union *sa, socklen_t salen);
|
||||
|
||||
int server_open_audit(Server*s);
|
@ -50,6 +50,7 @@
|
||||
#include "journald-stream.h"
|
||||
#include "journald-console.h"
|
||||
#include "journald-native.h"
|
||||
#include "journald-audit.h"
|
||||
#include "journald-server.h"
|
||||
|
||||
#ifdef HAVE_ACL
|
||||
@ -1112,7 +1113,7 @@ int process_datagram(sd_event_source *es, int fd, uint32_t revents, void *userda
|
||||
Server *s = userdata;
|
||||
|
||||
assert(s);
|
||||
assert(fd == s->native_fd || fd == s->syslog_fd);
|
||||
assert(fd == s->native_fd || fd == s->syslog_fd || fd == s->audit_fd);
|
||||
|
||||
if (revents != EPOLLIN) {
|
||||
log_error("Got invalid event from epoll for datagram fd: %"PRIx32, revents);
|
||||
@ -1142,28 +1143,37 @@ int process_datagram(sd_event_source *es, int fd, uint32_t revents, void *userda
|
||||
CMSG_SPACE(sizeof(int)) + /* fd */
|
||||
CMSG_SPACE(NAME_MAX)]; /* selinux label */
|
||||
} control = {};
|
||||
union sockaddr_union sa = {};
|
||||
struct msghdr msghdr = {
|
||||
.msg_iov = &iovec,
|
||||
.msg_iovlen = 1,
|
||||
.msg_control = &control,
|
||||
.msg_controllen = sizeof(control),
|
||||
.msg_name = &sa,
|
||||
.msg_namelen = sizeof(sa),
|
||||
};
|
||||
|
||||
ssize_t n;
|
||||
int v;
|
||||
int *fds = NULL;
|
||||
unsigned n_fds = 0;
|
||||
int v = 0;
|
||||
size_t m;
|
||||
|
||||
if (ioctl(fd, SIOCINQ, &v) < 0) {
|
||||
log_error("SIOCINQ failed: %m");
|
||||
return -errno;
|
||||
}
|
||||
/* Try to get the right size, if we can. (Not all
|
||||
* sockets support SIOCINQ, hence we just try, but
|
||||
* don't rely on it. */
|
||||
(void) ioctl(fd, SIOCINQ, &v);
|
||||
|
||||
if (!GREEDY_REALLOC(s->buffer, s->buffer_size, LINE_MAX + (size_t) v))
|
||||
/* Fix it up, if it is too small. We use the same fixed value as auditd here. Awful!*/
|
||||
m = PAGE_ALIGN(MAX3((size_t) v + 1,
|
||||
(size_t) LINE_MAX,
|
||||
ALIGN(sizeof(struct nlmsghdr)) + ALIGN((size_t) MAX_AUDIT_MESSAGE_LENGTH)) + 1);
|
||||
|
||||
if (!GREEDY_REALLOC(s->buffer, s->buffer_size, m))
|
||||
return log_oom();
|
||||
|
||||
iovec.iov_base = s->buffer;
|
||||
iovec.iov_len = s->buffer_size;
|
||||
iovec.iov_len = s->buffer_size - 1; /* Leave room for trailing NUL we add later */
|
||||
|
||||
n = recvmsg(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
|
||||
if (n < 0) {
|
||||
@ -1195,20 +1205,30 @@ int process_datagram(sd_event_source *es, int fd, uint32_t revents, void *userda
|
||||
}
|
||||
}
|
||||
|
||||
/* And a trailing NUL, just in case */
|
||||
s->buffer[n] = 0;
|
||||
|
||||
if (fd == s->syslog_fd) {
|
||||
if (n > 0 && n_fds == 0) {
|
||||
s->buffer[n] = 0;
|
||||
if (n > 0 && n_fds == 0)
|
||||
server_process_syslog_message(s, strstrip(s->buffer), ucred, tv, label, label_len);
|
||||
} else if (n_fds > 0)
|
||||
else if (n_fds > 0)
|
||||
log_warning("Got file descriptors via syslog socket. Ignoring.");
|
||||
|
||||
} else {
|
||||
} else if (fd == s->native_fd) {
|
||||
if (n > 0 && n_fds == 0)
|
||||
server_process_native_message(s, s->buffer, n, ucred, tv, label, label_len);
|
||||
else if (n == 0 && n_fds == 1)
|
||||
server_process_native_file(s, fds[0], ucred, tv, label, label_len);
|
||||
else if (n_fds > 0)
|
||||
log_warning("Got too many file descriptors via native socket. Ignoring.");
|
||||
|
||||
} else {
|
||||
assert(fd == s->audit_fd);
|
||||
|
||||
if (n > 0 && n_fds == 0)
|
||||
server_process_audit_message(s, s->buffer, n, ucred, tv, &sa, msghdr.msg_namelen);
|
||||
else if (n_fds > 0)
|
||||
log_warning("Got file descriptors via audit socket. Ignoring.");
|
||||
}
|
||||
|
||||
close_many(fds, n_fds);
|
||||
@ -1452,7 +1472,7 @@ int server_init(Server *s) {
|
||||
assert(s);
|
||||
|
||||
zero(*s);
|
||||
s->syslog_fd = s->native_fd = s->stdout_fd = s->dev_kmsg_fd = s->hostname_fd = -1;
|
||||
s->syslog_fd = s->native_fd = s->stdout_fd = s->dev_kmsg_fd = s->audit_fd = s->hostname_fd = -1;
|
||||
s->compress = true;
|
||||
s->seal = true;
|
||||
|
||||
@ -1537,6 +1557,15 @@ int server_init(Server *s) {
|
||||
|
||||
s->syslog_fd = fd;
|
||||
|
||||
} else if (sd_is_socket(fd, AF_NETLINK, SOCK_RAW, -1) > 0) {
|
||||
|
||||
if (s->audit_fd >= 0) {
|
||||
log_error("Too many audit sockets passed.");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
s->audit_fd = fd;
|
||||
|
||||
} else {
|
||||
log_error("Unknown socket passed.");
|
||||
return -EINVAL;
|
||||
@ -1559,6 +1588,10 @@ int server_init(Server *s) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = server_open_audit(s);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = server_open_kernel_seqnum(s);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -1632,6 +1665,7 @@ void server_done(Server *s) {
|
||||
sd_event_source_unref(s->native_event_source);
|
||||
sd_event_source_unref(s->stdout_event_source);
|
||||
sd_event_source_unref(s->dev_kmsg_event_source);
|
||||
sd_event_source_unref(s->audit_event_source);
|
||||
sd_event_source_unref(s->sync_event_source);
|
||||
sd_event_source_unref(s->sigusr1_event_source);
|
||||
sd_event_source_unref(s->sigusr2_event_source);
|
||||
@ -1644,6 +1678,7 @@ void server_done(Server *s) {
|
||||
safe_close(s->native_fd);
|
||||
safe_close(s->stdout_fd);
|
||||
safe_close(s->dev_kmsg_fd);
|
||||
safe_close(s->audit_fd);
|
||||
safe_close(s->hostname_fd);
|
||||
|
||||
if (s->rate_limit)
|
||||
|
@ -59,6 +59,7 @@ typedef struct Server {
|
||||
int native_fd;
|
||||
int stdout_fd;
|
||||
int dev_kmsg_fd;
|
||||
int audit_fd;
|
||||
int hostname_fd;
|
||||
|
||||
sd_event *event;
|
||||
@ -67,6 +68,7 @@ typedef struct Server {
|
||||
sd_event_source *native_event_source;
|
||||
sd_event_source *stdout_event_source;
|
||||
sd_event_source *dev_kmsg_event_source;
|
||||
sd_event_source *audit_event_source;
|
||||
sd_event_source *sync_event_source;
|
||||
sd_event_source *sigusr1_event_source;
|
||||
sd_event_source *sigusr2_event_source;
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include <linux/input.h>
|
||||
#include <linux/if_link.h>
|
||||
#include <linux/loop.h>
|
||||
#include <linux/audit.h>
|
||||
|
||||
#ifdef HAVE_AUDIT
|
||||
#include <libaudit.h>
|
||||
@ -557,3 +558,11 @@ static inline int setns(int fd, int nstype) {
|
||||
#ifndef LOOPBACK_IFINDEX
|
||||
#define LOOPBACK_IFINDEX 1
|
||||
#endif
|
||||
|
||||
#ifndef MAX_AUDIT_MESSAGE_LENGTH
|
||||
#define MAX_AUDIT_MESSAGE_LENGTH 8970
|
||||
#endif
|
||||
|
||||
#ifndef AUDIT_NLGRP_MAX
|
||||
#define AUDIT_NLGRP_READLOG 1
|
||||
#endif
|
||||
|
18
units/systemd-journald-audit.socket
Normal file
18
units/systemd-journald-audit.socket
Normal file
@ -0,0 +1,18 @@
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
[Unit]
|
||||
Description=Journal Audit Socket
|
||||
Documentation=man:systemd-journald.service(8) man:journald.conf(5)
|
||||
DefaultDependencies=no
|
||||
Before=sockets.target
|
||||
|
||||
[Socket]
|
||||
Service=systemd-journald.service
|
||||
ReceiveBuffer=128M
|
||||
ListenNetlink=audit 1
|
||||
PassCredentials=yes
|
@ -10,17 +10,17 @@ Description=Journal Service
|
||||
Documentation=man:systemd-journald.service(8) man:journald.conf(5)
|
||||
DefaultDependencies=no
|
||||
Requires=systemd-journald.socket
|
||||
After=systemd-journald.socket systemd-journald-dev-log.socket syslog.socket
|
||||
After=systemd-journald.socket systemd-journald-dev-log.socket systemd-journald-audit.socket syslog.socket
|
||||
Before=sysinit.target
|
||||
|
||||
[Service]
|
||||
Sockets=systemd-journald.socket systemd-journald-dev-log.socket
|
||||
Sockets=systemd-journald.socket systemd-journald-dev-log.socket systemd-journald-audit.socket
|
||||
ExecStart=@rootlibexecdir@/systemd-journald
|
||||
Restart=always
|
||||
RestartSec=0
|
||||
NotifyAccess=all
|
||||
StandardOutput=null
|
||||
CapabilityBoundingSet=CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_SYS_PTRACE CAP_SYSLOG CAP_AUDIT_CONTROL CAP_CHOWN CAP_DAC_READ_SEARCH CAP_FOWNER CAP_SETUID CAP_SETGID CAP_MAC_OVERRIDE
|
||||
CapabilityBoundingSet=CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_SYS_PTRACE CAP_SYSLOG CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_CHOWN CAP_DAC_READ_SEARCH CAP_FOWNER CAP_SETUID CAP_SETGID CAP_MAC_OVERRIDE
|
||||
WatchdogSec=1min
|
||||
|
||||
# Increase the default a bit in order to allow many simultaneous
|
||||
|
Loading…
Reference in New Issue
Block a user