From bc2a8f418e3b7bd9c2abd83e441a45ad59631f1f Mon Sep 17 00:00:00 2001 From: KaiGai Kohei Date: Tue, 1 Mar 2011 11:21:19 -0500 Subject: [PATCH] libselinux: add selinux_status_* interfaces for /selinux/status 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. Signed-off-by: Steve Lawrence --- libselinux/include/selinux/avc.h | 36 ++ libselinux/man/man3/selinux_status_close.3 | 1 + .../man/man3/selinux_status_deny_unknown.3 | 1 + .../man/man3/selinux_status_getenforce.3 | 1 + libselinux/man/man3/selinux_status_open.3 | 96 +++++ .../man/man3/selinux_status_policyload.3 | 1 + libselinux/man/man3/selinux_status_updated.3 | 1 + libselinux/src/sestatus.c | 340 ++++++++++++++++++ 8 files changed, 477 insertions(+) create mode 100644 libselinux/man/man3/selinux_status_close.3 create mode 100644 libselinux/man/man3/selinux_status_deny_unknown.3 create mode 100644 libselinux/man/man3/selinux_status_getenforce.3 create mode 100644 libselinux/man/man3/selinux_status_open.3 create mode 100644 libselinux/man/man3/selinux_status_policyload.3 create mode 100644 libselinux/man/man3/selinux_status_updated.3 create mode 100644 libselinux/src/sestatus.c diff --git a/libselinux/include/selinux/avc.h b/libselinux/include/selinux/avc.h index 37dd279a..da18e416 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); + +/** + * 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(void); + +/** + * 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 00000000..52a41690 --- /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 00000000..52a41690 --- /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 00000000..52a41690 --- /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 00000000..e897939e --- /dev/null +++ b/libselinux/man/man3/selinux_status_open.3 @@ -0,0 +1,96 @@ +.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 +.sp +.BI "int selinux_status_open(int " fallback, ");" +.sp +.BI "void selinux_status_close(void);" +.sp +.BI "int selinux_status_updated(void);" +.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 will face +unignorable cost to communicate with kernel space to check invalidation +of 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 in 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 corresponding callbacks ( setenforce and policyload). +Thus, we need to pay attention to the interaction with these interfaces, +when fallback mode is enabled. +.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 +informs us whether something has been updated since the last call. +It returns 0 if nothing was happened, however, 1 if something has been +updated in this duration, or -1 on error. +.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. +Thus, 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 +Also note that these interfaces are not thread-safe, so you have to protect +them from concurrent calls using exclusive locks when multiple threads are +performing. +.SH "RETURN VALUE" +.BR selinux_status_open +returns 0 or 1 on success. 1 means we are ready to use these interfaces, +but netlink socket was opened as fallback instead of the kernel status page. +On error, -1 shall be returned. +.sp +Any other functions with a return value shall return its characteristic +value as described above, or -1 on errors. +.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 00000000..52a41690 --- /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 00000000..52a41690 --- /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 00000000..10a6495d --- /dev/null +++ b/libselinux/src/sestatus.c @@ -0,0 +1,340 @@ +/* + * sestatus.c + * + * APIs to reference SELinux kernel status page (/selinux/status) + * + * Author: KaiGai Kohei + * + */ +#include +#include +#include +#include +#include +#include +#include +#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 last_seqno; + +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 = 0; + + do { + /* + * No need for sched_yield() in the first trial of + * this loop. + */ + if (seqno & 0x0001) + sched_yield(); + + seqno = status->sequence; + + __sync_synchronize(); + + } while (seqno & 0x0001); + + 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(void) +{ + 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); + } + + /* + * `curr_seqno' is always even-number, so it does not match with + * `last_seqno' being initialized to odd-number in the first call. + * We never return 'something was updated' in the first call, + * because this function focuses on status-updating since the last + * invocation. + */ + if (last_seqno & 0x0001) + last_seqno = curr_seqno; + + if (last_seqno != curr_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 += 2; + fallback_enforcing = enforcing; + + return 0; +} + +static int fallback_cb_policyload(int policyload) +{ + fallback_sequence += 2; + 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) +{ + 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; + last_seqno = (uint32_t)(-1); + + 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(); + last_seqno = (uint32_t)(-1); + + fallback_sequence = 0; + fallback_enforcing = security_getenforce(); + fallback_policyload = 0; + + return 1; + } + 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; + last_seqno = (uint32_t)(-1); +}