Home | History | Annotate | Download | only in src
      1 /*
      2  * sestatus.c
      3  *
      4  * APIs to reference SELinux kernel status page (/selinux/status)
      5  *
      6  * Author: KaiGai Kohei <kaigai (at) ak.jp.nec.com>
      7  *
      8  */
      9 #include <fcntl.h>
     10 #include <limits.h>
     11 #include <sched.h>
     12 #include <sys/mman.h>
     13 #include <sys/stat.h>
     14 #include <sys/types.h>
     15 #include <unistd.h>
     16 #include "avc_internal.h"
     17 #include "policy.h"
     18 
     19 /*
     20  * copied from the selinux/include/security.h
     21  */
     22 struct selinux_status_t
     23 {
     24 	uint32_t	version;	/* version number of thie structure */
     25 	uint32_t	sequence;	/* sequence number of seqlock logic */
     26 	uint32_t	enforcing;	/* current setting of enforcing mode */
     27 	uint32_t	policyload;	/* times of policy reloaded */
     28 	uint32_t	deny_unknown;	/* current setting of deny_unknown */
     29 	/* version > 0 support above status */
     30 } __attribute((packed));
     31 
     32 /*
     33  * `selinux_status'
     34  *
     35  * NULL : not initialized yet
     36  * MAP_FAILED : opened, but fallback-mode
     37  * Valid Pointer : opened and mapped correctly
     38  */
     39 static struct selinux_status_t *selinux_status = NULL;
     40 static int			selinux_status_fd;
     41 static uint32_t			last_seqno;
     42 
     43 static uint32_t			fallback_sequence;
     44 static int			fallback_enforcing;
     45 static int			fallback_policyload;
     46 
     47 /*
     48  * read_sequence
     49  *
     50  * A utility routine to reference kernel status page according to
     51  * seqlock logic. Since selinux_status->sequence is an odd value during
     52  * the kernel status page being updated, we try to synchronize completion
     53  * of this updating, but we assume it is rare.
     54  * The sequence is almost even number.
     55  *
     56  * __sync_synchronize is a portable memory barrier for various kind
     57  * of architecture that is supported by GCC.
     58  */
     59 static inline uint32_t read_sequence(struct selinux_status_t *status)
     60 {
     61 	uint32_t	seqno = 0;
     62 
     63 	do {
     64 		/*
     65 		 * No need for sched_yield() in the first trial of
     66 		 * this loop.
     67 		 */
     68 		if (seqno & 0x0001)
     69 			sched_yield();
     70 
     71 		seqno = status->sequence;
     72 
     73 		__sync_synchronize();
     74 
     75 	} while (seqno & 0x0001);
     76 
     77 	return seqno;
     78 }
     79 
     80 /*
     81  * selinux_status_updated
     82  *
     83  * It returns whether something has been happened since the last call.
     84  * Because `selinux_status->sequence' shall be always incremented on
     85  * both of setenforce/policyreload events, so differences from the last
     86  * value informs us something has been happened.
     87  */
     88 int selinux_status_updated(void)
     89 {
     90 	uint32_t	curr_seqno;
     91 	int		result = 0;
     92 
     93 	if (selinux_status == NULL) {
     94 		errno = EINVAL;
     95 		return -1;
     96 	}
     97 
     98 	if (selinux_status == MAP_FAILED) {
     99 		if (avc_netlink_check_nb() < 0)
    100 			return -1;
    101 
    102 		curr_seqno = fallback_sequence;
    103 	} else {
    104 		curr_seqno = read_sequence(selinux_status);
    105 	}
    106 
    107 	/*
    108 	 * `curr_seqno' is always even-number, so it does not match with
    109 	 * `last_seqno' being initialized to odd-number in the first call.
    110 	 * We never return 'something was updated' in the first call,
    111 	 * because this function focuses on status-updating since the last
    112 	 * invocation.
    113 	 */
    114 	if (last_seqno & 0x0001)
    115 		last_seqno = curr_seqno;
    116 
    117 	if (last_seqno != curr_seqno)
    118 	{
    119 		last_seqno = curr_seqno;
    120 		result = 1;
    121 	}
    122 	return result;
    123 }
    124 
    125 /*
    126  * selinux_status_getenforce
    127  *
    128  * It returns the current performing mode of SELinux.
    129  * 1 means currently we run in enforcing mode, or 0 means permissive mode.
    130  */
    131 int selinux_status_getenforce(void)
    132 {
    133 	uint32_t	seqno;
    134 	uint32_t	enforcing;
    135 
    136 	if (selinux_status == NULL) {
    137 		errno = EINVAL;
    138 		return -1;
    139 	}
    140 
    141 	if (selinux_status == MAP_FAILED) {
    142 		if (avc_netlink_check_nb() < 0)
    143 			return -1;
    144 
    145 		return fallback_enforcing;
    146 	}
    147 
    148 	/* sequence must not be changed during references */
    149 	do {
    150 		seqno = read_sequence(selinux_status);
    151 
    152 		enforcing = selinux_status->enforcing;
    153 
    154 	} while (seqno != read_sequence(selinux_status));
    155 
    156 	return enforcing ? 1 : 0;
    157 }
    158 
    159 /*
    160  * selinux_status_policyload
    161  *
    162  * It returns times of policy reloaded on the running system.
    163  * Note that it is not a reliable value on fallback-mode until it receives
    164  * the first event message via netlink socket, so, a correct usage of this
    165  * value is to compare it with the previous value to detect policy reloaded
    166  * event.
    167  */
    168 int selinux_status_policyload(void)
    169 {
    170 	uint32_t	seqno;
    171 	uint32_t	policyload;
    172 
    173 	if (selinux_status == NULL) {
    174 		errno = EINVAL;
    175 		return -1;
    176 	}
    177 
    178 	if (selinux_status == MAP_FAILED) {
    179 		if (avc_netlink_check_nb() < 0)
    180 			return -1;
    181 
    182 		return fallback_policyload;
    183 	}
    184 
    185 	/* sequence must not be changed during references */
    186 	do {
    187 		seqno = read_sequence(selinux_status);
    188 
    189 		policyload = selinux_status->policyload;
    190 
    191 	} while (seqno != read_sequence(selinux_status));
    192 
    193 	return policyload;
    194 }
    195 
    196 /*
    197  * selinux_status_deny_unknown
    198  *
    199  * It returns a guideline to handle undefined object classes or permissions.
    200  * 0 means SELinux treats policy queries on undefined stuff being allowed,
    201  * however, 1 means such queries are denied.
    202  */
    203 int selinux_status_deny_unknown(void)
    204 {
    205 	uint32_t	seqno;
    206 	uint32_t	deny_unknown;
    207 
    208 	if (selinux_status == NULL) {
    209 		errno = EINVAL;
    210 		return -1;
    211 	}
    212 
    213 	if (selinux_status == MAP_FAILED)
    214 		return security_deny_unknown();
    215 
    216 	/* sequence must not be changed during references */
    217 	do {
    218 		seqno = read_sequence(selinux_status);
    219 
    220 		deny_unknown = selinux_status->deny_unknown;
    221 
    222 	} while (seqno != read_sequence(selinux_status));
    223 
    224 	return deny_unknown ? 1 : 0;
    225 }
    226 
    227 /*
    228  * callback routines for fallback case using netlink socket
    229  */
    230 static int fallback_cb_setenforce(int enforcing)
    231 {
    232 	fallback_sequence += 2;
    233 	fallback_enforcing = enforcing;
    234 
    235 	return 0;
    236 }
    237 
    238 static int fallback_cb_policyload(int policyload)
    239 {
    240 	fallback_sequence += 2;
    241 	fallback_policyload = policyload;
    242 
    243 	return 0;
    244 }
    245 
    246 /*
    247  * selinux_status_open
    248  *
    249  * It tries to open and mmap kernel status page (/selinux/status).
    250  * Since Linux 2.6.37 or later supports this feature, we may run
    251  * fallback routine using a netlink socket on older kernels, if
    252  * the supplied `fallback' is not zero.
    253  * It returns 0 on success, or -1 on error.
    254  */
    255 int selinux_status_open(int fallback)
    256 {
    257 	int	fd;
    258 	char	path[PATH_MAX];
    259 	long	pagesize;
    260 
    261 	if (!selinux_mnt) {
    262 		errno = ENOENT;
    263 		return -1;
    264 	}
    265 
    266 	pagesize = sysconf(_SC_PAGESIZE);
    267 	if (pagesize < 0)
    268 		return -1;
    269 
    270 	snprintf(path, sizeof(path), "%s/status", selinux_mnt);
    271 	fd = open(path, O_RDONLY | O_CLOEXEC);
    272 	if (fd < 0)
    273 		goto error;
    274 
    275 	selinux_status = mmap(NULL, pagesize, PROT_READ, MAP_SHARED, fd, 0);
    276 	if (selinux_status == MAP_FAILED) {
    277 		close(fd);
    278 		goto error;
    279 	}
    280 	selinux_status_fd = fd;
    281 	last_seqno = (uint32_t)(-1);
    282 
    283 	return 0;
    284 
    285 error:
    286 	/*
    287 	 * If caller wants fallback routine, we try to provide
    288 	 * an equivalent functionality using existing netlink
    289 	 * socket, although it needs system call invocation to
    290 	 * receive event notification.
    291 	 */
    292 	if (fallback && avc_netlink_open(0) == 0) {
    293 		union selinux_callback	cb;
    294 
    295 		/* register my callbacks */
    296 		cb.func_setenforce = fallback_cb_setenforce;
    297 		selinux_set_callback(SELINUX_CB_SETENFORCE, cb);
    298 		cb.func_policyload = fallback_cb_policyload;
    299 		selinux_set_callback(SELINUX_CB_POLICYLOAD, cb);
    300 
    301 		/* mark as fallback mode */
    302 		selinux_status = MAP_FAILED;
    303 		selinux_status_fd = avc_netlink_acquire_fd();
    304 		last_seqno = (uint32_t)(-1);
    305 
    306 		fallback_sequence = 0;
    307 		fallback_enforcing = security_getenforce();
    308 		fallback_policyload = 0;
    309 
    310 		return 1;
    311 	}
    312 	selinux_status = NULL;
    313 
    314 	return -1;
    315 }
    316 
    317 /*
    318  * selinux_status_close
    319  *
    320  * It unmap and close the kernel status page, or close netlink socket
    321  * if fallback mode.
    322  */
    323 void selinux_status_close(void)
    324 {
    325 	long pagesize;
    326 
    327 	/* not opened */
    328 	if (selinux_status == NULL)
    329 		return;
    330 
    331 	/* fallback-mode */
    332 	if (selinux_status == MAP_FAILED)
    333 	{
    334 		avc_netlink_release_fd();
    335 		avc_netlink_close();
    336 		selinux_status = NULL;
    337 		return;
    338 	}
    339 
    340 	pagesize = sysconf(_SC_PAGESIZE);
    341 	/* not much we can do other than leak memory */
    342 	if (pagesize > 0)
    343 		munmap(selinux_status, pagesize);
    344 	selinux_status = NULL;
    345 
    346 	close(selinux_status_fd);
    347 	selinux_status_fd = -1;
    348 	last_seqno = (uint32_t)(-1);
    349 }
    350