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