logd: selinux auditd initial commit

Initial commit for an audit daemon that writes kernel audit
messages to the Android logger. The daemon searches dmesg
for all lines that contain "audit" and writes them. Then
receiving the messages from the netlink socket.

It also formats the messages so they are compatable with
ausearch (type=<t> <m> format)

Modified: Mark Salyzyn <salyzyn@google.com>

- do not start auditd
- merge into logd, stripping unnecessary file logging.
- Convert headers and code to support C++
- Fix bugs in libaudit
- squash timestamp (replace with 0.0) due to duplication
- squash pid due to duplication
- squash comm due to duplication

Change-Id: I421bcf33e7e670d596628b1b5c7c25536ce2d3fe
This commit is contained in:
William Roberts 2013-02-08 09:45:26 +09:00 committed by Mark Salyzyn
parent 4ba0387af5
commit 29d238d2a8
7 changed files with 639 additions and 2 deletions

View file

@ -19,7 +19,9 @@ LOCAL_SRC_FILES := \
LogBufferElement.cpp \
LogTimes.cpp \
LogStatistics.cpp \
LogWhiteBlackList.cpp
LogWhiteBlackList.cpp \
libaudit.c \
LogAudit.cpp
LOCAL_SHARED_LIBRARIES := \
libsysutils \

184
logd/LogAudit.cpp Normal file
View file

@ -0,0 +1,184 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#include <sys/klog.h>
#include "libaudit.h"
#include "LogAudit.h"
LogAudit::LogAudit(LogBuffer *buf, LogReader *reader)
: SocketListener(getLogSocket(), false)
, logbuf(buf)
, reader(reader) {
logDmsg();
}
bool LogAudit::onDataAvailable(SocketClient *cli) {
struct audit_message rep;
if (audit_get_reply(cli->getSocket(), &rep, GET_REPLY_BLOCKING, 0) < 0) {
SLOGE("Failed on audit_get_reply with error: %s", strerror(errno));
return false;
}
logPrint("type=%d %.*s", rep.nlh.nlmsg_type, rep.nlh.nlmsg_len, rep.data);
return true;
}
#define AUDIT_LOG_ID LOG_ID_MAIN
#define AUDIT_LOG_PRIO ANDROID_LOG_WARN
int LogAudit::logPrint(const char *fmt, ...) {
if (fmt == NULL) {
return -EINVAL;
}
va_list args;
char *str = NULL;
va_start(args, fmt);
int rc = vasprintf(&str, fmt, args);
va_end(args);
if (rc < 0) {
return rc;
}
pid_t pid = getpid();
pid_t tid = gettid();
uid_t uid = getuid();
log_time now;
static const char audit_str[] = " audit(";
char *timeptr = strstr(str, audit_str);
char *cp;
if (timeptr
&& ((cp = now.strptime(timeptr + sizeof(audit_str) - 1, "%s.%q")))
&& (*cp == ':')) {
memcpy(timeptr + sizeof(audit_str) - 1, "0.0", 3);
strcpy(timeptr + sizeof(audit_str) - 1 + 3, cp);
} else {
now.strptime("", ""); // side effect of setting CLOCK_REALTIME
}
static const char pid_str[] = " pid=";
char *pidptr = strstr(str, pid_str);
if (pidptr && isdigit(pidptr[sizeof(pid_str) - 1])) {
cp = pidptr + sizeof(pid_str) - 1;
pid = 0;
while (isdigit(*cp)) {
pid = (pid * 10) + (*cp - '0');
++cp;
}
tid = pid;
uid = logbuf->pidToUid(pid);
strcpy(pidptr, cp);
}
static const char comm_str[] = " comm=\"";
char *comm = strstr(str, comm_str);
if (comm) {
cp = comm;
comm += sizeof(comm_str) - 1;
char *ecomm = strchr(comm, '"');
if (ecomm) {
*ecomm = '\0';
}
comm = strdup(comm);
if (ecomm) {
strcpy(cp, ecomm + 1);
}
} else if (pid == getpid()) {
pid = tid;
comm = strdup("auditd");
} else if (!(comm = logbuf->pidToName(pid))) {
comm = strdup("unknown");
}
size_t l = strlen(comm) + 1;
size_t n = l + strlen(str) + 2;
char *newstr = reinterpret_cast<char *>(malloc(n));
if (!newstr) {
free(comm);
free(str);
return -ENOMEM;
}
*newstr = AUDIT_LOG_PRIO;
strcpy(newstr + 1, comm);
free(comm);
strcpy(newstr + 1 + l, str);
free(str);
unsigned short len = n; // cap to internal maximum
if (len != n) {
len = -1;
}
logbuf->log(AUDIT_LOG_ID, now, uid, pid, tid, newstr, len);
reader->notifyNewLog();
free(newstr);
return rc;
}
void LogAudit::logDmsg() {
int len = klogctl(KLOG_SIZE_BUFFER, NULL, 0);
if (len <= 0) {
return;
}
len++;
char buf[len];
int rc = klogctl(KLOG_READ_ALL, buf, len);
buf[len - 1] = '\0';
for(char *tok = buf; (rc >= 0) && ((tok = strtok(tok, "\r\n"))); tok = NULL) {
char *audit = strstr(tok, " audit(");
if (!audit) {
continue;
}
*audit++ = '\0';
char *type = strstr(tok, "type=");
if (type) {
rc = logPrint("%s %s", type, audit);
} else {
rc = logPrint("%s", audit);
}
}
}
int LogAudit::getLogSocket() {
int fd = audit_open();
if (fd < 0) {
return fd;
}
if (audit_set_pid(fd, getpid(), WAIT_YES) < 0) {
audit_close(fd);
fd = -1;
}
return fd;
}

