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