journald-audit.c revision cd556b6ca8aec8dd371806afedec45f852f8f724
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering/***
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering This file is part of systemd.
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering Copyright 2014 Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering systemd is free software; you can redistribute it and/or modify it
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering under the terms of the GNU Lesser General Public License as published by
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering the Free Software Foundation; either version 2.1 of the License, or
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering (at your option) any later version.
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering systemd is distributed in the hope that it will be useful, but
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering WITHOUT ANY WARRANTY; without even the implied warranty of
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering Lesser General Public License for more details.
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering You should have received a copy of the GNU Lesser General Public License
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering along with systemd; If not, see <http://www.gnu.org/licenses/>.
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering***/
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering#include "missing.h"
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering#include "journald-audit.h"
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poetteringtypedef struct MapField {
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering const char *audit_field;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering const char *journal_field;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering int (*map)(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering} MapField;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
322345fdb9865ef2477fba8e4bdde0e1183ef505Lennart Poetteringstatic int map_simple_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) {
623a4c97b9175f95c4b1c6fc34e36c56f1e4ddbfLennart Poettering _cleanup_free_ char *c = NULL;
623a4c97b9175f95c4b1c6fc34e36c56f1e4ddbfLennart Poettering size_t l = 0, allocated = 0;
39d8db043b599a7382f94bfc904d5e108af438bdLennart Poettering const char *e;
39d8db043b599a7382f94bfc904d5e108af438bdLennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering assert(field);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering assert(p);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering assert(iov);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering assert(n_iov);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering l = strlen(field);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering allocated = l + 1;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering c = malloc(allocated);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering if (!c)
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering return -ENOMEM;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering memcpy(c, field, l);
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering for (e = *p; *e != ' ' && *e != 0; e++) {
0dd25fb9f005d8ab7ac4bc10a609d00569f8c56aLennart Poettering if (!GREEDY_REALLOC(c, allocated, l+2))
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering return -ENOMEM;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering c[l++] = *e;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering }
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
322345fdb9865ef2477fba8e4bdde0e1183ef505Lennart Poettering c[l] = 0;
623a4c97b9175f95c4b1c6fc34e36c56f1e4ddbfLennart Poettering
322345fdb9865ef2477fba8e4bdde0e1183ef505Lennart Poettering if (!GREEDY_REALLOC(*iov, *n_iov_allocated, *n_iov + 1))
aea2429d6ec32261dbf6b9caa125fcc6ea9ea76aLennart Poettering return -ENOMEM;
aea2429d6ec32261dbf6b9caa125fcc6ea9ea76aLennart Poettering
ec2c5e4398f9d65e5dfe61530f2556224733d1e6Lennart Poettering (*iov)[*n_iov].iov_base = c;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering (*iov)[*n_iov].iov_len = l;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering (*n_iov) ++;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering *p = e;
0dd25fb9f005d8ab7ac4bc10a609d00569f8c56aLennart Poettering c = NULL;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering return 1;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering}
623a4c97b9175f95c4b1c6fc34e36c56f1e4ddbfLennart Poettering
ad867662936a4c7ab2c7116d804c272338801231Lennart Poetteringstatic int map_string_field_internal(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov, bool filter_printable) {
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering _cleanup_free_ char *c = NULL;
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering const char *s, *e;
623a4c97b9175f95c4b1c6fc34e36c56f1e4ddbfLennart Poettering size_t l;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
2c27fbca2d88214bd305272308a370a962818f1eLennart Poettering assert(field);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering assert(p);
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering assert(iov);
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering assert(n_iov);
623a4c97b9175f95c4b1c6fc34e36c56f1e4ddbfLennart Poettering
623a4c97b9175f95c4b1c6fc34e36c56f1e4ddbfLennart Poettering /* The kernel formats string fields in one of two formats. */
ec2c5e4398f9d65e5dfe61530f2556224733d1e6Lennart Poettering
dc4d47e2c79aafa3ef646e32ff3422c4ce935c1bLennart Poettering 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;
uint8_t x;
a = unhexchar(e[0]);
if (a < 0)
return 0;
b = unhexchar(e[1]);
if (b < 0)
return 0;
x = ((uint8_t) a << 4 | (uint8_t) b);
if (filter_printable && x < (uint8_t) ' ')
x = (uint8_t) ' ';
if (!GREEDY_REALLOC(c, allocated, l+2))
return -ENOMEM;
c[l++] = (char) x;
}
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_string_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) {
return map_string_field_internal(field, p, iov, n_iov_allocated, n_iov, false);
}
static int map_string_field_printable(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) {
return map_string_field_internal(field, p, iov, n_iov_allocated, n_iov, true);
}
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 == '_' || *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++) {
char x;
if (*f >= 'a' && *f <= 'z')
x = (*f - 'a') + 'A'; /* uppercase */
else if (*f == '-')
x = '_'; /* dashes → underscores */
else
x = *f;
*(t++) = x;
}
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 occurring 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_printable },
/* 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 those occurring 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)
return log_debug_errno(r, "Failed to parse audit array: %m");
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)
return log_debug_errno(r, "Failed to parse audit array: %m");
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) {
_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)];
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, "(%" PRIu64 ".%" PRIu64 ":%" PRIu64 "):%n",
&seconds,
&msec,
&id,
&k) != 3)
return;
p += k;
p += strspn(p, WHITESPACE);
if (isempty(p))
return;
n_iov_allocated = N_IOVEC_META_FIELDS + 7;
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);
assert_cc(32 == LOG_AUTH);
IOVEC_SET_STRING(iov[n_iov++], "SYSLOG_FACILITY=32");
IOVEC_SET_STRING(iov[n_iov++], "SYSLOG_IDENTIFIER=audit");
m = alloca(strlen("MESSAGE=<audit-") + DECIMAL_STR_MAX(int) + strlen("> ") + strlen(p) + 1);
sprintf(m, "MESSAGE=<audit-%i> %s", type, p);
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, NULL, 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 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)));
}
static int enable_audit(int fd, bool b) {
struct {
union {
struct nlmsghdr header;
uint8_t header_space[NLMSG_HDRLEN];
};
struct audit_status body;
} _packed_ request = {
.header.nlmsg_len = NLMSG_LENGTH(sizeof(struct audit_status)),
.header.nlmsg_type = AUDIT_SET,
.header.nlmsg_flags = NLM_F_REQUEST,
.header.nlmsg_seq = 1,
.header.nlmsg_pid = 0,
.body.mask = AUDIT_STATUS_ENABLED,
.body.enabled = b,
};
union sockaddr_union sa = {
.nl.nl_family = AF_NETLINK,
.nl.nl_pid = 0,
};
struct iovec iovec = {
.iov_base = &request,
.iov_len = NLMSG_LENGTH(sizeof(struct audit_status)),
};
struct msghdr mh = {
.msg_iov = &iovec,
.msg_iovlen = 1,
.msg_name = &sa.sa,
.msg_namelen = sizeof(sa.nl),
};
ssize_t n;
n = sendmsg(fd, &mh, MSG_NOSIGNAL);
if (n < 0)
return -errno;
if (n != NLMSG_LENGTH(sizeof(struct audit_status)))
return -EIO;
/* We don't wait for the result here, we can't do anything
* about it anyway */
return 0;
}
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_errno(errno, "Failed to create audit socket, ignoring: %m");
return 0;
}
r = bind(s->audit_fd, &sa.sa, sizeof(sa.nl));
if (r < 0)
return log_error_errno(errno, "Failed to join audit multicast group: %m");
} else
fd_nonblock(s->audit_fd, 1);
r = setsockopt(s->audit_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one));
if (r < 0)
return log_error_errno(errno, "Failed to set SO_PASSCRED on audit socket: %m");
r = sd_event_add_io(s->event, &s->audit_event_source, s->audit_fd, EPOLLIN, server_process_datagram, s);
if (r < 0)
return log_error_errno(r, "Failed to add audit fd to event loop: %m");
/* We are listening now, try to enable audit */
r = enable_audit(s->audit_fd, true);
if (r < 0)
log_warning_errno(r, "Failed to issue audit enable call: %m");
return 0;
}