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 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  */
read_sequence(struct selinux_status_t * status)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  */
selinux_status_updated(void)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  */
selinux_status_getenforce(void)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  */
selinux_status_policyload(void)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  */
selinux_status_deny_unknown(void)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  */
fallback_cb_setenforce(int enforcing)230 static int fallback_cb_setenforce(int enforcing)
231 {
232 	fallback_sequence += 2;
233 	fallback_enforcing = enforcing;
234 
235 	return 0;
236 }
237 
fallback_cb_policyload(int policyload)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  */
selinux_status_open(int fallback)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  */
selinux_status_close(void)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