• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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