| /* |
| * 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]; |
| long pagesize; |
| |
| if (!selinux_mnt) { |
| errno = ENOENT; |
| return -1; |
| } |
| |
| pagesize = sysconf(_SC_PAGESIZE); |
| if (pagesize < 0) |
| return -1; |
| |
| snprintf(path, sizeof(path), "%s/status", selinux_mnt); |
| fd = open(path, O_RDONLY | O_CLOEXEC); |
| if (fd < 0) |
| goto error; |
| |
| selinux_status = mmap(NULL, 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) |
| { |
| long pagesize; |
| |
| /* 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; |
| } |
| |
| pagesize = sysconf(_SC_PAGESIZE); |
| /* not much we can do other than leak memory */ |
| if (pagesize > 0) |
| munmap(selinux_status, pagesize); |
| selinux_status = NULL; |
| |
| close(selinux_status_fd); |
| selinux_status_fd = -1; |
| last_seqno = (uint32_t)(-1); |
| } |