diff options
Diffstat (limited to 'auditd/libaudit.c')
-rw-r--r-- | auditd/libaudit.c | 351 |
1 files changed, 302 insertions, 49 deletions
diff --git a/auditd/libaudit.c b/auditd/libaudit.c index 905e8af..06e5557 100644 --- a/auditd/libaudit.c +++ b/auditd/libaudit.c @@ -16,63 +16,316 @@ * Written by William Roberts <w.roberts@sta.samsung.com> * */ + #include <errno.h> +#include <fcntl.h> #include <string.h> +#include <unistd.h> +#include <stdint.h> +#define LOG_TAG "libaudit" +#include <cutils/log.h> #include <cutils/klog.h> -#include "auditd.h" #include "libaudit.h" -/* Only should be used internally */ -extern int audit_send(int fd, int type, const void *data, unsigned int size); - -/* Depending on static to initialize this to 0 */ -static const struct audit_status _new_audit_status; - -int audit_set_pid(int fd, uint32_t pid, rep_wait_t wmode) { - - int rc; - int line; - struct audit_reply rep; - struct audit_status status = _new_audit_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) { - line = __LINE__; - goto err; - } - - /* - * 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. - */ - (void)audit_get_reply(fd, &rep, GET_REPLY_NONBLOCKING, 0); - } +/** + * Copies the netlink message data to the reply structure. + * + * When the kernel sends a response back, we must adjust the response from the + * netlink message header. + * All the data is in rep->msg but belongs in the type enforced fields in the struct. + * + * @param rep + * The response + * @param len + * The length of the message, len must never be less than 0! + * @return + * This function returns 0 on success, else -error. + */ +static int set_internal_fields(struct audit_reply *rep, ssize_t len) +{ + int rc; + + /* + * We end up setting a specific field in the union, but since it + * is a union and they are all of type pointer, we can just clear + * one. + */ + rep->status = NULL; + + /* Set the response from the netlink message */ + rep->nlh = &rep->msg.nlh; + rep->len = rep->msg.nlh.nlmsg_len; + rep->type = rep->msg.nlh.nlmsg_type; + + /* Check if the reply from the kernel was ok */ + if (!NLMSG_OK(rep->nlh, (size_t)len)) { + rc = (len == sizeof(rep->msg)) ? -EFBIG : -EBADE; + SLOGE("Bad kernel response %s", strerror(-rc)); + return rc; + } + + /* Next we'll set the data structure to point to msg.data. This is + * to avoid having to use casts later. */ + if (rep->type == NLMSG_ERROR) { + rep->error = NLMSG_DATA(rep->nlh); + } else if (rep->type == AUDIT_GET) { + rep->status = NLMSG_DATA(rep->nlh); + } else if (rep->type == AUDIT_LIST_RULES) { + rep->ruledata = NLMSG_DATA(rep->nlh); + } else if (rep->type == AUDIT_SIGNAL_INFO) { + rep->signal_info = NLMSG_DATA(rep->nlh); + } + /* If it is not any of the above specific events, it must be a generic message */ + else { + rep->message = NLMSG_DATA(rep->nlh); + } + + return 0; +} + +/** + * 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_reply rep; + + /* Sanity check the input, 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.type == NLMSG_ERROR) { + audit_get_reply(fd, &rep, GET_REPLY_BLOCKING, 0); + if (rep.error->error) { + return -rep.error->error; + } + } + + 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, unsigned int 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 */ + do { + /* Try and send the netlink message */ + rc = sendto(fd, &req, req.nlh.nlmsg_len, 0, (struct sockaddr*) &addr, sizeof(addr)); + + } while (rc < 0 && errno == EINTR); + + /* Not all the bytes were sent */ + if ((uint32_t) rc != req.nlh.nlmsg_len) { + rc = -EPROTO; + goto out; + } else if (rc < 0) { + rc = -errno; + SLOGE("Error sending data over the netlink socket: %s", strerror(-errno)); + 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: - return rc; + /* 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_reply 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_reply *rep, reply_t block, int peek) +{ + ssize_t len; + int flags; + + 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. + */ + do { + len = recvfrom(fd, &rep->msg, sizeof(rep->msg), flags, (struct sockaddr*) &nladdr, + &nladdrlen); + + /* + * EAGAIN and EINTR should be re-tried until success or + * another error manifests. + */ + if (len < 0 && errno != EINTR) { + if (block == GET_REPLY_NONBLOCKING && errno == EAGAIN) { + /* If the request is non blocking and the errno is EAGAIN, just return 0 */ + return 0; + } + SLOGE("Error receiving from netlink socket, error: %s", strerror(errno)); + return -errno; + } + + /* 0 or greater indicates success */ + } while (len < 0); + + 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; + } + + return set_internal_fields(rep, len); +} -err: - errno = GETERRNO(rc); - ERROR("%s Failed in %s around line %d with error: %s\n", __FILE__, __FUNCTION__, line, strerror(errno)); - rc = -1; - goto out; +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; } |