chiark / gitweb /
4b1dc74e93b8dc6bce77fcfd976b6ad28cf5eb33
[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         }
302
303         if (!dbus_connection_get_unix_fd(connection, &fd)) {
304                 log_error("bus_connection_get_unix_fd failed %m");
305                 return -EINVAL;
306         }
307
308         r = getpeercon(fd, scon);
309         if (r < 0) {
310                 log_error("getpeercon failed %m");
311                 return -errno;
312         }
313
314         return 0;
315 }
316
317 /*
318    This function communicates with the kernel to check whether or not it should
319    allow the access.
320    If the machine is in permissive mode it will return ok.  Audit messages will
321    still be generated if the access would be denied in enforcing mode.
322 */
323 int selinux_access_check(
324                 DBusConnection *connection,
325                 DBusMessage *message,
326                 const char *path,
327                 const char *permission,
328                 DBusError *error) {
329
330         security_context_t scon = NULL, fcon = NULL;
331         int r = 0;
332         const char *tclass = NULL;
333         struct auditstruct audit;
334
335         assert(connection);
336         assert(message);
337         assert(permission);
338         assert(error);
339
340         if (!use_selinux())
341                 return 0;
342
343         r = selinux_access_init(error);
344         if (r < 0)
345                 return r;
346
347         log_debug("SELinux access check for path=%s permission=%s", strna(path), permission);
348
349         audit.uid = audit.loginuid = (uid_t) -1;
350         audit.gid = (gid_t) -1;
351         audit.cmdline = NULL;
352         audit.path = path;
353
354         r = get_calling_context(connection, message, &scon, error);
355         if (r < 0) {
356                 log_error("Failed to get caller's security context on: %m");
357                 goto finish;
358         }
359
360         if (path) {
361                 tclass = "service";
362                 /* get the file context of the unit file */
363                 r = getfilecon(path, &fcon);
364                 if (r < 0) {
365                         dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "Failed to get file context on %s.", path);
366                         r = -errno;
367                         log_error("Failed to get security context on %s: %m",path);
368                         goto finish;
369                 }
370
371         } else {
372                 tclass = "system";
373                 r = getcon(&fcon);
374                 if (r < 0) {
375                         dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "Failed to get current context.");
376                         r = -errno;
377                         log_error("Failed to get current process context on: %m");
378                         goto finish;
379                 }
380         }
381
382         (void) get_audit_data(connection, message, &audit, error);
383
384         errno = 0;
385         r = selinux_check_access(scon, fcon, tclass, permission, &audit);
386         if (r < 0) {
387                 dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "SELinux policy denies access.");
388                 r = -errno;
389                 log_error("SELinux policy denies access.");
390         }
391
392         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);
393
394 finish:
395         free(audit.cmdline);
396         freecon(scon);
397         freecon(fcon);
398
399         if (r && security_getenforce() != 1) {
400                 dbus_error_init(error);
401                 r = 0;
402         }
403
404         return r;
405 }
406
407 #else
408
409 int selinux_access_check(
410                 DBusConnection *connection,
411                 DBusMessage *message,
412                 const char *path,
413                 const char *permission,
414                 DBusError *error) {
415
416         return 0;
417 }
418
419 void selinux_access_free(void) {
420 }
421
422 #endif