chiark / gitweb /
selinux: rework method tail, make it into a nulstr array
[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 "util.h"
23 #include "job.h"
24 #include "manager.h"
25 #include "selinux-access.h"
26
27 #ifdef HAVE_SELINUX
28 #include "dbus.h"
29 #include "log.h"
30 #include "dbus-unit.h"
31 #include "bus-errors.h"
32 #include "dbus-common.h"
33 #include "audit.h"
34
35 #include <stdio.h>
36 #include <string.h>
37 #include <errno.h>
38 #include <selinux/selinux.h>
39 #include <selinux/avc.h>
40 #ifdef HAVE_AUDIT
41 #include <libaudit.h>
42 #endif
43 #include <limits.h>
44
45 /* FD to send audit messages to */
46 static int audit_fd = -1;
47 static int selinux_enabled = -1;
48 static int first_time = 1;
49 static int selinux_enforcing = 0;
50
51 struct auditstruct {
52         const char *path;
53         char *cmdline;
54         uid_t loginuid;
55         uid_t uid;
56         gid_t gid;
57 };
58
59 /*
60    Define a mapping between the systemd method calls and the SELinux access to check.
61    We define two tables, one for access checks on unit files, and one for
62    access checks for the system in general.
63
64    If we do not find a match in either table, then the "undefined" system
65    check will be called.
66 */
67
68 static const char unit_methods[] =
69         "DisableUnitFiles\0"       "disable\0"
70         "EnableUnitFiles\0"        "enable\0"
71         "GetUnit\0"                "status\0"
72         "GetUnitFileState\0"       "status\0"
73         "Kill\0"                   "stop\0"
74         "KillUnit\0"               "stop\0"
75         "LinkUnitFiles\0"          "enable\0"
76         "MaskUnitFiles\0"          "disable\0"
77         "PresetUnitFiles\0"        "enable\0"
78         "ReenableUnitFiles\0"      "enable\0"
79         "ReloadOrRestart\0"        "start\0"
80         "ReloadOrRestartUnit\0"    "start\0"
81         "ReloadOrTryRestart\0"     "start\0"
82         "ReloadOrTryRestartUnit\0" "start\0"
83         "Reload\0"                 "reload\0"
84         "ReloadUnit\0"             "reload\0"
85         "ResetFailedUnit\0"        "stop\0"
86         "Restart\0"                "start\0"
87         "RestartUnit\0"            "start\0"
88         "Start\0"                  "start\0"
89         "StartUnit\0"              "start\0"
90         "StartUnitReplace\0"       "start\0"
91         "Stop\0"                   "stop\0"
92         "StopUnit\0"               "stop\0"
93         "TryRestart\0"             "start\0"
94         "TryRestartUnit\0"         "start\0"
95         "UnmaskUnitFiles\0"        "enable\0";
96
97 static const char system_methods[] =
98         "ClearJobs\0"              "reboot\0"
99         "CreateSnapshot\0"         "status\0"
100         "Dump\0"                   "status\0"
101         "Exit\0"                   "halt\0"
102         "FlushDevices\0"           "halt\0"
103         "Get\0"                    "status\0"
104         "GetAll\0"                 "status\0"
105         "GetJob\0"                 "status\0"
106         "GetSeat\0"                "status\0"
107         "GetSession\0"             "status\0"
108         "GetSessionByPID\0"        "status\0"
109         "GetUnitByPID\0"           "status\0"
110         "GetUser\0"                "status\0"
111         "Halt\0"                   "halt\0"
112         "Introspect\0"             "status\0"
113         "KExec\0"                  "reboot\0"
114         "KillSession\0"            "halt\0"
115         "KillUser\0"               "halt\0"
116         "LoadUnit\0"               "reload\0"
117         "ListJobs\0"               "status\0"
118         "ListSeats\0"              "status\0"
119         "ListSessions\0"           "status\0"
120         "ListUnits\0"              "status\0"
121         "ListUnitFiles\0"          "status\0"
122         "ListUsers\0"              "status\0"
123         "LockSession\0"            "halt\0"
124         "PowerOff\0"               "halt\0"
125         "Reboot\0"                 "reboot\0"
126         "Reload\0"                 "reload\0"
127         "Reexecute\0"              "reload\0"
128         "ResetFailed\0"            "reload\0"
129         "Subscribe\0"              "status\0"
130         "SwithcRoot\0"             "reboot\0"
131         "SetEnvironment\0"         "status\0"
132         "SetUserLinger\0"          "halt\0"
133         "TerminateSeat\0"          "halt\0"
134         "TerminateSession\0"       "halt\0"
135         "TerminateUser\0"          "halt\0"
136         "Unsubscribe\0"            "status\0"
137         "UnsetEnvironment\0"       "status\0"
138         "UnsetAndSetEnvironment\0" "status\0";
139
140 /*
141    If the admin toggles the selinux enforcment mode this callback
142    will get called before the next access check
143 */
144 static int setenforce_callback(int enforcing)
145 {
146         selinux_enforcing = enforcing;
147         return 0;
148 }
149
150 /* This mimics dbus_bus_get_unix_user() */
151 static int bus_get_selinux_security_context(
152                 DBusConnection *connection,
153                 const char *name,
154                 char **scon,
155                 DBusError *error) {
156
157         DBusMessage *m = NULL, *reply = NULL;
158         int r;
159
160         m = dbus_message_new_method_call(
161                         DBUS_SERVICE_DBUS,
162                         DBUS_PATH_DBUS,
163                         DBUS_INTERFACE_DBUS,
164                         "GetConnectionSELinuxSecurityContext");
165         if (!m) {
166                 r = -errno;
167                 dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL);
168                 goto finish;
169         }
170
171         r = dbus_message_append_args(
172                 m,
173                 DBUS_TYPE_STRING, &name,
174                 DBUS_TYPE_INVALID);
175         if (!r) {
176                 r = -errno;
177                 dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL);
178                 goto finish;
179         }
180
181         reply = dbus_connection_send_with_reply_and_block(connection, m, -1, error);
182         if (!reply) {
183                 r = -errno;
184                 goto finish;
185         }
186
187         r = dbus_set_error_from_message(error, reply);
188         if (!r) {
189                 r = -errno;
190                 goto finish;
191         }
192
193         r = dbus_message_get_args(
194                 reply, error,
195                 DBUS_TYPE_STRING, scon,
196                 DBUS_TYPE_INVALID);
197         if (!r) {
198                 r = -errno;
199                 goto finish;
200         }
201
202         r = 0;
203 finish:
204         if (m)
205                 dbus_message_unref(m);
206
207         if (reply)
208                 dbus_message_unref(reply);
209
210         return r;
211 }
212
213 /* This mimics dbus_bus_get_unix_user() */
214 static int bus_get_audit_data(
215                 DBusConnection *connection,
216                 const char *name,
217                 struct auditstruct *audit,
218                 DBusError *error) {
219
220         pid_t pid;
221         int r;
222
223         pid = bus_get_unix_process_id(connection, name, error);
224         if (pid <= 0)
225                 return -EINVAL;
226
227         r = audit_loginuid_from_pid(pid, &audit->loginuid);
228         if (r < 0)
229                 return r;
230
231         r = get_process_uid(pid, &audit->uid);
232         if (r < 0)
233                 return r;
234
235         r = get_process_gid(pid, &audit->gid);
236         if (r < 0)
237                 return r;
238
239         r = get_process_cmdline(pid, LINE_MAX, true, &audit->cmdline);
240         if (r < 0)
241                 return r;
242
243         return 0;
244 }
245
246 /*
247    Any time an access gets denied this callback will be called
248    with the aduit data.  We then need to just copy the audit data into the msgbuf.
249 */
250 static int audit_callback(void *auditdata, security_class_t cls,
251                           char *msgbuf, size_t msgbufsize)
252 {
253         struct auditstruct *audit = (struct auditstruct *) auditdata;
254         snprintf(msgbuf, msgbufsize,
255                  "name=\"%s\" cmdline=\"%s\" auid=%d uid=%d gid=%d",
256                  audit->path, audit->cmdline, audit->loginuid,
257                  audit->uid, audit->gid);
258         return 0;
259 }
260
261 /*
262    Any time an access gets denied this callback will be called
263    code copied from dbus. If audit is turned on the messages will go as
264    user_avc's into the /var/log/audit/audit.log, otherwise they will be
265    sent to syslog.
266 */
267 static int log_callback(int type, const char *fmt, ...)
268 {
269         va_list ap;
270
271         va_start(ap, fmt);
272 #ifdef HAVE_AUDIT
273         if (audit_fd >= 0) {
274                 char buf[LINE_MAX*2];
275
276                 vsnprintf(buf, sizeof(buf), fmt, ap);
277                 audit_log_user_avc_message(audit_fd, AUDIT_USER_AVC,
278                                            buf, NULL, NULL, NULL, 0);
279                 return 0;
280         }
281 #endif
282         log_metav(LOG_USER | LOG_INFO, __FILE__, __LINE__, __FUNCTION__, fmt, ap);
283         va_end(ap);
284         return 0;
285 }
286
287 /*
288    Function must be called once to initialize the SELinux AVC environment.
289    Sets up callbacks.
290    If you want to cleanup memory you should need to call selinux_access_finish.
291 */
292 static int access_init(void) {
293
294         int r = -1;
295
296         if (avc_open(NULL, 0)) {
297                 log_full(LOG_ERR, "avc_open failed: %m\n");
298                 return -errno;
299         }
300
301         selinux_set_callback(SELINUX_CB_AUDIT, (union selinux_callback) &audit_callback);
302         selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) &log_callback);
303         selinux_set_callback(SELINUX_CB_SETENFORCE, (union selinux_callback) &setenforce_callback);
304
305         if ((r = security_getenforce()) >= 0) {
306                 setenforce_callback(r);
307                 return 0;
308         }
309         r = -errno;
310         avc_destroy();
311         return r;
312 }
313
314 static int selinux_init(Manager *m, DBusError *error) {
315
316         int r;
317
318 #ifdef HAVE_AUDIT
319         audit_fd = m->audit_fd;
320 #endif
321         if (!first_time)
322                 return 0;
323
324         if (selinux_enabled < 0)
325                 selinux_enabled = is_selinux_enabled() == 1;
326
327         if (selinux_enabled) {
328                 /* if not first time is not set, then initialize access */
329                 r = access_init();
330                 if (r < 0) {
331                         dbus_set_error(error, BUS_ERROR_ACCESS_DENIED, "Unable to initialize SELinux.");
332
333                         return r;
334                 }
335                 first_time = 0;
336         }
337
338         return 0;
339 }
340
341 static int get_audit_data(
342         DBusConnection *connection,
343         DBusMessage *message,
344         struct auditstruct *audit,
345         DBusError *error) {
346
347         const char *sender;
348         int r;
349
350         sender = dbus_message_get_sender(message);
351         if (sender)
352                 return bus_get_audit_data(connection, sender, audit, error);
353         else {
354                 int fd;
355                 struct ucred ucred;
356                 socklen_t len;
357
358                 if (!dbus_connection_get_unix_fd(connection, &fd))
359                         return -EINVAL;
360
361                 r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len);
362                 if (r < 0) {
363                         log_error("Failed to determine peer credentials: %m");
364                         return -errno;
365                 }
366
367                 audit->uid = ucred.uid;
368                 audit->gid = ucred.gid;
369
370                 r = audit_loginuid_from_pid(ucred.pid, &audit->loginuid);
371                 if (r < 0)
372                         return r;
373
374                 r = get_process_cmdline(ucred.pid, LINE_MAX, true, &audit->cmdline);
375                 if (r < 0)
376                         return r;
377
378                 return 0;
379         }
380 }
381
382 /*
383    This function returns the security context of the remote end of the dbus
384    connections.  Whether it is on the bus or a local connection.
385 */
386 static int get_calling_context(
387         DBusConnection *connection,
388         DBusMessage *message,
389         security_context_t *scon,
390         DBusError *error) {
391
392         const char *sender;
393         int r;
394
395         /*
396            If sender exists then
397            if sender is NULL this indicates a local connection.  Grab the fd
398            from dbus and do an getpeercon to peers process context
399         */
400         sender = dbus_message_get_sender(message);
401         if (sender) {
402                 r = bus_get_selinux_security_context(connection, sender, scon, error);
403                 if (r < 0)
404                         return -EINVAL;
405         } else {
406                 int fd;
407                 r = dbus_connection_get_unix_fd(connection, &fd);
408                 if (! r)
409                         return -EINVAL;
410
411                 r = getpeercon(fd, scon);
412                 if (r < 0)
413                         return -errno;
414         }
415
416         return 0;
417 }
418
419 /*
420    This function returns the SELinux permission to check and whether or not the
421    check requires a unit file.
422 */
423 static void selinux_perm_lookup(const char *method, const char **perm, bool *require_unit) {
424         const char *m, *p;
425
426         NULSTR_FOREACH_PAIR(m, p, unit_methods)
427                 if (streq(method, m)) {
428                         *perm = p;
429                         *require_unit = true;
430                         return;
431                 }
432
433         NULSTR_FOREACH_PAIR(m, p, system_methods)
434                 if (streq(method, m)) {
435                         *perm = p;
436                         *require_unit = false;
437                         return;
438                 }
439
440         *require_unit = false;
441         *perm = "undefined";
442 }
443
444 /*
445    This function communicates with the kernel to check whether or not it should
446    allow the access.
447    If the machine is in permissive mode it will return ok.  Audit messages will
448    still be generated if the access would be denied in enforcing mode.
449 */
450 static int selinux_access_check(DBusConnection *connection, DBusMessage *message, Manager *m, DBusError *error, const char *perm, const char *path) {
451         security_context_t scon = NULL;
452         security_context_t fcon = NULL;
453         int r = 0;
454         const char *tclass = NULL;
455         struct auditstruct audit;
456
457         audit.uid = audit.loginuid = (uid_t) -1;
458         audit.gid = (gid_t) -1;
459         audit.cmdline = NULL;
460         audit.path = path;
461
462         r = get_calling_context(connection, message, &scon, error);
463         if (r != 0)
464                 goto finish;
465
466         if (path) {
467                 tclass = "service";
468                 /* get the file context of the unit file */
469                 r = getfilecon(path, &fcon);
470                 if (r < 0) {
471                         log_full(LOG_ERR, "Failed to get security context on: %s %m\n",path);
472                         goto finish;
473                 }
474
475         } else {
476                 tclass = "system";
477                 r = getcon(&fcon);
478                 if (r < 0) {
479                         dbus_set_error(error, BUS_ERROR_ACCESS_DENIED, "Unable to get current context, SELinux policy denies access.");
480                         goto finish;
481                 }
482         }
483
484         (void) get_audit_data(connection, message, &audit, error);
485
486         errno= 0;
487         r = selinux_check_access(scon, fcon, tclass, perm, &audit);
488         if (r < 0) {
489                 r = -errno;
490                 log_error("SELinux Denied \"%s\"", audit.cmdline);
491
492                 dbus_set_error(error, BUS_ERROR_ACCESS_DENIED, "SELinux policy denies access.");
493         }
494
495         log_debug("SELinux checkaccess scon %s tcon %s tclass %s perm %s path %s: %d", scon, fcon, tclass, perm, path, r);
496 finish:
497         if (r)
498                 r = -errno;
499
500         free(audit.cmdline);
501         freecon(scon);
502         freecon(fcon);
503
504         return r;
505 }
506
507 /*
508   Clean up memory allocated in selinux_avc_init
509 */
510 void selinux_access_finish(void) {
511         if (!first_time)
512                 avc_destroy();
513         first_time = 1;
514 }
515
516 int selinux_unit_access_check(DBusConnection *connection, DBusMessage *message, Manager *m, const char *path, DBusError *error) {
517         const char *perm;
518         bool require_unit;
519         const char *member;
520         int r;
521
522         r = selinux_init(m, error);
523         if (r < 0)
524                 return r;
525
526         if (! selinux_enabled)
527                 return 0;
528
529         member = dbus_message_get_member(message);
530
531         selinux_perm_lookup(member, &perm, &require_unit);
532         log_debug("SELinux dbus-unit Look %s up perm %s require_unit %d", member, perm, require_unit);
533
534         r = selinux_access_check(connection, message, m, error, perm, path);
535         if (r < 0 && !selinux_enforcing) {
536                 dbus_error_init(error);
537                 r = 0;
538         }
539
540         return r;
541 }
542
543 int selinux_manager_access_check(DBusConnection *connection, DBusMessage *message, Manager *m, DBusError *error) {
544         int r = -1;
545         const char *member;
546         bool require_unit;
547         const char *perm;
548         char *path = NULL;
549
550         r = selinux_init(m, error);
551         if (r < 0)
552                 return r;
553
554         if (! selinux_enabled)
555                 return 0;
556
557         member = dbus_message_get_member(message);
558
559         selinux_perm_lookup(member, &perm, &require_unit);
560         log_debug("SELinux dbus-manager Lookup %s perm %s require_unit %d", member, perm, require_unit);
561
562         if (require_unit) {
563                 const char *name;
564                 Unit *u;
565
566                 if (!dbus_message_get_args(
567                         message,
568                         error,
569                         DBUS_TYPE_STRING, &name,
570                         DBUS_TYPE_INVALID)) {
571                         r = -EINVAL;
572                         goto finish;
573                 }
574
575                 r = manager_load_unit(m, name, NULL, error, &u);
576                 if (r < 0) {
577                         dbus_set_error(error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name);
578                         goto finish;
579                 }
580
581                 path = u->source_path ? u->source_path : u->fragment_path;
582         }
583         r = selinux_access_check(connection, message, m, error, perm, path);
584
585 finish:
586         /* if SELinux is in permissive mode return 0 */
587         if (r && (!selinux_enforcing)) {
588                 dbus_error_init(error);
589                 r = 0;
590         }
591         return r;
592 }
593
594 #else
595 int selinux_unit_access_check(DBusConnection *connection, DBusMessage *message, Manager *m, const char *path, DBusError *error) {
596         return 0;
597 }
598
599 int selinux_manager_access_check(DBusConnection *connection, DBusMessage *message, Manager *m, DBusError *error) {
600         return 0;
601 }
602
603 void selinux_access_finish(void) {
604 }
605 #endif