]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/core/selinux-access.c
Merge pull request #85 from keszybz/selinux-context
[thirdparty/systemd.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 Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser 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 <errno.h>
28 #include <selinux/selinux.h>
29 #include <selinux/avc.h>
30 #ifdef HAVE_AUDIT
31 #include <libaudit.h>
32 #endif
33
34 #include "sd-bus.h"
35 #include "bus-util.h"
36 #include "util.h"
37 #include "log.h"
38 #include "selinux-util.h"
39 #include "audit-fd.h"
40 #include "strv.h"
41
42 static bool initialized = false;
43
44 struct audit_info {
45 sd_bus_creds *creds;
46 const char *path;
47 const char *cmdline;
48 };
49
50 /*
51 Any time an access gets denied this callback will be called
52 with the audit data. We then need to just copy the audit data into the msgbuf.
53 */
54 static int audit_callback(
55 void *auditdata,
56 security_class_t cls,
57 char *msgbuf,
58 size_t msgbufsize) {
59
60 const struct audit_info *audit = auditdata;
61 uid_t uid = 0, login_uid = 0;
62 gid_t gid = 0;
63 char login_uid_buf[DECIMAL_STR_MAX(uid_t) + 1] = "n/a";
64 char uid_buf[DECIMAL_STR_MAX(uid_t) + 1] = "n/a";
65 char gid_buf[DECIMAL_STR_MAX(gid_t) + 1] = "n/a";
66
67 if (sd_bus_creds_get_audit_login_uid(audit->creds, &login_uid) >= 0)
68 xsprintf(login_uid_buf, UID_FMT, login_uid);
69 if (sd_bus_creds_get_euid(audit->creds, &uid) >= 0)
70 xsprintf(uid_buf, UID_FMT, uid);
71 if (sd_bus_creds_get_egid(audit->creds, &gid) >= 0)
72 xsprintf(gid_buf, GID_FMT, gid);
73
74 snprintf(msgbuf, msgbufsize,
75 "auid=%s uid=%s gid=%s%s%s%s%s%s%s",
76 login_uid_buf, uid_buf, gid_buf,
77 audit->path ? " path=\"" : "", strempty(audit->path), audit->path ? "\"" : "",
78 audit->cmdline ? " cmdline=\"" : "", strempty(audit->cmdline), audit->cmdline ? "\"" : "");
79
80 return 0;
81 }
82
83 static int callback_type_to_priority(int type) {
84 switch(type) {
85 case SELINUX_ERROR: return LOG_ERR;
86 case SELINUX_WARNING: return LOG_WARNING;
87 case SELINUX_INFO: return LOG_INFO;
88 case SELINUX_AVC:
89 default: return LOG_NOTICE;
90 }
91 }
92
93 /*
94 libselinux uses this callback when access gets denied or other
95 events happen. If audit is turned on, messages will be reported
96 using audit netlink, otherwise they will be logged using the usual
97 channels.
98
99 Code copied from dbus and modified.
100 */
101 _printf_(2, 3) static int log_callback(int type, const char *fmt, ...) {
102 va_list ap;
103
104 #ifdef HAVE_AUDIT
105 int fd;
106
107 fd = get_audit_fd();
108
109 if (fd >= 0) {
110 _cleanup_free_ char *buf = NULL;
111 int r;
112
113 va_start(ap, fmt);
114 r = vasprintf(&buf, fmt, ap);
115 va_end(ap);
116
117 if (r >= 0) {
118 audit_log_user_avc_message(fd, AUDIT_USER_AVC, buf, NULL, NULL, NULL, 0);
119 return 0;
120 }
121 }
122 #endif
123
124 va_start(ap, fmt);
125 log_internalv(LOG_AUTH | callback_type_to_priority(type),
126 0, __FILE__, __LINE__, __FUNCTION__, fmt, ap);
127 va_end(ap);
128
129 return 0;
130 }
131
132 /*
133 Function must be called once to initialize the SELinux AVC environment.
134 Sets up callbacks.
135 If you want to cleanup memory you should need to call selinux_access_finish.
136 */
137 static int access_init(void) {
138 int r = 0;
139
140 if (avc_open(NULL, 0))
141 return log_error_errno(errno, "avc_open() failed: %m");
142
143 selinux_set_callback(SELINUX_CB_AUDIT, (union selinux_callback) audit_callback);
144 selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) log_callback);
145
146 if (security_getenforce() < 0){
147 r = -errno;
148 avc_destroy();
149 }
150
151 return r;
152 }
153
154 static int mac_selinux_access_init(sd_bus_error *error) {
155 int r;
156
157 if (initialized)
158 return 0;
159
160 if (!mac_selinux_use())
161 return 0;
162
163 r = access_init();
164 if (r < 0)
165 return sd_bus_error_set(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to initialize SELinux.");
166
167 initialized = true;
168 return 0;
169 }
170 #endif
171
172 void mac_selinux_access_free(void) {
173
174 #ifdef HAVE_SELINUX
175 if (!initialized)
176 return;
177
178 avc_destroy();
179 initialized = false;
180 #endif
181 }
182
183 /*
184 This function communicates with the kernel to check whether or not it should
185 allow the access.
186 If the machine is in permissive mode it will return ok. Audit messages will
187 still be generated if the access would be denied in enforcing mode.
188 */
189 int mac_selinux_generic_access_check(
190 sd_bus_message *message,
191 const char *path,
192 const char *permission,
193 sd_bus_error *error) {
194
195 #ifdef HAVE_SELINUX
196 _cleanup_bus_creds_unref_ sd_bus_creds *creds = NULL;
197 const char *tclass = NULL, *scon = NULL;
198 struct audit_info audit_info = {};
199 _cleanup_free_ char *cl = NULL;
200 security_context_t fcon = NULL;
201 char **cmdline = NULL;
202 int r = 0;
203
204 assert(message);
205 assert(permission);
206 assert(error);
207
208 if (!mac_selinux_use())
209 return 0;
210
211 r = mac_selinux_access_init(error);
212 if (r < 0)
213 return r;
214
215 r = sd_bus_query_sender_creds(
216 message,
217 SD_BUS_CREDS_PID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_EGID|
218 SD_BUS_CREDS_CMDLINE|SD_BUS_CREDS_AUDIT_LOGIN_UID|
219 SD_BUS_CREDS_SELINUX_CONTEXT|
220 SD_BUS_CREDS_AUGMENT /* get more bits from /proc */,
221 &creds);
222 if (r < 0)
223 goto finish;
224
225 /* The SELinux context is something we really should have
226 * gotten directly from the message or sender, and not be an
227 * augmented field. If it was augmented we cannot use it for
228 * authorization, since this is racy and vulnerable. Let's add
229 * an extra check, just in case, even though this really
230 * shouldn't be possible. */
231 assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_SELINUX_CONTEXT) == 0, -EPERM);
232
233 r = sd_bus_creds_get_selinux_context(creds, &scon);
234 if (r < 0)
235 goto finish;
236
237 if (path) {
238 /* Get the file context of the unit file */
239
240 r = getfilecon(path, &fcon);
241 if (r < 0) {
242 r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to get file context on %s.", path);
243 goto finish;
244 }
245
246 tclass = "service";
247 } else {
248 r = getcon(&fcon);
249 if (r < 0) {
250 r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to get current context.");
251 goto finish;
252 }
253
254 tclass = "system";
255 }
256
257 sd_bus_creds_get_cmdline(creds, &cmdline);
258 cl = strv_join(cmdline, " ");
259
260 audit_info.creds = creds;
261 audit_info.path = path;
262 audit_info.cmdline = cl;
263
264 r = selinux_check_access(scon, fcon, tclass, permission, &audit_info);
265 if (r < 0)
266 r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "SELinux policy denies access.");
267
268 log_debug("SELinux access check scon=%s tcon=%s tclass=%s perm=%s path=%s cmdline=%s: %i", scon, fcon, tclass, permission, path, cl, r);
269
270 finish:
271 freecon(fcon);
272
273 if (r < 0 && security_getenforce() != 1) {
274 sd_bus_error_free(error);
275 r = 0;
276 }
277
278 return r;
279 #else
280 return 0;
281 #endif
282 }
283
284 int mac_selinux_unit_access_check_strv(char **units,
285 sd_bus_message *message,
286 Manager *m,
287 const char *permission,
288 sd_bus_error *error) {
289 #ifdef HAVE_SELINUX
290 char **i;
291 Unit *u;
292 int r;
293
294 STRV_FOREACH(i, units) {
295 u = manager_get_unit(m, *i);
296 if (u) {
297 r = mac_selinux_unit_access_check(u, message, permission, error);
298 if (r < 0)
299 return r;
300 }
301 }
302 #endif
303 return 0;
304 }