diff --git a/logd/Android.mk b/logd/Android.mk index b0bc746c9..61895c4a9 100644 --- a/logd/Android.mk +++ b/logd/Android.mk @@ -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 \ diff --git a/logd/LogAudit.cpp b/logd/LogAudit.cpp new file mode 100644 index 000000000..cc3e58329 --- /dev/null +++ b/logd/LogAudit.cpp @@ -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 +#include +#include +#include +#include + +#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(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; +} diff --git a/logd/LogAudit.h b/logd/LogAudit.h new file mode 100644 index 000000000..3e57eedc9 --- /dev/null +++ b/logd/LogAudit.h @@ -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 +#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 diff --git a/logd/README.auditd b/logd/README.auditd new file mode 100644 index 000000000..3f614a341 --- /dev/null +++ b/logd/README.auditd @@ -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. diff --git a/logd/libaudit.c b/logd/libaudit.c new file mode 100644 index 000000000..ca88d1b22 --- /dev/null +++ b/logd/libaudit.c @@ -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 + * + */ + +#include +#include +#include + +#define LOG_TAG "libaudit" +#include + +#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; +} diff --git a/logd/libaudit.h b/logd/libaudit.h new file mode 100644 index 000000000..cb114f91d --- /dev/null +++ b/logd/libaudit.h @@ -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 + */ + +#ifndef _LIBAUDIT_H_ +#define _LIBAUDIT_H_ + +#include +#include +#include +#include + +#include +#include + +__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 diff --git a/logd/main.cpp b/logd/main.cpp index 8792d3224..83ec6c0aa 100644 --- a/logd/main.cpp +++ b/logd/main.cpp @@ -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); }