40
logd/LogAudit.h Normal file
View file

@ -0,0 +1,40 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _LOGD_LOG_AUDIT_H__
#define _LOGD_LOG_AUDIT_H__
#include <sysutils/SocketListener.h>
#include "LogReader.h"
class LogAudit : public SocketListener {
LogBuffer *logbuf;
LogReader *reader;
public:
LogAudit(LogBuffer *buf, LogReader *reader);
protected:
virtual bool onDataAvailable(SocketClient *cli);
private:
static int getLogSocket();
void logDmsg();
int logPrint(const char *fmt, ...)
__attribute__ ((__format__ (__printf__, 2, 3)));
};
#endif

17
logd/README.auditd Normal file
View file

@ -0,0 +1,17 @@
Auditd Daemon
The audit daemon is a simplified version of its desktop
counterpart designed to gather the audit logs from the
audit kernel subsystem. The audit subsystem of the kernel
includes Linux Security Modules (LSM) messages as well.
To enable the audit subsystem, you must add this to your
kernel config:
CONFIG_AUDIT=y
To enable a LSM, you must consult that LSM's documentation, the
example below is for SELinux:
CONFIG_SECURITY_SELINUX=y
This does not include possible dependencies that may need to be
satisfied for that particular LSM.

276
logd/libaudit.c Normal file
View file

