]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/core/selinux-access.c
Add __attribute__((const, pure, format)) in various places
[thirdparty/systemd.git] / src / core / selinux-access.c
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
22 #include "selinux-access.h"
23
24 #ifdef HAVE_SELINUX
25
26 #include <stdio.h>
27 #include <string.h>
28 #include <errno.h>
29 #include <limits.h>
30 #include <selinux/selinux.h>
31 #include <selinux/avc.h>
32 #ifdef HAVE_AUDIT
33 #include <libaudit.h>
34 #endif
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"
44
45 static bool initialized = false;
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
55 static int bus_get_selinux_security_context(
56 DBusConnection *connection,
57 const char *name,
58 char **scon,
59 DBusError *error) {
60
61 _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL;
62 DBusMessageIter iter, sub;
63 const char *bytes;
64 char *b;
65 int nbytes;
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) {
73 dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL);
74 return -ENOMEM;
75 }
76
77 if (!dbus_message_append_args(
78 m,
79 DBUS_TYPE_STRING, &name,
80 DBUS_TYPE_INVALID)) {
81 dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL);
82 return -ENOMEM;
83 }
84
85 reply = dbus_connection_send_with_reply_and_block(connection, m, -1, error);
86 if (!reply)
87 return -EIO;
88
89 if (dbus_set_error_from_message(error, reply))
90 return -EIO;
91
92 if (!dbus_message_iter_init(reply, &iter))
93 return -EIO;
94
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
107 return 0;
108 }
109
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;
117 int r;
118
119 pid = bus_get_unix_process_id(connection, name, error);
120 if (pid <= 0)
121 return -EIO;
122
123 r = audit_loginuid_from_pid(pid, &audit->loginuid);
124 if (r < 0)
125 return r;
126
127 r = get_process_uid(pid, &audit->uid);
128 if (r < 0)
129 return r;
130
131 r = get_process_gid(pid, &audit->gid);
132 if (r < 0)
133 return r;
134
135 r = get_process_cmdline(pid, 0, true, &audit->cmdline);
136 if (r < 0)
137 return r;
138
139 return 0;
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 */
146 static int audit_callback(
147 void *auditdata,
148 security_class_t cls,
149 char *msgbuf,
150 size_t msgbufsize) {
151
152 struct auditstruct *audit = (struct auditstruct *) auditdata;
153
154 snprintf(msgbuf, msgbufsize,
155 "auid=%d uid=%d gid=%d%s%s%s%s%s%s",
156 audit->loginuid,
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 ? "\"" : ""));
165
166 msgbuf[msgbufsize-1] = 0;
167
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 */
177 _printf_attr_(2, 3) static int log_callback(int type, const char *fmt, ...) {
178 va_list ap;
179
180 va_start(ap, fmt);
181
182 #ifdef HAVE_AUDIT
183 if (get_audit_fd() >= 0) {
184 _cleanup_free_ char *buf = NULL;
185 int r;
186
187 r = vasprintf(&buf, fmt, ap);
188 va_end(ap);
189
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);
196 }
197 #endif
198 log_metav(LOG_USER | LOG_INFO, __FILE__, __LINE__, __FUNCTION__, fmt, ap);
199 va_end(ap);
200
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) {
210 int r;
211
212 if (avc_open(NULL, 0)) {
213 log_error("avc_open() failed: %m");
214 return -errno;
215 }
216
217 selinux_set_callback(SELINUX_CB_AUDIT, (union selinux_callback) audit_callback);
218 selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) log_callback);
219
220 if (security_getenforce() >= 0)
221 return 0;
222
223 r = -errno;
224 avc_destroy();
225
226 return r;
227 }
228
229 static int selinux_access_init(DBusError *error) {
230 int r;
231
232 if (initialized)
233 return 0;
234
235 if (use_selinux()) {
236 r = access_init();
237 if (r < 0) {
238 dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "Failed to initialize SELinux.");
239 return r;
240 }
241 }
242
243 initialized = true;
244 return 0;
245 }
246
247 void selinux_access_free(void) {
248 if (!initialized)
249 return;
250
251 avc_destroy();
252 initialized = false;
253 }
254
255 static int get_audit_data(
256 DBusConnection *connection,
257 DBusMessage *message,
258 struct auditstruct *audit,
259 DBusError *error) {
260
261 const char *sender;
262 int r, fd;
263 struct ucred ucred;
264 socklen_t len = sizeof(ucred);
265
266 sender = dbus_message_get_sender(message);
267 if (sender)
268 return bus_get_audit_data(connection, sender, audit, error);
269
270 if (!dbus_connection_get_unix_fd(connection, &fd))
271 return -EINVAL;
272
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 }
278
279 audit->uid = ucred.uid;
280 audit->gid = ucred.gid;
281
282 r = audit_loginuid_from_pid(ucred.pid, &audit->loginuid);
283 if (r < 0)
284 return r;
285
286 r = get_process_cmdline(ucred.pid, 0, true, &audit->cmdline);
287 if (r < 0)
288 return r;
289
290 return 0;
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(
298 DBusConnection *connection,
299 DBusMessage *message,
300 security_context_t *scon,
301 DBusError *error) {
302
303 const char *sender;
304 int r;
305 int fd;
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);
315 if (r >= 0)
316 return r;
317
318 log_error("bus_get_selinux_security_context failed: %m");
319 return r;
320 }
321
322 if (!dbus_connection_get_unix_fd(connection, &fd)) {
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;
331 }
332
333 return 0;
334 }
335
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 */
342 int selinux_access_check(
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;
350 int r = 0;
351 const char *tclass = NULL;
352 struct auditstruct audit;
353
354 assert(connection);
355 assert(message);
356 assert(permission);
357 assert(error);
358
359 if (!use_selinux())
360 return 0;
361
362 r = selinux_access_init(error);
363 if (r < 0)
364 return r;
365
366 audit.uid = audit.loginuid = (uid_t) -1;
367 audit.gid = (gid_t) -1;
368 audit.cmdline = NULL;
369 audit.path = path;
370
371 r = get_calling_context(connection, message, &scon, error);
372 if (r < 0) {
373 log_error("Failed to get caller's security context on: %m");
374 goto finish;
375 }
376
377 if (path) {
378 tclass = "service";
379 /* get the file context of the unit file */
380 r = getfilecon(path, &fcon);
381 if (r < 0) {
382 dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "Failed to get file context on %s.", path);
383 r = -errno;
384 log_error("Failed to get security context on %s: %m",path);
385 goto finish;
386 }
387
388 } else {
389 tclass = "system";
390 r = getcon(&fcon);
391 if (r < 0) {
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");
395 goto finish;
396 }
397 }
398
399 (void) get_audit_data(connection, message, &audit, error);
400
401 errno = 0;
402 r = selinux_check_access(scon, fcon, tclass, permission, &audit);
403 if (r < 0) {
404 dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "SELinux policy denies access.");
405 r = -errno;
406 log_error("SELinux policy denies access.");
407 }
408
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
411 finish:
412 free(audit.cmdline);
413 freecon(scon);
414 freecon(fcon);
415
416 if (r && security_getenforce() != 1) {
417 dbus_error_init(error);
418 r = 0;
419 }
420
421 return r;
422 }
423
424 #else
425
426 int selinux_access_check(
427 DBusConnection *connection,
428 DBusMessage *message,
429 const char *path,
430 const char *permission,
431 DBusError *error) {
432
433 return 0;
434 }
435
436 void selinux_access_free(void) {
437 }
438
439 #endif