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