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