]>
Commit | Line | Data |
---|---|---|
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 */ |