chiark / gitweb /
Revert "Implement SocketUser= and SocketGroup= for [Socket]"
[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         log_debug("GetConnectionSELinuxSecurityContext %s (pid %ld)", *scon, (long) bus_get_unix_process_id(connection, name, error));
108
109         return 0;
110 }
111
112 static int bus_get_audit_data(
113                 DBusConnection *connection,
114                 const char *name,
115                 struct auditstruct *audit,
116                 DBusError *error) {
117
118         pid_t pid;
119         int r;
120
121         pid = bus_get_unix_process_id(connection, name, error);
122         if (pid <= 0)
123                 return -EIO;
124
125         r = audit_loginuid_from_pid(pid, &audit->loginuid);
126         if (r < 0)
127                 return r;
128
129         r = get_process_uid(pid, &audit->uid);
130         if (r < 0)
131                 return r;
132
133         r = get_process_gid(pid, &audit->gid);
134         if (r < 0)
135                 return r;
136
137         r = get_process_cmdline(pid, LINE_MAX, true, &audit->cmdline);
138         if (r < 0)
139                 return r;
140
141         return 0;
142 }
143
144 /*
145    Any time an access gets denied this callback will be called
146    with the aduit data.  We then need to just copy the audit data into the msgbuf.
147 */
148 static int audit_callback(
149                 void *auditdata,
150                 security_class_t cls,
151                 char *msgbuf,
152                 size_t msgbufsize) {
153
154         struct auditstruct *audit = (struct auditstruct *) auditdata;
155
156         snprintf(msgbuf, msgbufsize,
157                  "auid=%d uid=%d gid=%d%s%s%s%s%s%s",
158                  audit->loginuid,
159                  audit->uid,
160                  audit->gid,
161                  (audit->path ? " path=\"" : ""),
162                  strempty(audit->path),
163                  (audit->path ? "\"" : ""),
164                  (audit->cmdline ? " cmdline=\"" : ""),
165                  strempty(audit->cmdline),
166                  (audit->cmdline ? "\"" : ""));
167
168         msgbuf[msgbufsize-1] = 0;
169
170         return 0;
171 }
172
173 /*
174    Any time an access gets denied this callback will be called
175    code copied from dbus. If audit is turned on the messages will go as
176    user_avc's into the /var/log/audit/audit.log, otherwise they will be
177    sent to syslog.
178 */
179 static int log_callback(int type, const char *fmt, ...) {
180         va_list ap;
181
182         va_start(ap, fmt);
183
184 #ifdef HAVE_AUDIT
185         if (get_audit_fd() >= 0) {
186                 char buf[LINE_MAX];
187
188                 vsnprintf(buf, sizeof(buf), fmt, ap);
189                 audit_log_user_avc_message(get_audit_fd(), AUDIT_USER_AVC, buf, NULL, NULL, NULL, 0);
190                 va_end(ap);
191
192                 return 0;
193         }
194 #endif
195         log_metav(LOG_USER | LOG_INFO, __FILE__, __LINE__, __FUNCTION__, fmt, ap);
196         va_end(ap);
197
198         return 0;
199 }
200
201 /*
202    Function must be called once to initialize the SELinux AVC environment.
203    Sets up callbacks.
204    If you want to cleanup memory you should need to call selinux_access_finish.
205 */
206 static int access_init(void) {
207         int r;
208
209         if (avc_open(NULL, 0)) {
210                 log_error("avc_open() failed: %m");
211                 return -errno;
212         }
213
214         selinux_set_callback(SELINUX_CB_AUDIT, (union selinux_callback) audit_callback);
215         selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) log_callback);
216
217         if (security_getenforce() >= 0)
218                 return 0;
219
220         r = -errno;
221         avc_destroy();
222
223         return r;
224 }
225
226 static int selinux_access_init(DBusError *error) {
227         int r;
228
229         if (initialized)
230                 return 0;
231
232         if (use_selinux()) {
233                 r = access_init();
234                 if (r < 0) {
235                         dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "Failed to initialize SELinux.");
236                         return r;
237                 }
238         }
239
240         initialized = true;
241         return 0;
242 }
243
244 void selinux_access_free(void) {
245         if (!initialized)
246                 return;
247
248         avc_destroy();
249         initialized = false;
250 }
251
252 static int get_audit_data(
253                 DBusConnection *connection,
254                 DBusMessage *message,
255                 struct auditstruct *audit,
256                 DBusError *error) {
257
258         const char *sender;
259         int r, fd;
260         struct ucred ucred;
261         socklen_t len;
262
263         sender = dbus_message_get_sender(message);
264         if (sender)
265                 return bus_get_audit_data(connection, sender, audit, error);
266
267         if (!dbus_connection_get_unix_fd(connection, &fd))
268                 return -EINVAL;
269
270         r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len);
271         if (r < 0) {
272                 log_error("Failed to determine peer credentials: %m");
273                 return -errno;
274         }
275
276         audit->uid = ucred.uid;
277         audit->gid = ucred.gid;
278
279         r = audit_loginuid_from_pid(ucred.pid, &audit->loginuid);
280         if (r < 0)
281                 return r;
282
283         r = get_process_cmdline(ucred.pid, LINE_MAX, true, &audit->cmdline);
284         if (r < 0)
285                 return r;
286
287         return 0;
288 }
289
290 /*
291    This function returns the security context of the remote end of the dbus
292    connections.  Whether it is on the bus or a local connection.
293 */
294 static int get_calling_context(
295                 DBusConnection *connection,
296                 DBusMessage *message,
297                 security_context_t *scon,
298                 DBusError *error) {
299
300         const char *sender;
301         int r;
302         int fd;
303
304         /*
305            If sender exists then
306            if sender is NULL this indicates a local connection.  Grab the fd
307            from dbus and do an getpeercon to peers process context
308         */
309         sender = dbus_message_get_sender(message);
310         if (sender) {
311                 log_error("SELinux Got Sender %s", sender);
312
313                 r = bus_get_selinux_security_context(connection, sender, scon, error);
314                 if (r >= 0)
315                         return r;
316
317                 log_error("bus_get_selinux_security_context failed: %m");
318                 return r;
319         }
320
321         log_debug("SELinux No Sender");
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         log_debug("SELinux access check for path=%s permission=%s", strna(path), permission);
367
368         audit.uid = audit.loginuid = (uid_t) -1;
369         audit.gid = (gid_t) -1;
370         audit.cmdline = NULL;
371         audit.path = path;
372
373         r = get_calling_context(connection, message, &scon, error);
374         if (r < 0) {
375                 log_error("Failed to get caller's security context on: %m");
376                 goto finish;
377         }
378
379         if (path) {
380                 tclass = "service";
381                 /* get the file context of the unit file */
382                 r = getfilecon(path, &fcon);
383                 if (r < 0) {
384                         dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "Failed to get file context on %s.", path);
385                         r = -errno;
386                         log_error("Failed to get security context on %s: %m",path);
387                         goto finish;
388                 }
389
390         } else {
391                 tclass = "system";
392                 r = getcon(&fcon);
393                 if (r < 0) {
394                         dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "Failed to get current context.");
395                         r = -errno;
396                         log_error("Failed to get current process context on: %m");
397                         goto finish;
398                 }
399         }
400
401         (void) get_audit_data(connection, message, &audit, error);
402
403         errno = 0;
404         r = selinux_check_access(scon, fcon, tclass, permission, &audit);
405         if (r < 0) {
406                 dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "SELinux policy denies access.");
407                 r = -errno;
408                 log_error("SELinux policy denies access.");
409         }
410
411         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);
412
413 finish:
414         free(audit.cmdline);
415         freecon(scon);
416         freecon(fcon);
417
418         if (r && security_getenforce() != 1) {
419                 dbus_error_init(error);
420                 r = 0;
421         }
422
423         return r;
424 }
425
426 #else
427
428 int selinux_access_check(
429                 DBusConnection *connection,
430                 DBusMessage *message,
431                 const char *path,
432                 const char *permission,
433                 DBusError *error) {
434
435         return 0;
436 }
437
438 void selinux_access_free(void) {
439 }
440
441 #endif