/* * Callbacks for user-supplied memory allocation, supplemental * auditing, and locking routines. * * Author : Eamon Walsh <ewalsh@epoch.ncsc.mil> * * Netlink code derived in part from sample code by * James Morris <jmorris@redhat.com>. */ #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <poll.h> #include <sys/types.h> #include <sys/socket.h> #include <linux/types.h> #include <linux/netlink.h> #include "callbacks.h" #include "selinux_netlink.h" #include "avc_internal.h" #ifndef NETLINK_SELINUX #define NETLINK_SELINUX 7 #endif /* callback pointers */ void *(*avc_func_malloc) (size_t) = NULL; void (*avc_func_free) (void *) = NULL; void (*avc_func_log) (const char *, ...) = NULL; void (*avc_func_audit) (void *, security_class_t, char *, size_t) = NULL; int avc_using_threads = 0; int avc_app_main_loop = 0; void *(*avc_func_create_thread) (void (*)(void)) = NULL; void (*avc_func_stop_thread) (void *) = NULL; void *(*avc_func_alloc_lock) (void) = NULL; void (*avc_func_get_lock) (void *) = NULL; void (*avc_func_release_lock) (void *) = NULL; void (*avc_func_free_lock) (void *) = NULL; /* message prefix string and avc enforcing mode */ char avc_prefix[AVC_PREFIX_SIZE] = "uavc"; int avc_running = 0; int avc_enforcing = 1; int avc_setenforce = 0; int avc_netlink_trouble = 0; /* netlink socket code */ static int fd = -1; int avc_netlink_open(int blocking) { int len, rc = 0; struct sockaddr_nl addr; fd = socket(PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_SELINUX); if (fd < 0) { rc = fd; goto out; } if (!blocking && fcntl(fd, F_SETFL, O_NONBLOCK)) { close(fd); fd = -1; rc = -1; goto out; } len = sizeof(addr); memset(&addr, 0, len); addr.nl_family = AF_NETLINK; addr.nl_groups = SELNL_GRP_AVC; if (bind(fd, (struct sockaddr *)&addr, len) < 0) { close(fd); fd = -1; rc = -1; goto out; } out: return rc; } void avc_netlink_close(void) { if (fd >= 0) close(fd); fd = -1; } static int avc_netlink_receive(void *buf, unsigned buflen, int blocking) { int rc; struct pollfd pfd = { fd, POLLIN | POLLPRI, 0 }; struct sockaddr_nl nladdr; socklen_t nladdrlen = sizeof nladdr; struct nlmsghdr *nlh = (struct nlmsghdr *)buf; do { rc = poll(&pfd, 1, (blocking ? -1 : 0)); } while (rc < 0 && errno == EINTR); if (rc == 0 && !blocking) { errno = EWOULDBLOCK; return -1; } else if (rc < 1) { avc_log(SELINUX_ERROR, "%s: netlink poll: error %d\n", avc_prefix, errno); return rc; } rc = recvfrom(fd, buf, buflen, 0, (struct sockaddr *)&nladdr, &nladdrlen); if (rc < 0) return rc; if (nladdrlen != sizeof nladdr) { avc_log(SELINUX_WARNING, "%s: warning: netlink address truncated, len %u?\n", avc_prefix, nladdrlen); return -1; } if (nladdr.nl_pid) { avc_log(SELINUX_WARNING, "%s: warning: received spoofed netlink packet from: %u\n", avc_prefix, nladdr.nl_pid); return -1; } if (rc == 0) { avc_log(SELINUX_WARNING, "%s: warning: received EOF on netlink socket\n", avc_prefix); errno = EBADFD; return -1; } if (nlh->nlmsg_flags & MSG_TRUNC || nlh->nlmsg_len > (unsigned)rc) { avc_log(SELINUX_WARNING, "%s: warning: incomplete netlink message\n", avc_prefix); return -1; } return 0; } static int avc_netlink_process(void *buf) { int rc; struct nlmsghdr *nlh = (struct nlmsghdr *)buf; switch (nlh->nlmsg_type) { case NLMSG_ERROR:{ struct nlmsgerr *err = NLMSG_DATA(nlh); /* Netlink ack */ if (err->error == 0) break; errno = -err->error; avc_log(SELINUX_ERROR, "%s: netlink error: %d\n", avc_prefix, errno); return -1; } case SELNL_MSG_SETENFORCE:{ struct selnl_msg_setenforce *msg = NLMSG_DATA(nlh); msg->val = !!msg->val; avc_log(SELINUX_INFO, "%s: received setenforce notice (enforcing=%d)\n", avc_prefix, msg->val); if (avc_setenforce) break; avc_enforcing = msg->val; if (avc_enforcing && (rc = avc_ss_reset(0)) < 0) { avc_log(SELINUX_ERROR, "%s: cache reset returned %d (errno %d)\n", avc_prefix, rc, errno); return rc; } rc = selinux_netlink_setenforce(msg->val); if (rc < 0) return rc; break; } case SELNL_MSG_POLICYLOAD:{ struct selnl_msg_policyload *msg = NLMSG_DATA(nlh); avc_log(SELINUX_INFO, "%s: received policyload notice (seqno=%u)\n", avc_prefix, msg->seqno); rc = avc_ss_reset(msg->seqno); if (rc < 0) { avc_log(SELINUX_ERROR, "%s: cache reset returned %d (errno %d)\n", avc_prefix, rc, errno); return rc; } rc = selinux_netlink_policyload(msg->seqno); if (rc < 0) return rc; break; } default: avc_log(SELINUX_WARNING, "%s: warning: unknown netlink message %d\n", avc_prefix, nlh->nlmsg_type); } return 0; } int avc_netlink_check_nb(void) { int rc; char buf[1024] __attribute__ ((aligned)); while (1) { errno = 0; rc = avc_netlink_receive(buf, sizeof(buf), 0); if (rc < 0) { if (errno == EWOULDBLOCK) return 0; if (errno == 0 || errno == EINTR) continue; else { avc_log(SELINUX_ERROR, "%s: netlink recvfrom: error %d\n", avc_prefix, errno); return rc; } } (void)avc_netlink_process(buf); } return 0; } /* run routine for the netlink listening thread */ void avc_netlink_loop(void) { int rc; char buf[1024] __attribute__ ((aligned)); while (1) { errno = 0; rc = avc_netlink_receive(buf, sizeof(buf), 1); if (rc < 0) { if (errno == 0 || errno == EINTR) continue; else { avc_log(SELINUX_ERROR, "%s: netlink recvfrom: error %d\n", avc_prefix, errno); break; } } rc = avc_netlink_process(buf); if (rc < 0) break; } close(fd); fd = -1; avc_netlink_trouble = 1; avc_log(SELINUX_ERROR, "%s: netlink thread: errors encountered, terminating\n", avc_prefix); } int avc_netlink_acquire_fd(void) { avc_app_main_loop = 1; return fd; } void avc_netlink_release_fd(void) { avc_app_main_loop = 0; }