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