]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/core/selinux-access.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / core / selinux-access.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2012 Dan Walsh
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include "selinux-access.h"
22
23 #if HAVE_SELINUX
24
25 #include <errno.h>
26 #include <selinux/avc.h>
27 #include <selinux/selinux.h>
28 #include <stdio.h>
29 #if HAVE_AUDIT
30 #include <libaudit.h>
31 #endif
32
33 #include "sd-bus.h"
34
35 #include "alloc-util.h"
36 #include "audit-fd.h"
37 #include "bus-util.h"
38 #include "log.h"
39 #include "path-util.h"
40 #include "selinux-util.h"
41 #include "stdio-util.h"
42 #include "strv.h"
43 #include "util.h"
44
45 static bool initialized = false;
46
47 struct audit_info {
48 sd_bus_creds *creds;
49 const char *path;
50 const char *cmdline;
51 };
52
53 /*
54 Any time an access gets denied this callback will be called
55 with the audit data. We then need to just copy the audit data into the msgbuf.
56 */
57 static int audit_callback(
58 void *auditdata,
59 security_class_t cls,
60 char *msgbuf,
61 size_t msgbufsize) {
62
63 const struct audit_info *audit = auditdata;
64 uid_t uid = 0, login_uid = 0;
65 gid_t gid = 0;
66 char login_uid_buf[DECIMAL_STR_MAX(uid_t) + 1] = "n/a";
67 char uid_buf[DECIMAL_STR_MAX(uid_t) + 1] = "n/a";
68 char gid_buf[DECIMAL_STR_MAX(gid_t) + 1] = "n/a";
69
70 if (sd_bus_creds_get_audit_login_uid(audit->creds, &login_uid) >= 0)
71 xsprintf(login_uid_buf, UID_FMT, login_uid);
72 if (sd_bus_creds_get_euid(audit->creds, &uid) >= 0)
73 xsprintf(uid_buf, UID_FMT, uid);
74 if (sd_bus_creds_get_egid(audit->creds, &gid) >= 0)
75 xsprintf(gid_buf, GID_FMT, gid);
76
77 snprintf(msgbuf, msgbufsize,
78 "auid=%s uid=%s gid=%s%s%s%s%s%s%s",
79 login_uid_buf, uid_buf, gid_buf,
80 audit->path ? " path=\"" : "", strempty(audit->path), audit->path ? "\"" : "",
81 audit->cmdline ? " cmdline=\"" : "", strempty(audit->cmdline), audit->cmdline ? "\"" : "");
82
83 return 0;
84 }
85
86 static int callback_type_to_priority(int type) {
87 switch(type) {
88
89 case SELINUX_ERROR:
90 return LOG_ERR;
91
92 case SELINUX_WARNING:
93 return LOG_WARNING;
94
95 case SELINUX_INFO:
96 return LOG_INFO;
97
98 case SELINUX_AVC:
99 default:
100 return LOG_NOTICE;
101 }
102 }
103
104 /*
105 libselinux uses this callback when access gets denied or other
106 events happen. If audit is turned on, messages will be reported
107 using audit netlink, otherwise they will be logged using the usual
108 channels.
109
110 Code copied from dbus and modified.
111 */
112 _printf_(2, 3) static int log_callback(int type, const char *fmt, ...) {
113 va_list ap;
114 const char *fmt2;
115
116 #if HAVE_AUDIT
117 int fd;
118
119 fd = get_audit_fd();
120
121 if (fd >= 0) {
122 _cleanup_free_ char *buf = NULL;
123 int r;
124
125 va_start(ap, fmt);
126 r = vasprintf(&buf, fmt, ap);
127 va_end(ap);
128
129 if (r >= 0) {
130 audit_log_user_avc_message(fd, AUDIT_USER_AVC, buf, NULL, NULL, NULL, 0);
131 return 0;
132 }
133 }
134 #endif
135
136 fmt2 = strjoina("selinux: ", fmt);
137
138 va_start(ap, fmt);
139 #pragma GCC diagnostic push
140 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
141 log_internalv(LOG_AUTH | callback_type_to_priority(type),
142 0, __FILE__, __LINE__, __FUNCTION__,
143 fmt2, ap);
144 #pragma GCC diagnostic pop
145 va_end(ap);
146
147 return 0;
148 }
149
150 static int access_init(sd_bus_error *error) {
151
152 if (!mac_selinux_use())
153 return 0;
154
155 if (initialized)
156 return 1;
157
158 if (avc_open(NULL, 0) != 0) {
159 int enforce, saved_errno = errno;
160
161 enforce = security_getenforce();
162 log_full_errno(enforce != 0 ? LOG_ERR : LOG_WARNING, saved_errno, "Failed to open the SELinux AVC: %m");
163
164 /* If enforcement isn't on, then let's suppress this
165 * error, and just don't do any AVC checks. The
166 * warning we printed is hence all the admin will
167 * see. */
168 if (enforce == 0)
169 return 0;
170
171 /* Return an access denied error, if we couldn't load
172 * the AVC but enforcing mode was on, or we couldn't
173 * determine whether it is one. */
174 return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to open the SELinux AVC: %s", strerror(saved_errno));
175 }
176
177 selinux_set_callback(SELINUX_CB_AUDIT, (union selinux_callback) audit_callback);
178 selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) log_callback);
179
180 initialized = true;
181 return 1;
182 }
183
184 /*
185 This function communicates with the kernel to check whether or not it should
186 allow the access.
187 If the machine is in permissive mode it will return ok. Audit messages will
188 still be generated if the access would be denied in enforcing mode.
189 */
190 int mac_selinux_generic_access_check(
191 sd_bus_message *message,
192 const char *path,
193 const char *permission,
194 sd_bus_error *error) {
195
196 _cleanup_(sd_bus_creds_unrefp) 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 char *fcon = NULL;
201 char **cmdline = NULL;
202 int r = 0;
203
204 assert(message);
205 assert(permission);
206 assert(error);
207
208 r = access_init(error);
209 if (r <= 0)
210 return r;
211
212 r = sd_bus_query_sender_creds(
213 message,
214 SD_BUS_CREDS_PID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_EGID|
215 SD_BUS_CREDS_CMDLINE|SD_BUS_CREDS_AUDIT_LOGIN_UID|
216 SD_BUS_CREDS_SELINUX_CONTEXT|
217 SD_BUS_CREDS_AUGMENT /* get more bits from /proc */,
218 &creds);
219 if (r < 0)
220 goto finish;
221
222 /* The SELinux context is something we really should have
223 * gotten directly from the message or sender, and not be an
224 * augmented field. If it was augmented we cannot use it for
225 * authorization, since this is racy and vulnerable. Let's add
226 * an extra check, just in case, even though this really
227 * shouldn't be possible. */
228 assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_SELINUX_CONTEXT) == 0, -EPERM);
229
230 r = sd_bus_creds_get_selinux_context(creds, &scon);
231 if (r < 0)
232 goto finish;
233
234 if (path) {
235 /* Get the file context of the unit file */
236
237 r = getfilecon_raw(path, &fcon);
238 if (r < 0) {
239 r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to get file context on %s.", path);
240 goto finish;
241 }
242
243 tclass = "service";
244 } else {
245 r = getcon_raw(&fcon);
246 if (r < 0) {
247 r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to get current context.");
248 goto finish;
249 }
250
251 tclass = "system";
252 }
253
254 sd_bus_creds_get_cmdline(creds, &cmdline);
255 cl = strv_join(cmdline, " ");
256
257 audit_info.creds = creds;
258 audit_info.path = path;
259 audit_info.cmdline = cl;
260
261 r = selinux_check_access(scon, fcon, tclass, permission, &audit_info);
262 if (r < 0)
263 r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "SELinux policy denies access.");
264
265 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);
266
267 finish:
268 freecon(fcon);
269
270 if (r < 0 && security_getenforce() != 1) {
271 sd_bus_error_free(error);
272 r = 0;
273 }
274
275 return r;
276 }
277
278 #else
279
280 int mac_selinux_generic_access_check(
281 sd_bus_message *message,
282 const char *path,
283 const char *permission,
284 sd_bus_error *error) {
285
286 return 0;
287 }
288
289 #endif