chiark / gitweb /
be728d5d5bbbf8c979da5cdef2654dcaceb5b3a8
[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 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 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                 char buf[LINE_MAX];
185
186                 vsnprintf(buf, sizeof(buf), fmt, ap);
187                 audit_log_user_avc_message(get_audit_fd(), AUDIT_USER_AVC, buf, NULL, NULL, NULL, 0);
188                 va_end(ap);
189
190                 return 0;
191         }
192 #endif
193         log_metav(LOG_USER | LOG_INFO, __FILE__, __LINE__, __FUNCTION__, fmt, ap);
194         va_end(ap);
195
196         return 0;
197 }
198
199 /*
200    Function must be called once to initialize the SELinux AVC environment.
201    Sets up callbacks.
202    If you want to cleanup memory you should need to call selinux_access_finish.
203 */
204 static int access_init(void) {
205         int r;
206
207         if (avc_open(NULL, 0)) {
208                 log_error("avc_open() failed: %m");
209                 return -errno;
210         }
211
212         selinux_set_callback(SELINUX_CB_AUDIT, (union selinux_callback) audit_callback);
213         selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) log_callback);
214
215         if (security_getenforce() >= 0)
216                 return 0;
217
218         r = -errno;
219         avc_destroy();
220
221         return r;
222 }
223
224 static int selinux_access_init(DBusError *error) {
225         int r;
226
227         if (initialized)
228                 return 0;
229
230         if (use_selinux()) {
231                 r = access_init();
232                 if (r < 0) {
233                         dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "Failed to initialize SELinux.");
234                         return r;
235                 }
236         }
237
238         initialized = true;
239         return 0;
240 }
241
242 void selinux_access_free(void) {
243         if (!initialized)
244                 return;
245
246         avc_destroy();
247         initialized = false;
248 }
249
250 static int get_audit_data(
251                 DBusConnection *connection,
252                 DBusMessage *message,
253                 struct auditstruct *audit,
254                 DBusError *error) {
255
256         const char *sender;
257         int r, fd;
258         struct ucred ucred;
259         socklen_t len;
260
261         sender = dbus_message_get_sender(message);
262         if (sender)
263                 return bus_get_audit_data(connection, sender, audit, error);
264
265         if (!dbus_connection_get_unix_fd(connection, &fd))
266                 return -EINVAL;
267
268         r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len);
269         if (r < 0) {
270                 log_error("Failed to determine peer credentials: %m");
271                 return -errno;
272         }
273
274         audit->uid = ucred.uid;
275         audit->gid = ucred.gid;
276
277         r = audit_loginuid_from_pid(ucred.pid, &audit->loginuid);
278         if (r < 0)
279                 return r;
280
281         r = get_process_cmdline(ucred.pid, 0, true, &audit->cmdline);
282         if (r < 0)
283                 return r;
284
285         return 0;
286 }
287
288 /*
289    This function returns the security context of the remote end of the dbus
290    connections.  Whether it is on the bus or a local connection.
291 */
292 static int get_calling_context(
293                 DBusConnection *connection,
294                 DBusMessage *message,
295                 security_context_t *scon,
296                 DBusError *error) {
297
298         const char *sender;
299         int r;
300         int fd;
301
302         /*
303            If sender exists then
304            if sender is NULL this indicates a local connection.  Grab the fd
305            from dbus and do an getpeercon to peers process context
306         */
307         sender = dbus_message_get_sender(message);
308         if (sender) {
309                 r = bus_get_selinux_security_context(connection, sender, scon, error);
310                 if (r >= 0)
311                         return r;
312
313                 log_error("bus_get_selinux_security_context failed: %m");
314                 return r;
315         }
316
317         if (!dbus_connection_get_unix_fd(connection, &fd)) {
318                 log_error("bus_connection_get_unix_fd failed %m");
319                 return -EINVAL;
320         }
321
322         r = getpeercon(fd, scon);
323         if (r < 0) {
324                 log_error("getpeercon failed %m");
325                 return -errno;
326         }
327
328         return 0;
329 }
330
331 /*
332    This function communicates with the kernel to check whether or not it should
333    allow the access.
334    If the machine is in permissive mode it will return ok.  Audit messages will
335    still be generated if the access would be denied in enforcing mode.
336 */
337 int selinux_access_check(
338                 DBusConnection *connection,
339                 DBusMessage *message,
340                 const char *path,
341                 const char *permission,
342                 DBusError *error) {
343
344         security_context_t scon = NULL, fcon = NULL;
345         int r = 0;
346         const char *tclass = NULL;
347         struct auditstruct audit;
348
349         assert(connection);
350         assert(message);
351         assert(permission);
352         assert(error);
353
354         if (!use_selinux())
355                 return 0;
356
357         r = selinux_access_init(error);
358         if (r < 0)
359                 return r;
360
361         audit.uid = audit.loginuid = (uid_t) -1;
362         audit.gid = (gid_t) -1;
363         audit.cmdline = NULL;
364         audit.path = path;
365
366         r = get_calling_context(connection, message, &scon, error);
367         if (r < 0) {
368                 log_error("Failed to get caller's security context on: %m");
369                 goto finish;
370         }
371
372         if (path) {
373                 tclass = "service";
374                 /* get the file context of the unit file */
375                 r = getfilecon(path, &fcon);
376                 if (r < 0) {
377                         dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "Failed to get file context on %s.", path);
378                         r = -errno;
379                         log_error("Failed to get security context on %s: %m",path);
380                         goto finish;
381                 }
382
383         } else {
384                 tclass = "system";
385                 r = getcon(&fcon);
386                 if (r < 0) {
387                         dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "Failed to get current context.");
388                         r = -errno;
389                         log_error("Failed to get current process context on: %m");
390                         goto finish;
391                 }
392         }
393
394         (void) get_audit_data(connection, message, &audit, error);
395
396         errno = 0;
397         r = selinux_check_access(scon, fcon, tclass, permission, &audit);
398         if (r < 0) {
399                 dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "SELinux policy denies access.");
400                 r = -errno;
401                 log_error("SELinux policy denies access.");
402         }
403
404         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);
405
406 finish:
407         free(audit.cmdline);
408         freecon(scon);
409         freecon(fcon);
410
411         if (r && security_getenforce() != 1) {
412                 dbus_error_init(error);
413                 r = 0;
414         }
415
416         return r;
417 }
418
419 #else
420
421 int selinux_access_check(
422                 DBusConnection *connection,
423                 DBusMessage *message,
424                 const char *path,
425                 const char *permission,
426                 DBusError *error) {
427
428         return 0;
429 }
430
431 void selinux_access_free(void) {
432 }
433
434 #endif