/*
|
* 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;
|
}
|