]>
Commit | Line | Data |
---|---|---|
8f69975d BS |
1 | From: John Johansen <jjohansen@suse.de> |
2 | Subject: AppArmor: Main Part | |
3 | ||
4 | The underlying functions by which the AppArmor LSM hooks are implemented. | |
5 | ||
6 | Signed-off-by: John Johansen <jjohansen@suse.de> | |
7 | Signed-off-by: Andreas Gruenbacher <agruen@suse.de> | |
8 | ||
9 | --- | |
10 | security/apparmor/main.c | 1478 +++++++++++++++++++++++++++++++++++++++++++++++ | |
11 | 1 file changed, 1478 insertions(+) | |
12 | ||
13 | --- /dev/null | |
14 | +++ b/security/apparmor/main.c | |
15 | @@ -0,0 +1,1478 @@ | |
16 | +/* | |
17 | + * Copyright (C) 2002-2007 Novell/SUSE | |
18 | + * | |
19 | + * This program is free software; you can redistribute it and/or | |
20 | + * modify it under the terms of the GNU General Public License as | |
21 | + * published by the Free Software Foundation, version 2 of the | |
22 | + * License. | |
23 | + * | |
24 | + * AppArmor Core | |
25 | + */ | |
26 | + | |
27 | +#include <linux/security.h> | |
28 | +#include <linux/namei.h> | |
29 | +#include <linux/audit.h> | |
30 | +#include <linux/mount.h> | |
31 | +#include <linux/ptrace.h> | |
32 | + | |
33 | +#include "apparmor.h" | |
34 | + | |
35 | +#include "inline.h" | |
36 | + | |
37 | +/* | |
38 | + * Table of capability names: we generate it from capabilities.h. | |
39 | + */ | |
40 | +static const char *capability_names[] = { | |
41 | +#include "capability_names.h" | |
42 | +}; | |
43 | + | |
44 | +struct aa_namespace *default_namespace; | |
45 | + | |
46 | +static int aa_inode_mode(struct inode *inode) | |
47 | +{ | |
48 | + /* if the inode doesn't exist the user is creating it */ | |
49 | + if (!inode || current->fsuid == inode->i_uid) | |
50 | + return AA_USER_SHIFT; | |
51 | + return AA_OTHER_SHIFT; | |
52 | +} | |
53 | + | |
54 | +int alloc_default_namespace(void) | |
55 | +{ | |
56 | + struct aa_namespace *ns; | |
57 | + char *name = kstrdup("default", GFP_KERNEL); | |
58 | + if (!name) | |
59 | + return -ENOMEM; | |
60 | + ns = alloc_aa_namespace(name); | |
61 | + if (!ns) { | |
62 | + kfree(name); | |
63 | + return -ENOMEM; | |
64 | + } | |
65 | + | |
66 | + write_lock(&profile_ns_list_lock); | |
67 | + default_namespace = ns; | |
68 | + aa_get_namespace(ns); | |
69 | + list_add(&ns->list, &profile_ns_list); | |
70 | + write_unlock(&profile_ns_list_lock); | |
71 | + | |
72 | + return 0; | |
73 | +} | |
74 | + | |
75 | +void free_default_namespace(void) | |
76 | +{ | |
77 | + write_lock(&profile_ns_list_lock); | |
78 | + list_del_init(&default_namespace->list); | |
79 | + write_unlock(&profile_ns_list_lock); | |
80 | + aa_put_namespace(default_namespace); | |
81 | + default_namespace = NULL; | |
82 | +} | |
83 | + | |
84 | +static void aa_audit_file_sub_mask(struct audit_buffer *ab, char *buffer, | |
85 | + int mask) | |
86 | +{ | |
87 | + const char unsafex[] = "upcn"; | |
88 | + const char safex[] = "UPCN"; | |
89 | + char *m = buffer; | |
90 | + | |
91 | + if (mask & AA_EXEC_MMAP) | |
92 | + *m++ = 'm'; | |
93 | + if (mask & MAY_READ) | |
94 | + *m++ = 'r'; | |
95 | + if (mask & MAY_WRITE) | |
96 | + *m++ = 'w'; | |
97 | + else if (mask & MAY_APPEND) | |
98 | + *m++ = 'a'; | |
99 | + if (mask & MAY_EXEC) { | |
100 | + int index = AA_EXEC_INDEX(mask); | |
101 | + /* all indexes > 4 are also named transitions */ | |
102 | + if (index > 4) | |
103 | + index = 4; | |
104 | + if (index > 0) { | |
105 | + if (mask & AA_EXEC_UNSAFE) | |
106 | + *m++ = unsafex[index - 1]; | |
107 | + else | |
108 | + *m++ = safex[index - 1]; | |
109 | + } | |
110 | + if (mask & AA_EXEC_INHERIT) | |
111 | + *m++ = 'i'; | |
112 | + *m++ = 'x'; | |
113 | + } | |
114 | + if (mask & AA_MAY_LINK) | |
115 | + *m++ = 'l'; | |
116 | + if (mask & AA_MAY_LOCK) | |
117 | + *m++ = 'k'; | |
118 | + *m++ = '\0'; | |
119 | +} | |
120 | + | |
121 | +static void aa_audit_file_mask(struct audit_buffer *ab, const char *name, | |
122 | + int mask) | |
123 | +{ | |
124 | + char user[10], other[10]; | |
125 | + | |
126 | + aa_audit_file_sub_mask(ab, user, | |
127 | + (mask & AA_USER_PERMS) >> AA_USER_SHIFT); | |
128 | + aa_audit_file_sub_mask(ab, other, | |
129 | + (mask & AA_OTHER_PERMS) >> AA_OTHER_SHIFT); | |
130 | + | |
131 | + audit_log_format(ab, " %s=\"%s::%s\"", name, user, other); | |
132 | +} | |
133 | + | |
134 | +/** | |
135 | + * aa_audit - Log an audit event to the audit subsystem | |
136 | + * @profile: profile to check against | |
137 | + * @sa: audit event | |
138 | + * @audit_cxt: audit context to log message to | |
139 | + * @type: audit event number | |
140 | + */ | |
141 | +static int aa_audit_base(struct aa_profile *profile, struct aa_audit *sa, | |
142 | + struct audit_context *audit_cxt, int type) | |
143 | +{ | |
144 | + struct audit_buffer *ab = NULL; | |
145 | + | |
146 | + ab = audit_log_start(audit_cxt, sa->gfp_mask, type); | |
147 | + | |
148 | + if (!ab) { | |
149 | + AA_ERROR("Unable to log event (%d) to audit subsys\n", | |
150 | + type); | |
151 | + /* don't fail operations in complain mode even if logging | |
152 | + * fails */ | |
153 | + return type == AUDIT_APPARMOR_ALLOWED ? 0 : -ENOMEM; | |
154 | + } | |
155 | + | |
156 | + if (sa->operation) | |
157 | + audit_log_format(ab, "operation=\"%s\"", sa->operation); | |
158 | + | |
159 | + if (sa->info) { | |
160 | + audit_log_format(ab, " info=\"%s\"", sa->info); | |
161 | + if (sa->error_code) | |
162 | + audit_log_format(ab, " error=%d", sa->error_code); | |
163 | + } | |
164 | + | |
165 | + if (sa->request_mask) | |
166 | + aa_audit_file_mask(ab, "requested_mask", sa->request_mask); | |
167 | + | |
168 | + if (sa->denied_mask) | |
169 | + aa_audit_file_mask(ab, "denied_mask", sa->denied_mask); | |
170 | + | |
171 | + if (sa->request_mask) | |
172 | + audit_log_format(ab, " fsuid=%d", current->fsuid); | |
173 | + | |
174 | + if (sa->iattr) { | |
175 | + struct iattr *iattr = sa->iattr; | |
176 | + | |
177 | + audit_log_format(ab, " attribute=\"%s%s%s%s%s%s%s\"", | |
178 | + iattr->ia_valid & ATTR_MODE ? "mode," : "", | |
179 | + iattr->ia_valid & ATTR_UID ? "uid," : "", | |
180 | + iattr->ia_valid & ATTR_GID ? "gid," : "", | |
181 | + iattr->ia_valid & ATTR_SIZE ? "size," : "", | |
182 | + iattr->ia_valid & (ATTR_ATIME | ATTR_ATIME_SET) ? | |
183 | + "atime," : "", | |
184 | + iattr->ia_valid & (ATTR_MTIME | ATTR_MTIME_SET) ? | |
185 | + "mtime," : "", | |
186 | + iattr->ia_valid & ATTR_CTIME ? "ctime," : ""); | |
187 | + } | |
188 | + | |
189 | + if (sa->task) | |
190 | + audit_log_format(ab, " task=%d", sa->task); | |
191 | + | |
192 | + if (sa->parent) | |
193 | + audit_log_format(ab, " parent=%d", sa->parent); | |
194 | + | |
195 | + if (sa->name) { | |
196 | + audit_log_format(ab, " name="); | |
197 | + audit_log_untrustedstring(ab, sa->name); | |
198 | + } | |
199 | + | |
200 | + if (sa->name2) { | |
201 | + audit_log_format(ab, " name2="); | |
202 | + audit_log_untrustedstring(ab, sa->name2); | |
203 | + } | |
204 | + | |
205 | + audit_log_format(ab, " pid=%d", current->pid); | |
206 | + | |
207 | + if (profile) { | |
208 | + audit_log_format(ab, " profile="); | |
209 | + audit_log_untrustedstring(ab, profile->name); | |
210 | + | |
211 | + if (profile->ns != default_namespace) { | |
212 | + audit_log_format(ab, " namespace="); | |
213 | + audit_log_untrustedstring(ab, profile->ns->name); | |
214 | + } | |
215 | + } | |
216 | + | |
217 | + audit_log_end(ab); | |
218 | + | |
219 | + return type == AUDIT_APPARMOR_ALLOWED ? 0 : sa->error_code; | |
220 | +} | |
221 | + | |
222 | +/** | |
223 | + * aa_audit_syscallreject - Log a syscall rejection to the audit subsystem | |
224 | + * @profile: profile to check against | |
225 | + * @gfp: memory allocation flags | |
226 | + * @msg: string describing syscall being rejected | |
227 | + */ | |
228 | +int aa_audit_syscallreject(struct aa_profile *profile, gfp_t gfp, | |
229 | + const char *msg) | |
230 | +{ | |
231 | + struct aa_audit sa; | |
232 | + memset(&sa, 0, sizeof(sa)); | |
233 | + sa.operation = "syscall"; | |
234 | + sa.name = msg; | |
235 | + sa.gfp_mask = gfp; | |
236 | + sa.error_code = -EPERM; | |
237 | + | |
238 | + return aa_audit_base(profile, &sa, current->audit_context, | |
239 | + AUDIT_APPARMOR_DENIED); | |
240 | +} | |
241 | + | |
242 | +int aa_audit_message(struct aa_profile *profile, struct aa_audit *sa, | |
243 | + int type) | |
244 | +{ | |
245 | + struct audit_context *audit_cxt; | |
246 | + | |
247 | + audit_cxt = apparmor_logsyscall ? current->audit_context : NULL; | |
248 | + return aa_audit_base(profile, sa, audit_cxt, type); | |
249 | +} | |
250 | + | |
251 | +void aa_audit_hint(struct aa_profile *profile, struct aa_audit *sa) | |
252 | +{ | |
253 | + aa_audit_message(profile, sa, AUDIT_APPARMOR_HINT); | |
254 | +} | |
255 | + | |
256 | +void aa_audit_status(struct aa_profile *profile, struct aa_audit *sa) | |
257 | +{ | |
258 | + aa_audit_message(profile, sa, AUDIT_APPARMOR_STATUS); | |
259 | +} | |
260 | + | |
261 | +int aa_audit_reject(struct aa_profile *profile, struct aa_audit *sa) | |
262 | +{ | |
263 | + return aa_audit_message(profile, sa, AUDIT_APPARMOR_DENIED); | |
264 | +} | |
265 | + | |
266 | +/** | |
267 | + * aa_audit - Log an audit event to the audit subsystem | |
268 | + * @profile: profile to check against | |
269 | + * @sa: audit event | |
270 | + */ | |
271 | +int aa_audit(struct aa_profile *profile, struct aa_audit *sa) | |
272 | +{ | |
273 | + int type = AUDIT_APPARMOR_DENIED; | |
274 | + struct audit_context *audit_cxt; | |
275 | + | |
276 | + if (likely(!sa->error_code)) | |
277 | + type = AUDIT_APPARMOR_AUDIT; | |
278 | + else if (PROFILE_COMPLAIN(profile)) | |
279 | + type = AUDIT_APPARMOR_ALLOWED; | |
280 | + | |
281 | + audit_cxt = apparmor_logsyscall ? current->audit_context : NULL; | |
282 | + return aa_audit_base(profile, sa, audit_cxt, type); | |
283 | +} | |
284 | + | |
285 | +static int aa_audit_file(struct aa_profile *profile, struct aa_audit *sa) | |
286 | +{ | |
287 | + if (likely(!sa->error_code)) { | |
288 | + int mask = sa->audit_mask & AUDIT_FILE_MASK; | |
289 | + | |
290 | + if (unlikely(PROFILE_AUDIT(profile))) | |
291 | + mask |= AUDIT_FILE_MASK; | |
292 | + | |
293 | + if (likely(!(sa->request_mask & mask))) | |
294 | + return 0; | |
295 | + | |
296 | + /* mask off perms that are not being force audited */ | |
297 | + sa->request_mask &= mask | ALL_AA_EXEC_TYPE; | |
298 | + } else { | |
299 | + int mask = AUDIT_QUIET_MASK(sa->audit_mask); | |
300 | + | |
301 | + if (!(sa->denied_mask & ~mask)) | |
302 | + return sa->error_code; | |
303 | + | |
304 | + /* mask off perms whose denial is being silenced */ | |
305 | + sa->denied_mask &= (~mask) | ALL_AA_EXEC_TYPE; | |
306 | + } | |
307 | + | |
308 | + return aa_audit(profile, sa); | |
309 | +} | |
310 | + | |
311 | +static int aa_audit_caps(struct aa_profile *profile, struct aa_audit *sa, | |
312 | + int cap) | |
313 | +{ | |
314 | + if (likely(!sa->error_code)) { | |
315 | + if (likely(!PROFILE_AUDIT(profile) && | |
316 | + !cap_raised(profile->audit_caps, cap))) | |
317 | + return 0; | |
318 | + } | |
319 | + | |
320 | + /* quieting of capabilities is handled the caps_logged cache */ | |
321 | + return aa_audit(profile, sa); | |
322 | +} | |
323 | + | |
324 | +/** | |
325 | + * aa_file_denied - check for @mask access on a file | |
326 | + * @profile: profile to check against | |
327 | + * @name: pathname of file | |
328 | + * @mask: permission mask requested for file | |
329 | + * @audit_mask: return audit mask for the match | |
330 | + * | |
331 | + * Return %0 on success, or else the permissions in @mask that the | |
332 | + * profile denies. | |
333 | + */ | |
334 | +static int aa_file_denied(struct aa_profile *profile, const char *name, | |
335 | + int mask, int *audit_mask) | |
336 | +{ | |
337 | + return (mask & ~aa_match(profile->file_rules, name, audit_mask)); | |
338 | +} | |
339 | + | |
340 | +/** | |
341 | + * aa_link_denied - check for permission to link a file | |
342 | + * @profile: profile to check against | |
343 | + * @link: pathname of link being created | |
344 | + * @target: pathname of target to be linked to | |
345 | + * @target_mode: UGO shift for target inode | |
346 | + * @request_mask: the permissions subset valid only if link succeeds | |
347 | + * @audit_mask: return the audit_mask for the link permission | |
348 | + * Return %0 on success, or else the permissions that the profile denies. | |
349 | + */ | |
350 | +static int aa_link_denied(struct aa_profile *profile, const char *link, | |
351 | + const char *target, int target_mode, | |
352 | + int *request_mask, int *audit_mask) | |
353 | +{ | |
354 | + unsigned int state; | |
355 | + int l_mode, t_mode, l_x, t_x, denied_mask = 0; | |
356 | + int link_mask = AA_MAY_LINK << target_mode; | |
357 | + | |
358 | + *request_mask = link_mask; | |
359 | + | |
360 | + l_mode = aa_match_state(profile->file_rules, DFA_START, link, &state); | |
361 | + | |
362 | + if (l_mode & link_mask) { | |
363 | + int mode; | |
364 | + /* test to see if target can be paired with link */ | |
365 | + state = aa_dfa_null_transition(profile->file_rules, state); | |
366 | + mode = aa_match_state(profile->file_rules, state, target, | |
367 | + &state); | |
368 | + | |
369 | + if (!(mode & link_mask)) | |
370 | + denied_mask |= link_mask; | |
371 | + | |
372 | + *audit_mask = dfa_audit_mask(profile->file_rules, state); | |
373 | + | |
374 | + /* return if link subset test is not required */ | |
375 | + if (!(mode & (AA_LINK_SUBSET_TEST << target_mode))) | |
376 | + return denied_mask; | |
377 | + } | |
378 | + | |
379 | + /* Do link perm subset test requiring permission on link are a | |
380 | + * subset of the permissions on target. | |
381 | + * If a subset test is required a permission subset test of the | |
382 | + * perms for the link are done against the user::other of the | |
383 | + * target's 'r', 'w', 'x', 'a', 'k', and 'm' permissions. | |
384 | + * | |
385 | + * If the link has 'x', an exact match of all the execute flags | |
386 | + * must match. | |
387 | + */ | |
388 | + denied_mask |= ~l_mode & link_mask; | |
389 | + | |
390 | + t_mode = aa_match(profile->file_rules, target, NULL); | |
391 | + | |
392 | + l_x = l_mode & (ALL_AA_EXEC_TYPE | AA_EXEC_BITS); | |
393 | + t_x = t_mode & (ALL_AA_EXEC_TYPE | AA_EXEC_BITS); | |
394 | + | |
395 | + /* For actual subset test ignore valid-profile-transition flags, | |
396 | + * and link bits | |
397 | + */ | |
398 | + l_mode &= AA_FILE_PERMS & ~AA_LINK_BITS; | |
399 | + t_mode &= AA_FILE_PERMS & ~AA_LINK_BITS; | |
400 | + | |
401 | + *request_mask = l_mode | link_mask; | |
402 | + | |
403 | + if (l_mode) { | |
404 | + int x = l_x | (t_x & ALL_AA_EXEC_UNSAFE); | |
405 | + denied_mask |= l_mode & ~t_mode; | |
406 | + /* mask off x modes not used by link */ | |
407 | + | |
408 | + /* handle exec subset | |
409 | + * - link safe exec issubset of unsafe exec | |
410 | + * - no link x perm is subset of target having x perm | |
411 | + */ | |
412 | + if ((l_mode & AA_USER_EXEC) && | |
413 | + (x & AA_USER_EXEC_TYPE) != (t_x & AA_USER_EXEC_TYPE)) | |
414 | + denied_mask = AA_USER_EXEC | (l_x & AA_USER_EXEC_TYPE); | |
415 | + if ((l_mode & AA_OTHER_EXEC) && | |
416 | + (x & AA_OTHER_EXEC_TYPE) != (t_x & AA_OTHER_EXEC_TYPE)) | |
417 | + denied_mask = AA_OTHER_EXEC | (l_x & AA_OTHER_EXEC_TYPE); | |
418 | + } | |
419 | + | |
420 | + return denied_mask; | |
421 | +} | |
422 | + | |
423 | +/** | |
424 | + * aa_get_name - compute the pathname of a file | |
425 | + * @dentry: dentry of the file | |
426 | + * @mnt: vfsmount of the file | |
427 | + * @buffer: buffer that aa_get_name() allocated | |
428 | + * @check: AA_CHECK_DIR is set if the file is a directory | |
429 | + * | |
430 | + * Returns a pointer to the beginning of the pathname (which usually differs | |
431 | + * from the beginning of the buffer), or an error code. | |
432 | + * | |
433 | + * We need @check to indicate whether the file is a directory or not because | |
434 | + * the file may not yet exist, and so we cannot check the inode's file type. | |
435 | + */ | |
436 | +static char *aa_get_name(struct dentry *dentry, struct vfsmount *mnt, | |
437 | + char **buffer, int check) | |
438 | +{ | |
439 | + char *name; | |
440 | + int is_dir, size = 256; | |
441 | + | |
442 | + is_dir = (check & AA_CHECK_DIR) ? 1 : 0; | |
443 | + | |
444 | + for (;;) { | |
445 | + char *buf = kmalloc(size, GFP_KERNEL); | |
446 | + if (!buf) | |
447 | + return ERR_PTR(-ENOMEM); | |
448 | + | |
449 | + name = d_namespace_path(dentry, mnt, buf, size - is_dir); | |
450 | + if (!IS_ERR(name)) { | |
451 | + if (name[0] != '/') { | |
452 | + /* | |
453 | + * This dentry is not connected to the | |
454 | + * namespace root -- reject access. | |
455 | + */ | |
456 | + kfree(buf); | |
457 | + return ERR_PTR(-ENOENT); | |
458 | + } | |
459 | + if (is_dir && name[1] != '\0') { | |
460 | + /* | |
461 | + * Append "/" to the pathname. The root | |
462 | + * directory is a special case; it already | |
463 | + * ends in slash. | |
464 | + */ | |
465 | + buf[size - 2] = '/'; | |
466 | + buf[size - 1] = '\0'; | |
467 | + } | |
468 | + | |
469 | + *buffer = buf; | |
470 | + return name; | |
471 | + } | |
472 | + if (PTR_ERR(name) != -ENAMETOOLONG) | |
473 | + return name; | |
474 | + | |
475 | + kfree(buf); | |
476 | + size <<= 1; | |
477 | + if (size > apparmor_path_max) | |
478 | + return ERR_PTR(-ENAMETOOLONG); | |
479 | + } | |
480 | +} | |
481 | + | |
482 | +static char *new_compound_name(const char *n1, const char *n2) | |
483 | +{ | |
484 | + char *name = kmalloc(strlen(n1) + strlen(n2) + 3, GFP_KERNEL); | |
485 | + if (name) | |
486 | + sprintf(name, "%s//%s", n1, n2); | |
487 | + return name; | |
488 | +} | |
489 | +static inline void aa_put_name_buffer(char *buffer) | |
490 | +{ | |
491 | + kfree(buffer); | |
492 | +} | |
493 | + | |
494 | +/** | |
495 | + * aa_perm_dentry - check if @profile allows @mask for a file | |
496 | + * @profile: profile to check against | |
497 | + * @dentry: dentry of the file | |
498 | + * @mnt: vfsmount o the file | |
499 | + * @sa: audit context | |
500 | + * @mask: requested profile permissions | |
501 | + * @check: kind of check to perform | |
502 | + * | |
503 | + * Returns 0 upon success, or else an error code. | |
504 | + * | |
505 | + * @check indicates the file type, and whether the file was accessed through | |
506 | + * an open file descriptor (AA_CHECK_FD) or not. | |
507 | + */ | |
508 | +static int aa_perm_dentry(struct aa_profile *profile, struct dentry *dentry, | |
509 | + struct vfsmount *mnt, struct aa_audit *sa, int check) | |
510 | +{ | |
511 | + int error; | |
512 | + char *buffer = NULL; | |
513 | + | |
514 | + sa->name = aa_get_name(dentry, mnt, &buffer, check); | |
515 | + sa->request_mask <<= aa_inode_mode(dentry->d_inode); | |
516 | + if (IS_ERR(sa->name)) { | |
517 | + /* | |
518 | + * deleted files are given a pass on permission checks when | |
519 | + * accessed through a file descriptor. | |
520 | + */ | |
521 | + if (PTR_ERR(sa->name) == -ENOENT && (check & AA_CHECK_FD)) | |
522 | + sa->denied_mask = 0; | |
523 | + else { | |
524 | + sa->denied_mask = sa->request_mask; | |
525 | + sa->error_code = PTR_ERR(sa->name); | |
526 | + if (sa->error_code == -ENOENT) | |
527 | + sa->info = "Failed name resolution - object not a valid entry"; | |
528 | + else if (sa->error_code == -ENAMETOOLONG) | |
529 | + sa->info = "Failed name resolution - name too long"; | |
530 | + else | |
531 | + sa->info = "Failed name resolution"; | |
532 | + } | |
533 | + sa->name = NULL; | |
534 | + } else | |
535 | + sa->denied_mask = aa_file_denied(profile, sa->name, | |
536 | + sa->request_mask, | |
537 | + &sa->audit_mask); | |
538 | + | |
539 | + if (!sa->denied_mask) | |
540 | + sa->error_code = 0; | |
541 | + | |
542 | + error = aa_audit_file(profile, sa); | |
543 | + aa_put_name_buffer(buffer); | |
544 | + | |
545 | + return error; | |
546 | +} | |
547 | + | |
548 | +/** | |
549 | + * aa_attr - check if attribute change is allowed | |
550 | + * @profile: profile to check against | |
551 | + * @dentry: dentry of the file to check | |
552 | + * @mnt: vfsmount of the file to check | |
553 | + * @iattr: attribute changes requested | |
554 | + */ | |
555 | +int aa_attr(struct aa_profile *profile, struct dentry *dentry, | |
556 | + struct vfsmount *mnt, struct iattr *iattr) | |
557 | +{ | |
558 | + struct inode *inode = dentry->d_inode; | |
559 | + int error, check; | |
560 | + struct aa_audit sa; | |
561 | + | |
562 | + memset(&sa, 0, sizeof(sa)); | |
563 | + sa.operation = "setattr"; | |
564 | + sa.gfp_mask = GFP_KERNEL; | |
565 | + sa.iattr = iattr; | |
566 | + sa.request_mask = MAY_WRITE; | |
567 | + sa.error_code = -EACCES; | |
568 | + | |
569 | + check = 0; | |
570 | + if (inode && S_ISDIR(inode->i_mode)) | |
571 | + check |= AA_CHECK_DIR; | |
572 | + if (iattr->ia_valid & ATTR_FILE) | |
573 | + check |= AA_CHECK_FD; | |
574 | + | |
575 | + error = aa_perm_dentry(profile, dentry, mnt, &sa, check); | |
576 | + | |
577 | + return error; | |
578 | +} | |
579 | + | |
580 | +/** | |
581 | + * aa_perm_xattr - check if xattr attribute change is allowed | |
582 | + * @profile: profile to check against | |
583 | + * @dentry: dentry of the file to check | |
584 | + * @mnt: vfsmount of the file to check | |
585 | + * @operation: xattr operation being done | |
586 | + * @mask: access mode requested | |
587 | + * @check: kind of check to perform | |
588 | + */ | |
589 | +int aa_perm_xattr(struct aa_profile *profile, const char *operation, | |
590 | + struct dentry *dentry, struct vfsmount *mnt, int mask, | |
591 | + int check) | |
592 | +{ | |
593 | + struct inode *inode = dentry->d_inode; | |
594 | + int error; | |
595 | + struct aa_audit sa; | |
596 | + | |
597 | + memset(&sa, 0, sizeof(sa)); | |
598 | + sa.operation = operation; | |
599 | + sa.gfp_mask = GFP_KERNEL; | |
600 | + sa.request_mask = mask; | |
601 | + sa.error_code = -EACCES; | |
602 | + | |
603 | + if (inode && S_ISDIR(inode->i_mode)) | |
604 | + check |= AA_CHECK_DIR; | |
605 | + | |
606 | + error = aa_perm_dentry(profile, dentry, mnt, &sa, check); | |
607 | + | |
608 | + return error; | |
609 | +} | |
610 | + | |
611 | +/** | |
612 | + * aa_perm - basic apparmor permissions check | |
613 | + * @profile: profile to check against | |
614 | + * @dentry: dentry of the file to check | |
615 | + * @mnt: vfsmount of the file to check | |
616 | + * @mask: access mode requested | |
617 | + * @check: kind of check to perform | |
618 | + * | |
619 | + * Determine if access @mask for the file is authorized by @profile. | |
620 | + * Returns 0 on success, or else an error code. | |
621 | + */ | |
622 | +int aa_perm(struct aa_profile *profile, const char *operation, | |
623 | + struct dentry *dentry, struct vfsmount *mnt, int mask, int check) | |
624 | +{ | |
625 | + struct aa_audit sa; | |
626 | + int error = 0; | |
627 | + | |
628 | + if (mask == 0) | |
629 | + goto out; | |
630 | + | |
631 | + memset(&sa, 0, sizeof(sa)); | |
632 | + sa.operation = operation; | |
633 | + sa.gfp_mask = GFP_KERNEL; | |
634 | + sa.request_mask = mask; | |
635 | + sa.error_code = -EACCES; | |
636 | + | |
637 | + error = aa_perm_dentry(profile, dentry, mnt, &sa, check); | |
638 | + | |
639 | +out: | |
640 | + return error; | |
641 | +} | |
642 | + | |
643 | +/** | |
644 | + * aa_perm_dir | |
645 | + * @profile: profile to check against | |
646 | + * @dentry: dentry of directory to check | |
647 | + * @mnt: vfsmount of directory to check | |
648 | + * @operation: directory operation being performed | |
649 | + * @mask: access mode requested | |
650 | + * | |
651 | + * Determine if directory operation (make/remove) for dentry is authorized | |
652 | + * by @profile. | |
653 | + * Returns 0 on success, or else an error code. | |
654 | + */ | |
655 | +int aa_perm_dir(struct aa_profile *profile, const char *operation, | |
656 | + struct dentry *dentry, struct vfsmount *mnt, int mask) | |
657 | +{ | |
658 | + struct aa_audit sa; | |
659 | + | |
660 | + memset(&sa, 0, sizeof(sa)); | |
661 | + sa.operation = operation; | |
662 | + sa.gfp_mask = GFP_KERNEL; | |
663 | + sa.request_mask = mask; | |
664 | + sa.error_code = -EACCES; | |
665 | + | |
666 | + return aa_perm_dentry(profile, dentry, mnt, &sa, AA_CHECK_DIR); | |
667 | +} | |
668 | + | |
669 | +int aa_perm_path(struct aa_profile *profile, const char *operation, | |
670 | + const char *name, int mask, uid_t uid) | |
671 | +{ | |
672 | + struct aa_audit sa; | |
673 | + | |
674 | + memset(&sa, 0, sizeof(sa)); | |
675 | + sa.operation = operation; | |
676 | + sa.gfp_mask = GFP_KERNEL; | |
677 | + sa.request_mask = mask; | |
678 | + sa.name = name; | |
679 | + if (current->fsuid == uid) | |
680 | + sa.request_mask = mask << AA_USER_SHIFT; | |
681 | + else | |
682 | + sa.request_mask = mask << AA_OTHER_SHIFT; | |
683 | + | |
684 | + sa.denied_mask = aa_file_denied(profile, name, sa.request_mask, | |
685 | + &sa.audit_mask) ; | |
686 | + sa.error_code = sa.denied_mask ? -EACCES : 0; | |
687 | + | |
688 | + return aa_audit_file(profile, &sa); | |
689 | +} | |
690 | + | |
691 | +/** | |
692 | + * aa_capability - test permission to use capability | |
693 | + * @cxt: aa_task_context with profile to check against | |
694 | + * @cap: capability to be tested | |
695 | + * | |
696 | + * Look up capability in profile capability set. | |
697 | + * Returns 0 on success, or else an error code. | |
698 | + */ | |
699 | +int aa_capability(struct aa_task_context *cxt, int cap) | |
700 | +{ | |
701 | + int error = cap_raised(cxt->profile->capabilities, cap) ? 0 : -EPERM; | |
702 | + struct aa_audit sa; | |
703 | + | |
704 | + /* test if cap has alread been logged */ | |
705 | + if (cap_raised(cxt->caps_logged, cap)) { | |
706 | + if (PROFILE_COMPLAIN(cxt->profile)) | |
707 | + error = 0; | |
708 | + return error; | |
709 | + } else | |
710 | + /* don't worry about rcu replacement of the cxt here. | |
711 | + * caps_logged is a cache to reduce the occurence of | |
712 | + * duplicate messages in the log. The worst that can | |
713 | + * happen is duplicate capability messages shows up in | |
714 | + * the audit log | |
715 | + */ | |
716 | + cap_raise(cxt->caps_logged, cap); | |
717 | + | |
718 | + memset(&sa, 0, sizeof(sa)); | |
719 | + sa.operation = "capable"; | |
720 | + sa.gfp_mask = GFP_ATOMIC; | |
721 | + sa.name = capability_names[cap]; | |
722 | + sa.error_code = error; | |
723 | + | |
724 | + error = aa_audit_caps(cxt->profile, &sa, cap); | |
725 | + | |
726 | + return error; | |
727 | +} | |
728 | + | |
729 | +/* must be used inside rcu_read_lock or task_lock */ | |
730 | +int aa_may_ptrace(struct aa_task_context *cxt, struct aa_profile *tracee) | |
731 | +{ | |
732 | + if (!cxt || cxt->profile == tracee) | |
733 | + return 0; | |
734 | + return aa_capability(cxt, CAP_SYS_PTRACE); | |
735 | +} | |
736 | + | |
737 | +/** | |
738 | + * aa_link - hard link check | |
739 | + * @profile: profile to check against | |
740 | + * @link: dentry of link being created | |
741 | + * @link_mnt: vfsmount of link being created | |
742 | + * @target: dentry of link target | |
743 | + * @target_mnt: vfsmunt of link target | |
744 | + * | |
745 | + * Returns 0 on success, or else an error code. | |
746 | + */ | |
747 | +int aa_link(struct aa_profile *profile, | |
748 | + struct dentry *link, struct vfsmount *link_mnt, | |
749 | + struct dentry *target, struct vfsmount *target_mnt) | |
750 | +{ | |
751 | + int error; | |
752 | + struct aa_audit sa; | |
753 | + char *buffer = NULL, *buffer2 = NULL; | |
754 | + | |
755 | + memset(&sa, 0, sizeof(sa)); | |
756 | + sa.operation = "inode_link"; | |
757 | + sa.gfp_mask = GFP_KERNEL; | |
758 | + sa.name = aa_get_name(link, link_mnt, &buffer, 0); | |
759 | + sa.name2 = aa_get_name(target, target_mnt, &buffer2, 0); | |
760 | + | |
761 | + if (IS_ERR(sa.name)) { | |
762 | + sa.error_code = PTR_ERR(sa.name); | |
763 | + sa.name = NULL; | |
764 | + } | |
765 | + if (IS_ERR(sa.name2)) { | |
766 | + sa.error_code = PTR_ERR(sa.name2); | |
767 | + sa.name2 = NULL; | |
768 | + } | |
769 | + | |
770 | + if (sa.name && sa.name2) { | |
771 | + sa.denied_mask = aa_link_denied(profile, sa.name, sa.name2, | |
772 | + aa_inode_mode(target->d_inode), | |
773 | + &sa.request_mask, | |
774 | + &sa.audit_mask); | |
775 | + sa.error_code = sa.denied_mask ? -EACCES : 0; | |
776 | + } | |
777 | + | |
778 | + error = aa_audit_file(profile, &sa); | |
779 | + | |
780 | + aa_put_name_buffer(buffer); | |
781 | + aa_put_name_buffer(buffer2); | |
782 | + | |
783 | + return error; | |
784 | +} | |
785 | + | |
786 | +/******************************* | |
787 | + * Global task related functions | |
788 | + *******************************/ | |
789 | + | |
790 | +/** | |
791 | + * aa_clone - initialize the task context for a new task | |
792 | + * @child: task that is being created | |
793 | + * | |
794 | + * Returns 0 on success, or else an error code. | |
795 | + */ | |
796 | +int aa_clone(struct task_struct *child) | |
797 | +{ | |
798 | + struct aa_task_context *cxt, *child_cxt; | |
799 | + struct aa_profile *profile; | |
800 | + | |
801 | + if (!aa_task_context(current)) | |
802 | + return 0; | |
803 | + child_cxt = aa_alloc_task_context(GFP_KERNEL); | |
804 | + if (!child_cxt) | |
805 | + return -ENOMEM; | |
806 | + | |
807 | +repeat: | |
808 | + profile = aa_get_profile(current); | |
809 | + if (profile) { | |
810 | + lock_profile(profile); | |
811 | + cxt = aa_task_context(current); | |
812 | + if (unlikely(profile->isstale || !cxt || | |
813 | + cxt->profile != profile)) { | |
814 | + /** | |
815 | + * Race with profile replacement or removal, or with | |
816 | + * task context removal. | |
817 | + */ | |
818 | + unlock_profile(profile); | |
819 | + aa_put_profile(profile); | |
820 | + goto repeat; | |
821 | + } | |
822 | + | |
823 | + /* No need to grab the child's task lock here. */ | |
824 | + aa_change_task_context(child, child_cxt, profile, | |
825 | + cxt->cookie, cxt->previous_profile); | |
826 | + unlock_profile(profile); | |
827 | + | |
828 | + if (APPARMOR_COMPLAIN(child_cxt) && | |
829 | + profile == profile->ns->null_complain_profile) { | |
830 | + struct aa_audit sa; | |
831 | + memset(&sa, 0, sizeof(sa)); | |
832 | + sa.operation = "clone"; | |
833 | + sa.gfp_mask = GFP_KERNEL; | |
834 | + sa.task = child->pid; | |
835 | + aa_audit_hint(profile, &sa); | |
836 | + } | |
837 | + aa_put_profile(profile); | |
838 | + } else | |
839 | + aa_free_task_context(child_cxt); | |
840 | + | |
841 | + return 0; | |
842 | +} | |
843 | + | |
844 | +static struct aa_profile * | |
845 | +aa_register_find(struct aa_profile *profile, const char* ns_name, | |
846 | + const char *name, int mandatory, int complain, | |
847 | + struct aa_audit *sa) | |
848 | +{ | |
849 | + struct aa_namespace *ns; | |
850 | + struct aa_profile *new_profile; | |
851 | + int ns_ref = 0; | |
852 | + | |
853 | + if (profile) | |
854 | + ns = profile->ns; | |
855 | + else | |
856 | + ns = default_namespace; | |
857 | + | |
858 | + if (ns_name) { | |
859 | + /* locate the profile namespace */ | |
860 | + ns = aa_find_namespace(ns_name); | |
861 | + if (!ns) { | |
862 | + if (mandatory) { | |
863 | + sa->info = "profile namespace not found"; | |
864 | + sa->denied_mask = sa->request_mask; | |
865 | + sa->error_code = -ENOENT; | |
866 | + return ERR_PTR(-ENOENT); | |
867 | + } else { | |
868 | + return NULL; | |
869 | + } | |
870 | + } | |
871 | + ns_ref++; | |
872 | + } | |
873 | + | |
874 | + /* Locate new profile */ | |
875 | + new_profile = aa_find_profile(ns, name); | |
876 | + | |
877 | + if (new_profile) { | |
878 | + AA_DEBUG("%s: setting profile %s\n", | |
879 | + __FUNCTION__, new_profile->name); | |
880 | + } else if (mandatory && profile) { | |
881 | + sa->info = "mandatory profile missing"; | |
882 | + sa->denied_mask = sa->request_mask; /* shifted MAY_EXEC */ | |
883 | + if (complain) { | |
884 | + aa_audit_hint(profile, sa); | |
885 | + new_profile = | |
886 | + aa_dup_profile(profile->ns->null_complain_profile); | |
887 | + } else { | |
888 | + sa->error_code = -EACCES; | |
889 | + if (ns_ref) | |
890 | + aa_put_namespace(ns); | |
891 | + return ERR_PTR(-EACCES); | |
892 | + } | |
893 | + } else { | |
894 | + /* Only way we can get into this code is if task | |
895 | + * is unconfined, pix, nix. | |
896 | + */ | |
897 | + AA_DEBUG("%s: No profile found for exec image '%s'\n", | |
898 | + __FUNCTION__, | |
899 | + name); | |
900 | + } | |
901 | + if (ns_ref) | |
902 | + aa_put_namespace(ns); | |
903 | + return new_profile; | |
904 | +} | |
905 | + | |
906 | +static struct aa_profile * | |
907 | +aa_x_to_profile(struct aa_profile *profile, const char *filename, int xmode, | |
908 | + struct aa_audit *sa, char **child) | |
909 | +{ | |
910 | + struct aa_profile *new_profile = NULL; | |
911 | + int ix = xmode & AA_EXEC_INHERIT; | |
912 | + int complain = PROFILE_COMPLAIN(profile); | |
913 | + int index; | |
914 | + | |
915 | + *child = NULL; | |
916 | + switch (xmode & AA_EXEC_MODIFIERS) { | |
917 | + case 0: | |
918 | + /* only valid with ix flag */ | |
919 | + ix = 1; | |
920 | + break; | |
921 | + case AA_EXEC_UNCONFINED: | |
922 | + /* only valid without ix flag */ | |
923 | + ix = 0; | |
924 | + break; | |
925 | + case AA_EXEC_PROFILE: | |
926 | + new_profile = aa_register_find(profile, NULL, filename, !ix, | |
927 | + complain, sa); | |
928 | + break; | |
929 | + case AA_EXEC_CHILD: | |
930 | + *child = new_compound_name(profile->name, filename); | |
931 | + sa->name2 = *child; | |
932 | + if (!*child) { | |
933 | + sa->info = "Failed name resolution - exec failed"; | |
934 | + sa->error_code = -ENOMEM; | |
935 | + new_profile = ERR_PTR(-ENOMEM); | |
936 | + } else { | |
937 | + new_profile = aa_register_find(profile, NULL, *child, | |
938 | + !ix, complain, sa); | |
939 | + } | |
940 | + break; | |
941 | + default: | |
942 | + /* all other indexes are named transitions */ | |
943 | + index = AA_EXEC_INDEX(xmode); | |
944 | + if (index - 4 > profile->exec_table_size) { | |
945 | + sa->info = "invalid named transition - exec failed"; | |
946 | + sa->error_code = -EACCES; | |
947 | + new_profile = ERR_PTR(-EACCES); | |
948 | + } else { | |
949 | + char *ns_name = NULL; | |
950 | + char *name = profile->exec_table[index - 4]; | |
951 | + if (*name == ':') { | |
952 | + ns_name = name + 1; | |
953 | + name = ns_name + strlen(ns_name) + 1; | |
954 | + } | |
955 | + sa->name2 = name; | |
956 | + sa->name3 = ns_name; | |
957 | + new_profile = | |
958 | + aa_register_find(profile, ns_name, name, | |
959 | + !ix, complain, sa); | |
960 | + } | |
961 | + } | |
962 | + if (IS_ERR(new_profile)) | |
963 | + /* all these failures must be audited - no quieting */ | |
964 | + return ERR_PTR(aa_audit_reject(profile, sa)); | |
965 | + return new_profile; | |
966 | +} | |
967 | + | |
968 | +/** | |
969 | + * aa_register - register a new program | |
970 | + * @bprm: binprm of program being registered | |
971 | + * | |
972 | + * Try to register a new program during execve(). This should give the | |
973 | + * new program a valid aa_task_context if confined. | |
974 | + */ | |
975 | +int aa_register(struct linux_binprm *bprm) | |
976 | +{ | |
977 | + const char *filename; | |
978 | + char *buffer = NULL, *child = NULL; | |
979 | + struct file *filp = bprm->file; | |
980 | + struct aa_profile *profile, *old_profile, *new_profile = NULL; | |
981 | + int exec_mode, complain = 0, shift; | |
982 | + struct aa_audit sa; | |
983 | + | |
984 | + AA_DEBUG("%s\n", __FUNCTION__); | |
985 | + | |
986 | + profile = aa_get_profile(current); | |
987 | + | |
988 | + shift = aa_inode_mode(filp->f_dentry->d_inode); | |
989 | + memset(&sa, 0, sizeof(sa)); | |
990 | + sa.operation = "exec"; | |
991 | + sa.gfp_mask = GFP_KERNEL; | |
992 | + sa.request_mask = MAY_EXEC << shift; | |
993 | + | |
994 | + filename = aa_get_name(filp->f_dentry, filp->f_vfsmnt, &buffer, 0); | |
995 | + if (IS_ERR(filename)) { | |
996 | + if (profile) { | |
997 | + sa.info = "Failed name resolution - exec failed"; | |
998 | + sa.error_code = PTR_ERR(filename); | |
999 | + aa_audit_file(profile, &sa); | |
1000 | + return sa.error_code; | |
1001 | + } else | |
1002 | + return 0; | |
1003 | + } | |
1004 | + sa.name = filename; | |
1005 | + | |
1006 | + exec_mode = AA_EXEC_UNSAFE << shift; | |
1007 | + | |
1008 | +repeat: | |
1009 | + if (profile) { | |
1010 | + complain = PROFILE_COMPLAIN(profile); | |
1011 | + | |
1012 | + /* Confined task, determine what mode inherit, unconfined or | |
1013 | + * mandatory to load new profile | |
1014 | + */ | |
1015 | + exec_mode = aa_match(profile->file_rules, filename, | |
1016 | + &sa.audit_mask); | |
1017 | + | |
1018 | + | |
1019 | + if (exec_mode & sa.request_mask) { | |
1020 | + int xm = exec_mode >> shift; | |
1021 | + new_profile = aa_x_to_profile(profile, filename, | |
1022 | + xm, &sa, &child); | |
1023 | + | |
1024 | + if (!new_profile && (xm & AA_EXEC_INHERIT)) | |
1025 | + /* (p|c|n|)ix - don't change profile */ | |
1026 | + goto cleanup; | |
1027 | + /* error case caught below */ | |
1028 | + | |
1029 | + } else if (sa.request_mask & AUDIT_QUIET_MASK(sa.audit_mask)) { | |
1030 | + /* quiet failed exit */ | |
1031 | + new_profile = ERR_PTR(-EACCES); | |
1032 | + } else if (complain) { | |
1033 | + /* There was no entry in calling profile | |
1034 | + * describing mode to execute image in. | |
1035 | + * Drop into null-profile (disabling secure exec). | |
1036 | + */ | |
1037 | + new_profile = | |
1038 | + aa_dup_profile(profile->ns->null_complain_profile); | |
1039 | + exec_mode |= AA_EXEC_UNSAFE << shift; | |
1040 | + } else { | |
1041 | + sa.denied_mask = sa.request_mask; | |
1042 | + sa.error_code = -EACCES; | |
1043 | + new_profile = ERR_PTR(aa_audit_file(profile, &sa)); | |
1044 | + } | |
1045 | + } else { | |
1046 | + /* Unconfined task, load profile if it exists */ | |
1047 | + new_profile = aa_register_find(NULL, NULL, filename, 0, 0, &sa); | |
1048 | + if (new_profile == NULL) | |
1049 | + goto cleanup; | |
1050 | + } | |
1051 | + | |
1052 | + if (IS_ERR(new_profile)) | |
1053 | + goto cleanup; | |
1054 | + | |
1055 | + old_profile = __aa_replace_profile(current, new_profile); | |
1056 | + if (IS_ERR(old_profile)) { | |
1057 | + aa_put_profile(new_profile); | |
1058 | + aa_put_profile(profile); | |
1059 | + if (PTR_ERR(old_profile) == -ESTALE) { | |
1060 | + profile = aa_get_profile(current); | |
1061 | + goto repeat; | |
1062 | + } | |
1063 | + if (PTR_ERR(old_profile) == -EPERM) { | |
1064 | + sa.denied_mask = sa.request_mask; | |
1065 | + sa.info = "unable to set profile due to ptrace"; | |
1066 | + sa.task = current->parent->pid; | |
1067 | + aa_audit_reject(profile, &sa); | |
1068 | + } | |
1069 | + new_profile = old_profile; | |
1070 | + goto cleanup; | |
1071 | + } | |
1072 | + aa_put_profile(old_profile); | |
1073 | + aa_put_profile(profile); | |
1074 | + | |
1075 | + /* Handle confined exec. | |
1076 | + * Can be at this point for the following reasons: | |
1077 | + * 1. unconfined switching to confined | |
1078 | + * 2. confined switching to different confinement | |
1079 | + * 3. confined switching to unconfined | |
1080 | + * | |
1081 | + * Cases 2 and 3 are marked as requiring secure exec | |
1082 | + * (unless policy specified "unsafe exec") | |
1083 | + */ | |
1084 | + if (!(exec_mode & (AA_EXEC_UNSAFE << shift))) { | |
1085 | + unsigned long bprm_flags; | |
1086 | + | |
1087 | + bprm_flags = AA_SECURE_EXEC_NEEDED; | |
1088 | + bprm->security = (void*) | |
1089 | + ((unsigned long)bprm->security | bprm_flags); | |
1090 | + } | |
1091 | + | |
1092 | + if (complain && new_profile && | |
1093 | + new_profile == new_profile->ns->null_complain_profile) { | |
1094 | + sa.request_mask = 0; | |
1095 | + sa.name = NULL; | |
1096 | + sa.info = "set profile"; | |
1097 | + aa_audit_hint(new_profile, &sa); | |
1098 | + } | |
1099 | + | |
1100 | +cleanup: | |
1101 | + aa_put_name_buffer(child); | |
1102 | + aa_put_name_buffer(buffer); | |
1103 | + if (IS_ERR(new_profile)) | |
1104 | + return PTR_ERR(new_profile); | |
1105 | + aa_put_profile(new_profile); | |
1106 | + return 0; | |
1107 | +} | |
1108 | + | |
1109 | +/** | |
1110 | + * aa_release - release a task context | |
1111 | + * @task: task being released | |
1112 | + * | |
1113 | + * This is called after a task has exited and the parent has reaped it. | |
1114 | + */ | |
1115 | +void aa_release(struct task_struct *task) | |
1116 | +{ | |
1117 | + struct aa_task_context *cxt; | |
1118 | + struct aa_profile *profile; | |
1119 | + /* | |
1120 | + * While the task context is still on a profile's task context | |
1121 | + * list, another process could replace the profile under us, | |
1122 | + * leaving us with a locked profile that is no longer attached | |
1123 | + * to this task. So after locking the profile, we check that | |
1124 | + * the profile is still attached. The profile lock is | |
1125 | + * sufficient to prevent the replacement race so we do not lock | |
1126 | + * the task. | |
1127 | + * | |
1128 | + * Use lock subtyping to avoid lockdep reporting a false irq | |
1129 | + * possible inversion between the task_lock and profile_lock | |
1130 | + * | |
1131 | + * We also avoid taking the task_lock here because lock_dep | |
1132 | + * would report another false {softirq-on-W} potential irq_lock | |
1133 | + * inversion. | |
1134 | + * | |
1135 | + * If the task does not have a profile attached we are safe; | |
1136 | + * nothing can race with us at this point. | |
1137 | + */ | |
1138 | + | |
1139 | +repeat: | |
1140 | + profile = aa_get_profile(task); | |
1141 | + if (profile) { | |
1142 | + lock_profile_nested(profile, aa_lock_task_release); | |
1143 | + cxt = aa_task_context(task); | |
1144 | + if (unlikely(!cxt || cxt->profile != profile)) { | |
1145 | + unlock_profile(profile); | |
1146 | + aa_put_profile(profile); | |
1147 | + goto repeat; | |
1148 | + } | |
1149 | + aa_change_task_context(task, NULL, NULL, 0, NULL); | |
1150 | + unlock_profile(profile); | |
1151 | + aa_put_profile(profile); | |
1152 | + } | |
1153 | +} | |
1154 | + | |
1155 | +static int do_change_profile(struct aa_profile *expected, | |
1156 | + struct aa_namespace *ns, const char *name, | |
1157 | + u64 cookie, int restore, int hat, | |
1158 | + struct aa_audit *sa) | |
1159 | +{ | |
1160 | + struct aa_profile *new_profile = NULL, *old_profile = NULL, | |
1161 | + *previous_profile = NULL; | |
1162 | + struct aa_task_context *new_cxt, *cxt; | |
1163 | + int error = 0; | |
1164 | + | |
1165 | + sa->name = name; | |
1166 | + | |
1167 | + new_cxt = aa_alloc_task_context(GFP_KERNEL); | |
1168 | + if (!new_cxt) | |
1169 | + return -ENOMEM; | |
1170 | + | |
1171 | + new_profile = aa_find_profile(ns, name); | |
1172 | + if (!new_profile && !restore) { | |
1173 | + if (!PROFILE_COMPLAIN(expected)) { | |
1174 | + aa_free_task_context(new_cxt); | |
1175 | + return -ENOENT; | |
1176 | + } | |
1177 | + new_profile = aa_dup_profile(ns->null_complain_profile); | |
1178 | + } else if (new_profile && hat && !PROFILE_IS_HAT(new_profile)) { | |
1179 | + aa_free_task_context(new_cxt); | |
1180 | + aa_put_profile(new_profile); | |
1181 | + return error; | |
1182 | + } | |
1183 | + | |
1184 | + cxt = lock_task_and_profiles(current, new_profile); | |
1185 | + if (!cxt) { | |
1186 | + error = -EPERM; | |
1187 | + goto out; | |
1188 | + } | |
1189 | + old_profile = cxt->profile; | |
1190 | + | |
1191 | + if (cxt->profile != expected || (new_profile && new_profile->isstale)) { | |
1192 | + error = -ESTALE; | |
1193 | + goto out; | |
1194 | + } | |
1195 | + | |
1196 | + if (cxt->previous_profile) { | |
1197 | + if (cxt->cookie != cookie) { | |
1198 | + error = -EACCES; | |
1199 | + sa->info = "killing process"; | |
1200 | + aa_audit_reject(cxt->profile, sa); | |
1201 | + /* terminate process */ | |
1202 | + (void)send_sig_info(SIGKILL, NULL, current); | |
1203 | + goto out; | |
1204 | + } | |
1205 | + | |
1206 | + if (!restore) | |
1207 | + previous_profile = cxt->previous_profile; | |
1208 | + } else | |
1209 | + previous_profile = cxt->profile; | |
1210 | + | |
1211 | + if ((current->ptrace & PT_PTRACED) && aa_may_ptrace(cxt, new_profile)) { | |
1212 | + error = -EACCES; | |
1213 | + goto out; | |
1214 | + } | |
1215 | + | |
1216 | + if (new_profile == ns->null_complain_profile) | |
1217 | + aa_audit_hint(cxt->profile, sa); | |
1218 | + | |
1219 | + if (APPARMOR_AUDIT(cxt)) | |
1220 | + aa_audit_message(cxt->profile, sa, AUDIT_APPARMOR_AUDIT); | |
1221 | + | |
1222 | + if (!restore && cookie) | |
1223 | + aa_change_task_context(current, new_cxt, new_profile, cookie, | |
1224 | + previous_profile); | |
1225 | + else | |
1226 | + /* either return to previous_profile, or a permanent change */ | |
1227 | + aa_change_task_context(current, new_cxt, new_profile, 0, NULL); | |
1228 | + | |
1229 | +out: | |
1230 | + if (aa_task_context(current) != new_cxt) | |
1231 | + aa_free_task_context(new_cxt); | |
1232 | + task_unlock(current); | |
1233 | + unlock_both_profiles(old_profile, new_profile); | |
1234 | + aa_put_profile(new_profile); | |
1235 | + return error; | |
1236 | +} | |
1237 | + | |
1238 | +/** | |
1239 | + * aa_change_profile - perform a one-way profile transition | |
1240 | + * @ns_name: name of the profile namespace to change to | |
1241 | + * @name: name of profile to change to | |
1242 | + * Change to new profile @name. Unlike with hats, there is no way | |
1243 | + * to change back. | |
1244 | + * | |
1245 | + * Returns %0 on success, error otherwise. | |
1246 | + */ | |
1247 | +int aa_change_profile(const char *ns_name, const char *name) | |
1248 | +{ | |
1249 | + struct aa_task_context *cxt; | |
1250 | + struct aa_profile *profile = NULL; | |
1251 | + struct aa_namespace *ns = NULL; | |
1252 | + struct aa_audit sa; | |
1253 | + unsigned int state; | |
1254 | + int error = -EINVAL; | |
1255 | + | |
1256 | + if (!name) | |
1257 | + return -EINVAL; | |
1258 | + | |
1259 | + memset(&sa, 0, sizeof(sa)); | |
1260 | + sa.gfp_mask = GFP_ATOMIC; | |
1261 | + sa.operation = "change_profile"; | |
1262 | + | |
1263 | +repeat: | |
1264 | + task_lock(current); | |
1265 | + cxt = aa_task_context(current); | |
1266 | + if (cxt) | |
1267 | + profile = aa_dup_profile(cxt->profile); | |
1268 | + task_unlock(current); | |
1269 | + | |
1270 | + if (ns_name) | |
1271 | + ns = aa_find_namespace(ns_name); | |
1272 | + else if (profile) | |
1273 | + ns = aa_get_namespace(profile->ns); | |
1274 | + else | |
1275 | + ns = aa_get_namespace(default_namespace); | |
1276 | + | |
1277 | + if (!ns) { | |
1278 | + aa_put_profile(profile); | |
1279 | + return -ENOENT; | |
1280 | + } | |
1281 | + | |
1282 | + if (!profile || PROFILE_COMPLAIN(profile) || | |
1283 | + (ns == profile->ns && | |
1284 | + (aa_match(profile->file_rules, name, NULL) & AA_CHANGE_PROFILE))) | |
1285 | + error = do_change_profile(profile, ns, name, 0, 0, 0, &sa); | |
1286 | + else { | |
1287 | + /* check for a rule with a namespace prepended */ | |
1288 | + aa_match_state(profile->file_rules, DFA_START, ns->name, | |
1289 | + &state); | |
1290 | + state = aa_dfa_null_transition(profile->file_rules, state); | |
1291 | + if ((aa_match_state(profile->file_rules, state, name, NULL) & | |
1292 | + AA_CHANGE_PROFILE)) | |
1293 | + error = do_change_profile(profile, ns, name, 0, 0, 0, | |
1294 | + &sa); | |
1295 | + else | |
1296 | + /* no permission to transition to profile @name */ | |
1297 | + error = -EACCES; | |
1298 | + } | |
1299 | + | |
1300 | + aa_put_namespace(ns); | |
1301 | + aa_put_profile(profile); | |
1302 | + if (error == -ESTALE) | |
1303 | + goto repeat; | |
1304 | + | |
1305 | + return error; | |
1306 | +} | |
1307 | + | |
1308 | +/** | |
1309 | + * aa_change_hat - change hat to/from subprofile | |
1310 | + * @hat_name: hat to change to | |
1311 | + * @cookie: magic value to validate the hat change | |
1312 | + * | |
1313 | + * Change to new @hat_name, and store the @hat_magic in the current task | |
1314 | + * context. If the new @hat_name is %NULL and the @cookie matches that | |
1315 | + * stored in the current task context and is not 0, return to the top level | |
1316 | + * profile. | |
1317 | + * Returns %0 on success, error otherwise. | |
1318 | + */ | |
1319 | +int aa_change_hat(const char *hat_name, u64 cookie) | |
1320 | +{ | |
1321 | + struct aa_task_context *cxt; | |
1322 | + struct aa_profile *profile, *previous_profile; | |
1323 | + struct aa_audit sa; | |
1324 | + int error = 0; | |
1325 | + | |
1326 | + memset(&sa, 0, sizeof(sa)); | |
1327 | + sa.gfp_mask = GFP_ATOMIC; | |
1328 | + sa.operation = "change_hat"; | |
1329 | + | |
1330 | +repeat: | |
1331 | + task_lock(current); | |
1332 | + cxt = aa_task_context(current); | |
1333 | + if (!cxt) { | |
1334 | + task_unlock(current); | |
1335 | + return -EPERM; | |
1336 | + } | |
1337 | + profile = aa_dup_profile(cxt->profile); | |
1338 | + previous_profile = aa_dup_profile(cxt->previous_profile); | |
1339 | + task_unlock(current); | |
1340 | + | |
1341 | + if (hat_name) { | |
1342 | + char *name, *profile_name; | |
1343 | + | |
1344 | + if (previous_profile) | |
1345 | + profile_name = previous_profile->name; | |
1346 | + else | |
1347 | + profile_name = profile->name; | |
1348 | + | |
1349 | + name = new_compound_name(profile_name, hat_name); | |
1350 | + if (!name) { | |
1351 | + error = -ENOMEM; | |
1352 | + goto out; | |
1353 | + } | |
1354 | + error = do_change_profile(profile, profile->ns, name, cookie, | |
1355 | + 0, 1, &sa); | |
1356 | + aa_put_name_buffer(name); | |
1357 | + } else if (previous_profile) | |
1358 | + error = do_change_profile(profile, profile->ns, | |
1359 | + previous_profile->name, cookie, 1, 0, | |
1360 | + &sa); | |
1361 | + /* else ignore restores when there is no saved profile */ | |
1362 | + | |
1363 | +out: | |
1364 | + aa_put_profile(previous_profile); | |
1365 | + aa_put_profile(profile); | |
1366 | + if (error == -ESTALE) | |
1367 | + goto repeat; | |
1368 | + | |
1369 | + return error; | |
1370 | +} | |
1371 | + | |
1372 | +/** | |
1373 | + * __aa_replace_profile - replace a task's profile | |
1374 | + * @task: task to switch the profile of | |
1375 | + * @profile: profile to switch to | |
1376 | + * | |
1377 | + * Returns a handle to the previous profile upon success, or else an | |
1378 | + * error code. | |
1379 | + */ | |
1380 | +struct aa_profile *__aa_replace_profile(struct task_struct *task, | |
1381 | + struct aa_profile *profile) | |
1382 | +{ | |
1383 | + struct aa_task_context *cxt, *new_cxt = NULL; | |
1384 | + struct aa_profile *old_profile = NULL; | |
1385 | + | |
1386 | + if (profile) { | |
1387 | + new_cxt = aa_alloc_task_context(GFP_KERNEL); | |
1388 | + if (!new_cxt) | |
1389 | + return ERR_PTR(-ENOMEM); | |
1390 | + } | |
1391 | + | |
1392 | + cxt = lock_task_and_profiles(task, profile); | |
1393 | + if (unlikely(profile && profile->isstale)) { | |
1394 | + task_unlock(task); | |
1395 | + unlock_both_profiles(profile, cxt ? cxt->profile : NULL); | |
1396 | + aa_free_task_context(new_cxt); | |
1397 | + return ERR_PTR(-ESTALE); | |
1398 | + } | |
1399 | + | |
1400 | + if ((current->ptrace & PT_PTRACED) && aa_may_ptrace(cxt, profile)) { | |
1401 | + task_unlock(task); | |
1402 | + unlock_both_profiles(profile, cxt ? cxt->profile : NULL); | |
1403 | + aa_free_task_context(new_cxt); | |
1404 | + return ERR_PTR(-EPERM); | |
1405 | + } | |
1406 | + | |
1407 | + if (cxt) | |
1408 | + old_profile = aa_dup_profile(cxt->profile); | |
1409 | + aa_change_task_context(task, new_cxt, profile, 0, NULL); | |
1410 | + | |
1411 | + task_unlock(task); | |
1412 | + unlock_both_profiles(profile, old_profile); | |
1413 | + return old_profile; | |
1414 | +} | |
1415 | + | |
1416 | +/** | |
1417 | + * lock_task_and_profiles - lock the task and confining profiles and @profile | |
1418 | + * @task: task to lock | |
1419 | + * @profile: extra profile to lock in addition to the current profile | |
1420 | + * | |
1421 | + * Handle the spinning on locking to make sure the task context and | |
1422 | + * profile are consistent once all locks are aquired. | |
1423 | + * | |
1424 | + * return the aa_task_context currently confining the task. The task lock | |
1425 | + * will be held whether or not the task is confined. | |
1426 | + */ | |
1427 | +struct aa_task_context * | |
1428 | +lock_task_and_profiles(struct task_struct *task, struct aa_profile *profile) | |
1429 | +{ | |
1430 | + struct aa_task_context *cxt; | |
1431 | + struct aa_profile *old_profile = NULL; | |
1432 | + | |
1433 | + rcu_read_lock(); | |
1434 | +repeat: | |
1435 | + cxt = aa_task_context(task); | |
1436 | + if (cxt) | |
1437 | + old_profile = cxt->profile; | |
1438 | + | |
1439 | + lock_both_profiles(profile, old_profile); | |
1440 | + task_lock(task); | |
1441 | + | |
1442 | + /* check for race with profile transition, replacement or removal */ | |
1443 | + if (unlikely(cxt != aa_task_context(task))) { | |
1444 | + task_unlock(task); | |
1445 | + unlock_both_profiles(profile, old_profile); | |
1446 | + old_profile = NULL; | |
1447 | + goto repeat; | |
1448 | + } | |
1449 | + rcu_read_unlock(); | |
1450 | + return cxt; | |
1451 | +} | |
1452 | + | |
1453 | +static void free_aa_task_context_rcu_callback(struct rcu_head *head) | |
1454 | +{ | |
1455 | + struct aa_task_context *cxt; | |
1456 | + | |
1457 | + cxt = container_of(head, struct aa_task_context, rcu); | |
1458 | + aa_free_task_context(cxt); | |
1459 | +} | |
1460 | + | |
1461 | +/** | |
1462 | + * aa_change_task_context - switch a task to use a new context and profile | |
1463 | + * @task: task that is having its task context changed | |
1464 | + * @new_cxt: new task context to use after the switch | |
1465 | + * @profile: new profile to use after the switch | |
1466 | + * @cookie: magic value to switch to | |
1467 | + * @previous_profile: profile the task can return to | |
1468 | + */ | |
1469 | +void aa_change_task_context(struct task_struct *task, | |
1470 | + struct aa_task_context *new_cxt, | |
1471 | + struct aa_profile *profile, u64 cookie, | |
1472 | + struct aa_profile *previous_profile) | |
1473 | +{ | |
1474 | + struct aa_task_context *old_cxt = aa_task_context(task); | |
1475 | + | |
1476 | + if (old_cxt) { | |
1477 | + list_del_init(&old_cxt->list); | |
1478 | + call_rcu(&old_cxt->rcu, free_aa_task_context_rcu_callback); | |
1479 | + } | |
1480 | + if (new_cxt) { | |
1481 | + /* set the caps_logged cache to the quiet_caps mask | |
1482 | + * this has the effect of quieting caps that are not | |
1483 | + * supposed to be logged | |
1484 | + */ | |
1485 | + new_cxt->caps_logged = profile->quiet_caps; | |
1486 | + new_cxt->cookie = cookie; | |
1487 | + new_cxt->task = task; | |
1488 | + new_cxt->profile = aa_dup_profile(profile); | |
1489 | + new_cxt->previous_profile = aa_dup_profile(previous_profile); | |
1490 | + list_move(&new_cxt->list, &profile->task_contexts); | |
1491 | + } | |
1492 | + rcu_assign_pointer(task->security, new_cxt); | |
1493 | +} |