| /* |
| * 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_setup(int fd, uint32_t pid) |
| { |
| 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 | AUDIT_STATUS_RATE_LIMIT; |
| status.rate_limit = 5; // audit entries per second |
| |
| /* 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. |
| * |
| * 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 | SOCK_CLOEXEC, 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; |
| } |