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 */
22 struct 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 */
39 static struct selinux_status_t *selinux_status = NULL;
40 static uint32_t last_seqno;
41 static uint32_t last_policyload;
42
43 static uint32_t fallback_sequence;
44 static int fallback_enforcing;
45 static int fallback_policyload;
46
47 static 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 */
read_sequence(struct selinux_status_t * status)61 static 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 */
selinux_status_updated(void)90 int 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 */
selinux_status_getenforce(void)152 int 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 */
selinux_status_policyload(void)189 int 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 */
selinux_status_deny_unknown(void)224 int 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 */
fallback_cb_setenforce(int enforcing)251 static int fallback_cb_setenforce(int enforcing)
252 {
253 fallback_sequence += 2;
254 fallback_enforcing = enforcing;
255
256 return 0;
257 }
258
fallback_cb_policyload(int policyload)259 static 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 */
selinux_status_open(int fallback)278 int 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
323 error:
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 */
selinux_status_close(void)365 void 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