@ -0,0 +1,276 @@
/*
* Copyright 2012, Samsung Telecommunications of America
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Written by William Roberts <w.roberts@sta.samsung.com>
*
*/
#include <errno.h>
#include <string.h>
#include <unistd.h>
#define LOG_TAG "libaudit"
#include <log/log.h>
#include "libaudit.h"
/**
* Waits for an ack from the kernel
* @param fd
* The netlink socket fd
* @param seq
* The current sequence number were acking on
* @return
* This function returns 0 on success, else -errno.
*/
static int get_ack(int fd, int16_t seq)
{
int rc;
struct audit_message rep;
/* Sanity check, this is an internal interface this shouldn't happen */
if (fd < 0) {
return -EINVAL;
}
rc = audit_get_reply(fd, &rep, GET_REPLY_BLOCKING, MSG_PEEK);
if (rc < 0) {
return rc;
}
if (rep.nlh.nlmsg_type == NLMSG_ERROR) {
audit_get_reply(fd, &rep, GET_REPLY_BLOCKING, 0);
rc = ((struct nlmsgerr *)rep.data)->error;
if (rc) {
return -rc;
}
}
if ((int16_t)rep.nlh.nlmsg_seq != seq) {
SLOGW("Expected sequence number between user space and kernel space is out of skew, "
"expected %u got %u", seq, rep.nlh.nlmsg_seq);
}
return 0;
}
/**
*
* @param fd
* The netlink socket fd
* @param type
* The type of netlink message
* @param data
* The data to send
* @param size
* The length of the data in bytes
* @return
* This function returns a positive sequence number on success, else -errno.
*/
static int audit_send(int fd, int type, const void *data, size_t size)
{
int rc;
static int16_t sequence = 0;
struct audit_message req;
struct sockaddr_nl addr;
memset(&req, 0, sizeof(req));
memset(&addr, 0, sizeof(addr));
/* We always send netlink messaged */
addr.nl_family = AF_NETLINK;
/* Set up the netlink headers */
req.nlh.nlmsg_type = type;
req.nlh.nlmsg_len = NLMSG_SPACE(size);
req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
/*
* Check for a valid fd, even though sendto would catch this, its easier
* to always blindly increment the sequence number
*/
if (fd < 0) {
return -EBADF;
}
/* Ensure the message is not too big */
if (NLMSG_SPACE(size) > MAX_AUDIT_MESSAGE_LENGTH) {
SLOGE("netlink message is too large");
return -EINVAL;
}
/* Only memcpy in the data if it was specified */
if (size && data) {
memcpy(NLMSG_DATA(&req.nlh), data, size);
}
/*
* Only increment the sequence number on a guarantee
* you will send it to the kernel.
*
* Also, the sequence is defined as a u32 in the kernel
* struct. Using an int here might not work on 32/64 bit splits. A
* signed 64 bit value can overflow a u32..but a u32
* might not fit in the response, so we need to use s32.
* Which is still kind of hackish since int could be 16 bits
* in size. The only safe type to use here is a signed 16
* bit value.
*/
req.nlh.nlmsg_seq = ++sequence;
/* While failing and its due to interrupts */
rc = TEMP_FAILURE_RETRY(sendto(fd, &req, req.nlh.nlmsg_len, 0,
(struct sockaddr*) &addr, sizeof(addr)));
/* Not all the bytes were sent */
if (rc < 0) {
rc = -errno;
SLOGE("Error sending data over the netlink socket: %s", strerror(-errno));
goto out;
} else if ((uint32_t) rc != req.nlh.nlmsg_len) {
rc = -EPROTO;
goto out;
}
/* We sent all the bytes, get the ack */
rc = get_ack(fd, sequence);
/* If the ack failed, return the error, else return the sequence number */
rc = (rc == 0) ? (int) sequence : rc;
out:
/* Don't let sequence roll to negative */
if (sequence < 0) {
SLOGW("Auditd to Kernel sequence number has rolled over");
sequence = 0;
}
return rc;
}
int audit_set_pid(int fd, uint32_t pid, rep_wait_t wmode)
{
int rc;
struct audit_message rep;
struct audit_status status;
memset(&status, 0, sizeof(status));
/*
* In order to set the auditd PID we send an audit message over the netlink
* socket with the pid field of the status struct set to our current pid,
* and the the mask set to AUDIT_STATUS_PID
*/
status.pid = pid;
status.mask = AUDIT_STATUS_PID;
/* Let the kernel know this pid will be registering for audit events */
rc = audit_send(fd, AUDIT_SET, &status, sizeof(status));
if (rc < 0) {
SLOGE("Could net set pid for audit events, error: %s", strerror(-rc));
return rc;
}
/*
* In a request where we need to wait for a response, wait for the message
* and discard it. This message confirms and sync's us with the kernel.
* This daemon is now registered as the audit logger. Only wait if the
* wmode is != WAIT_NO
*/
if (wmode != WAIT_NO) {
/* TODO
* If the daemon dies and restarts the message didn't come back,
* so I went to non-blocking and it seemed to fix the bug.
* Need to investigate further.
*/
audit_get_reply(fd, &rep, GET_REPLY_NONBLOCKING, 0);
}
return 0;
}
int audit_open()
{
return socket(PF_NETLINK, SOCK_RAW, NETLINK_AUDIT);
}
int audit_get_reply(int fd, struct audit_message *rep, reply_t block, int peek)
{
ssize_t len;
int flags;
int rc = 0;
struct sockaddr_nl nladdr;
socklen_t nladdrlen = sizeof(nladdr);
if (fd < 0) {
return -EBADF;
}
/* Set up the flags for recv from */
flags = (block == GET_REPLY_NONBLOCKING) ? MSG_DONTWAIT : 0;
flags |= peek;
/*
* Get the data from the netlink socket but on error we need to be carefull,
* the interface shows that EINTR can never be returned, other errors,
* however, can be returned.
*/
len = TEMP_FAILURE_RETRY(recvfrom(fd, rep, sizeof(*rep), flags,
(struct sockaddr*) &nladdr, &nladdrlen));
/*
* EAGAIN should be re-tried until success or another error manifests.
*/
if (len < 0) {
rc = -errno;
if (block == GET_REPLY_NONBLOCKING && rc == -EAGAIN) {
/* If request is non blocking and errno is EAGAIN, just return 0 */
return 0;
}
SLOGE("Error receiving from netlink socket, error: %s", strerror(-rc));
return rc;
}
if (nladdrlen != sizeof(nladdr)) {
SLOGE("Protocol fault, error: %s", strerror(EPROTO));
return -EPROTO;
}
/* Make sure the netlink message was not spoof'd */
if (nladdr.nl_pid) {
SLOGE("Invalid netlink pid received, expected 0 got: %d", nladdr.nl_pid);
return -EINVAL;
}
/* Check if the reply from the kernel was ok */
if (!NLMSG_OK(&rep->nlh, (size_t)len)) {
rc = (len == sizeof(*rep)) ? -EFBIG : -EBADE;
SLOGE("Bad kernel response %s", strerror(-rc));
}
return rc;
}
void audit_close(int fd)
{
int rc = close(fd);
if (rc < 0) {
SLOGE("Attempting to close invalid fd %d, error: %s", fd, strerror(errno));
}
return;
}

