chiark / gitweb /
macro: clean up usage of gcc attributes
[elogind.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 Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 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   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser 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_(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