1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * AppArmor security module |
4 | * |
5 | * This file contains AppArmor policy attachment and domain transitions |
6 | * |
7 | * Copyright (C) 2002-2008 Novell/SUSE |
8 | * Copyright 2009-2010 Canonical Ltd. |
9 | */ |
10 | |
11 | #include <linux/errno.h> |
12 | #include <linux/fdtable.h> |
13 | #include <linux/fs.h> |
14 | #include <linux/file.h> |
15 | #include <linux/mount.h> |
16 | #include <linux/syscalls.h> |
17 | #include <linux/personality.h> |
18 | #include <linux/xattr.h> |
19 | #include <linux/user_namespace.h> |
20 | |
21 | #include "include/audit.h" |
22 | #include "include/apparmorfs.h" |
23 | #include "include/cred.h" |
24 | #include "include/domain.h" |
25 | #include "include/file.h" |
26 | #include "include/ipc.h" |
27 | #include "include/match.h" |
28 | #include "include/path.h" |
29 | #include "include/policy.h" |
30 | #include "include/policy_ns.h" |
31 | |
32 | /** |
33 | * may_change_ptraced_domain - check if can change profile on ptraced task |
34 | * @to_cred: cred of task changing domain |
35 | * @to_label: profile to change to (NOT NULL) |
36 | * @info: message if there is an error |
37 | * |
38 | * Check if current is ptraced and if so if the tracing task is allowed |
39 | * to trace the new domain |
40 | * |
41 | * Returns: %0 or error if change not allowed |
42 | */ |
43 | static int may_change_ptraced_domain(const struct cred *to_cred, |
44 | struct aa_label *to_label, |
45 | const char **info) |
46 | { |
47 | struct task_struct *tracer; |
48 | struct aa_label *tracerl = NULL; |
49 | const struct cred *tracer_cred = NULL; |
50 | |
51 | int error = 0; |
52 | |
53 | rcu_read_lock(); |
54 | tracer = ptrace_parent(current); |
55 | if (tracer) { |
56 | /* released below */ |
57 | tracerl = aa_get_task_label(task: tracer); |
58 | tracer_cred = get_task_cred(tracer); |
59 | } |
60 | /* not ptraced */ |
61 | if (!tracer || unconfined(tracerl)) |
62 | goto out; |
63 | |
64 | error = aa_may_ptrace(tracer_cred, tracer: tracerl, tracee_cred: to_cred, tracee: to_label, |
65 | PTRACE_MODE_ATTACH); |
66 | |
67 | out: |
68 | rcu_read_unlock(); |
69 | aa_put_label(l: tracerl); |
70 | put_cred(cred: tracer_cred); |
71 | |
72 | if (error) |
73 | *info = "ptrace prevents transition" ; |
74 | return error; |
75 | } |
76 | |
77 | /**** TODO: dedup to aa_label_match - needs perm and dfa, merging |
78 | * specifically this is an exact copy of aa_label_match except |
79 | * aa_compute_perms is replaced with aa_compute_fperms |
80 | * and policy->dfa with file->dfa |
81 | ****/ |
82 | /* match a profile and its associated ns component if needed |
83 | * Assumes visibility test has already been done. |
84 | * If a subns profile is not to be matched should be prescreened with |
85 | * visibility test. |
86 | */ |
87 | static inline aa_state_t match_component(struct aa_profile *profile, |
88 | struct aa_profile *tp, |
89 | bool stack, aa_state_t state) |
90 | { |
91 | struct aa_ruleset *rules = list_first_entry(&profile->rules, |
92 | typeof(*rules), list); |
93 | const char *ns_name; |
94 | |
95 | if (stack) |
96 | state = aa_dfa_match(dfa: rules->file->dfa, start: state, str: "&" ); |
97 | if (profile->ns == tp->ns) |
98 | return aa_dfa_match(dfa: rules->file->dfa, start: state, str: tp->base.hname); |
99 | |
100 | /* try matching with namespace name and then profile */ |
101 | ns_name = aa_ns_name(parent: profile->ns, child: tp->ns, subns: true); |
102 | state = aa_dfa_match_len(dfa: rules->file->dfa, start: state, str: ":" , len: 1); |
103 | state = aa_dfa_match(dfa: rules->file->dfa, start: state, str: ns_name); |
104 | state = aa_dfa_match_len(dfa: rules->file->dfa, start: state, str: ":" , len: 1); |
105 | return aa_dfa_match(dfa: rules->file->dfa, start: state, str: tp->base.hname); |
106 | } |
107 | |
108 | /** |
109 | * label_compound_match - find perms for full compound label |
110 | * @profile: profile to find perms for |
111 | * @label: label to check access permissions for |
112 | * @stack: whether this is a stacking request |
113 | * @state: state to start match in |
114 | * @subns: whether to do permission checks on components in a subns |
115 | * @request: permissions to request |
116 | * @perms: perms struct to set |
117 | * |
118 | * Returns: 0 on success else ERROR |
119 | * |
120 | * For the label A//&B//&C this does the perm match for A//&B//&C |
121 | * @perms should be preinitialized with allperms OR a previous permission |
122 | * check to be stacked. |
123 | */ |
124 | static int label_compound_match(struct aa_profile *profile, |
125 | struct aa_label *label, bool stack, |
126 | aa_state_t state, bool subns, u32 request, |
127 | struct aa_perms *perms) |
128 | { |
129 | struct aa_ruleset *rules = list_first_entry(&profile->rules, |
130 | typeof(*rules), list); |
131 | struct aa_profile *tp; |
132 | struct label_it i; |
133 | struct path_cond cond = { }; |
134 | |
135 | /* find first subcomponent that is visible */ |
136 | label_for_each(i, label, tp) { |
137 | if (!aa_ns_visible(curr: profile->ns, view: tp->ns, subns)) |
138 | continue; |
139 | state = match_component(profile, tp, stack, state); |
140 | if (!state) |
141 | goto fail; |
142 | goto next; |
143 | } |
144 | |
145 | /* no component visible */ |
146 | *perms = allperms; |
147 | return 0; |
148 | |
149 | next: |
150 | label_for_each_cont(i, label, tp) { |
151 | if (!aa_ns_visible(curr: profile->ns, view: tp->ns, subns)) |
152 | continue; |
153 | state = aa_dfa_match(dfa: rules->file->dfa, start: state, str: "//&" ); |
154 | state = match_component(profile, tp, stack: false, state); |
155 | if (!state) |
156 | goto fail; |
157 | } |
158 | *perms = *(aa_lookup_fperms(file_rules: rules->file, state, cond: &cond)); |
159 | aa_apply_modes_to_perms(profile, perms); |
160 | if ((perms->allow & request) != request) |
161 | return -EACCES; |
162 | |
163 | return 0; |
164 | |
165 | fail: |
166 | *perms = nullperms; |
167 | return -EACCES; |
168 | } |
169 | |
170 | /** |
171 | * label_components_match - find perms for all subcomponents of a label |
172 | * @profile: profile to find perms for |
173 | * @label: label to check access permissions for |
174 | * @stack: whether this is a stacking request |
175 | * @start: state to start match in |
176 | * @subns: whether to do permission checks on components in a subns |
177 | * @request: permissions to request |
178 | * @perms: an initialized perms struct to add accumulation to |
179 | * |
180 | * Returns: 0 on success else ERROR |
181 | * |
182 | * For the label A//&B//&C this does the perm match for each of A and B and C |
183 | * @perms should be preinitialized with allperms OR a previous permission |
184 | * check to be stacked. |
185 | */ |
186 | static int label_components_match(struct aa_profile *profile, |
187 | struct aa_label *label, bool stack, |
188 | aa_state_t start, bool subns, u32 request, |
189 | struct aa_perms *perms) |
190 | { |
191 | struct aa_ruleset *rules = list_first_entry(&profile->rules, |
192 | typeof(*rules), list); |
193 | struct aa_profile *tp; |
194 | struct label_it i; |
195 | struct aa_perms tmp; |
196 | struct path_cond cond = { }; |
197 | aa_state_t state = 0; |
198 | |
199 | /* find first subcomponent to test */ |
200 | label_for_each(i, label, tp) { |
201 | if (!aa_ns_visible(curr: profile->ns, view: tp->ns, subns)) |
202 | continue; |
203 | state = match_component(profile, tp, stack, state: start); |
204 | if (!state) |
205 | goto fail; |
206 | goto next; |
207 | } |
208 | |
209 | /* no subcomponents visible - no change in perms */ |
210 | return 0; |
211 | |
212 | next: |
213 | tmp = *(aa_lookup_fperms(file_rules: rules->file, state, cond: &cond)); |
214 | aa_apply_modes_to_perms(profile, perms: &tmp); |
215 | aa_perms_accum(accum: perms, addend: &tmp); |
216 | label_for_each_cont(i, label, tp) { |
217 | if (!aa_ns_visible(curr: profile->ns, view: tp->ns, subns)) |
218 | continue; |
219 | state = match_component(profile, tp, stack, state: start); |
220 | if (!state) |
221 | goto fail; |
222 | tmp = *(aa_lookup_fperms(file_rules: rules->file, state, cond: &cond)); |
223 | aa_apply_modes_to_perms(profile, perms: &tmp); |
224 | aa_perms_accum(accum: perms, addend: &tmp); |
225 | } |
226 | |
227 | if ((perms->allow & request) != request) |
228 | return -EACCES; |
229 | |
230 | return 0; |
231 | |
232 | fail: |
233 | *perms = nullperms; |
234 | return -EACCES; |
235 | } |
236 | |
237 | /** |
238 | * label_match - do a multi-component label match |
239 | * @profile: profile to match against (NOT NULL) |
240 | * @label: label to match (NOT NULL) |
241 | * @stack: whether this is a stacking request |
242 | * @state: state to start in |
243 | * @subns: whether to match subns components |
244 | * @request: permission request |
245 | * @perms: Returns computed perms (NOT NULL) |
246 | * |
247 | * Returns: the state the match finished in, may be the none matching state |
248 | */ |
249 | static int label_match(struct aa_profile *profile, struct aa_label *label, |
250 | bool stack, aa_state_t state, bool subns, u32 request, |
251 | struct aa_perms *perms) |
252 | { |
253 | int error; |
254 | |
255 | *perms = nullperms; |
256 | error = label_compound_match(profile, label, stack, state, subns, |
257 | request, perms); |
258 | if (!error) |
259 | return error; |
260 | |
261 | *perms = allperms; |
262 | return label_components_match(profile, label, stack, start: state, subns, |
263 | request, perms); |
264 | } |
265 | |
266 | /******* end TODO: dedup *****/ |
267 | |
268 | /** |
269 | * change_profile_perms - find permissions for change_profile |
270 | * @profile: the current profile (NOT NULL) |
271 | * @target: label to transition to (NOT NULL) |
272 | * @stack: whether this is a stacking request |
273 | * @request: requested perms |
274 | * @start: state to start matching in |
275 | * @perms: Returns computed perms (NOT NULL) |
276 | * |
277 | * |
278 | * Returns: permission set |
279 | * |
280 | * currently only matches full label A//&B//&C or individual components A, B, C |
281 | * not arbitrary combinations. Eg. A//&B, C |
282 | */ |
283 | static int change_profile_perms(struct aa_profile *profile, |
284 | struct aa_label *target, bool stack, |
285 | u32 request, aa_state_t start, |
286 | struct aa_perms *perms) |
287 | { |
288 | if (profile_unconfined(profile)) { |
289 | perms->allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC; |
290 | perms->audit = perms->quiet = perms->kill = 0; |
291 | return 0; |
292 | } |
293 | |
294 | /* TODO: add profile in ns screening */ |
295 | return label_match(profile, label: target, stack, state: start, subns: true, request, perms); |
296 | } |
297 | |
298 | /** |
299 | * aa_xattrs_match - check whether a file matches the xattrs defined in profile |
300 | * @bprm: binprm struct for the process to validate |
301 | * @profile: profile to match against (NOT NULL) |
302 | * @state: state to start match in |
303 | * |
304 | * Returns: number of extended attributes that matched, or < 0 on error |
305 | */ |
306 | static int aa_xattrs_match(const struct linux_binprm *bprm, |
307 | struct aa_profile *profile, aa_state_t state) |
308 | { |
309 | int i; |
310 | struct dentry *d; |
311 | char *value = NULL; |
312 | struct aa_attachment *attach = &profile->attach; |
313 | int size, value_size = 0, ret = attach->xattr_count; |
314 | |
315 | if (!bprm || !attach->xattr_count) |
316 | return 0; |
317 | might_sleep(); |
318 | |
319 | /* transition from exec match to xattr set */ |
320 | state = aa_dfa_outofband_transition(dfa: attach->xmatch->dfa, state); |
321 | d = bprm->file->f_path.dentry; |
322 | |
323 | for (i = 0; i < attach->xattr_count; i++) { |
324 | size = vfs_getxattr_alloc(idmap: &nop_mnt_idmap, dentry: d, name: attach->xattrs[i], |
325 | xattr_value: &value, size: value_size, GFP_KERNEL); |
326 | if (size >= 0) { |
327 | u32 index, perm; |
328 | |
329 | /* |
330 | * Check the xattr presence before value. This ensure |
331 | * that not present xattr can be distinguished from a 0 |
332 | * length value or rule that matches any value |
333 | */ |
334 | state = aa_dfa_null_transition(dfa: attach->xmatch->dfa, |
335 | start: state); |
336 | /* Check xattr value */ |
337 | state = aa_dfa_match_len(dfa: attach->xmatch->dfa, start: state, |
338 | str: value, len: size); |
339 | index = ACCEPT_TABLE(attach->xmatch->dfa)[state]; |
340 | perm = attach->xmatch->perms[index].allow; |
341 | if (!(perm & MAY_EXEC)) { |
342 | ret = -EINVAL; |
343 | goto out; |
344 | } |
345 | } |
346 | /* transition to next element */ |
347 | state = aa_dfa_outofband_transition(dfa: attach->xmatch->dfa, state); |
348 | if (size < 0) { |
349 | /* |
350 | * No xattr match, so verify if transition to |
351 | * next element was valid. IFF so the xattr |
352 | * was optional. |
353 | */ |
354 | if (!state) { |
355 | ret = -EINVAL; |
356 | goto out; |
357 | } |
358 | /* don't count missing optional xattr as matched */ |
359 | ret--; |
360 | } |
361 | } |
362 | |
363 | out: |
364 | kfree(objp: value); |
365 | return ret; |
366 | } |
367 | |
368 | /** |
369 | * find_attach - do attachment search for unconfined processes |
370 | * @bprm: binprm structure of transitioning task |
371 | * @ns: the current namespace (NOT NULL) |
372 | * @head: profile list to walk (NOT NULL) |
373 | * @name: to match against (NOT NULL) |
374 | * @info: info message if there was an error (NOT NULL) |
375 | * |
376 | * Do a linear search on the profiles in the list. There is a matching |
377 | * preference where an exact match is preferred over a name which uses |
378 | * expressions to match, and matching expressions with the greatest |
379 | * xmatch_len are preferred. |
380 | * |
381 | * Requires: @head not be shared or have appropriate locks held |
382 | * |
383 | * Returns: label or NULL if no match found |
384 | */ |
385 | static struct aa_label *find_attach(const struct linux_binprm *bprm, |
386 | struct aa_ns *ns, struct list_head *head, |
387 | const char *name, const char **info) |
388 | { |
389 | int candidate_len = 0, candidate_xattrs = 0; |
390 | bool conflict = false; |
391 | struct aa_profile *profile, *candidate = NULL; |
392 | |
393 | AA_BUG(!name); |
394 | AA_BUG(!head); |
395 | |
396 | rcu_read_lock(); |
397 | restart: |
398 | list_for_each_entry_rcu(profile, head, base.list) { |
399 | struct aa_attachment *attach = &profile->attach; |
400 | |
401 | if (profile->label.flags & FLAG_NULL && |
402 | &profile->label == ns_unconfined(profile->ns)) |
403 | continue; |
404 | |
405 | /* Find the "best" matching profile. Profiles must |
406 | * match the path and extended attributes (if any) |
407 | * associated with the file. A more specific path |
408 | * match will be preferred over a less specific one, |
409 | * and a match with more matching extended attributes |
410 | * will be preferred over one with fewer. If the best |
411 | * match has both the same level of path specificity |
412 | * and the same number of matching extended attributes |
413 | * as another profile, signal a conflict and refuse to |
414 | * match. |
415 | */ |
416 | if (attach->xmatch->dfa) { |
417 | unsigned int count; |
418 | aa_state_t state; |
419 | u32 index, perm; |
420 | |
421 | state = aa_dfa_leftmatch(dfa: attach->xmatch->dfa, |
422 | start: attach->xmatch->start[AA_CLASS_XMATCH], |
423 | str: name, count: &count); |
424 | index = ACCEPT_TABLE(attach->xmatch->dfa)[state]; |
425 | perm = attach->xmatch->perms[index].allow; |
426 | /* any accepting state means a valid match. */ |
427 | if (perm & MAY_EXEC) { |
428 | int ret = 0; |
429 | |
430 | if (count < candidate_len) |
431 | continue; |
432 | |
433 | if (bprm && attach->xattr_count) { |
434 | long rev = READ_ONCE(ns->revision); |
435 | |
436 | if (!aa_get_profile_not0(p: profile)) |
437 | goto restart; |
438 | rcu_read_unlock(); |
439 | ret = aa_xattrs_match(bprm, profile, |
440 | state); |
441 | rcu_read_lock(); |
442 | aa_put_profile(p: profile); |
443 | if (rev != |
444 | READ_ONCE(ns->revision)) |
445 | /* policy changed */ |
446 | goto restart; |
447 | /* |
448 | * Fail matching if the xattrs don't |
449 | * match |
450 | */ |
451 | if (ret < 0) |
452 | continue; |
453 | } |
454 | /* |
455 | * TODO: allow for more flexible best match |
456 | * |
457 | * The new match isn't more specific |
458 | * than the current best match |
459 | */ |
460 | if (count == candidate_len && |
461 | ret <= candidate_xattrs) { |
462 | /* Match is equivalent, so conflict */ |
463 | if (ret == candidate_xattrs) |
464 | conflict = true; |
465 | continue; |
466 | } |
467 | |
468 | /* Either the same length with more matching |
469 | * xattrs, or a longer match |
470 | */ |
471 | candidate = profile; |
472 | candidate_len = max(count, attach->xmatch_len); |
473 | candidate_xattrs = ret; |
474 | conflict = false; |
475 | } |
476 | } else if (!strcmp(profile->base.name, name)) { |
477 | /* |
478 | * old exact non-re match, without conditionals such |
479 | * as xattrs. no more searching required |
480 | */ |
481 | candidate = profile; |
482 | goto out; |
483 | } |
484 | } |
485 | |
486 | if (!candidate || conflict) { |
487 | if (conflict) |
488 | *info = "conflicting profile attachments" ; |
489 | rcu_read_unlock(); |
490 | return NULL; |
491 | } |
492 | |
493 | out: |
494 | candidate = aa_get_newest_profile(p: candidate); |
495 | rcu_read_unlock(); |
496 | |
497 | return &candidate->label; |
498 | } |
499 | |
500 | static const char *next_name(int xtype, const char *name) |
501 | { |
502 | return NULL; |
503 | } |
504 | |
505 | /** |
506 | * x_table_lookup - lookup an x transition name via transition table |
507 | * @profile: current profile (NOT NULL) |
508 | * @xindex: index into x transition table |
509 | * @name: returns: name tested to find label (NOT NULL) |
510 | * |
511 | * Returns: refcounted label, or NULL on failure (MAYBE NULL) |
512 | */ |
513 | struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, |
514 | const char **name) |
515 | { |
516 | struct aa_ruleset *rules = list_first_entry(&profile->rules, |
517 | typeof(*rules), list); |
518 | struct aa_label *label = NULL; |
519 | u32 xtype = xindex & AA_X_TYPE_MASK; |
520 | int index = xindex & AA_X_INDEX_MASK; |
521 | |
522 | AA_BUG(!name); |
523 | |
524 | /* index is guaranteed to be in range, validated at load time */ |
525 | /* TODO: move lookup parsing to unpack time so this is a straight |
526 | * index into the resultant label |
527 | */ |
528 | for (*name = rules->file->trans.table[index]; !label && *name; |
529 | *name = next_name(xtype, name: *name)) { |
530 | if (xindex & AA_X_CHILD) { |
531 | struct aa_profile *new_profile; |
532 | /* release by caller */ |
533 | new_profile = aa_find_child(parent: profile, name: *name); |
534 | if (new_profile) |
535 | label = &new_profile->label; |
536 | continue; |
537 | } |
538 | label = aa_label_parse(base: &profile->label, str: *name, GFP_KERNEL, |
539 | create: true, force_stack: false); |
540 | if (IS_ERR(ptr: label)) |
541 | label = NULL; |
542 | } |
543 | |
544 | /* released by caller */ |
545 | |
546 | return label; |
547 | } |
548 | |
549 | /** |
550 | * x_to_label - get target label for a given xindex |
551 | * @profile: current profile (NOT NULL) |
552 | * @bprm: binprm structure of transitioning task |
553 | * @name: name to lookup (NOT NULL) |
554 | * @xindex: index into x transition table |
555 | * @lookupname: returns: name used in lookup if one was specified (NOT NULL) |
556 | * @info: info message if there was an error (NOT NULL) |
557 | * |
558 | * find label for a transition index |
559 | * |
560 | * Returns: refcounted label or NULL if not found available |
561 | */ |
562 | static struct aa_label *x_to_label(struct aa_profile *profile, |
563 | const struct linux_binprm *bprm, |
564 | const char *name, u32 xindex, |
565 | const char **lookupname, |
566 | const char **info) |
567 | { |
568 | struct aa_ruleset *rules = list_first_entry(&profile->rules, |
569 | typeof(*rules), list); |
570 | struct aa_label *new = NULL; |
571 | struct aa_ns *ns = profile->ns; |
572 | u32 xtype = xindex & AA_X_TYPE_MASK; |
573 | const char *stack = NULL; |
574 | |
575 | switch (xtype) { |
576 | case AA_X_NONE: |
577 | /* fail exec unless ix || ux fallback - handled by caller */ |
578 | *lookupname = NULL; |
579 | break; |
580 | case AA_X_TABLE: |
581 | /* TODO: fix when perm mapping done at unload */ |
582 | stack = rules->file->trans.table[xindex & AA_X_INDEX_MASK]; |
583 | if (*stack != '&') { |
584 | /* released by caller */ |
585 | new = x_table_lookup(profile, xindex, name: lookupname); |
586 | stack = NULL; |
587 | break; |
588 | } |
589 | fallthrough; /* to X_NAME */ |
590 | case AA_X_NAME: |
591 | if (xindex & AA_X_CHILD) |
592 | /* released by caller */ |
593 | new = find_attach(bprm, ns, head: &profile->base.profiles, |
594 | name, info); |
595 | else |
596 | /* released by caller */ |
597 | new = find_attach(bprm, ns, head: &ns->base.profiles, |
598 | name, info); |
599 | *lookupname = name; |
600 | break; |
601 | } |
602 | |
603 | if (!new) { |
604 | if (xindex & AA_X_INHERIT) { |
605 | /* (p|c|n)ix - don't change profile but do |
606 | * use the newest version |
607 | */ |
608 | *info = "ix fallback" ; |
609 | /* no profile && no error */ |
610 | new = aa_get_newest_label(l: &profile->label); |
611 | } else if (xindex & AA_X_UNCONFINED) { |
612 | new = aa_get_newest_label(ns_unconfined(profile->ns)); |
613 | *info = "ux fallback" ; |
614 | } |
615 | } |
616 | |
617 | if (new && stack) { |
618 | /* base the stack on post domain transition */ |
619 | struct aa_label *base = new; |
620 | |
621 | new = aa_label_parse(base, str: stack, GFP_KERNEL, create: true, force_stack: false); |
622 | if (IS_ERR(ptr: new)) |
623 | new = NULL; |
624 | aa_put_label(l: base); |
625 | } |
626 | |
627 | /* released by caller */ |
628 | return new; |
629 | } |
630 | |
631 | static struct aa_label *profile_transition(const struct cred *subj_cred, |
632 | struct aa_profile *profile, |
633 | const struct linux_binprm *bprm, |
634 | char *buffer, struct path_cond *cond, |
635 | bool *secure_exec) |
636 | { |
637 | struct aa_ruleset *rules = list_first_entry(&profile->rules, |
638 | typeof(*rules), list); |
639 | struct aa_label *new = NULL; |
640 | const char *info = NULL, *name = NULL, *target = NULL; |
641 | aa_state_t state = rules->file->start[AA_CLASS_FILE]; |
642 | struct aa_perms perms = {}; |
643 | bool nonewprivs = false; |
644 | int error = 0; |
645 | |
646 | AA_BUG(!profile); |
647 | AA_BUG(!bprm); |
648 | AA_BUG(!buffer); |
649 | |
650 | error = aa_path_name(path: &bprm->file->f_path, flags: profile->path_flags, buffer, |
651 | name: &name, info: &info, disconnected: profile->disconnected); |
652 | if (error) { |
653 | if (profile_unconfined(profile) || |
654 | (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) { |
655 | AA_DEBUG("name lookup ix on error" ); |
656 | error = 0; |
657 | new = aa_get_newest_label(l: &profile->label); |
658 | } |
659 | name = bprm->filename; |
660 | goto audit; |
661 | } |
662 | |
663 | if (profile_unconfined(profile)) { |
664 | new = find_attach(bprm, ns: profile->ns, |
665 | head: &profile->ns->base.profiles, name, info: &info); |
666 | if (new) { |
667 | AA_DEBUG("unconfined attached to new label" ); |
668 | return new; |
669 | } |
670 | AA_DEBUG("unconfined exec no attachment" ); |
671 | return aa_get_newest_label(l: &profile->label); |
672 | } |
673 | |
674 | /* find exec permissions for name */ |
675 | state = aa_str_perms(file_rules: rules->file, start: state, name, cond, perms: &perms); |
676 | if (perms.allow & MAY_EXEC) { |
677 | /* exec permission determine how to transition */ |
678 | new = x_to_label(profile, bprm, name, xindex: perms.xindex, lookupname: &target, |
679 | info: &info); |
680 | if (new && new->proxy == profile->label.proxy && info) { |
681 | /* hack ix fallback - improve how this is detected */ |
682 | goto audit; |
683 | } else if (!new) { |
684 | error = -EACCES; |
685 | info = "profile transition not found" ; |
686 | /* remove MAY_EXEC to audit as failure */ |
687 | perms.allow &= ~MAY_EXEC; |
688 | } |
689 | } else if (COMPLAIN_MODE(profile)) { |
690 | /* no exec permission - learning mode */ |
691 | struct aa_profile *new_profile = NULL; |
692 | |
693 | new_profile = aa_new_learning_profile(parent: profile, hat: false, base: name, |
694 | GFP_KERNEL); |
695 | if (!new_profile) { |
696 | error = -ENOMEM; |
697 | info = "could not create null profile" ; |
698 | } else { |
699 | error = -EACCES; |
700 | new = &new_profile->label; |
701 | } |
702 | perms.xindex |= AA_X_UNSAFE; |
703 | } else |
704 | /* fail exec */ |
705 | error = -EACCES; |
706 | |
707 | if (!new) |
708 | goto audit; |
709 | |
710 | |
711 | if (!(perms.xindex & AA_X_UNSAFE)) { |
712 | if (DEBUG_ON) { |
713 | dbg_printk("apparmor: scrubbing environment variables" |
714 | " for %s profile=" , name); |
715 | aa_label_printk(label: new, GFP_KERNEL); |
716 | dbg_printk("\n" ); |
717 | } |
718 | *secure_exec = true; |
719 | } |
720 | |
721 | audit: |
722 | aa_audit_file(cred: subj_cred, profile, perms: &perms, OP_EXEC, MAY_EXEC, name, |
723 | target, tlabel: new, |
724 | ouid: cond->uid, info, error); |
725 | if (!new || nonewprivs) { |
726 | aa_put_label(l: new); |
727 | return ERR_PTR(error); |
728 | } |
729 | |
730 | return new; |
731 | } |
732 | |
733 | static int profile_onexec(const struct cred *subj_cred, |
734 | struct aa_profile *profile, struct aa_label *onexec, |
735 | bool stack, const struct linux_binprm *bprm, |
736 | char *buffer, struct path_cond *cond, |
737 | bool *secure_exec) |
738 | { |
739 | struct aa_ruleset *rules = list_first_entry(&profile->rules, |
740 | typeof(*rules), list); |
741 | aa_state_t state = rules->file->start[AA_CLASS_FILE]; |
742 | struct aa_perms perms = {}; |
743 | const char *xname = NULL, *info = "change_profile onexec" ; |
744 | int error = -EACCES; |
745 | |
746 | AA_BUG(!profile); |
747 | AA_BUG(!onexec); |
748 | AA_BUG(!bprm); |
749 | AA_BUG(!buffer); |
750 | |
751 | if (profile_unconfined(profile)) { |
752 | /* change_profile on exec already granted */ |
753 | /* |
754 | * NOTE: Domain transitions from unconfined are allowed |
755 | * even when no_new_privs is set because this aways results |
756 | * in a further reduction of permissions. |
757 | */ |
758 | return 0; |
759 | } |
760 | |
761 | error = aa_path_name(path: &bprm->file->f_path, flags: profile->path_flags, buffer, |
762 | name: &xname, info: &info, disconnected: profile->disconnected); |
763 | if (error) { |
764 | if (profile_unconfined(profile) || |
765 | (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) { |
766 | AA_DEBUG("name lookup ix on error" ); |
767 | error = 0; |
768 | } |
769 | xname = bprm->filename; |
770 | goto audit; |
771 | } |
772 | |
773 | /* find exec permissions for name */ |
774 | state = aa_str_perms(file_rules: rules->file, start: state, name: xname, cond, perms: &perms); |
775 | if (!(perms.allow & AA_MAY_ONEXEC)) { |
776 | info = "no change_onexec valid for executable" ; |
777 | goto audit; |
778 | } |
779 | /* test if this exec can be paired with change_profile onexec. |
780 | * onexec permission is linked to exec with a standard pairing |
781 | * exec\0change_profile |
782 | */ |
783 | state = aa_dfa_null_transition(dfa: rules->file->dfa, start: state); |
784 | error = change_profile_perms(profile, target: onexec, stack, AA_MAY_ONEXEC, |
785 | start: state, perms: &perms); |
786 | if (error) { |
787 | perms.allow &= ~AA_MAY_ONEXEC; |
788 | goto audit; |
789 | } |
790 | |
791 | if (!(perms.xindex & AA_X_UNSAFE)) { |
792 | if (DEBUG_ON) { |
793 | dbg_printk("apparmor: scrubbing environment " |
794 | "variables for %s label=" , xname); |
795 | aa_label_printk(label: onexec, GFP_KERNEL); |
796 | dbg_printk("\n" ); |
797 | } |
798 | *secure_exec = true; |
799 | } |
800 | |
801 | audit: |
802 | return aa_audit_file(cred: subj_cred, profile, perms: &perms, OP_EXEC, |
803 | AA_MAY_ONEXEC, name: xname, |
804 | NULL, tlabel: onexec, ouid: cond->uid, info, error); |
805 | } |
806 | |
807 | /* ensure none ns domain transitions are correctly applied with onexec */ |
808 | |
809 | static struct aa_label *handle_onexec(const struct cred *subj_cred, |
810 | struct aa_label *label, |
811 | struct aa_label *onexec, bool stack, |
812 | const struct linux_binprm *bprm, |
813 | char *buffer, struct path_cond *cond, |
814 | bool *unsafe) |
815 | { |
816 | struct aa_profile *profile; |
817 | struct aa_label *new; |
818 | int error; |
819 | |
820 | AA_BUG(!label); |
821 | AA_BUG(!onexec); |
822 | AA_BUG(!bprm); |
823 | AA_BUG(!buffer); |
824 | |
825 | if (!stack) { |
826 | error = fn_for_each_in_ns(label, profile, |
827 | profile_onexec(subj_cred, profile, onexec, stack, |
828 | bprm, buffer, cond, unsafe)); |
829 | if (error) |
830 | return ERR_PTR(error); |
831 | new = fn_label_build_in_ns(label, profile, GFP_KERNEL, |
832 | aa_get_newest_label(onexec), |
833 | profile_transition(subj_cred, profile, bprm, |
834 | buffer, |
835 | cond, unsafe)); |
836 | |
837 | } else { |
838 | /* TODO: determine how much we want to loosen this */ |
839 | error = fn_for_each_in_ns(label, profile, |
840 | profile_onexec(subj_cred, profile, onexec, stack, bprm, |
841 | buffer, cond, unsafe)); |
842 | if (error) |
843 | return ERR_PTR(error); |
844 | new = fn_label_build_in_ns(label, profile, GFP_KERNEL, |
845 | aa_label_merge(&profile->label, onexec, |
846 | GFP_KERNEL), |
847 | profile_transition(subj_cred, profile, bprm, |
848 | buffer, |
849 | cond, unsafe)); |
850 | } |
851 | |
852 | if (new) |
853 | return new; |
854 | |
855 | /* TODO: get rid of GLOBAL_ROOT_UID */ |
856 | error = fn_for_each_in_ns(label, profile, |
857 | aa_audit_file(subj_cred, profile, &nullperms, |
858 | OP_CHANGE_ONEXEC, |
859 | AA_MAY_ONEXEC, bprm->filename, NULL, |
860 | onexec, GLOBAL_ROOT_UID, |
861 | "failed to build target label" , -ENOMEM)); |
862 | return ERR_PTR(error); |
863 | } |
864 | |
865 | /** |
866 | * apparmor_bprm_creds_for_exec - Update the new creds on the bprm struct |
867 | * @bprm: binprm for the exec (NOT NULL) |
868 | * |
869 | * Returns: %0 or error on failure |
870 | * |
871 | * TODO: once the other paths are done see if we can't refactor into a fn |
872 | */ |
873 | int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm) |
874 | { |
875 | struct aa_task_ctx *ctx; |
876 | struct aa_label *label, *new = NULL; |
877 | const struct cred *subj_cred; |
878 | struct aa_profile *profile; |
879 | char *buffer = NULL; |
880 | const char *info = NULL; |
881 | int error = 0; |
882 | bool unsafe = false; |
883 | vfsuid_t vfsuid = i_uid_into_vfsuid(idmap: file_mnt_idmap(file: bprm->file), |
884 | inode: file_inode(f: bprm->file)); |
885 | struct path_cond cond = { |
886 | vfsuid_into_kuid(vfsuid), |
887 | file_inode(f: bprm->file)->i_mode |
888 | }; |
889 | |
890 | subj_cred = current_cred(); |
891 | ctx = task_ctx(current); |
892 | AA_BUG(!cred_label(bprm->cred)); |
893 | AA_BUG(!ctx); |
894 | |
895 | label = aa_get_newest_label(l: cred_label(cred: bprm->cred)); |
896 | |
897 | /* |
898 | * Detect no new privs being set, and store the label it |
899 | * occurred under. Ideally this would happen when nnp |
900 | * is set but there isn't a good way to do that yet. |
901 | * |
902 | * Testing for unconfined must be done before the subset test |
903 | */ |
904 | if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) && !unconfined(label) && |
905 | !ctx->nnp) |
906 | ctx->nnp = aa_get_label(l: label); |
907 | |
908 | /* buffer freed below, name is pointer into buffer */ |
909 | buffer = aa_get_buffer(in_atomic: false); |
910 | if (!buffer) { |
911 | error = -ENOMEM; |
912 | goto done; |
913 | } |
914 | |
915 | /* Test for onexec first as onexec override other x transitions. */ |
916 | if (ctx->onexec) |
917 | new = handle_onexec(subj_cred, label, onexec: ctx->onexec, stack: ctx->token, |
918 | bprm, buffer, cond: &cond, unsafe: &unsafe); |
919 | else |
920 | new = fn_label_build(label, profile, GFP_KERNEL, |
921 | profile_transition(subj_cred, profile, bprm, |
922 | buffer, |
923 | &cond, &unsafe)); |
924 | |
925 | AA_BUG(!new); |
926 | if (IS_ERR(ptr: new)) { |
927 | error = PTR_ERR(ptr: new); |
928 | goto done; |
929 | } else if (!new) { |
930 | error = -ENOMEM; |
931 | goto done; |
932 | } |
933 | |
934 | /* Policy has specified a domain transitions. If no_new_privs and |
935 | * confined ensure the transition is to confinement that is subset |
936 | * of the confinement when the task entered no new privs. |
937 | * |
938 | * NOTE: Domain transitions from unconfined and to stacked |
939 | * subsets are allowed even when no_new_privs is set because this |
940 | * aways results in a further reduction of permissions. |
941 | */ |
942 | if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) && |
943 | !unconfined(label) && |
944 | !aa_label_is_unconfined_subset(set: new, sub: ctx->nnp)) { |
945 | error = -EPERM; |
946 | info = "no new privs" ; |
947 | goto audit; |
948 | } |
949 | |
950 | if (bprm->unsafe & LSM_UNSAFE_SHARE) { |
951 | /* FIXME: currently don't mediate shared state */ |
952 | ; |
953 | } |
954 | |
955 | if (bprm->unsafe & (LSM_UNSAFE_PTRACE)) { |
956 | /* TODO: test needs to be profile of label to new */ |
957 | error = may_change_ptraced_domain(to_cred: bprm->cred, to_label: new, info: &info); |
958 | if (error) |
959 | goto audit; |
960 | } |
961 | |
962 | if (unsafe) { |
963 | if (DEBUG_ON) { |
964 | dbg_printk("scrubbing environment variables for %s " |
965 | "label=" , bprm->filename); |
966 | aa_label_printk(label: new, GFP_KERNEL); |
967 | dbg_printk("\n" ); |
968 | } |
969 | bprm->secureexec = 1; |
970 | } |
971 | |
972 | if (label->proxy != new->proxy) { |
973 | /* when transitioning clear unsafe personality bits */ |
974 | if (DEBUG_ON) { |
975 | dbg_printk("apparmor: clearing unsafe personality " |
976 | "bits. %s label=" , bprm->filename); |
977 | aa_label_printk(label: new, GFP_KERNEL); |
978 | dbg_printk("\n" ); |
979 | } |
980 | bprm->per_clear |= PER_CLEAR_ON_SETID; |
981 | } |
982 | aa_put_label(l: cred_label(cred: bprm->cred)); |
983 | /* transfer reference, released when cred is freed */ |
984 | set_cred_label(cred: bprm->cred, label: new); |
985 | |
986 | done: |
987 | aa_put_label(l: label); |
988 | aa_put_buffer(buf: buffer); |
989 | |
990 | return error; |
991 | |
992 | audit: |
993 | error = fn_for_each(label, profile, |
994 | aa_audit_file(current_cred(), profile, &nullperms, |
995 | OP_EXEC, MAY_EXEC, |
996 | bprm->filename, NULL, new, |
997 | vfsuid_into_kuid(vfsuid), info, error)); |
998 | aa_put_label(l: new); |
999 | goto done; |
1000 | } |
1001 | |
1002 | /* |
1003 | * Functions for self directed profile change |
1004 | */ |
1005 | |
1006 | |
1007 | /* helper fn for change_hat |
1008 | * |
1009 | * Returns: label for hat transition OR ERR_PTR. Does NOT return NULL |
1010 | */ |
1011 | static struct aa_label *build_change_hat(const struct cred *subj_cred, |
1012 | struct aa_profile *profile, |
1013 | const char *name, bool sibling) |
1014 | { |
1015 | struct aa_profile *root, *hat = NULL; |
1016 | const char *info = NULL; |
1017 | int error = 0; |
1018 | |
1019 | if (sibling && PROFILE_IS_HAT(profile)) { |
1020 | root = aa_get_profile_rcu(p: &profile->parent); |
1021 | } else if (!sibling && !PROFILE_IS_HAT(profile)) { |
1022 | root = aa_get_profile(p: profile); |
1023 | } else { |
1024 | info = "conflicting target types" ; |
1025 | error = -EPERM; |
1026 | goto audit; |
1027 | } |
1028 | |
1029 | hat = aa_find_child(parent: root, name); |
1030 | if (!hat) { |
1031 | error = -ENOENT; |
1032 | if (COMPLAIN_MODE(profile)) { |
1033 | hat = aa_new_learning_profile(parent: profile, hat: true, base: name, |
1034 | GFP_KERNEL); |
1035 | if (!hat) { |
1036 | info = "failed null profile create" ; |
1037 | error = -ENOMEM; |
1038 | } |
1039 | } |
1040 | } |
1041 | aa_put_profile(p: root); |
1042 | |
1043 | audit: |
1044 | aa_audit_file(cred: subj_cred, profile, perms: &nullperms, OP_CHANGE_HAT, |
1045 | AA_MAY_CHANGEHAT, |
1046 | name, target: hat ? hat->base.hname : NULL, |
1047 | tlabel: hat ? &hat->label : NULL, GLOBAL_ROOT_UID, info, |
1048 | error); |
1049 | if (!hat || (error && error != -ENOENT)) |
1050 | return ERR_PTR(error); |
1051 | /* if hat && error - complain mode, already audited and we adjust for |
1052 | * complain mode allow by returning hat->label |
1053 | */ |
1054 | return &hat->label; |
1055 | } |
1056 | |
1057 | /* helper fn for changing into a hat |
1058 | * |
1059 | * Returns: label for hat transition or ERR_PTR. Does not return NULL |
1060 | */ |
1061 | static struct aa_label *change_hat(const struct cred *subj_cred, |
1062 | struct aa_label *label, const char *hats[], |
1063 | int count, int flags) |
1064 | { |
1065 | struct aa_profile *profile, *root, *hat = NULL; |
1066 | struct aa_label *new; |
1067 | struct label_it it; |
1068 | bool sibling = false; |
1069 | const char *name, *info = NULL; |
1070 | int i, error; |
1071 | |
1072 | AA_BUG(!label); |
1073 | AA_BUG(!hats); |
1074 | AA_BUG(count < 1); |
1075 | |
1076 | if (PROFILE_IS_HAT(labels_profile(label))) |
1077 | sibling = true; |
1078 | |
1079 | /*find first matching hat */ |
1080 | for (i = 0; i < count && !hat; i++) { |
1081 | name = hats[i]; |
1082 | label_for_each_in_ns(it, labels_ns(label), label, profile) { |
1083 | if (sibling && PROFILE_IS_HAT(profile)) { |
1084 | root = aa_get_profile_rcu(p: &profile->parent); |
1085 | } else if (!sibling && !PROFILE_IS_HAT(profile)) { |
1086 | root = aa_get_profile(p: profile); |
1087 | } else { /* conflicting change type */ |
1088 | info = "conflicting targets types" ; |
1089 | error = -EPERM; |
1090 | goto fail; |
1091 | } |
1092 | hat = aa_find_child(parent: root, name); |
1093 | aa_put_profile(p: root); |
1094 | if (!hat) { |
1095 | if (!COMPLAIN_MODE(profile)) |
1096 | goto outer_continue; |
1097 | /* complain mode succeed as if hat */ |
1098 | } else if (!PROFILE_IS_HAT(hat)) { |
1099 | info = "target not hat" ; |
1100 | error = -EPERM; |
1101 | aa_put_profile(p: hat); |
1102 | goto fail; |
1103 | } |
1104 | aa_put_profile(p: hat); |
1105 | } |
1106 | /* found a hat for all profiles in ns */ |
1107 | goto build; |
1108 | outer_continue: |
1109 | ; |
1110 | } |
1111 | /* no hats that match, find appropriate error |
1112 | * |
1113 | * In complain mode audit of the failure is based off of the first |
1114 | * hat supplied. This is done due how userspace interacts with |
1115 | * change_hat. |
1116 | */ |
1117 | name = NULL; |
1118 | label_for_each_in_ns(it, labels_ns(label), label, profile) { |
1119 | if (!list_empty(head: &profile->base.profiles)) { |
1120 | info = "hat not found" ; |
1121 | error = -ENOENT; |
1122 | goto fail; |
1123 | } |
1124 | } |
1125 | info = "no hats defined" ; |
1126 | error = -ECHILD; |
1127 | |
1128 | fail: |
1129 | label_for_each_in_ns(it, labels_ns(label), label, profile) { |
1130 | /* |
1131 | * no target as it has failed to be found or built |
1132 | * |
1133 | * change_hat uses probing and should not log failures |
1134 | * related to missing hats |
1135 | */ |
1136 | /* TODO: get rid of GLOBAL_ROOT_UID */ |
1137 | if (count > 1 || COMPLAIN_MODE(profile)) { |
1138 | aa_audit_file(cred: subj_cred, profile, perms: &nullperms, |
1139 | OP_CHANGE_HAT, |
1140 | AA_MAY_CHANGEHAT, name, NULL, NULL, |
1141 | GLOBAL_ROOT_UID, info, error); |
1142 | } |
1143 | } |
1144 | return ERR_PTR(error); |
1145 | |
1146 | build: |
1147 | new = fn_label_build_in_ns(label, profile, GFP_KERNEL, |
1148 | build_change_hat(subj_cred, profile, name, |
1149 | sibling), |
1150 | aa_get_label(&profile->label)); |
1151 | if (!new) { |
1152 | info = "label build failed" ; |
1153 | error = -ENOMEM; |
1154 | goto fail; |
1155 | } /* else if (IS_ERR) build_change_hat has logged error so return new */ |
1156 | |
1157 | return new; |
1158 | } |
1159 | |
1160 | /** |
1161 | * aa_change_hat - change hat to/from subprofile |
1162 | * @hats: vector of hat names to try changing into (MAYBE NULL if @count == 0) |
1163 | * @count: number of hat names in @hats |
1164 | * @token: magic value to validate the hat change |
1165 | * @flags: flags affecting behavior of the change |
1166 | * |
1167 | * Returns %0 on success, error otherwise. |
1168 | * |
1169 | * Change to the first profile specified in @hats that exists, and store |
1170 | * the @hat_magic in the current task context. If the count == 0 and the |
1171 | * @token matches that stored in the current task context, return to the |
1172 | * top level profile. |
1173 | * |
1174 | * change_hat only applies to profiles in the current ns, and each profile |
1175 | * in the ns must make the same transition otherwise change_hat will fail. |
1176 | */ |
1177 | int aa_change_hat(const char *hats[], int count, u64 token, int flags) |
1178 | { |
1179 | const struct cred *subj_cred; |
1180 | struct aa_task_ctx *ctx = task_ctx(current); |
1181 | struct aa_label *label, *previous, *new = NULL, *target = NULL; |
1182 | struct aa_profile *profile; |
1183 | struct aa_perms perms = {}; |
1184 | const char *info = NULL; |
1185 | int error = 0; |
1186 | |
1187 | /* released below */ |
1188 | subj_cred = get_current_cred(); |
1189 | label = aa_get_newest_cred_label(cred: subj_cred); |
1190 | previous = aa_get_newest_label(l: ctx->previous); |
1191 | |
1192 | /* |
1193 | * Detect no new privs being set, and store the label it |
1194 | * occurred under. Ideally this would happen when nnp |
1195 | * is set but there isn't a good way to do that yet. |
1196 | * |
1197 | * Testing for unconfined must be done before the subset test |
1198 | */ |
1199 | if (task_no_new_privs(current) && !unconfined(label) && !ctx->nnp) |
1200 | ctx->nnp = aa_get_label(l: label); |
1201 | |
1202 | if (unconfined(label)) { |
1203 | info = "unconfined can not change_hat" ; |
1204 | error = -EPERM; |
1205 | goto fail; |
1206 | } |
1207 | |
1208 | if (count) { |
1209 | new = change_hat(subj_cred, label, hats, count, flags); |
1210 | AA_BUG(!new); |
1211 | if (IS_ERR(ptr: new)) { |
1212 | error = PTR_ERR(ptr: new); |
1213 | new = NULL; |
1214 | /* already audited */ |
1215 | goto out; |
1216 | } |
1217 | |
1218 | /* target cred is the same as current except new label */ |
1219 | error = may_change_ptraced_domain(to_cred: subj_cred, to_label: new, info: &info); |
1220 | if (error) |
1221 | goto fail; |
1222 | |
1223 | /* |
1224 | * no new privs prevents domain transitions that would |
1225 | * reduce restrictions. |
1226 | */ |
1227 | if (task_no_new_privs(current) && !unconfined(label) && |
1228 | !aa_label_is_unconfined_subset(set: new, sub: ctx->nnp)) { |
1229 | /* not an apparmor denial per se, so don't log it */ |
1230 | AA_DEBUG("no_new_privs - change_hat denied" ); |
1231 | error = -EPERM; |
1232 | goto out; |
1233 | } |
1234 | |
1235 | if (flags & AA_CHANGE_TEST) |
1236 | goto out; |
1237 | |
1238 | target = new; |
1239 | error = aa_set_current_hat(label: new, token); |
1240 | if (error == -EACCES) |
1241 | /* kill task in case of brute force attacks */ |
1242 | goto kill; |
1243 | } else if (previous && !(flags & AA_CHANGE_TEST)) { |
1244 | /* |
1245 | * no new privs prevents domain transitions that would |
1246 | * reduce restrictions. |
1247 | */ |
1248 | if (task_no_new_privs(current) && !unconfined(label) && |
1249 | !aa_label_is_unconfined_subset(set: previous, sub: ctx->nnp)) { |
1250 | /* not an apparmor denial per se, so don't log it */ |
1251 | AA_DEBUG("no_new_privs - change_hat denied" ); |
1252 | error = -EPERM; |
1253 | goto out; |
1254 | } |
1255 | |
1256 | /* Return to saved label. Kill task if restore fails |
1257 | * to avoid brute force attacks |
1258 | */ |
1259 | target = previous; |
1260 | error = aa_restore_previous_label(cookie: token); |
1261 | if (error) { |
1262 | if (error == -EACCES) |
1263 | goto kill; |
1264 | goto fail; |
1265 | } |
1266 | } /* else ignore @flags && restores when there is no saved profile */ |
1267 | |
1268 | out: |
1269 | aa_put_label(l: new); |
1270 | aa_put_label(l: previous); |
1271 | aa_put_label(l: label); |
1272 | put_cred(cred: subj_cred); |
1273 | |
1274 | return error; |
1275 | |
1276 | kill: |
1277 | info = "failed token match" ; |
1278 | perms.kill = AA_MAY_CHANGEHAT; |
1279 | |
1280 | fail: |
1281 | fn_for_each_in_ns(label, profile, |
1282 | aa_audit_file(subj_cred, profile, &perms, OP_CHANGE_HAT, |
1283 | AA_MAY_CHANGEHAT, NULL, NULL, target, |
1284 | GLOBAL_ROOT_UID, info, error)); |
1285 | |
1286 | goto out; |
1287 | } |
1288 | |
1289 | |
1290 | static int change_profile_perms_wrapper(const char *op, const char *name, |
1291 | const struct cred *subj_cred, |
1292 | struct aa_profile *profile, |
1293 | struct aa_label *target, bool stack, |
1294 | u32 request, struct aa_perms *perms) |
1295 | { |
1296 | struct aa_ruleset *rules = list_first_entry(&profile->rules, |
1297 | typeof(*rules), list); |
1298 | const char *info = NULL; |
1299 | int error = 0; |
1300 | |
1301 | if (!error) |
1302 | error = change_profile_perms(profile, target, stack, request, |
1303 | start: rules->file->start[AA_CLASS_FILE], |
1304 | perms); |
1305 | if (error) |
1306 | error = aa_audit_file(cred: subj_cred, profile, perms, op, request, |
1307 | name, |
1308 | NULL, tlabel: target, GLOBAL_ROOT_UID, info, |
1309 | error); |
1310 | |
1311 | return error; |
1312 | } |
1313 | |
1314 | const char *stack_msg = "change_profile unprivileged unconfined converted to stacking" ; |
1315 | |
1316 | /** |
1317 | * aa_change_profile - perform a one-way profile transition |
1318 | * @fqname: name of profile may include namespace (NOT NULL) |
1319 | * @flags: flags affecting change behavior |
1320 | * |
1321 | * Change to new profile @name. Unlike with hats, there is no way |
1322 | * to change back. If @name isn't specified the current profile name is |
1323 | * used. |
1324 | * If @onexec then the transition is delayed until |
1325 | * the next exec. |
1326 | * |
1327 | * Returns %0 on success, error otherwise. |
1328 | */ |
1329 | int aa_change_profile(const char *fqname, int flags) |
1330 | { |
1331 | struct aa_label *label, *new = NULL, *target = NULL; |
1332 | struct aa_profile *profile; |
1333 | struct aa_perms perms = {}; |
1334 | const char *info = NULL; |
1335 | const char *auditname = fqname; /* retain leading & if stack */ |
1336 | bool stack = flags & AA_CHANGE_STACK; |
1337 | struct aa_task_ctx *ctx = task_ctx(current); |
1338 | const struct cred *subj_cred = get_current_cred(); |
1339 | int error = 0; |
1340 | char *op; |
1341 | u32 request; |
1342 | |
1343 | label = aa_get_current_label(); |
1344 | |
1345 | /* |
1346 | * Detect no new privs being set, and store the label it |
1347 | * occurred under. Ideally this would happen when nnp |
1348 | * is set but there isn't a good way to do that yet. |
1349 | * |
1350 | * Testing for unconfined must be done before the subset test |
1351 | */ |
1352 | if (task_no_new_privs(current) && !unconfined(label) && !ctx->nnp) |
1353 | ctx->nnp = aa_get_label(l: label); |
1354 | |
1355 | if (!fqname || !*fqname) { |
1356 | aa_put_label(l: label); |
1357 | AA_DEBUG("no profile name" ); |
1358 | return -EINVAL; |
1359 | } |
1360 | |
1361 | if (flags & AA_CHANGE_ONEXEC) { |
1362 | request = AA_MAY_ONEXEC; |
1363 | if (stack) |
1364 | op = OP_STACK_ONEXEC; |
1365 | else |
1366 | op = OP_CHANGE_ONEXEC; |
1367 | } else { |
1368 | request = AA_MAY_CHANGE_PROFILE; |
1369 | if (stack) |
1370 | op = OP_STACK; |
1371 | else |
1372 | op = OP_CHANGE_PROFILE; |
1373 | } |
1374 | |
1375 | /* This should move to a per profile test. Requires pushing build |
1376 | * into callback |
1377 | */ |
1378 | if (!stack && unconfined(label) && |
1379 | label == &labels_ns(label)->unconfined->label && |
1380 | aa_unprivileged_unconfined_restricted && |
1381 | /* TODO: refactor so this check is a fn */ |
1382 | cap_capable(current_cred(), ns: &init_user_ns, CAP_MAC_OVERRIDE, |
1383 | CAP_OPT_NOAUDIT)) { |
1384 | /* regardless of the request in this case apparmor |
1385 | * stacks against unconfined so admin set policy can't be |
1386 | * by-passed |
1387 | */ |
1388 | stack = true; |
1389 | perms.audit = request; |
1390 | (void) fn_for_each_in_ns(label, profile, |
1391 | aa_audit_file(subj_cred, profile, &perms, op, |
1392 | request, auditname, NULL, target, |
1393 | GLOBAL_ROOT_UID, stack_msg, 0)); |
1394 | perms.audit = 0; |
1395 | } |
1396 | |
1397 | if (*fqname == '&') { |
1398 | stack = true; |
1399 | /* don't have label_parse() do stacking */ |
1400 | fqname++; |
1401 | } |
1402 | target = aa_label_parse(base: label, str: fqname, GFP_KERNEL, create: true, force_stack: false); |
1403 | if (IS_ERR(ptr: target)) { |
1404 | struct aa_profile *tprofile; |
1405 | |
1406 | info = "label not found" ; |
1407 | error = PTR_ERR(ptr: target); |
1408 | target = NULL; |
1409 | /* |
1410 | * TODO: fixme using labels_profile is not right - do profile |
1411 | * per complain profile |
1412 | */ |
1413 | if ((flags & AA_CHANGE_TEST) || |
1414 | !COMPLAIN_MODE(labels_profile(label))) |
1415 | goto audit; |
1416 | /* released below */ |
1417 | tprofile = aa_new_learning_profile(labels_profile(label), hat: false, |
1418 | base: fqname, GFP_KERNEL); |
1419 | if (!tprofile) { |
1420 | info = "failed null profile create" ; |
1421 | error = -ENOMEM; |
1422 | goto audit; |
1423 | } |
1424 | target = &tprofile->label; |
1425 | goto check; |
1426 | } |
1427 | |
1428 | /* |
1429 | * self directed transitions only apply to current policy ns |
1430 | * TODO: currently requiring perms for stacking and straight change |
1431 | * stacking doesn't strictly need this. Determine how much |
1432 | * we want to loosen this restriction for stacking |
1433 | * |
1434 | * if (!stack) { |
1435 | */ |
1436 | error = fn_for_each_in_ns(label, profile, |
1437 | change_profile_perms_wrapper(op, auditname, |
1438 | subj_cred, |
1439 | profile, target, stack, |
1440 | request, &perms)); |
1441 | if (error) |
1442 | /* auditing done in change_profile_perms_wrapper */ |
1443 | goto out; |
1444 | |
1445 | /* } */ |
1446 | |
1447 | check: |
1448 | /* check if tracing task is allowed to trace target domain */ |
1449 | error = may_change_ptraced_domain(to_cred: subj_cred, to_label: target, info: &info); |
1450 | if (error && !fn_for_each_in_ns(label, profile, |
1451 | COMPLAIN_MODE(profile))) |
1452 | goto audit; |
1453 | |
1454 | /* TODO: add permission check to allow this |
1455 | * if ((flags & AA_CHANGE_ONEXEC) && !current_is_single_threaded()) { |
1456 | * info = "not a single threaded task"; |
1457 | * error = -EACCES; |
1458 | * goto audit; |
1459 | * } |
1460 | */ |
1461 | if (flags & AA_CHANGE_TEST) |
1462 | goto out; |
1463 | |
1464 | /* stacking is always a subset, so only check the nonstack case */ |
1465 | if (!stack) { |
1466 | new = fn_label_build_in_ns(label, profile, GFP_KERNEL, |
1467 | aa_get_label(target), |
1468 | aa_get_label(&profile->label)); |
1469 | /* |
1470 | * no new privs prevents domain transitions that would |
1471 | * reduce restrictions. |
1472 | */ |
1473 | if (task_no_new_privs(current) && !unconfined(label) && |
1474 | !aa_label_is_unconfined_subset(set: new, sub: ctx->nnp)) { |
1475 | /* not an apparmor denial per se, so don't log it */ |
1476 | AA_DEBUG("no_new_privs - change_hat denied" ); |
1477 | error = -EPERM; |
1478 | goto out; |
1479 | } |
1480 | } |
1481 | |
1482 | if (!(flags & AA_CHANGE_ONEXEC)) { |
1483 | /* only transition profiles in the current ns */ |
1484 | if (stack) |
1485 | new = aa_label_merge(a: label, b: target, GFP_KERNEL); |
1486 | if (IS_ERR_OR_NULL(ptr: new)) { |
1487 | info = "failed to build target label" ; |
1488 | if (!new) |
1489 | error = -ENOMEM; |
1490 | else |
1491 | error = PTR_ERR(ptr: new); |
1492 | new = NULL; |
1493 | perms.allow = 0; |
1494 | goto audit; |
1495 | } |
1496 | error = aa_replace_current_label(label: new); |
1497 | } else { |
1498 | if (new) { |
1499 | aa_put_label(l: new); |
1500 | new = NULL; |
1501 | } |
1502 | |
1503 | /* full transition will be built in exec path */ |
1504 | aa_set_current_onexec(label: target, stack); |
1505 | } |
1506 | |
1507 | audit: |
1508 | error = fn_for_each_in_ns(label, profile, |
1509 | aa_audit_file(subj_cred, |
1510 | profile, &perms, op, request, auditname, |
1511 | NULL, new ? new : target, |
1512 | GLOBAL_ROOT_UID, info, error)); |
1513 | |
1514 | out: |
1515 | aa_put_label(l: new); |
1516 | aa_put_label(l: target); |
1517 | aa_put_label(l: label); |
1518 | put_cred(cred: subj_cred); |
1519 | |
1520 | return error; |
1521 | } |
1522 | |