104
logd/libaudit.h Normal file
View file

@ -0,0 +1,104 @@
/*
* Copyright 2012, Samsung Telecommunications of America
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Written by William Roberts <w.roberts@sta.samsung.com>
*/
#ifndef _LIBAUDIT_H_
#define _LIBAUDIT_H_
#include <stdint.h>
#include <sys/cdefs.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <linux/netlink.h>
#include <linux/audit.h>
__BEGIN_DECLS
#define MAX_AUDIT_MESSAGE_LENGTH 8970
typedef enum {
GET_REPLY_BLOCKING=0,
GET_REPLY_NONBLOCKING
} reply_t;
typedef enum {
WAIT_NO,
WAIT_YES
} rep_wait_t;
/* type == AUDIT_SIGNAL_INFO */
struct audit_sig_info {
uid_t uid;
pid_t pid;
char ctx[0];
};
struct audit_message {
struct nlmsghdr nlh;
char data[MAX_AUDIT_MESSAGE_LENGTH];
};
/**
* Opens a connection to the Audit netlink socket
* @return
* A valid fd on success or < 0 on error with errno set.
* Returns the same errors as man 2 socket.
*/
extern int audit_open(void);
/**
* Closes the fd returned from audit_open()
* @param fd
* The fd to close
*/
extern void audit_close(int fd);
/**
*
* @param fd
* The fd returned by a call to audit_open()
* @param rep
* The response struct to store the response in.
* @param block
* Whether or not to block on IO
* @param peek
* Whether or not we are to remove the message from
* the queue when we do a read on the netlink socket.
* @return
* This function returns 0 on success, else -errno.
*/
extern int audit_get_reply(int fd, struct audit_message *rep, reply_t block,
int peek);
/**
* Sets a pid to recieve audit netlink events from the kernel
* @param fd
* The fd returned by a call to audit_open()
* @param pid
* The pid whom to set as the reciever of audit messages
* @param wmode
* Whether or not to block on the underlying socket io calls.
* @return
* This function returns 0 on success, -errno on error.
*/
extern int audit_set_pid(int fd, uint32_t pid, rep_wait_t wmode);
__END_DECLS
#endif

View file

@ -34,6 +34,7 @@
#include "CommandListener.h"
#include "LogBuffer.h"
#include "LogListener.h"
#include "LogAudit.h"
static int drop_privs() {
struct sched_param param;
@ -63,7 +64,10 @@ static int drop_privs() {
capheader.pid = 0;
capdata[CAP_TO_INDEX(CAP_SYSLOG)].permitted = CAP_TO_MASK(CAP_SYSLOG);
capdata[CAP_TO_INDEX(CAP_SYSLOG)].effective = CAP_TO_MASK(CAP_SYSLOG);
capdata[CAP_TO_INDEX(CAP_AUDIT_CONTROL)].permitted |= CAP_TO_MASK(CAP_AUDIT_CONTROL);
capdata[0].effective = capdata[0].permitted;
capdata[1].effective = capdata[1].permitted;
capdata[0].inheritable = 0;
capdata[1].inheritable = 0;
@ -127,6 +131,16 @@ int main() {
exit(1);
}
// LogAudit listens on NETLINK_AUDIT socket for selinux
// initiated log messages. New log entries are added to LogBuffer
// and LogReader is notified to send updates to connected clients.
// failure is an option ... messages are in dmesg (required by standard)
LogAudit *al = new LogAudit(logBuf, reader);
if (al->startListener()) {
delete al;
}
pause();
exit(0);
}