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