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 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 */
39static struct selinux_status_t *selinux_status = NULL;
40static int			selinux_status_fd;
41static uint32_t			last_seqno;
42
43static uint32_t			fallback_sequence;
44static int			fallback_enforcing;
45static 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 */
59static 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 */
88int 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 */
131int 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 */
168int 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 */
203int 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 */
230static int fallback_cb_setenforce(int enforcing)
231{
232	fallback_sequence += 2;
233	fallback_enforcing = enforcing;
234
235	return 0;
236}
237
238static 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 */
255int 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
285error:
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 */
323void 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