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 <slawrence@tresys.com>
This commit is contained in:
parent
b676c84dbd
commit
bc2a8f418e
8 changed files with 477 additions and 0 deletions
|
@ -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
|
||||
|
|
1
libselinux/man/man3/selinux_status_close.3
Normal file
1
libselinux/man/man3/selinux_status_close.3
Normal file
|
@ -0,0 +1 @@
|
|||
.so man3/selinux_status_open.3
|
1
libselinux/man/man3/selinux_status_deny_unknown.3
Normal file
1
libselinux/man/man3/selinux_status_deny_unknown.3
Normal file
|
@ -0,0 +1 @@
|
|||
.so man3/selinux_status_open.3
|
1
libselinux/man/man3/selinux_status_getenforce.3
Normal file
1
libselinux/man/man3/selinux_status_getenforce.3
Normal file
|
@ -0,0 +1 @@
|
|||
.so man3/selinux_status_open.3
|
96
libselinux/man/man3/selinux_status_open.3
Normal file
96
libselinux/man/man3/selinux_status_open.3
Normal file
|
@ -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 <selinux/avc.h>
|
||||
.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)
|
1
libselinux/man/man3/selinux_status_policyload.3
Normal file
1
libselinux/man/man3/selinux_status_policyload.3
Normal file
|
@ -0,0 +1 @@
|
|||
.so man3/selinux_status_open.3
|
1
libselinux/man/man3/selinux_status_updated.3
Normal file
1
libselinux/man/man3/selinux_status_updated.3
Normal file
|
@ -0,0 +1 @@
|
|||
.so man3/selinux_status_open.3
|
340
libselinux/src/sestatus.c
Normal file
340
libselinux/src/sestatus.c
Normal file
|
@ -0,0 +1,340 @@
|
|||
/*
|
||||
* 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 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);
|
||||
}
|
Loading…
Reference in a new issue