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