]>
Commit | Line | Data |
---|---|---|
e2417e41 DW |
1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
2 | ||
3 | /*** | |
4 | This file is part of systemd. | |
5 | ||
6 | Copyright 2012 Dan Walsh | |
7 | ||
8 | systemd is free software; you can redistribute it and/or modify it | |
03e22642 KS |
9 | under the terms of the GNU Lesser General Public License as published by |
10 | the Free Software Foundation; either version 2.1 of the License, or | |
e2417e41 DW |
11 | (at your option) any later version. |
12 | ||
13 | systemd is distributed in the hope that it will be useful, but | |
14 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
03e22642 | 16 | Lesser General Public License for more details. |
e2417e41 | 17 | |
03e22642 | 18 | You should have received a copy of the GNU Lesser General Public License |
e2417e41 DW |
19 | along with systemd; If not, see <http://www.gnu.org/licenses/>. |
20 | ***/ | |
21 | ||
e2417e41 DW |
22 | #include "selinux-access.h" |
23 | ||
24 | #ifdef HAVE_SELINUX | |
e2417e41 DW |
25 | |
26 | #include <stdio.h> | |
27 | #include <string.h> | |
28 | #include <errno.h> | |
ffc227c9 | 29 | #include <limits.h> |
e2417e41 DW |
30 | #include <selinux/selinux.h> |
31 | #include <selinux/avc.h> | |
32 | #ifdef HAVE_AUDIT | |
33 | #include <libaudit.h> | |
34 | #endif | |
ffc227c9 | 35 | |
718db961 LP |
36 | #include "sd-bus.h" |
37 | #include "bus-util.h" | |
ffc227c9 LP |
38 | #include "util.h" |
39 | #include "log.h" | |
ffc227c9 LP |
40 | #include "audit.h" |
41 | #include "selinux-util.h" | |
42 | #include "audit-fd.h" | |
e2417e41 | 43 | |
cad45ba1 | 44 | static bool initialized = false; |
e2417e41 DW |
45 | |
46 | struct auditstruct { | |
47 | const char *path; | |
48 | char *cmdline; | |
49 | uid_t loginuid; | |
50 | uid_t uid; | |
51 | gid_t gid; | |
52 | }; | |
53 | ||
e2417e41 | 54 | static int bus_get_selinux_security_context( |
718db961 | 55 | sd_bus *bus, |
e2417e41 | 56 | const char *name, |
718db961 LP |
57 | sd_bus_error *error, |
58 | char **ret) { | |
e2417e41 | 59 | |
718db961 LP |
60 | _cleanup_bus_message_unref_ sd_bus_message *m = NULL; |
61 | const void *p; | |
62 | size_t sz; | |
a33c48d8 | 63 | char *b; |
718db961 | 64 | int r; |
e2417e41 | 65 | |
718db961 LP |
66 | assert(bus); |
67 | assert(name); | |
68 | assert(ret); | |
69 | ||
70 | r = sd_bus_call_method( | |
71 | bus, | |
72 | "org.freedesktop.DBus", | |
73 | "/org/freedesktop/DBus", | |
74 | "org.freedesktop.DBus", | |
75 | "GetConnectionSELinuxSecurityContext", | |
76 | error, &m, | |
77 | "s", name); | |
78 | if (r < 0) | |
79 | return r; | |
a33c48d8 | 80 | |
718db961 LP |
81 | r = sd_bus_message_read_array(m, 'y', &p, &sz); |
82 | if (r < 0) | |
83 | return r; | |
a33c48d8 | 84 | |
718db961 | 85 | b = strndup(p, sz); |
a33c48d8 DW |
86 | if (!b) |
87 | return -ENOMEM; | |
88 | ||
718db961 | 89 | *ret = b; |
cad45ba1 | 90 | return 0; |
e2417e41 DW |
91 | } |
92 | ||
e2417e41 | 93 | static int bus_get_audit_data( |
718db961 | 94 | sd_bus *bus, |
e2417e41 | 95 | const char *name, |
718db961 | 96 | struct auditstruct *audit) { |
e2417e41 DW |
97 | |
98 | pid_t pid; | |
c3090674 | 99 | int r; |
e2417e41 | 100 | |
718db961 LP |
101 | assert(bus); |
102 | assert(name); | |
103 | assert(audit); | |
104 | ||
105 | r = sd_bus_get_owner_pid(bus, name, &pid); | |
106 | if (r < 0) | |
107 | return r; | |
e2417e41 | 108 | |
c3090674 LP |
109 | r = audit_loginuid_from_pid(pid, &audit->loginuid); |
110 | if (r < 0) | |
111 | return r; | |
e2417e41 | 112 | |
c3090674 LP |
113 | r = get_process_uid(pid, &audit->uid); |
114 | if (r < 0) | |
115 | return r; | |
e2417e41 | 116 | |
c3090674 LP |
117 | r = get_process_gid(pid, &audit->gid); |
118 | if (r < 0) | |
119 | return r; | |
e2417e41 | 120 | |
9bdbc2e2 | 121 | r = get_process_cmdline(pid, 0, true, &audit->cmdline); |
c3090674 LP |
122 | if (r < 0) |
123 | return r; | |
e2417e41 | 124 | |
c3090674 | 125 | return 0; |
e2417e41 DW |
126 | } |
127 | ||
128 | /* | |
129 | Any time an access gets denied this callback will be called | |
130 | with the aduit data. We then need to just copy the audit data into the msgbuf. | |
131 | */ | |
cad45ba1 LP |
132 | static int audit_callback( |
133 | void *auditdata, | |
134 | security_class_t cls, | |
135 | char *msgbuf, | |
136 | size_t msgbufsize) { | |
137 | ||
e2417e41 | 138 | struct auditstruct *audit = (struct auditstruct *) auditdata; |
cad45ba1 | 139 | |
e2417e41 | 140 | snprintf(msgbuf, msgbufsize, |
cad45ba1 | 141 | "auid=%d uid=%d gid=%d%s%s%s%s%s%s", |
d67227c8 | 142 | audit->loginuid, |
cad45ba1 LP |
143 | audit->uid, |
144 | audit->gid, | |
145 | (audit->path ? " path=\"" : ""), | |
146 | strempty(audit->path), | |
147 | (audit->path ? "\"" : ""), | |
148 | (audit->cmdline ? " cmdline=\"" : ""), | |
149 | strempty(audit->cmdline), | |
150 | (audit->cmdline ? "\"" : "")); | |
d67227c8 | 151 | |
cad45ba1 | 152 | msgbuf[msgbufsize-1] = 0; |
d67227c8 | 153 | |
e2417e41 DW |
154 | return 0; |
155 | } | |
156 | ||
157 | /* | |
158 | Any time an access gets denied this callback will be called | |
159 | code copied from dbus. If audit is turned on the messages will go as | |
160 | user_avc's into the /var/log/audit/audit.log, otherwise they will be | |
161 | sent to syslog. | |
162 | */ | |
44b601bc | 163 | _printf_(2, 3) static int log_callback(int type, const char *fmt, ...) { |
e2417e41 DW |
164 | va_list ap; |
165 | ||
166 | va_start(ap, fmt); | |
cad45ba1 | 167 | |
e2417e41 | 168 | #ifdef HAVE_AUDIT |
c1165f82 | 169 | if (get_audit_fd() >= 0) { |
ace188cf LP |
170 | _cleanup_free_ char *buf = NULL; |
171 | int r; | |
e2417e41 | 172 | |
ace188cf | 173 | r = vasprintf(&buf, fmt, ap); |
7f1736f7 | 174 | va_end(ap); |
cad45ba1 | 175 | |
ace188cf LP |
176 | if (r >= 0) { |
177 | audit_log_user_avc_message(get_audit_fd(), AUDIT_USER_AVC, buf, NULL, NULL, NULL, 0); | |
178 | return 0; | |
179 | } | |
180 | ||
181 | va_start(ap, fmt); | |
e2417e41 DW |
182 | } |
183 | #endif | |
184 | log_metav(LOG_USER | LOG_INFO, __FILE__, __LINE__, __FUNCTION__, fmt, ap); | |
185 | va_end(ap); | |
cad45ba1 | 186 | |
e2417e41 DW |
187 | return 0; |
188 | } | |
189 | ||
190 | /* | |
191 | Function must be called once to initialize the SELinux AVC environment. | |
192 | Sets up callbacks. | |
193 | If you want to cleanup memory you should need to call selinux_access_finish. | |
194 | */ | |
195 | static int access_init(void) { | |
718db961 | 196 | int r = 0; |
e2417e41 DW |
197 | |
198 | if (avc_open(NULL, 0)) { | |
cad45ba1 | 199 | log_error("avc_open() failed: %m"); |
e2417e41 DW |
200 | return -errno; |
201 | } | |
202 | ||
cad45ba1 LP |
203 | selinux_set_callback(SELINUX_CB_AUDIT, (union selinux_callback) audit_callback); |
204 | selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) log_callback); | |
e2417e41 | 205 | |
718db961 LP |
206 | if (security_getenforce() < 0){ |
207 | r = -errno; | |
208 | avc_destroy(); | |
209 | } | |
cad45ba1 | 210 | |
e2417e41 DW |
211 | return r; |
212 | } | |
213 | ||
718db961 | 214 | static int selinux_access_init(sd_bus_error *error) { |
e2417e41 DW |
215 | int r; |
216 | ||
cad45ba1 | 217 | if (initialized) |
e2417e41 DW |
218 | return 0; |
219 | ||
718db961 LP |
220 | if (!use_selinux()) |
221 | return 0; | |
222 | ||
223 | r = access_init(); | |
224 | if (r < 0) | |
225 | return sd_bus_error_set(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to initialize SELinux."); | |
e2417e41 | 226 | |
cad45ba1 | 227 | initialized = true; |
e2417e41 DW |
228 | return 0; |
229 | } | |
230 | ||
ffc227c9 | 231 | void selinux_access_free(void) { |
718db961 | 232 | |
ffc227c9 LP |
233 | if (!initialized) |
234 | return; | |
235 | ||
236 | avc_destroy(); | |
237 | initialized = false; | |
238 | } | |
239 | ||
e2417e41 | 240 | static int get_audit_data( |
718db961 LP |
241 | sd_bus *bus, |
242 | sd_bus_message *message, | |
243 | struct auditstruct *audit) { | |
e2417e41 | 244 | |
718db961 | 245 | struct ucred ucred; |
e2417e41 | 246 | const char *sender; |
718db961 | 247 | socklen_t len; |
cad45ba1 | 248 | int r, fd; |
e2417e41 | 249 | |
718db961 | 250 | sender = sd_bus_message_get_sender(message); |
c3090674 | 251 | if (sender) |
718db961 | 252 | return bus_get_audit_data(bus, sender, audit); |
c3090674 | 253 | |
718db961 LP |
254 | fd = sd_bus_get_fd(bus); |
255 | if (fd < 0) | |
256 | return fd; | |
e2417e41 | 257 | |
718db961 | 258 | len = sizeof(ucred); |
cad45ba1 | 259 | r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len); |
718db961 | 260 | if (r < 0) |
cad45ba1 | 261 | return -errno; |
c3090674 | 262 | |
cad45ba1 LP |
263 | audit->uid = ucred.uid; |
264 | audit->gid = ucred.gid; | |
e2417e41 | 265 | |
cad45ba1 LP |
266 | r = audit_loginuid_from_pid(ucred.pid, &audit->loginuid); |
267 | if (r < 0) | |
268 | return r; | |
e2417e41 | 269 | |
9bdbc2e2 | 270 | r = get_process_cmdline(ucred.pid, 0, true, &audit->cmdline); |
cad45ba1 LP |
271 | if (r < 0) |
272 | return r; | |
e2417e41 | 273 | |
cad45ba1 | 274 | return 0; |
e2417e41 DW |
275 | } |
276 | ||
277 | /* | |
278 | This function returns the security context of the remote end of the dbus | |
279 | connections. Whether it is on the bus or a local connection. | |
280 | */ | |
281 | static int get_calling_context( | |
718db961 LP |
282 | sd_bus *bus, |
283 | sd_bus_message *message, | |
284 | sd_bus_error *error, | |
285 | security_context_t *ret) { | |
e2417e41 DW |
286 | |
287 | const char *sender; | |
718db961 | 288 | int r, fd; |
e2417e41 DW |
289 | |
290 | /* | |
291 | If sender exists then | |
292 | if sender is NULL this indicates a local connection. Grab the fd | |
293 | from dbus and do an getpeercon to peers process context | |
294 | */ | |
718db961 LP |
295 | sender = sd_bus_message_get_sender(message); |
296 | if (sender) | |
297 | return bus_get_selinux_security_context(bus, sender, error, ret); | |
d67227c8 | 298 | |
718db961 LP |
299 | fd = sd_bus_get_fd(bus); |
300 | if (fd < 0) | |
301 | return fd; | |
d67227c8 | 302 | |
718db961 LP |
303 | r = getpeercon(fd, ret); |
304 | if (r < 0) | |
d67227c8 | 305 | return -errno; |
e2417e41 DW |
306 | |
307 | return 0; | |
308 | } | |
309 | ||
e2417e41 DW |
310 | /* |
311 | This function communicates with the kernel to check whether or not it should | |
312 | allow the access. | |
313 | If the machine is in permissive mode it will return ok. Audit messages will | |
314 | still be generated if the access would be denied in enforcing mode. | |
315 | */ | |
ffc227c9 | 316 | int selinux_access_check( |
718db961 LP |
317 | sd_bus *bus, |
318 | sd_bus_message *message, | |
cad45ba1 LP |
319 | const char *path, |
320 | const char *permission, | |
718db961 | 321 | sd_bus_error *error) { |
cad45ba1 LP |
322 | |
323 | security_context_t scon = NULL, fcon = NULL; | |
e2417e41 DW |
324 | const char *tclass = NULL; |
325 | struct auditstruct audit; | |
718db961 | 326 | int r = 0; |
c3090674 | 327 | |
718db961 | 328 | assert(bus); |
cad45ba1 LP |
329 | assert(message); |
330 | assert(permission); | |
331 | assert(error); | |
332 | ||
cad45ba1 LP |
333 | if (!use_selinux()) |
334 | return 0; | |
335 | ||
ffc227c9 LP |
336 | r = selinux_access_init(error); |
337 | if (r < 0) | |
338 | return r; | |
339 | ||
c3090674 LP |
340 | audit.uid = audit.loginuid = (uid_t) -1; |
341 | audit.gid = (gid_t) -1; | |
e2417e41 DW |
342 | audit.cmdline = NULL; |
343 | audit.path = path; | |
344 | ||
718db961 LP |
345 | r = get_calling_context(bus, message, error, &scon); |
346 | if (r < 0) | |
e2417e41 | 347 | goto finish; |
cad45ba1 | 348 | |
e2417e41 | 349 | if (path) { |
718db961 LP |
350 | /* Get the file context of the unit file */ |
351 | ||
e2417e41 DW |
352 | r = getfilecon(path, &fcon); |
353 | if (r < 0) { | |
718db961 | 354 | r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to get file context on %s.", path); |
e2417e41 DW |
355 | goto finish; |
356 | } | |
357 | ||
718db961 | 358 | tclass = "service"; |
e2417e41 | 359 | } else { |
e2417e41 DW |
360 | r = getcon(&fcon); |
361 | if (r < 0) { | |
718db961 | 362 | r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to get current context."); |
e2417e41 DW |
363 | goto finish; |
364 | } | |
718db961 LP |
365 | |
366 | tclass = "system"; | |
e2417e41 DW |
367 | } |
368 | ||
718db961 | 369 | get_audit_data(bus, message, &audit); |
e2417e41 | 370 | |
cad45ba1 LP |
371 | errno = 0; |
372 | r = selinux_check_access(scon, fcon, tclass, permission, &audit); | |
718db961 LP |
373 | if (r < 0) |
374 | r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "SELinux policy denies access."); | |
e2417e41 | 375 | |
cad45ba1 LP |
376 | log_debug("SELinux access check scon=%s tcon=%s tclass=%s perm=%s path=%s cmdline=%s: %i", scon, fcon, tclass, permission, path, audit.cmdline, r); |
377 | ||
e2417e41 | 378 | finish: |
e2417e41 DW |
379 | free(audit.cmdline); |
380 | freecon(scon); | |
381 | freecon(fcon); | |
382 | ||
cad45ba1 | 383 | if (r && security_getenforce() != 1) { |
718db961 | 384 | sd_bus_error_free(error); |
e2417e41 DW |
385 | r = 0; |
386 | } | |
387 | ||
388 | return r; | |
389 | } | |
390 | ||
e2417e41 | 391 | #else |
cad45ba1 | 392 | |
ffc227c9 | 393 | int selinux_access_check( |
718db961 LP |
394 | sd_bus *bus, |
395 | sd_bus_message *message, | |
ffc227c9 | 396 | const char *path, |
cad45ba1 | 397 | const char *permission, |
718db961 | 398 | sd_bus_error *error) { |
cad45ba1 | 399 | |
e2417e41 DW |
400 | return 0; |
401 | } | |
402 | ||
ffc227c9 | 403 | void selinux_access_free(void) { |
c3090674 | 404 | } |
cad45ba1 | 405 | |
e2417e41 | 406 | #endif |