]>
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 | |
9 | under the terms of the GNU General Public License as published by | |
10 | the Free Software Foundation; either version 2 of the License, or | |
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 | |
16 | General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU General Public License | |
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 LP |
35 | #include <dbus.h> |
36 | ||
37 | #include "util.h" | |
38 | #include "log.h" | |
39 | #include "bus-errors.h" | |
40 | #include "dbus-common.h" | |
41 | #include "audit.h" | |
42 | #include "selinux-util.h" | |
43 | #include "audit-fd.h" | |
e2417e41 | 44 | |
cad45ba1 | 45 | static bool initialized = false; |
e2417e41 DW |
46 | |
47 | struct auditstruct { | |
48 | const char *path; | |
49 | char *cmdline; | |
50 | uid_t loginuid; | |
51 | uid_t uid; | |
52 | gid_t gid; | |
53 | }; | |
54 | ||
e2417e41 DW |
55 | static int bus_get_selinux_security_context( |
56 | DBusConnection *connection, | |
57 | const char *name, | |
58 | char **scon, | |
59 | DBusError *error) { | |
60 | ||
cad45ba1 | 61 | _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL; |
a33c48d8 DW |
62 | DBusMessageIter iter, sub; |
63 | const char *bytes; | |
64 | char *b; | |
65 | int nbytes; | |
e2417e41 DW |
66 | |
67 | m = dbus_message_new_method_call( | |
68 | DBUS_SERVICE_DBUS, | |
69 | DBUS_PATH_DBUS, | |
70 | DBUS_INTERFACE_DBUS, | |
71 | "GetConnectionSELinuxSecurityContext"); | |
72 | if (!m) { | |
e2417e41 | 73 | dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL); |
cad45ba1 | 74 | return -ENOMEM; |
e2417e41 DW |
75 | } |
76 | ||
cad45ba1 LP |
77 | if (!dbus_message_append_args( |
78 | m, | |
79 | DBUS_TYPE_STRING, &name, | |
80 | DBUS_TYPE_INVALID)) { | |
e2417e41 | 81 | dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL); |
cad45ba1 | 82 | return -ENOMEM; |
e2417e41 DW |
83 | } |
84 | ||
85 | reply = dbus_connection_send_with_reply_and_block(connection, m, -1, error); | |
cad45ba1 LP |
86 | if (!reply) |
87 | return -EIO; | |
e2417e41 | 88 | |
cad45ba1 LP |
89 | if (dbus_set_error_from_message(error, reply)) |
90 | return -EIO; | |
e2417e41 | 91 | |
a33c48d8 | 92 | if (!dbus_message_iter_init(reply, &iter)) |
cad45ba1 | 93 | return -EIO; |
e2417e41 | 94 | |
a33c48d8 DW |
95 | if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) |
96 | return -EIO; | |
97 | ||
98 | dbus_message_iter_recurse(&iter, &sub); | |
99 | dbus_message_iter_get_fixed_array(&sub, &bytes, &nbytes); | |
100 | ||
101 | b = strndup(bytes, nbytes); | |
102 | if (!b) | |
103 | return -ENOMEM; | |
104 | ||
105 | *scon = b; | |
106 | ||
cad45ba1 | 107 | return 0; |
e2417e41 DW |
108 | } |
109 | ||
e2417e41 DW |
110 | static int bus_get_audit_data( |
111 | DBusConnection *connection, | |
112 | const char *name, | |
113 | struct auditstruct *audit, | |
114 | DBusError *error) { | |
115 | ||
116 | pid_t pid; | |
c3090674 | 117 | int r; |
e2417e41 | 118 | |
c3090674 LP |
119 | pid = bus_get_unix_process_id(connection, name, error); |
120 | if (pid <= 0) | |
cad45ba1 | 121 | return -EIO; |
e2417e41 | 122 | |
c3090674 LP |
123 | r = audit_loginuid_from_pid(pid, &audit->loginuid); |
124 | if (r < 0) | |
125 | return r; | |
e2417e41 | 126 | |
c3090674 LP |
127 | r = get_process_uid(pid, &audit->uid); |
128 | if (r < 0) | |
129 | return r; | |
e2417e41 | 130 | |
c3090674 LP |
131 | r = get_process_gid(pid, &audit->gid); |
132 | if (r < 0) | |
133 | return r; | |
e2417e41 | 134 | |
9bdbc2e2 | 135 | r = get_process_cmdline(pid, 0, true, &audit->cmdline); |
c3090674 LP |
136 | if (r < 0) |
137 | return r; | |
e2417e41 | 138 | |
c3090674 | 139 | return 0; |
e2417e41 DW |
140 | } |
141 | ||
142 | /* | |
143 | Any time an access gets denied this callback will be called | |
144 | with the aduit data. We then need to just copy the audit data into the msgbuf. | |
145 | */ | |
cad45ba1 LP |
146 | static int audit_callback( |
147 | void *auditdata, | |
148 | security_class_t cls, | |
149 | char *msgbuf, | |
150 | size_t msgbufsize) { | |
151 | ||
e2417e41 | 152 | struct auditstruct *audit = (struct auditstruct *) auditdata; |
cad45ba1 | 153 | |
e2417e41 | 154 | snprintf(msgbuf, msgbufsize, |
cad45ba1 | 155 | "auid=%d uid=%d gid=%d%s%s%s%s%s%s", |
d67227c8 | 156 | audit->loginuid, |
cad45ba1 LP |
157 | audit->uid, |
158 | audit->gid, | |
159 | (audit->path ? " path=\"" : ""), | |
160 | strempty(audit->path), | |
161 | (audit->path ? "\"" : ""), | |
162 | (audit->cmdline ? " cmdline=\"" : ""), | |
163 | strempty(audit->cmdline), | |
164 | (audit->cmdline ? "\"" : "")); | |
d67227c8 | 165 | |
cad45ba1 | 166 | msgbuf[msgbufsize-1] = 0; |
d67227c8 | 167 | |
e2417e41 DW |
168 | return 0; |
169 | } | |
170 | ||
171 | /* | |
172 | Any time an access gets denied this callback will be called | |
173 | code copied from dbus. If audit is turned on the messages will go as | |
174 | user_avc's into the /var/log/audit/audit.log, otherwise they will be | |
175 | sent to syslog. | |
176 | */ | |
44a6b1b6 | 177 | _printf_attr_(2, 3) static int log_callback(int type, const char *fmt, ...) { |
e2417e41 DW |
178 | va_list ap; |
179 | ||
180 | va_start(ap, fmt); | |
cad45ba1 | 181 | |
e2417e41 | 182 | #ifdef HAVE_AUDIT |
c1165f82 | 183 | if (get_audit_fd() >= 0) { |
ace188cf LP |
184 | _cleanup_free_ char *buf = NULL; |
185 | int r; | |
e2417e41 | 186 | |
ace188cf | 187 | r = vasprintf(&buf, fmt, ap); |
7f1736f7 | 188 | va_end(ap); |
cad45ba1 | 189 | |
ace188cf LP |
190 | if (r >= 0) { |
191 | audit_log_user_avc_message(get_audit_fd(), AUDIT_USER_AVC, buf, NULL, NULL, NULL, 0); | |
192 | return 0; | |
193 | } | |
194 | ||
195 | va_start(ap, fmt); | |
e2417e41 DW |
196 | } |
197 | #endif | |
198 | log_metav(LOG_USER | LOG_INFO, __FILE__, __LINE__, __FUNCTION__, fmt, ap); | |
199 | va_end(ap); | |
cad45ba1 | 200 | |
e2417e41 DW |
201 | return 0; |
202 | } | |
203 | ||
204 | /* | |
205 | Function must be called once to initialize the SELinux AVC environment. | |
206 | Sets up callbacks. | |
207 | If you want to cleanup memory you should need to call selinux_access_finish. | |
208 | */ | |
209 | static int access_init(void) { | |
cad45ba1 | 210 | int r; |
e2417e41 DW |
211 | |
212 | if (avc_open(NULL, 0)) { | |
cad45ba1 | 213 | log_error("avc_open() failed: %m"); |
e2417e41 DW |
214 | return -errno; |
215 | } | |
216 | ||
cad45ba1 LP |
217 | selinux_set_callback(SELINUX_CB_AUDIT, (union selinux_callback) audit_callback); |
218 | selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) log_callback); | |
e2417e41 | 219 | |
cad45ba1 | 220 | if (security_getenforce() >= 0) |
e2417e41 | 221 | return 0; |
cad45ba1 | 222 | |
e2417e41 DW |
223 | r = -errno; |
224 | avc_destroy(); | |
cad45ba1 | 225 | |
e2417e41 DW |
226 | return r; |
227 | } | |
228 | ||
ffc227c9 | 229 | static int selinux_access_init(DBusError *error) { |
e2417e41 DW |
230 | int r; |
231 | ||
cad45ba1 | 232 | if (initialized) |
e2417e41 DW |
233 | return 0; |
234 | ||
cad45ba1 | 235 | if (use_selinux()) { |
e2417e41 DW |
236 | r = access_init(); |
237 | if (r < 0) { | |
d67227c8 | 238 | dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "Failed to initialize SELinux."); |
e2417e41 DW |
239 | return r; |
240 | } | |
e2417e41 DW |
241 | } |
242 | ||
cad45ba1 | 243 | initialized = true; |
e2417e41 DW |
244 | return 0; |
245 | } | |
246 | ||
ffc227c9 LP |
247 | void selinux_access_free(void) { |
248 | if (!initialized) | |
249 | return; | |
250 | ||
251 | avc_destroy(); | |
252 | initialized = false; | |
253 | } | |
254 | ||
e2417e41 | 255 | static int get_audit_data( |
cad45ba1 LP |
256 | DBusConnection *connection, |
257 | DBusMessage *message, | |
258 | struct auditstruct *audit, | |
259 | DBusError *error) { | |
e2417e41 DW |
260 | |
261 | const char *sender; | |
cad45ba1 LP |
262 | int r, fd; |
263 | struct ucred ucred; | |
0b9cc004 | 264 | socklen_t len = sizeof(ucred); |
e2417e41 DW |
265 | |
266 | sender = dbus_message_get_sender(message); | |
c3090674 LP |
267 | if (sender) |
268 | return bus_get_audit_data(connection, sender, audit, error); | |
c3090674 | 269 | |
cad45ba1 LP |
270 | if (!dbus_connection_get_unix_fd(connection, &fd)) |
271 | return -EINVAL; | |
e2417e41 | 272 | |
cad45ba1 LP |
273 | r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len); |
274 | if (r < 0) { | |
275 | log_error("Failed to determine peer credentials: %m"); | |
276 | return -errno; | |
277 | } | |
c3090674 | 278 | |
cad45ba1 LP |
279 | audit->uid = ucred.uid; |
280 | audit->gid = ucred.gid; | |
e2417e41 | 281 | |
cad45ba1 LP |
282 | r = audit_loginuid_from_pid(ucred.pid, &audit->loginuid); |
283 | if (r < 0) | |
284 | return r; | |
e2417e41 | 285 | |
9bdbc2e2 | 286 | r = get_process_cmdline(ucred.pid, 0, true, &audit->cmdline); |
cad45ba1 LP |
287 | if (r < 0) |
288 | return r; | |
e2417e41 | 289 | |
cad45ba1 | 290 | return 0; |
e2417e41 DW |
291 | } |
292 | ||
293 | /* | |
294 | This function returns the security context of the remote end of the dbus | |
295 | connections. Whether it is on the bus or a local connection. | |
296 | */ | |
297 | static int get_calling_context( | |
cad45ba1 LP |
298 | DBusConnection *connection, |
299 | DBusMessage *message, | |
300 | security_context_t *scon, | |
301 | DBusError *error) { | |
e2417e41 DW |
302 | |
303 | const char *sender; | |
304 | int r; | |
d67227c8 | 305 | int fd; |
e2417e41 DW |
306 | |
307 | /* | |
308 | If sender exists then | |
309 | if sender is NULL this indicates a local connection. Grab the fd | |
310 | from dbus and do an getpeercon to peers process context | |
311 | */ | |
312 | sender = dbus_message_get_sender(message); | |
313 | if (sender) { | |
314 | r = bus_get_selinux_security_context(connection, sender, scon, error); | |
cad45ba1 LP |
315 | if (r >= 0) |
316 | return r; | |
e2417e41 | 317 | |
23635a85 | 318 | log_error("bus_get_selinux_security_context failed: %m"); |
a33c48d8 | 319 | return r; |
d67227c8 DW |
320 | } |
321 | ||
cad45ba1 | 322 | if (!dbus_connection_get_unix_fd(connection, &fd)) { |
d67227c8 DW |
323 | log_error("bus_connection_get_unix_fd failed %m"); |
324 | return -EINVAL; | |
325 | } | |
326 | ||
327 | r = getpeercon(fd, scon); | |
328 | if (r < 0) { | |
329 | log_error("getpeercon failed %m"); | |
330 | return -errno; | |
e2417e41 DW |
331 | } |
332 | ||
333 | return 0; | |
334 | } | |
335 | ||
e2417e41 DW |
336 | /* |
337 | This function communicates with the kernel to check whether or not it should | |
338 | allow the access. | |
339 | If the machine is in permissive mode it will return ok. Audit messages will | |
340 | still be generated if the access would be denied in enforcing mode. | |
341 | */ | |
ffc227c9 | 342 | int selinux_access_check( |
cad45ba1 LP |
343 | DBusConnection *connection, |
344 | DBusMessage *message, | |
345 | const char *path, | |
346 | const char *permission, | |
347 | DBusError *error) { | |
348 | ||
349 | security_context_t scon = NULL, fcon = NULL; | |
e2417e41 DW |
350 | int r = 0; |
351 | const char *tclass = NULL; | |
352 | struct auditstruct audit; | |
c3090674 | 353 | |
cad45ba1 LP |
354 | assert(connection); |
355 | assert(message); | |
356 | assert(permission); | |
357 | assert(error); | |
358 | ||
cad45ba1 LP |
359 | if (!use_selinux()) |
360 | return 0; | |
361 | ||
ffc227c9 LP |
362 | r = selinux_access_init(error); |
363 | if (r < 0) | |
364 | return r; | |
365 | ||
c3090674 LP |
366 | audit.uid = audit.loginuid = (uid_t) -1; |
367 | audit.gid = (gid_t) -1; | |
e2417e41 DW |
368 | audit.cmdline = NULL; |
369 | audit.path = path; | |
370 | ||
371 | r = get_calling_context(connection, message, &scon, error); | |
cad45ba1 | 372 | if (r < 0) { |
d67227c8 | 373 | log_error("Failed to get caller's security context on: %m"); |
e2417e41 | 374 | goto finish; |
d67227c8 | 375 | } |
cad45ba1 | 376 | |
e2417e41 DW |
377 | if (path) { |
378 | tclass = "service"; | |
379 | /* get the file context of the unit file */ | |
380 | r = getfilecon(path, &fcon); | |
381 | if (r < 0) { | |
d67227c8 DW |
382 | dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "Failed to get file context on %s.", path); |
383 | r = -errno; | |
cad45ba1 | 384 | log_error("Failed to get security context on %s: %m",path); |
e2417e41 DW |
385 | goto finish; |
386 | } | |
387 | ||
388 | } else { | |
389 | tclass = "system"; | |
390 | r = getcon(&fcon); | |
391 | if (r < 0) { | |
d67227c8 DW |
392 | dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "Failed to get current context."); |
393 | r = -errno; | |
394 | log_error("Failed to get current process context on: %m"); | |
e2417e41 DW |
395 | goto finish; |
396 | } | |
397 | } | |
398 | ||
399 | (void) get_audit_data(connection, message, &audit, error); | |
400 | ||
cad45ba1 LP |
401 | errno = 0; |
402 | r = selinux_check_access(scon, fcon, tclass, permission, &audit); | |
c3090674 | 403 | if (r < 0) { |
cad45ba1 | 404 | dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "SELinux policy denies access."); |
e2417e41 | 405 | r = -errno; |
d67227c8 | 406 | log_error("SELinux policy denies access."); |
e2417e41 DW |
407 | } |
408 | ||
cad45ba1 LP |
409 | 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); |
410 | ||
e2417e41 | 411 | finish: |
e2417e41 DW |
412 | free(audit.cmdline); |
413 | freecon(scon); | |
414 | freecon(fcon); | |
415 | ||
cad45ba1 | 416 | if (r && security_getenforce() != 1) { |
e2417e41 DW |
417 | dbus_error_init(error); |
418 | r = 0; | |
419 | } | |
420 | ||
421 | return r; | |
422 | } | |
423 | ||
e2417e41 | 424 | #else |
cad45ba1 | 425 | |
ffc227c9 | 426 | int selinux_access_check( |
cad45ba1 LP |
427 | DBusConnection *connection, |
428 | DBusMessage *message, | |
ffc227c9 | 429 | const char *path, |
cad45ba1 LP |
430 | const char *permission, |
431 | DBusError *error) { | |
432 | ||
e2417e41 DW |
433 | return 0; |
434 | } | |
435 | ||
ffc227c9 | 436 | void selinux_access_free(void) { |
c3090674 | 437 | } |
cad45ba1 | 438 | |
e2417e41 | 439 | #endif |