All of lore.kernel.org
 help / color / mirror / Atom feed
* libselinux: add selinux_status_* interfaces for /selinux/status
@ 2011-01-22 13:42 Kohei KaiGai
  2011-01-27  1:02 ` KaiGai Kohei
  0 siblings, 1 reply; 7+ messages in thread
From: Kohei KaiGai @ 2011-01-22 13:42 UTC (permalink / raw)
  To: SELinux-NSA; +Cc: KaiGai Kohei

[-- Attachment #1: Type: text/plain, Size: 656 bytes --]

The attached patch adds several interfaces to reference /selinux/status
according to sequential-lock logic.

selinux_status_open() open the kernel status page and mmap it with
read-only mode, or open netlink socket as a fallback in older kernels.

Then, we can obtain status information from the mmap'ed page using
selinux_status_updated(), selinux_status_getenfoce(),
selinux_status_policyload() or selinux_status_deny_unknown().

It enables to help to implement userspace avc with heavy access control
decision; that we cannot ignore the cost to communicate with kernel for
validation of userspace caches.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>

[-- Attachment #2: libselinux-status.1.patch --]
[-- Type: application/octet-stream, Size: 14212 bytes --]

 Signed-off-by: KaiGai Kohei <kaigai@ak.jp.nec.com>
--
 libselinux/include/selinux/avc.h                  |   36 +++
 libselinux/man/man3/selinux_status_close.3        |    1 +
 libselinux/man/man3/selinux_status_deny_unknown.3 |    1 +
 libselinux/man/man3/selinux_status_getenforce.3   |    1 +
 libselinux/man/man3/selinux_status_open.3         |   92 ++++++
 libselinux/man/man3/selinux_status_policyload.3   |    1 +
 libselinux/man/man3/selinux_status_updated.3      |    1 +
 libselinux/src/sestatus.c                         |  325 +++++++++++++++++++++
 8 files changed, 458 insertions(+), 0 deletions(-)

diff --git a/libselinux/include/selinux/avc.h b/libselinux/include/selinux/avc.h
index 37dd279..41b4fee 100644
--- a/libselinux/include/selinux/avc.h
+++ b/libselinux/include/selinux/avc.h
@@ -465,6 +465,42 @@ void avc_netlink_release_fd(void);
  */
 int avc_netlink_check_nb(void);
 
+/**
+ * selinux_status_open - Open and map SELinux kernel status page
+ *
+ */
+int selinux_status_open(int fallback, uint32_t *last_seqno);
+
+/**
+ * selinux_status_close - Unmap and close SELinux kernel status page
+ *
+ */
+void selinux_status_close(void);
+
+/**
+ * selinux_status_updated - Inform us whether the kernel status has been updated
+ *
+ */
+int selinux_status_updated(uint32_t *last_seqno);
+
+/**
+ * selinux_status_getenforce - Get the enforce flag value
+ *
+ */
+int selinux_status_getenforce(void);
+
+/**
+ * selinux_status_policyload - Get the number of policy reloaded
+ *
+ */
+int selinux_status_policyload(void);
+
+/**
+ * selinux_status_deny_unknown - Get the  behavior for undefined classes/permissions
+ *
+ */
+int selinux_status_deny_unknown(void);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/libselinux/man/man3/selinux_status_close.3 b/libselinux/man/man3/selinux_status_close.3
new file mode 100644
index 0000000..52a4169
--- /dev/null
+++ b/libselinux/man/man3/selinux_status_close.3
@@ -0,0 +1 @@
+.so man3/selinux_status_open.3
diff --git a/libselinux/man/man3/selinux_status_deny_unknown.3 b/libselinux/man/man3/selinux_status_deny_unknown.3
new file mode 100644
index 0000000..52a4169
--- /dev/null
+++ b/libselinux/man/man3/selinux_status_deny_unknown.3
@@ -0,0 +1 @@
+.so man3/selinux_status_open.3
diff --git a/libselinux/man/man3/selinux_status_getenforce.3 b/libselinux/man/man3/selinux_status_getenforce.3
new file mode 100644
index 0000000..52a4169
--- /dev/null
+++ b/libselinux/man/man3/selinux_status_getenforce.3
@@ -0,0 +1 @@
+.so man3/selinux_status_open.3
diff --git a/libselinux/man/man3/selinux_status_open.3 b/libselinux/man/man3/selinux_status_open.3
new file mode 100644
index 0000000..1af4259
--- /dev/null
+++ b/libselinux/man/man3/selinux_status_open.3
@@ -0,0 +1,92 @@
+.TH "selinux_status_open" "3" "22 January 2011" "kaigai@ak.jp.nec.com" "SELinux API documentation"
+.SH "NAME"
+selinux_status_open, selinux_status_close, selinux_status_updated,
+selinux_status_getenforce, selinux_status_policyload and
+selinux_status_deny_unknown \- reference the SELinux kernel status
+without invocation of system calls.
+.SH "SYNOPSIS"
+.B #include <selinux/avc.h>
+.sp
+.BI "int selinux_status_open(int " fallback, " uint32_t * " last_seqno ");"
+.sp
+.BI "void selinux_status_close(void);"
+.sp
+.BI "int selinux_status_updated(uint32_t * " last_seqno ");"
+.sp
+.BI "int selinux_status_getenforce(void);"
+.sp
+.BI "int selinux_status_policyload(void);"
+.sp
+.BI "int selinux_status_deny_unknown(void);"
+.sp
+.SH "DESCRIPTION"
+Linux 2.6.37 or later provides a SELinux kernel status page; being mostly
+placed on
+.I /selinux/status
+entry. It enables userspace applications to mmap this page with read-only
+mode, then it informs some status without system call invocations.
+.sp
+In some cases that a userspace application tries to apply heavy frequest
+access control; such as row\-level security in databases, it becomes
+unignorable cost to communicate with kernel space for invalidation check
+on userspace avc.
+.sp
+These functions provides applications a way to know some kernel events
+without system\-call invocation or worker thread for monitoring.
+.sp
+.BR selinux_status_open
+tries to
+.BR open (2)
+.I /selinux/status
+and
+.BR mmap (2)
+it with read-only mode. The file-descriptor and pointer to the page shall
+be stored internally; Don't touch them directly.
+Set 1 on the
+.I fallback
+argument to handle a case of older kernels without kernel status page support.
+In this case, this function tries to open a netlink socket using
+.BR avc_netlink_open (3)
+and overwrite callback settings on setenforce and policyreload.
+So, please pay attention to the interaction with these interfaces.
+The
+.I last_seqno
+is a pointer to store sequencial number of the kernel page on references;
+.B NULL
+is acceptable, if you never use
+.BR selinux_status_updated .
+.sp
+.BR selinux_status_close
+unmap the kernel status page and close its file descriptor, or close the
+netlink socket if fallbacked.
+.sp
+.BR selinux_status_updated
+returns 0 if nothing were happened since the last call, 1 if something has
+been happened, or -1 on error.
+The
+.I last_seqno
+is a pointer to the variable being initialized at
+.BR selinux_status_open .
+.sp
+.BR selinux_status_getenforce
+returns 0 if SELinux is running in permissive mode, 1 if enforcing mode,
+or -1 on error.
+Same as
+.BR security_getenforce (3)
+except with or without system call invocation.
+.sp
+.BR selinux_status_policyload
+returns times of policy reloaded on the running system, or -1 on error.
+Note that it is not a reliable value on fallback-mode until it receive
+the first event message via netlink socket.
+So, don't use this value to know actual times of policy reloaded.
+.sp
+.BR selinux_status_deny_unknown
+returns 0 if SELinux treats policy queries on undefined object classes or
+permissions as being allowed, 1 if such queries are denied, or -1 on error.
+.sp
+.SH "SEE ALSO"
+.BR mmap (2)
+.BR avc_netlink_open (3)
+.BR security_getenforce (3)
+.BR security_deny_unknown (3)
diff --git a/libselinux/man/man3/selinux_status_policyload.3 b/libselinux/man/man3/selinux_status_policyload.3
new file mode 100644
index 0000000..52a4169
--- /dev/null
+++ b/libselinux/man/man3/selinux_status_policyload.3
@@ -0,0 +1 @@
+.so man3/selinux_status_open.3
diff --git a/libselinux/man/man3/selinux_status_updated.3 b/libselinux/man/man3/selinux_status_updated.3
new file mode 100644
index 0000000..52a4169
--- /dev/null
+++ b/libselinux/man/man3/selinux_status_updated.3
@@ -0,0 +1 @@
+.so man3/selinux_status_open.3
diff --git a/libselinux/src/sestatus.c b/libselinux/src/sestatus.c
new file mode 100644
index 0000000..b764c9d
--- /dev/null
+++ b/libselinux/src/sestatus.c
@@ -0,0 +1,325 @@
+/*
+ * sestatus.c
+ *
+ * APIs to reference SELinux kernel status page (/selinux/status)
+ *
+ * Author: KaiGai Kohei <kaigai@ak.jp.nec.com>
+ *
+ */
+#include <fcntl.h>
+#include <limits.h>
+#include <sched.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include "avc_internal.h"
+#include "policy.h"
+
+/*
+ * copied from the selinux/include/security.h
+ */
+struct selinux_status_t
+{
+	uint32_t	version;	/* version number of thie structure */
+	uint32_t	sequence;	/* sequence number of seqlock logic */
+	uint32_t	enforcing;	/* current setting of enforcing mode */
+	uint32_t	policyload;	/* times of policy reloaded */
+	uint32_t	deny_unknown;	/* current setting of deny_unknown */
+	/* version > 0 support above status */
+} __attribute((packed));
+
+/*
+ * `selinux_status'
+ *
+ * NULL : not initialized yet
+ * MAP_FAILED : opened, but fallback-mode
+ * Valid Pointer : opened and mapped correctly
+ */
+static struct selinux_status_t *selinux_status = NULL;
+static int			selinux_status_fd;
+
+static uint32_t			fallback_sequence;
+static int			fallback_enforcing;
+static int			fallback_policyload;
+
+/*
+ * read_sequence
+ *
+ * A utility routine to reference kernel status page according to
+ * seqlock logic. Since selinux_status->sequence is an odd value during
+ * the kernel status page being updated, we try to synchronize completion
+ * of this updating, but we assume it is rare.
+ * The sequence is almost even number.
+ *
+ * __sync_synchronize is a portable memory barrier for various kind
+ * of architecture that is supported by GCC.
+ */
+static inline uint32_t read_sequence(struct selinux_status_t *status)
+{
+	uint32_t	seqno;
+
+repeat:
+	seqno = status->sequence;
+
+	__sync_synchronize();
+
+	if (seqno & 0x0001) {
+		sched_yield();
+		goto repeat;
+	}
+	return seqno;
+}
+
+/*
+ * selinux_status_updated
+ *
+ * It returns whether something has been happened since the last call.
+ * Because `selinux_status->sequence' shall be always incremented on
+ * both of setenforce/policyreload events, so differences from the last
+ * value informs us something has been happened.
+ */
+int selinux_status_updated(uint32_t *last_seqno)
+{
+	uint32_t	curr_seqno;
+	int		result = 0;
+
+	if (selinux_status == NULL) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	if (selinux_status == MAP_FAILED) {
+		if (avc_netlink_check_nb() < 0)
+			return -1;
+
+		curr_seqno = fallback_sequence;
+	} else {
+		curr_seqno = read_sequence(selinux_status);
+	}
+
+	if (curr_seqno != *last_seqno)
+	{
+		*last_seqno = curr_seqno;
+		result = 1;
+	}
+	return result;
+}
+
+/*
+ * selinux_status_getenforce
+ *
+ * It returns the current performing mode of SELinux.
+ * 1 means currently we run in enforcing mode, or 0 means permissive mode.
+ */
+int selinux_status_getenforce(void)
+{
+	uint32_t	seqno;
+	uint32_t	enforcing;
+
+	if (selinux_status == NULL) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	if (selinux_status == MAP_FAILED) {
+		if (avc_netlink_check_nb() < 0)
+			return -1;
+
+		return fallback_enforcing;
+	}
+
+	/* sequence must not be changed during references */
+	do {
+		seqno = read_sequence(selinux_status);
+
+		enforcing = selinux_status->enforcing;
+
+	} while (seqno != read_sequence(selinux_status));
+
+	return enforcing ? 1 : 0;
+}
+
+/*
+ * selinux_status_policyload
+ *
+ * It returns times of policy reloaded on the running system.
+ * Note that it is not a reliable value on fallback-mode until it receives
+ * the first event message via netlink socket, so, a correct usage of this
+ * value is to compare it with the previous value to detect policy reloaded
+ * event.
+ */
+int selinux_status_policyload(void)
+{
+	uint32_t	seqno;
+	uint32_t	policyload;
+
+	if (selinux_status == NULL) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	if (selinux_status == MAP_FAILED) {
+		if (avc_netlink_check_nb() < 0)
+			return -1;
+
+		return fallback_policyload;
+	}
+
+	/* sequence must not be changed during references */
+	do {
+		seqno = read_sequence(selinux_status);
+
+		policyload = selinux_status->policyload;
+
+	} while (seqno != read_sequence(selinux_status));
+
+	return policyload;
+}
+
+/*
+ * selinux_status_deny_unknown
+ *
+ * It returns a guideline to handle undefined object classes or permissions.
+ * 0 means SELinux treats policy queries on undefined stuff being allowed,
+ * however, 1 means such queries are denied.
+ */
+int selinux_status_deny_unknown(void)
+{
+	uint32_t	seqno;
+	uint32_t	deny_unknown;
+
+	if (selinux_status == NULL) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	if (selinux_status == MAP_FAILED)
+		return security_deny_unknown();
+
+	/* sequence must not be changed during references */
+	do {
+		seqno = read_sequence(selinux_status);
+
+		deny_unknown = selinux_status->deny_unknown;
+
+	} while (seqno != read_sequence(selinux_status));
+
+	return deny_unknown ? 1 : 0;
+}
+
+/*
+ * callback routines for fallback case using netlink socket
+ */
+static int fallback_cb_setenforce(int enforcing)
+{
+	fallback_sequence++;
+	fallback_enforcing = enforcing;
+
+	return 0;
+}
+
+static int fallback_cb_policyload(int policyload)
+{
+	fallback_sequence++;
+	fallback_policyload = policyload;
+
+	return 0;
+}
+
+/*
+ * selinux_status_open
+ *
+ * It tries to open and mmap kernel status page (/selinux/status).
+ * Since Linux 2.6.37 or later supports this feature, we may run
+ * fallback routine using a netlink socket on older kernels, if
+ * the supplied `fallback' is not zero.
+ * It returns 0 on success, or -1 on error.
+ */
+int selinux_status_open(int fallback, uint32_t *last_seqno)
+{
+	int	fd;
+	char	path[PATH_MAX];
+
+	if (!selinux_mnt) {
+		errno = ENOENT;
+		return -1;
+	}
+
+	snprintf(path, sizeof(path), "%s/status", selinux_mnt);
+	fd = open(path, O_RDONLY);
+	if (fd < 0)
+		goto error;
+
+	selinux_status = mmap(NULL, sysconf(_SC_PAGESIZE),
+			      PROT_READ, MAP_SHARED, fd, 0);
+	if (selinux_status == MAP_FAILED) {
+		close(fd);
+		goto error;
+	}
+	selinux_status_fd = fd;
+	if (last_seqno)
+		*last_seqno = read_sequence(selinux_status);
+
+	return 0;
+
+error:
+	/*
+	 * If caller wants fallback routine, we try to provide
+	 * an equivalent functionality using existing netlink
+	 * socket, although it needs system call invocation to
+	 * receive event notification.
+	 */
+	if (fallback && avc_netlink_open(0) == 0) {
+		union selinux_callback	cb;
+
+		/* register my callbacks */
+		cb.func_setenforce = fallback_cb_setenforce;
+		selinux_set_callback(SELINUX_CB_SETENFORCE, cb);
+		cb.func_policyload = fallback_cb_policyload;
+		selinux_set_callback(SELINUX_CB_POLICYLOAD, cb);
+
+		/* mark as fallback mode */
+		selinux_status = MAP_FAILED;
+		selinux_status_fd = avc_netlink_acquire_fd();
+
+		fallback_sequence = 0;
+		fallback_enforcing = security_getenforce();
+		fallback_policyload = 0;
+		if (last_seqno)
+			*last_seqno = 0;
+
+		return 0;
+	}
+	selinux_status = NULL;
+
+	return -1;
+}
+
+/*
+ * selinux_status_close
+ *
+ * It unmap and close the kernel status page, or close netlink socket
+ * if fallback mode.
+ */
+void selinux_status_close(void)
+{
+	/* not opened */
+	if (selinux_status == NULL)
+		return;
+
+	/* fallback-mode */
+	if (selinux_status == MAP_FAILED)
+	{
+		avc_netlink_release_fd();
+		avc_netlink_close();
+		selinux_status = NULL;
+		return;
+	}
+
+	munmap(selinux_status, sysconf(_SC_PAGESIZE));
+	selinux_status = NULL;
+
+	close(selinux_status_fd);
+	selinux_status_fd = -1;
+}

^ permalink raw reply related	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2011-03-07 17:08 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2011-01-22 13:42 libselinux: add selinux_status_* interfaces for /selinux/status Kohei KaiGai
2011-01-27  1:02 ` KaiGai Kohei
2011-02-09 14:05   ` Kohei Kaigai
2011-02-11 20:27   ` Steve Lawrence
2011-02-11 21:09     ` Kohei KaiGai
2011-03-01 17:53       ` Steve Lawrence
2011-03-07 17:07         ` Kohei Kaigai

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.