1b886d83cSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 2898127c3SJohn Johansen /* 3898127c3SJohn Johansen * AppArmor security module 4898127c3SJohn Johansen * 5898127c3SJohn Johansen * This file contains AppArmor policy attachment and domain transitions 6898127c3SJohn Johansen * 7898127c3SJohn Johansen * Copyright (C) 2002-2008 Novell/SUSE 8898127c3SJohn Johansen * Copyright 2009-2010 Canonical Ltd. 9898127c3SJohn Johansen */ 10898127c3SJohn Johansen 11898127c3SJohn Johansen #include <linux/errno.h> 12898127c3SJohn Johansen #include <linux/fdtable.h> 133cee6079SChristian Brauner #include <linux/fs.h> 14898127c3SJohn Johansen #include <linux/file.h> 15898127c3SJohn Johansen #include <linux/mount.h> 16898127c3SJohn Johansen #include <linux/syscalls.h> 17898127c3SJohn Johansen #include <linux/personality.h> 188e51f908SMatthew Garrett #include <linux/xattr.h> 193cee6079SChristian Brauner #include <linux/user_namespace.h> 20898127c3SJohn Johansen 21898127c3SJohn Johansen #include "include/audit.h" 22898127c3SJohn Johansen #include "include/apparmorfs.h" 23d8889d49SJohn Johansen #include "include/cred.h" 24898127c3SJohn Johansen #include "include/domain.h" 25898127c3SJohn Johansen #include "include/file.h" 26898127c3SJohn Johansen #include "include/ipc.h" 27898127c3SJohn Johansen #include "include/match.h" 28898127c3SJohn Johansen #include "include/path.h" 29898127c3SJohn Johansen #include "include/policy.h" 30cff281f6SJohn Johansen #include "include/policy_ns.h" 31898127c3SJohn Johansen 32898127c3SJohn Johansen /** 33898127c3SJohn Johansen * may_change_ptraced_domain - check if can change profile on ptraced task 3490c436a6SJohn Johansen * @cred: cred of task changing domain 35b2d09ae4SJohn Johansen * @to_label: profile to change to (NOT NULL) 36b2d09ae4SJohn Johansen * @info: message if there is an error 37898127c3SJohn Johansen * 3851775fe7SOleg Nesterov * Check if current is ptraced and if so if the tracing task is allowed 39898127c3SJohn Johansen * to trace the new domain 40898127c3SJohn Johansen * 41898127c3SJohn Johansen * Returns: %0 or error if change not allowed 42898127c3SJohn Johansen */ 4390c436a6SJohn Johansen static int may_change_ptraced_domain(const struct cred *to_cred, 4490c436a6SJohn Johansen struct aa_label *to_label, 45b2d09ae4SJohn Johansen const char **info) 46898127c3SJohn Johansen { 47898127c3SJohn Johansen struct task_struct *tracer; 48637f688dSJohn Johansen struct aa_label *tracerl = NULL; 4990c436a6SJohn Johansen const struct cred *tracer_cred = NULL; 5090c436a6SJohn Johansen 51898127c3SJohn Johansen int error = 0; 52898127c3SJohn Johansen 53898127c3SJohn Johansen rcu_read_lock(); 5451775fe7SOleg Nesterov tracer = ptrace_parent(current); 5590c436a6SJohn Johansen if (tracer) { 56898127c3SJohn Johansen /* released below */ 57637f688dSJohn Johansen tracerl = aa_get_task_label(tracer); 5890c436a6SJohn Johansen tracer_cred = get_task_cred(tracer); 5990c436a6SJohn Johansen } 60898127c3SJohn Johansen /* not ptraced */ 61637f688dSJohn Johansen if (!tracer || unconfined(tracerl)) 62898127c3SJohn Johansen goto out; 63898127c3SJohn Johansen 6490c436a6SJohn Johansen error = aa_may_ptrace(tracer_cred, tracerl, to_cred, to_label, 6590c436a6SJohn Johansen PTRACE_MODE_ATTACH); 66898127c3SJohn Johansen 67898127c3SJohn Johansen out: 6804fdc099SJohn Johansen rcu_read_unlock(); 69637f688dSJohn Johansen aa_put_label(tracerl); 7090c436a6SJohn Johansen put_cred(tracer_cred); 71898127c3SJohn Johansen 72b2d09ae4SJohn Johansen if (error) 73b2d09ae4SJohn Johansen *info = "ptrace prevents transition"; 74898127c3SJohn Johansen return error; 75898127c3SJohn Johansen } 76898127c3SJohn Johansen 7793c98a48SJohn Johansen /**** TODO: dedup to aa_label_match - needs perm and dfa, merging 7893c98a48SJohn Johansen * specifically this is an exact copy of aa_label_match except 7993c98a48SJohn Johansen * aa_compute_perms is replaced with aa_compute_fperms 80*98b824ffSJohn Johansen * and policy->dfa with file->dfa 8193c98a48SJohn Johansen ****/ 8293c98a48SJohn Johansen /* match a profile and its associated ns component if needed 8393c98a48SJohn Johansen * Assumes visibility test has already been done. 8493c98a48SJohn Johansen * If a subns profile is not to be matched should be prescreened with 8593c98a48SJohn Johansen * visibility test. 8693c98a48SJohn Johansen */ 8733fc95d8SJohn Johansen static inline aa_state_t match_component(struct aa_profile *profile, 8893c98a48SJohn Johansen struct aa_profile *tp, 8933fc95d8SJohn Johansen bool stack, aa_state_t state) 9093c98a48SJohn Johansen { 911ad22fccSJohn Johansen struct aa_ruleset *rules = list_first_entry(&profile->rules, 921ad22fccSJohn Johansen typeof(*rules), list); 9393c98a48SJohn Johansen const char *ns_name; 9493c98a48SJohn Johansen 9593c98a48SJohn Johansen if (stack) 96*98b824ffSJohn Johansen state = aa_dfa_match(rules->file->dfa, state, "&"); 9793c98a48SJohn Johansen if (profile->ns == tp->ns) 98*98b824ffSJohn Johansen return aa_dfa_match(rules->file->dfa, state, tp->base.hname); 9993c98a48SJohn Johansen 10093c98a48SJohn Johansen /* try matching with namespace name and then profile */ 10193c98a48SJohn Johansen ns_name = aa_ns_name(profile->ns, tp->ns, true); 102*98b824ffSJohn Johansen state = aa_dfa_match_len(rules->file->dfa, state, ":", 1); 103*98b824ffSJohn Johansen state = aa_dfa_match(rules->file->dfa, state, ns_name); 104*98b824ffSJohn Johansen state = aa_dfa_match_len(rules->file->dfa, state, ":", 1); 105*98b824ffSJohn Johansen return aa_dfa_match(rules->file->dfa, state, tp->base.hname); 10693c98a48SJohn Johansen } 10793c98a48SJohn Johansen 10893c98a48SJohn Johansen /** 10993c98a48SJohn Johansen * label_compound_match - find perms for full compound label 11093c98a48SJohn Johansen * @profile: profile to find perms for 11193c98a48SJohn Johansen * @label: label to check access permissions for 11293c98a48SJohn Johansen * @stack: whether this is a stacking request 113bab1f77fSYang Li * @state: state to start match in 11493c98a48SJohn Johansen * @subns: whether to do permission checks on components in a subns 11593c98a48SJohn Johansen * @request: permissions to request 11693c98a48SJohn Johansen * @perms: perms struct to set 11793c98a48SJohn Johansen * 11893c98a48SJohn Johansen * Returns: 0 on success else ERROR 11993c98a48SJohn Johansen * 12093c98a48SJohn Johansen * For the label A//&B//&C this does the perm match for A//&B//&C 12193c98a48SJohn Johansen * @perms should be preinitialized with allperms OR a previous permission 12293c98a48SJohn Johansen * check to be stacked. 12393c98a48SJohn Johansen */ 12493c98a48SJohn Johansen static int label_compound_match(struct aa_profile *profile, 12593c98a48SJohn Johansen struct aa_label *label, bool stack, 12633fc95d8SJohn Johansen aa_state_t state, bool subns, u32 request, 12793c98a48SJohn Johansen struct aa_perms *perms) 12893c98a48SJohn Johansen { 1291ad22fccSJohn Johansen struct aa_ruleset *rules = list_first_entry(&profile->rules, 1301ad22fccSJohn Johansen typeof(*rules), list); 13193c98a48SJohn Johansen struct aa_profile *tp; 13293c98a48SJohn Johansen struct label_it i; 13393c98a48SJohn Johansen struct path_cond cond = { }; 13493c98a48SJohn Johansen 13593c98a48SJohn Johansen /* find first subcomponent that is visible */ 13693c98a48SJohn Johansen label_for_each(i, label, tp) { 13793c98a48SJohn Johansen if (!aa_ns_visible(profile->ns, tp->ns, subns)) 13893c98a48SJohn Johansen continue; 13993c98a48SJohn Johansen state = match_component(profile, tp, stack, state); 14093c98a48SJohn Johansen if (!state) 14193c98a48SJohn Johansen goto fail; 14293c98a48SJohn Johansen goto next; 14393c98a48SJohn Johansen } 14493c98a48SJohn Johansen 14593c98a48SJohn Johansen /* no component visible */ 14693c98a48SJohn Johansen *perms = allperms; 14793c98a48SJohn Johansen return 0; 14893c98a48SJohn Johansen 14993c98a48SJohn Johansen next: 15093c98a48SJohn Johansen label_for_each_cont(i, label, tp) { 15193c98a48SJohn Johansen if (!aa_ns_visible(profile->ns, tp->ns, subns)) 15293c98a48SJohn Johansen continue; 153*98b824ffSJohn Johansen state = aa_dfa_match(rules->file->dfa, state, "//&"); 15493c98a48SJohn Johansen state = match_component(profile, tp, false, state); 15593c98a48SJohn Johansen if (!state) 15693c98a48SJohn Johansen goto fail; 15793c98a48SJohn Johansen } 158*98b824ffSJohn Johansen *perms = *(aa_lookup_fperms(rules->file, state, &cond)); 15993c98a48SJohn Johansen aa_apply_modes_to_perms(profile, perms); 16093c98a48SJohn Johansen if ((perms->allow & request) != request) 16193c98a48SJohn Johansen return -EACCES; 16293c98a48SJohn Johansen 16393c98a48SJohn Johansen return 0; 16493c98a48SJohn Johansen 16593c98a48SJohn Johansen fail: 16693c98a48SJohn Johansen *perms = nullperms; 16793c98a48SJohn Johansen return -EACCES; 16893c98a48SJohn Johansen } 16993c98a48SJohn Johansen 17093c98a48SJohn Johansen /** 17193c98a48SJohn Johansen * label_components_match - find perms for all subcomponents of a label 17293c98a48SJohn Johansen * @profile: profile to find perms for 17393c98a48SJohn Johansen * @label: label to check access permissions for 17493c98a48SJohn Johansen * @stack: whether this is a stacking request 17593c98a48SJohn Johansen * @start: state to start match in 17693c98a48SJohn Johansen * @subns: whether to do permission checks on components in a subns 17793c98a48SJohn Johansen * @request: permissions to request 17893c98a48SJohn Johansen * @perms: an initialized perms struct to add accumulation to 17993c98a48SJohn Johansen * 18093c98a48SJohn Johansen * Returns: 0 on success else ERROR 18193c98a48SJohn Johansen * 18293c98a48SJohn Johansen * For the label A//&B//&C this does the perm match for each of A and B and C 18393c98a48SJohn Johansen * @perms should be preinitialized with allperms OR a previous permission 18493c98a48SJohn Johansen * check to be stacked. 18593c98a48SJohn Johansen */ 18693c98a48SJohn Johansen static int label_components_match(struct aa_profile *profile, 18793c98a48SJohn Johansen struct aa_label *label, bool stack, 18833fc95d8SJohn Johansen aa_state_t start, bool subns, u32 request, 18993c98a48SJohn Johansen struct aa_perms *perms) 19093c98a48SJohn Johansen { 1911ad22fccSJohn Johansen struct aa_ruleset *rules = list_first_entry(&profile->rules, 1921ad22fccSJohn Johansen typeof(*rules), list); 19393c98a48SJohn Johansen struct aa_profile *tp; 19493c98a48SJohn Johansen struct label_it i; 19593c98a48SJohn Johansen struct aa_perms tmp; 19693c98a48SJohn Johansen struct path_cond cond = { }; 19733fc95d8SJohn Johansen aa_state_t state = 0; 19893c98a48SJohn Johansen 19993c98a48SJohn Johansen /* find first subcomponent to test */ 20093c98a48SJohn Johansen label_for_each(i, label, tp) { 20193c98a48SJohn Johansen if (!aa_ns_visible(profile->ns, tp->ns, subns)) 20293c98a48SJohn Johansen continue; 20393c98a48SJohn Johansen state = match_component(profile, tp, stack, start); 20493c98a48SJohn Johansen if (!state) 20593c98a48SJohn Johansen goto fail; 20693c98a48SJohn Johansen goto next; 20793c98a48SJohn Johansen } 20893c98a48SJohn Johansen 20993c98a48SJohn Johansen /* no subcomponents visible - no change in perms */ 21093c98a48SJohn Johansen return 0; 21193c98a48SJohn Johansen 21293c98a48SJohn Johansen next: 213*98b824ffSJohn Johansen tmp = *(aa_lookup_fperms(rules->file, state, &cond)); 21493c98a48SJohn Johansen aa_apply_modes_to_perms(profile, &tmp); 21593c98a48SJohn Johansen aa_perms_accum(perms, &tmp); 21693c98a48SJohn Johansen label_for_each_cont(i, label, tp) { 21793c98a48SJohn Johansen if (!aa_ns_visible(profile->ns, tp->ns, subns)) 21893c98a48SJohn Johansen continue; 21993c98a48SJohn Johansen state = match_component(profile, tp, stack, start); 22093c98a48SJohn Johansen if (!state) 22193c98a48SJohn Johansen goto fail; 222*98b824ffSJohn Johansen tmp = *(aa_lookup_fperms(rules->file, state, &cond)); 22393c98a48SJohn Johansen aa_apply_modes_to_perms(profile, &tmp); 22493c98a48SJohn Johansen aa_perms_accum(perms, &tmp); 22593c98a48SJohn Johansen } 22693c98a48SJohn Johansen 22793c98a48SJohn Johansen if ((perms->allow & request) != request) 22893c98a48SJohn Johansen return -EACCES; 22993c98a48SJohn Johansen 23093c98a48SJohn Johansen return 0; 23193c98a48SJohn Johansen 23293c98a48SJohn Johansen fail: 23393c98a48SJohn Johansen *perms = nullperms; 23493c98a48SJohn Johansen return -EACCES; 23593c98a48SJohn Johansen } 23693c98a48SJohn Johansen 23793c98a48SJohn Johansen /** 23893c98a48SJohn Johansen * label_match - do a multi-component label match 23993c98a48SJohn Johansen * @profile: profile to match against (NOT NULL) 24093c98a48SJohn Johansen * @label: label to match (NOT NULL) 24193c98a48SJohn Johansen * @stack: whether this is a stacking request 24293c98a48SJohn Johansen * @state: state to start in 24393c98a48SJohn Johansen * @subns: whether to match subns components 24493c98a48SJohn Johansen * @request: permission request 24593c98a48SJohn Johansen * @perms: Returns computed perms (NOT NULL) 24693c98a48SJohn Johansen * 24793c98a48SJohn Johansen * Returns: the state the match finished in, may be the none matching state 24893c98a48SJohn Johansen */ 24993c98a48SJohn Johansen static int label_match(struct aa_profile *profile, struct aa_label *label, 25033fc95d8SJohn Johansen bool stack, aa_state_t state, bool subns, u32 request, 25193c98a48SJohn Johansen struct aa_perms *perms) 25293c98a48SJohn Johansen { 25393c98a48SJohn Johansen int error; 25493c98a48SJohn Johansen 25593c98a48SJohn Johansen *perms = nullperms; 25693c98a48SJohn Johansen error = label_compound_match(profile, label, stack, state, subns, 25793c98a48SJohn Johansen request, perms); 25893c98a48SJohn Johansen if (!error) 25993c98a48SJohn Johansen return error; 26093c98a48SJohn Johansen 26193c98a48SJohn Johansen *perms = allperms; 26293c98a48SJohn Johansen return label_components_match(profile, label, stack, state, subns, 26393c98a48SJohn Johansen request, perms); 26493c98a48SJohn Johansen } 26593c98a48SJohn Johansen 26693c98a48SJohn Johansen /******* end TODO: dedup *****/ 26793c98a48SJohn Johansen 268898127c3SJohn Johansen /** 269898127c3SJohn Johansen * change_profile_perms - find permissions for change_profile 270898127c3SJohn Johansen * @profile: the current profile (NOT NULL) 27193c98a48SJohn Johansen * @target: label to transition to (NOT NULL) 27293c98a48SJohn Johansen * @stack: whether this is a stacking request 273898127c3SJohn Johansen * @request: requested perms 274898127c3SJohn Johansen * @start: state to start matching in 27576426c9dSGaosheng Cui * @perms: Returns computed perms (NOT NULL) 276898127c3SJohn Johansen * 27793c98a48SJohn Johansen * 278898127c3SJohn Johansen * Returns: permission set 27993c98a48SJohn Johansen * 28093c98a48SJohn Johansen * currently only matches full label A//&B//&C or individual components A, B, C 28193c98a48SJohn Johansen * not arbitrary combinations. Eg. A//&B, C 282898127c3SJohn Johansen */ 28393c98a48SJohn Johansen static int change_profile_perms(struct aa_profile *profile, 28493c98a48SJohn Johansen struct aa_label *target, bool stack, 28533fc95d8SJohn Johansen u32 request, aa_state_t start, 28693c98a48SJohn Johansen struct aa_perms *perms) 28793c98a48SJohn Johansen { 28893c98a48SJohn Johansen if (profile_unconfined(profile)) { 28993c98a48SJohn Johansen perms->allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC; 29093c98a48SJohn Johansen perms->audit = perms->quiet = perms->kill = 0; 29193c98a48SJohn Johansen return 0; 29293c98a48SJohn Johansen } 29393c98a48SJohn Johansen 29493c98a48SJohn Johansen /* TODO: add profile in ns screening */ 29593c98a48SJohn Johansen return label_match(profile, target, stack, start, true, request, perms); 29693c98a48SJohn Johansen } 29793c98a48SJohn Johansen 298898127c3SJohn Johansen /** 2998e51f908SMatthew Garrett * aa_xattrs_match - check whether a file matches the xattrs defined in profile 3008e51f908SMatthew Garrett * @bprm: binprm struct for the process to validate 3018e51f908SMatthew Garrett * @profile: profile to match against (NOT NULL) 30273f488cdSJohn Johansen * @state: state to start match in 3038e51f908SMatthew Garrett * 3048e51f908SMatthew Garrett * Returns: number of extended attributes that matched, or < 0 on error 3058e51f908SMatthew Garrett */ 3068e51f908SMatthew Garrett static int aa_xattrs_match(const struct linux_binprm *bprm, 30733fc95d8SJohn Johansen struct aa_profile *profile, aa_state_t state) 3088e51f908SMatthew Garrett { 3098e51f908SMatthew Garrett int i; 3108e51f908SMatthew Garrett struct dentry *d; 3118e51f908SMatthew Garrett char *value = NULL; 312217af7e2SJohn Johansen struct aa_attachment *attach = &profile->attach; 31393761c93SLinus Torvalds int size, value_size = 0, ret = attach->xattr_count; 3148e51f908SMatthew Garrett 315217af7e2SJohn Johansen if (!bprm || !attach->xattr_count) 3168e51f908SMatthew Garrett return 0; 3178c62ed27SJohn Johansen might_sleep(); 3188e51f908SMatthew Garrett 31973f488cdSJohn Johansen /* transition from exec match to xattr set */ 320*98b824ffSJohn Johansen state = aa_dfa_outofband_transition(attach->xmatch->dfa, state); 3218e51f908SMatthew Garrett d = bprm->file->f_path.dentry; 3228e51f908SMatthew Garrett 323217af7e2SJohn Johansen for (i = 0; i < attach->xattr_count; i++) { 3244609e1f1SChristian Brauner size = vfs_getxattr_alloc(&nop_mnt_idmap, d, attach->xattrs[i], 325c7c7a1a1STycho Andersen &value, value_size, GFP_KERNEL); 32673f488cdSJohn Johansen if (size >= 0) { 3272d63dd43SJohn Johansen u32 index, perm; 3288e51f908SMatthew Garrett 3290df34a64SJohn Johansen /* 3300df34a64SJohn Johansen * Check the xattr presence before value. This ensure 3310df34a64SJohn Johansen * that not present xattr can be distinguished from a 0 3320df34a64SJohn Johansen * length value or rule that matches any value 3330df34a64SJohn Johansen */ 334*98b824ffSJohn Johansen state = aa_dfa_null_transition(attach->xmatch->dfa, 335048d4954SJohn Johansen state); 3360df34a64SJohn Johansen /* Check xattr value */ 337*98b824ffSJohn Johansen state = aa_dfa_match_len(attach->xmatch->dfa, state, 338048d4954SJohn Johansen value, size); 339*98b824ffSJohn Johansen index = ACCEPT_TABLE(attach->xmatch->dfa)[state]; 340*98b824ffSJohn Johansen perm = attach->xmatch->perms[index].allow; 34173f488cdSJohn Johansen if (!(perm & MAY_EXEC)) { 3428e51f908SMatthew Garrett ret = -EINVAL; 3438e51f908SMatthew Garrett goto out; 3448e51f908SMatthew Garrett } 34573f488cdSJohn Johansen } 34673f488cdSJohn Johansen /* transition to next element */ 347*98b824ffSJohn Johansen state = aa_dfa_outofband_transition(attach->xmatch->dfa, state); 34873f488cdSJohn Johansen if (size < 0) { 34973f488cdSJohn Johansen /* 35073f488cdSJohn Johansen * No xattr match, so verify if transition to 35173f488cdSJohn Johansen * next element was valid. IFF so the xattr 35273f488cdSJohn Johansen * was optional. 35373f488cdSJohn Johansen */ 35473f488cdSJohn Johansen if (!state) { 3558e51f908SMatthew Garrett ret = -EINVAL; 3568e51f908SMatthew Garrett goto out; 3578e51f908SMatthew Garrett } 35873f488cdSJohn Johansen /* don't count missing optional xattr as matched */ 35973f488cdSJohn Johansen ret--; 3608e51f908SMatthew Garrett } 3618e51f908SMatthew Garrett } 3628e51f908SMatthew Garrett 3638e51f908SMatthew Garrett out: 3648e51f908SMatthew Garrett kfree(value); 3658e51f908SMatthew Garrett return ret; 3668e51f908SMatthew Garrett } 3678e51f908SMatthew Garrett 3688e51f908SMatthew Garrett /** 3698c62ed27SJohn Johansen * find_attach - do attachment search for unconfined processes 37076426c9dSGaosheng Cui * @bprm: binprm structure of transitioning task 3718c62ed27SJohn Johansen * @ns: the current namespace (NOT NULL) 37276426c9dSGaosheng Cui * @head: profile list to walk (NOT NULL) 37376426c9dSGaosheng Cui * @name: to match against (NOT NULL) 37476426c9dSGaosheng Cui * @info: info message if there was an error (NOT NULL) 375898127c3SJohn Johansen * 376898127c3SJohn Johansen * Do a linear search on the profiles in the list. There is a matching 377898127c3SJohn Johansen * preference where an exact match is preferred over a name which uses 378898127c3SJohn Johansen * expressions to match, and matching expressions with the greatest 379898127c3SJohn Johansen * xmatch_len are preferred. 380898127c3SJohn Johansen * 381898127c3SJohn Johansen * Requires: @head not be shared or have appropriate locks held 382898127c3SJohn Johansen * 3838c62ed27SJohn Johansen * Returns: label or NULL if no match found 384898127c3SJohn Johansen */ 3858c62ed27SJohn Johansen static struct aa_label *find_attach(const struct linux_binprm *bprm, 3868c62ed27SJohn Johansen struct aa_ns *ns, struct list_head *head, 3878c62ed27SJohn Johansen const char *name, const char **info) 388898127c3SJohn Johansen { 38921f60661SJohn Johansen int candidate_len = 0, candidate_xattrs = 0; 390844b8292SJohn Johansen bool conflict = false; 391898127c3SJohn Johansen struct aa_profile *profile, *candidate = NULL; 392898127c3SJohn Johansen 39321f60661SJohn Johansen AA_BUG(!name); 39421f60661SJohn Johansen AA_BUG(!head); 39521f60661SJohn Johansen 3968c62ed27SJohn Johansen rcu_read_lock(); 3978c62ed27SJohn Johansen restart: 39801e2b670SJohn Johansen list_for_each_entry_rcu(profile, head, base.list) { 399217af7e2SJohn Johansen struct aa_attachment *attach = &profile->attach; 400217af7e2SJohn Johansen 40106d426d1SJohn Johansen if (profile->label.flags & FLAG_NULL && 40206d426d1SJohn Johansen &profile->label == ns_unconfined(profile->ns)) 403898127c3SJohn Johansen continue; 40406d426d1SJohn Johansen 4058e51f908SMatthew Garrett /* Find the "best" matching profile. Profiles must 4068e51f908SMatthew Garrett * match the path and extended attributes (if any) 4078e51f908SMatthew Garrett * associated with the file. A more specific path 4088e51f908SMatthew Garrett * match will be preferred over a less specific one, 4098e51f908SMatthew Garrett * and a match with more matching extended attributes 4108e51f908SMatthew Garrett * will be preferred over one with fewer. If the best 4118e51f908SMatthew Garrett * match has both the same level of path specificity 4128e51f908SMatthew Garrett * and the same number of matching extended attributes 4138e51f908SMatthew Garrett * as another profile, signal a conflict and refuse to 4148e51f908SMatthew Garrett * match. 4158e51f908SMatthew Garrett */ 416*98b824ffSJohn Johansen if (attach->xmatch->dfa) { 41733fc95d8SJohn Johansen unsigned int count; 41833fc95d8SJohn Johansen aa_state_t state; 4192d63dd43SJohn Johansen u32 index, perm; 420844b8292SJohn Johansen 421*98b824ffSJohn Johansen state = aa_dfa_leftmatch(attach->xmatch->dfa, 422*98b824ffSJohn Johansen attach->xmatch->start[AA_CLASS_XMATCH], 42321f60661SJohn Johansen name, &count); 424*98b824ffSJohn Johansen index = ACCEPT_TABLE(attach->xmatch->dfa)[state]; 425*98b824ffSJohn Johansen perm = attach->xmatch->perms[index].allow; 426898127c3SJohn Johansen /* any accepting state means a valid match. */ 427898127c3SJohn Johansen if (perm & MAY_EXEC) { 4288c62ed27SJohn Johansen int ret = 0; 4298e51f908SMatthew Garrett 43021f60661SJohn Johansen if (count < candidate_len) 43121f60661SJohn Johansen continue; 43221f60661SJohn Johansen 433217af7e2SJohn Johansen if (bprm && attach->xattr_count) { 4348c62ed27SJohn Johansen long rev = READ_ONCE(ns->revision); 4358c62ed27SJohn Johansen 4368c62ed27SJohn Johansen if (!aa_get_profile_not0(profile)) 4378c62ed27SJohn Johansen goto restart; 4388c62ed27SJohn Johansen rcu_read_unlock(); 4398c62ed27SJohn Johansen ret = aa_xattrs_match(bprm, profile, 4408c62ed27SJohn Johansen state); 4418c62ed27SJohn Johansen rcu_read_lock(); 4428c62ed27SJohn Johansen aa_put_profile(profile); 4438c62ed27SJohn Johansen if (rev != 4448c62ed27SJohn Johansen READ_ONCE(ns->revision)) 4458c62ed27SJohn Johansen /* policy changed */ 4468c62ed27SJohn Johansen goto restart; 4478c62ed27SJohn Johansen /* 4488c62ed27SJohn Johansen * Fail matching if the xattrs don't 4498c62ed27SJohn Johansen * match 4508c62ed27SJohn Johansen */ 4518e51f908SMatthew Garrett if (ret < 0) 4528e51f908SMatthew Garrett continue; 4538c62ed27SJohn Johansen } 45473f488cdSJohn Johansen /* 45573f488cdSJohn Johansen * TODO: allow for more flexible best match 45673f488cdSJohn Johansen * 45773f488cdSJohn Johansen * The new match isn't more specific 4588e51f908SMatthew Garrett * than the current best match 4598e51f908SMatthew Garrett */ 46021f60661SJohn Johansen if (count == candidate_len && 46121f60661SJohn Johansen ret <= candidate_xattrs) { 4628e51f908SMatthew Garrett /* Match is equivalent, so conflict */ 46321f60661SJohn Johansen if (ret == candidate_xattrs) 4641a3881d3SMatthew Garrett conflict = true; 4651a3881d3SMatthew Garrett continue; 4661a3881d3SMatthew Garrett } 4678e51f908SMatthew Garrett 4688e51f908SMatthew Garrett /* Either the same length with more matching 4698e51f908SMatthew Garrett * xattrs, or a longer match 4708e51f908SMatthew Garrett */ 471898127c3SJohn Johansen candidate = profile; 472217af7e2SJohn Johansen candidate_len = max(count, attach->xmatch_len); 47321f60661SJohn Johansen candidate_xattrs = ret; 474844b8292SJohn Johansen conflict = false; 475844b8292SJohn Johansen } 4768c62ed27SJohn Johansen } else if (!strcmp(profile->base.name, name)) { 47773f488cdSJohn Johansen /* 47873f488cdSJohn Johansen * old exact non-re match, without conditionals such 47973f488cdSJohn Johansen * as xattrs. no more searching required 48073f488cdSJohn Johansen */ 4818c62ed27SJohn Johansen candidate = profile; 4828c62ed27SJohn Johansen goto out; 4838c62ed27SJohn Johansen } 484898127c3SJohn Johansen } 485898127c3SJohn Johansen 4868c62ed27SJohn Johansen if (!candidate || conflict) { 4878c62ed27SJohn Johansen if (conflict) 488844b8292SJohn Johansen *info = "conflicting profile attachments"; 4898c62ed27SJohn Johansen rcu_read_unlock(); 490844b8292SJohn Johansen return NULL; 491844b8292SJohn Johansen } 492844b8292SJohn Johansen 4938c62ed27SJohn Johansen out: 4948c62ed27SJohn Johansen candidate = aa_get_newest_profile(candidate); 49501e2b670SJohn Johansen rcu_read_unlock(); 496898127c3SJohn Johansen 4978c62ed27SJohn Johansen return &candidate->label; 498898127c3SJohn Johansen } 499898127c3SJohn Johansen 500898127c3SJohn Johansen static const char *next_name(int xtype, const char *name) 501898127c3SJohn Johansen { 502898127c3SJohn Johansen return NULL; 503898127c3SJohn Johansen } 504898127c3SJohn Johansen 505898127c3SJohn Johansen /** 506898127c3SJohn Johansen * x_table_lookup - lookup an x transition name via transition table 507898127c3SJohn Johansen * @profile: current profile (NOT NULL) 508898127c3SJohn Johansen * @xindex: index into x transition table 50993c98a48SJohn Johansen * @name: returns: name tested to find label (NOT NULL) 510898127c3SJohn Johansen * 51193c98a48SJohn Johansen * Returns: refcounted label, or NULL on failure (MAYBE NULL) 512898127c3SJohn Johansen */ 5132ea3ffb7SJohn Johansen struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, 51493c98a48SJohn Johansen const char **name) 515898127c3SJohn Johansen { 5161ad22fccSJohn Johansen struct aa_ruleset *rules = list_first_entry(&profile->rules, 5171ad22fccSJohn Johansen typeof(*rules), list); 51893c98a48SJohn Johansen struct aa_label *label = NULL; 519898127c3SJohn Johansen u32 xtype = xindex & AA_X_TYPE_MASK; 520898127c3SJohn Johansen int index = xindex & AA_X_INDEX_MASK; 52193c98a48SJohn Johansen 52293c98a48SJohn Johansen AA_BUG(!name); 523898127c3SJohn Johansen 524898127c3SJohn Johansen /* index is guaranteed to be in range, validated at load time */ 52593c98a48SJohn Johansen /* TODO: move lookup parsing to unpack time so this is a straight 52693c98a48SJohn Johansen * index into the resultant label 52793c98a48SJohn Johansen */ 528*98b824ffSJohn Johansen for (*name = rules->file->trans.table[index]; !label && *name; 52993c98a48SJohn Johansen *name = next_name(xtype, *name)) { 530898127c3SJohn Johansen if (xindex & AA_X_CHILD) { 53193c98a48SJohn Johansen struct aa_profile *new_profile; 532898127c3SJohn Johansen /* release by caller */ 53393c98a48SJohn Johansen new_profile = aa_find_child(profile, *name); 53493c98a48SJohn Johansen if (new_profile) 53593c98a48SJohn Johansen label = &new_profile->label; 536898127c3SJohn Johansen continue; 537898127c3SJohn Johansen } 5388ac2ca32SSebastian Andrzej Siewior label = aa_label_parse(&profile->label, *name, GFP_KERNEL, 53993c98a48SJohn Johansen true, false); 54093c98a48SJohn Johansen if (IS_ERR(label)) 54193c98a48SJohn Johansen label = NULL; 542898127c3SJohn Johansen } 543898127c3SJohn Johansen 544898127c3SJohn Johansen /* released by caller */ 545898127c3SJohn Johansen 54693c98a48SJohn Johansen return label; 547898127c3SJohn Johansen } 548898127c3SJohn Johansen 549898127c3SJohn Johansen /** 55093c98a48SJohn Johansen * x_to_label - get target label for a given xindex 551898127c3SJohn Johansen * @profile: current profile (NOT NULL) 5528e51f908SMatthew Garrett * @bprm: binprm structure of transitioning task 553898127c3SJohn Johansen * @name: name to lookup (NOT NULL) 554898127c3SJohn Johansen * @xindex: index into x transition table 55593c98a48SJohn Johansen * @lookupname: returns: name used in lookup if one was specified (NOT NULL) 55676426c9dSGaosheng Cui * @info: info message if there was an error (NOT NULL) 557898127c3SJohn Johansen * 55893c98a48SJohn Johansen * find label for a transition index 559898127c3SJohn Johansen * 56093c98a48SJohn Johansen * Returns: refcounted label or NULL if not found available 561898127c3SJohn Johansen */ 56293c98a48SJohn Johansen static struct aa_label *x_to_label(struct aa_profile *profile, 5638e51f908SMatthew Garrett const struct linux_binprm *bprm, 56493c98a48SJohn Johansen const char *name, u32 xindex, 56593c98a48SJohn Johansen const char **lookupname, 56693c98a48SJohn Johansen const char **info) 567898127c3SJohn Johansen { 5681ad22fccSJohn Johansen struct aa_ruleset *rules = list_first_entry(&profile->rules, 5691ad22fccSJohn Johansen typeof(*rules), list); 57093c98a48SJohn Johansen struct aa_label *new = NULL; 57198849dffSJohn Johansen struct aa_ns *ns = profile->ns; 572898127c3SJohn Johansen u32 xtype = xindex & AA_X_TYPE_MASK; 57393c98a48SJohn Johansen const char *stack = NULL; 574898127c3SJohn Johansen 575898127c3SJohn Johansen switch (xtype) { 576898127c3SJohn Johansen case AA_X_NONE: 577898127c3SJohn Johansen /* fail exec unless ix || ux fallback - handled by caller */ 57893c98a48SJohn Johansen *lookupname = NULL; 57993c98a48SJohn Johansen break; 58093c98a48SJohn Johansen case AA_X_TABLE: 58193c98a48SJohn Johansen /* TODO: fix when perm mapping done at unload */ 582*98b824ffSJohn Johansen stack = rules->file->trans.table[xindex & AA_X_INDEX_MASK]; 58393c98a48SJohn Johansen if (*stack != '&') { 58493c98a48SJohn Johansen /* released by caller */ 58593c98a48SJohn Johansen new = x_table_lookup(profile, xindex, lookupname); 58693c98a48SJohn Johansen stack = NULL; 58793c98a48SJohn Johansen break; 58893c98a48SJohn Johansen } 589df561f66SGustavo A. R. Silva fallthrough; /* to X_NAME */ 590898127c3SJohn Johansen case AA_X_NAME: 591898127c3SJohn Johansen if (xindex & AA_X_CHILD) 592898127c3SJohn Johansen /* released by caller */ 5938e51f908SMatthew Garrett new = find_attach(bprm, ns, &profile->base.profiles, 594844b8292SJohn Johansen name, info); 595898127c3SJohn Johansen else 596898127c3SJohn Johansen /* released by caller */ 5978e51f908SMatthew Garrett new = find_attach(bprm, ns, &ns->base.profiles, 598844b8292SJohn Johansen name, info); 59993c98a48SJohn Johansen *lookupname = name; 600898127c3SJohn Johansen break; 601898127c3SJohn Johansen } 602898127c3SJohn Johansen 60393c98a48SJohn Johansen if (!new) { 60493c98a48SJohn Johansen if (xindex & AA_X_INHERIT) { 60593c98a48SJohn Johansen /* (p|c|n)ix - don't change profile but do 60693c98a48SJohn Johansen * use the newest version 60793c98a48SJohn Johansen */ 60893c98a48SJohn Johansen *info = "ix fallback"; 60993c98a48SJohn Johansen /* no profile && no error */ 61093c98a48SJohn Johansen new = aa_get_newest_label(&profile->label); 61193c98a48SJohn Johansen } else if (xindex & AA_X_UNCONFINED) { 61293c98a48SJohn Johansen new = aa_get_newest_label(ns_unconfined(profile->ns)); 61393c98a48SJohn Johansen *info = "ux fallback"; 61493c98a48SJohn Johansen } 61593c98a48SJohn Johansen } 61693c98a48SJohn Johansen 61793c98a48SJohn Johansen if (new && stack) { 61893c98a48SJohn Johansen /* base the stack on post domain transition */ 61993c98a48SJohn Johansen struct aa_label *base = new; 62093c98a48SJohn Johansen 6218ac2ca32SSebastian Andrzej Siewior new = aa_label_parse(base, stack, GFP_KERNEL, true, false); 62293c98a48SJohn Johansen if (IS_ERR(new)) 62393c98a48SJohn Johansen new = NULL; 62493c98a48SJohn Johansen aa_put_label(base); 62593c98a48SJohn Johansen } 62693c98a48SJohn Johansen 627898127c3SJohn Johansen /* released by caller */ 62893c98a48SJohn Johansen return new; 62993c98a48SJohn Johansen } 63093c98a48SJohn Johansen 63190c436a6SJohn Johansen static struct aa_label *profile_transition(const struct cred *subj_cred, 63290c436a6SJohn Johansen struct aa_profile *profile, 63393c98a48SJohn Johansen const struct linux_binprm *bprm, 63493c98a48SJohn Johansen char *buffer, struct path_cond *cond, 63593c98a48SJohn Johansen bool *secure_exec) 63693c98a48SJohn Johansen { 6371ad22fccSJohn Johansen struct aa_ruleset *rules = list_first_entry(&profile->rules, 6381ad22fccSJohn Johansen typeof(*rules), list); 63993c98a48SJohn Johansen struct aa_label *new = NULL; 64093c98a48SJohn Johansen const char *info = NULL, *name = NULL, *target = NULL; 641*98b824ffSJohn Johansen aa_state_t state = rules->file->start[AA_CLASS_FILE]; 64293c98a48SJohn Johansen struct aa_perms perms = {}; 64393c98a48SJohn Johansen bool nonewprivs = false; 64493c98a48SJohn Johansen int error = 0; 64593c98a48SJohn Johansen 64693c98a48SJohn Johansen AA_BUG(!profile); 64793c98a48SJohn Johansen AA_BUG(!bprm); 64893c98a48SJohn Johansen AA_BUG(!buffer); 64993c98a48SJohn Johansen 65093c98a48SJohn Johansen error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer, 65193c98a48SJohn Johansen &name, &info, profile->disconnected); 65293c98a48SJohn Johansen if (error) { 65393c98a48SJohn Johansen if (profile_unconfined(profile) || 65493c98a48SJohn Johansen (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) { 65593c98a48SJohn Johansen AA_DEBUG("name lookup ix on error"); 65693c98a48SJohn Johansen error = 0; 65793c98a48SJohn Johansen new = aa_get_newest_label(&profile->label); 65893c98a48SJohn Johansen } 65993c98a48SJohn Johansen name = bprm->filename; 66093c98a48SJohn Johansen goto audit; 66193c98a48SJohn Johansen } 66293c98a48SJohn Johansen 66393c98a48SJohn Johansen if (profile_unconfined(profile)) { 6648e51f908SMatthew Garrett new = find_attach(bprm, profile->ns, 6658e51f908SMatthew Garrett &profile->ns->base.profiles, name, &info); 66693c98a48SJohn Johansen if (new) { 66793c98a48SJohn Johansen AA_DEBUG("unconfined attached to new label"); 66893c98a48SJohn Johansen return new; 66993c98a48SJohn Johansen } 67093c98a48SJohn Johansen AA_DEBUG("unconfined exec no attachment"); 67193c98a48SJohn Johansen return aa_get_newest_label(&profile->label); 67293c98a48SJohn Johansen } 67393c98a48SJohn Johansen 67493c98a48SJohn Johansen /* find exec permissions for name */ 675*98b824ffSJohn Johansen state = aa_str_perms(rules->file, state, name, cond, &perms); 67693c98a48SJohn Johansen if (perms.allow & MAY_EXEC) { 67793c98a48SJohn Johansen /* exec permission determine how to transition */ 6788e51f908SMatthew Garrett new = x_to_label(profile, bprm, name, perms.xindex, &target, 6798e51f908SMatthew Garrett &info); 68093c98a48SJohn Johansen if (new && new->proxy == profile->label.proxy && info) { 68193c98a48SJohn Johansen /* hack ix fallback - improve how this is detected */ 68293c98a48SJohn Johansen goto audit; 68393c98a48SJohn Johansen } else if (!new) { 68493c98a48SJohn Johansen error = -EACCES; 68593c98a48SJohn Johansen info = "profile transition not found"; 68693c98a48SJohn Johansen /* remove MAY_EXEC to audit as failure */ 68793c98a48SJohn Johansen perms.allow &= ~MAY_EXEC; 68893c98a48SJohn Johansen } 68993c98a48SJohn Johansen } else if (COMPLAIN_MODE(profile)) { 69093c98a48SJohn Johansen /* no exec permission - learning mode */ 6915d7c44efSJohn Johansen struct aa_profile *new_profile = NULL; 6925d7c44efSJohn Johansen 69358f89ce5SJohn Johansen new_profile = aa_new_learning_profile(profile, false, name, 6945d7c44efSJohn Johansen GFP_KERNEL); 69593c98a48SJohn Johansen if (!new_profile) { 69693c98a48SJohn Johansen error = -ENOMEM; 69793c98a48SJohn Johansen info = "could not create null profile"; 69893c98a48SJohn Johansen } else { 69993c98a48SJohn Johansen error = -EACCES; 70093c98a48SJohn Johansen new = &new_profile->label; 70193c98a48SJohn Johansen } 70293c98a48SJohn Johansen perms.xindex |= AA_X_UNSAFE; 70393c98a48SJohn Johansen } else 70493c98a48SJohn Johansen /* fail exec */ 70593c98a48SJohn Johansen error = -EACCES; 70693c98a48SJohn Johansen 70793c98a48SJohn Johansen if (!new) 70893c98a48SJohn Johansen goto audit; 70993c98a48SJohn Johansen 71093c98a48SJohn Johansen 71193c98a48SJohn Johansen if (!(perms.xindex & AA_X_UNSAFE)) { 71293c98a48SJohn Johansen if (DEBUG_ON) { 71393c98a48SJohn Johansen dbg_printk("apparmor: scrubbing environment variables" 71493c98a48SJohn Johansen " for %s profile=", name); 7158ac2ca32SSebastian Andrzej Siewior aa_label_printk(new, GFP_KERNEL); 71693c98a48SJohn Johansen dbg_printk("\n"); 71793c98a48SJohn Johansen } 71893c98a48SJohn Johansen *secure_exec = true; 71993c98a48SJohn Johansen } 72093c98a48SJohn Johansen 72193c98a48SJohn Johansen audit: 72290c436a6SJohn Johansen aa_audit_file(subj_cred, profile, &perms, OP_EXEC, MAY_EXEC, name, 72390c436a6SJohn Johansen target, new, 72493c98a48SJohn Johansen cond->uid, info, error); 72593c98a48SJohn Johansen if (!new || nonewprivs) { 72693c98a48SJohn Johansen aa_put_label(new); 72793c98a48SJohn Johansen return ERR_PTR(error); 72893c98a48SJohn Johansen } 72993c98a48SJohn Johansen 73093c98a48SJohn Johansen return new; 73193c98a48SJohn Johansen } 73293c98a48SJohn Johansen 73390c436a6SJohn Johansen static int profile_onexec(const struct cred *subj_cred, 73490c436a6SJohn Johansen struct aa_profile *profile, struct aa_label *onexec, 73593c98a48SJohn Johansen bool stack, const struct linux_binprm *bprm, 73693c98a48SJohn Johansen char *buffer, struct path_cond *cond, 73793c98a48SJohn Johansen bool *secure_exec) 73893c98a48SJohn Johansen { 7391ad22fccSJohn Johansen struct aa_ruleset *rules = list_first_entry(&profile->rules, 7401ad22fccSJohn Johansen typeof(*rules), list); 741*98b824ffSJohn Johansen aa_state_t state = rules->file->start[AA_CLASS_FILE]; 74293c98a48SJohn Johansen struct aa_perms perms = {}; 74393c98a48SJohn Johansen const char *xname = NULL, *info = "change_profile onexec"; 74493c98a48SJohn Johansen int error = -EACCES; 74593c98a48SJohn Johansen 74693c98a48SJohn Johansen AA_BUG(!profile); 74793c98a48SJohn Johansen AA_BUG(!onexec); 74893c98a48SJohn Johansen AA_BUG(!bprm); 74993c98a48SJohn Johansen AA_BUG(!buffer); 75093c98a48SJohn Johansen 75193c98a48SJohn Johansen if (profile_unconfined(profile)) { 75293c98a48SJohn Johansen /* change_profile on exec already granted */ 75393c98a48SJohn Johansen /* 75493c98a48SJohn Johansen * NOTE: Domain transitions from unconfined are allowed 75593c98a48SJohn Johansen * even when no_new_privs is set because this aways results 75693c98a48SJohn Johansen * in a further reduction of permissions. 75793c98a48SJohn Johansen */ 75893c98a48SJohn Johansen return 0; 75993c98a48SJohn Johansen } 76093c98a48SJohn Johansen 76193c98a48SJohn Johansen error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer, 76293c98a48SJohn Johansen &xname, &info, profile->disconnected); 76393c98a48SJohn Johansen if (error) { 76493c98a48SJohn Johansen if (profile_unconfined(profile) || 76593c98a48SJohn Johansen (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) { 76693c98a48SJohn Johansen AA_DEBUG("name lookup ix on error"); 76793c98a48SJohn Johansen error = 0; 76893c98a48SJohn Johansen } 76993c98a48SJohn Johansen xname = bprm->filename; 77093c98a48SJohn Johansen goto audit; 77193c98a48SJohn Johansen } 77293c98a48SJohn Johansen 77393c98a48SJohn Johansen /* find exec permissions for name */ 774*98b824ffSJohn Johansen state = aa_str_perms(rules->file, state, xname, cond, &perms); 77593c98a48SJohn Johansen if (!(perms.allow & AA_MAY_ONEXEC)) { 77693c98a48SJohn Johansen info = "no change_onexec valid for executable"; 77793c98a48SJohn Johansen goto audit; 77893c98a48SJohn Johansen } 77993c98a48SJohn Johansen /* test if this exec can be paired with change_profile onexec. 78093c98a48SJohn Johansen * onexec permission is linked to exec with a standard pairing 78193c98a48SJohn Johansen * exec\0change_profile 78293c98a48SJohn Johansen */ 783*98b824ffSJohn Johansen state = aa_dfa_null_transition(rules->file->dfa, state); 78493c98a48SJohn Johansen error = change_profile_perms(profile, onexec, stack, AA_MAY_ONEXEC, 78593c98a48SJohn Johansen state, &perms); 78693c98a48SJohn Johansen if (error) { 78793c98a48SJohn Johansen perms.allow &= ~AA_MAY_ONEXEC; 78893c98a48SJohn Johansen goto audit; 78993c98a48SJohn Johansen } 79093c98a48SJohn Johansen 79193c98a48SJohn Johansen if (!(perms.xindex & AA_X_UNSAFE)) { 79293c98a48SJohn Johansen if (DEBUG_ON) { 79393c98a48SJohn Johansen dbg_printk("apparmor: scrubbing environment " 79493c98a48SJohn Johansen "variables for %s label=", xname); 7958ac2ca32SSebastian Andrzej Siewior aa_label_printk(onexec, GFP_KERNEL); 79693c98a48SJohn Johansen dbg_printk("\n"); 79793c98a48SJohn Johansen } 79893c98a48SJohn Johansen *secure_exec = true; 79993c98a48SJohn Johansen } 80093c98a48SJohn Johansen 80193c98a48SJohn Johansen audit: 80290c436a6SJohn Johansen return aa_audit_file(subj_cred, profile, &perms, OP_EXEC, 80390c436a6SJohn Johansen AA_MAY_ONEXEC, xname, 80493c98a48SJohn Johansen NULL, onexec, cond->uid, info, error); 80593c98a48SJohn Johansen } 80693c98a48SJohn Johansen 80793c98a48SJohn Johansen /* ensure none ns domain transitions are correctly applied with onexec */ 80893c98a48SJohn Johansen 80990c436a6SJohn Johansen static struct aa_label *handle_onexec(const struct cred *subj_cred, 81090c436a6SJohn Johansen struct aa_label *label, 81193c98a48SJohn Johansen struct aa_label *onexec, bool stack, 81293c98a48SJohn Johansen const struct linux_binprm *bprm, 81393c98a48SJohn Johansen char *buffer, struct path_cond *cond, 81493c98a48SJohn Johansen bool *unsafe) 81593c98a48SJohn Johansen { 81693c98a48SJohn Johansen struct aa_profile *profile; 81793c98a48SJohn Johansen struct aa_label *new; 81893c98a48SJohn Johansen int error; 81993c98a48SJohn Johansen 82093c98a48SJohn Johansen AA_BUG(!label); 82193c98a48SJohn Johansen AA_BUG(!onexec); 82293c98a48SJohn Johansen AA_BUG(!bprm); 82393c98a48SJohn Johansen AA_BUG(!buffer); 82493c98a48SJohn Johansen 82593c98a48SJohn Johansen if (!stack) { 82693c98a48SJohn Johansen error = fn_for_each_in_ns(label, profile, 82790c436a6SJohn Johansen profile_onexec(subj_cred, profile, onexec, stack, 82893c98a48SJohn Johansen bprm, buffer, cond, unsafe)); 82993c98a48SJohn Johansen if (error) 83093c98a48SJohn Johansen return ERR_PTR(error); 8318ac2ca32SSebastian Andrzej Siewior new = fn_label_build_in_ns(label, profile, GFP_KERNEL, 83293c98a48SJohn Johansen aa_get_newest_label(onexec), 83390c436a6SJohn Johansen profile_transition(subj_cred, profile, bprm, 83490c436a6SJohn Johansen buffer, 83593c98a48SJohn Johansen cond, unsafe)); 83693c98a48SJohn Johansen 83793c98a48SJohn Johansen } else { 838b2c2086cSZygmunt Krynicki /* TODO: determine how much we want to loosen this */ 83993c98a48SJohn Johansen error = fn_for_each_in_ns(label, profile, 84090c436a6SJohn Johansen profile_onexec(subj_cred, profile, onexec, stack, bprm, 84193c98a48SJohn Johansen buffer, cond, unsafe)); 84293c98a48SJohn Johansen if (error) 84393c98a48SJohn Johansen return ERR_PTR(error); 8448ac2ca32SSebastian Andrzej Siewior new = fn_label_build_in_ns(label, profile, GFP_KERNEL, 84593c98a48SJohn Johansen aa_label_merge(&profile->label, onexec, 8468ac2ca32SSebastian Andrzej Siewior GFP_KERNEL), 84790c436a6SJohn Johansen profile_transition(subj_cred, profile, bprm, 84890c436a6SJohn Johansen buffer, 84993c98a48SJohn Johansen cond, unsafe)); 85093c98a48SJohn Johansen } 85193c98a48SJohn Johansen 85293c98a48SJohn Johansen if (new) 85393c98a48SJohn Johansen return new; 85493c98a48SJohn Johansen 85593c98a48SJohn Johansen /* TODO: get rid of GLOBAL_ROOT_UID */ 85693c98a48SJohn Johansen error = fn_for_each_in_ns(label, profile, 85790c436a6SJohn Johansen aa_audit_file(subj_cred, profile, &nullperms, 85890c436a6SJohn Johansen OP_CHANGE_ONEXEC, 85993c98a48SJohn Johansen AA_MAY_ONEXEC, bprm->filename, NULL, 86093c98a48SJohn Johansen onexec, GLOBAL_ROOT_UID, 86193c98a48SJohn Johansen "failed to build target label", -ENOMEM)); 86293c98a48SJohn Johansen return ERR_PTR(error); 863898127c3SJohn Johansen } 864898127c3SJohn Johansen 865898127c3SJohn Johansen /** 866b8bff599SEric W. Biederman * apparmor_bprm_creds_for_exec - Update the new creds on the bprm struct 867898127c3SJohn Johansen * @bprm: binprm for the exec (NOT NULL) 868898127c3SJohn Johansen * 869898127c3SJohn Johansen * Returns: %0 or error on failure 87093c98a48SJohn Johansen * 87193c98a48SJohn Johansen * TODO: once the other paths are done see if we can't refactor into a fn 872898127c3SJohn Johansen */ 873b8bff599SEric W. Biederman int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm) 874898127c3SJohn Johansen { 875f175221aSJohn Johansen struct aa_task_ctx *ctx; 87693c98a48SJohn Johansen struct aa_label *label, *new = NULL; 87790c436a6SJohn Johansen const struct cred *subj_cred; 87893c98a48SJohn Johansen struct aa_profile *profile; 879898127c3SJohn Johansen char *buffer = NULL; 88093c98a48SJohn Johansen const char *info = NULL; 88193c98a48SJohn Johansen int error = 0; 88293c98a48SJohn Johansen bool unsafe = false; 883e67fe633SChristian Brauner vfsuid_t vfsuid = i_uid_into_vfsuid(file_mnt_idmap(bprm->file), 8843cee6079SChristian Brauner file_inode(bprm->file)); 885898127c3SJohn Johansen struct path_cond cond = { 8865e26a01eSChristian Brauner vfsuid_into_kuid(vfsuid), 887496ad9aaSAl Viro file_inode(bprm->file)->i_mode 888898127c3SJohn Johansen }; 889898127c3SJohn Johansen 89090c436a6SJohn Johansen subj_cred = current_cred(); 891de62de59SJohn Johansen ctx = task_ctx(current); 892d9087c49SJohn Johansen AA_BUG(!cred_label(bprm->cred)); 893f175221aSJohn Johansen AA_BUG(!ctx); 894898127c3SJohn Johansen 895d9087c49SJohn Johansen label = aa_get_newest_label(cred_label(bprm->cred)); 8964227c333SJohn Johansen 8979fcf78ccSJohn Johansen /* 8989fcf78ccSJohn Johansen * Detect no new privs being set, and store the label it 8999fcf78ccSJohn Johansen * occurred under. Ideally this would happen when nnp 9009fcf78ccSJohn Johansen * is set but there isn't a good way to do that yet. 9019fcf78ccSJohn Johansen * 9029fcf78ccSJohn Johansen * Testing for unconfined must be done before the subset test 9039fcf78ccSJohn Johansen */ 9049fcf78ccSJohn Johansen if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) && !unconfined(label) && 9059fcf78ccSJohn Johansen !ctx->nnp) 9069fcf78ccSJohn Johansen ctx->nnp = aa_get_label(label); 9079fcf78ccSJohn Johansen 9084227c333SJohn Johansen /* buffer freed below, name is pointer into buffer */ 909341c1fdaSJohn Johansen buffer = aa_get_buffer(false); 910df323337SSebastian Andrzej Siewior if (!buffer) { 911df323337SSebastian Andrzej Siewior error = -ENOMEM; 912df323337SSebastian Andrzej Siewior goto done; 913df323337SSebastian Andrzej Siewior } 914df323337SSebastian Andrzej Siewior 91593c98a48SJohn Johansen /* Test for onexec first as onexec override other x transitions. */ 916f175221aSJohn Johansen if (ctx->onexec) 91790c436a6SJohn Johansen new = handle_onexec(subj_cred, label, ctx->onexec, ctx->token, 91893c98a48SJohn Johansen bprm, buffer, &cond, &unsafe); 919898127c3SJohn Johansen else 9208ac2ca32SSebastian Andrzej Siewior new = fn_label_build(label, profile, GFP_KERNEL, 92190c436a6SJohn Johansen profile_transition(subj_cred, profile, bprm, 92290c436a6SJohn Johansen buffer, 92393c98a48SJohn Johansen &cond, &unsafe)); 924898127c3SJohn Johansen 92593c98a48SJohn Johansen AA_BUG(!new); 92693c98a48SJohn Johansen if (IS_ERR(new)) { 92793c98a48SJohn Johansen error = PTR_ERR(new); 92893c98a48SJohn Johansen goto done; 92993c98a48SJohn Johansen } else if (!new) { 930898127c3SJohn Johansen error = -ENOMEM; 93193c98a48SJohn Johansen goto done; 932c29bceb3SJohn Johansen } 933c29bceb3SJohn Johansen 9349fcf78ccSJohn Johansen /* Policy has specified a domain transitions. If no_new_privs and 9359fcf78ccSJohn Johansen * confined ensure the transition is to confinement that is subset 9369fcf78ccSJohn Johansen * of the confinement when the task entered no new privs. 9379fcf78ccSJohn Johansen * 9389fcf78ccSJohn Johansen * NOTE: Domain transitions from unconfined and to stacked 9399fcf78ccSJohn Johansen * subsets are allowed even when no_new_privs is set because this 9409fcf78ccSJohn Johansen * aways results in a further reduction of permissions. 9419fcf78ccSJohn Johansen */ 9429fcf78ccSJohn Johansen if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) && 9433ed4aaa9SJohn Johansen !unconfined(label) && 9443ed4aaa9SJohn Johansen !aa_label_is_unconfined_subset(new, ctx->nnp)) { 9459fcf78ccSJohn Johansen error = -EPERM; 9469fcf78ccSJohn Johansen info = "no new privs"; 9479fcf78ccSJohn Johansen goto audit; 9489fcf78ccSJohn Johansen } 949898127c3SJohn Johansen 950898127c3SJohn Johansen if (bprm->unsafe & LSM_UNSAFE_SHARE) { 951898127c3SJohn Johansen /* FIXME: currently don't mediate shared state */ 952898127c3SJohn Johansen ; 953898127c3SJohn Johansen } 954898127c3SJohn Johansen 95593c98a48SJohn Johansen if (bprm->unsafe & (LSM_UNSAFE_PTRACE)) { 95693c98a48SJohn Johansen /* TODO: test needs to be profile of label to new */ 95790c436a6SJohn Johansen error = may_change_ptraced_domain(bprm->cred, new, &info); 958f7da2de0SJohn Johansen if (error) 959898127c3SJohn Johansen goto audit; 960898127c3SJohn Johansen } 961898127c3SJohn Johansen 96293c98a48SJohn Johansen if (unsafe) { 96393c98a48SJohn Johansen if (DEBUG_ON) { 96493c98a48SJohn Johansen dbg_printk("scrubbing environment variables for %s " 96593c98a48SJohn Johansen "label=", bprm->filename); 9668ac2ca32SSebastian Andrzej Siewior aa_label_printk(new, GFP_KERNEL); 96793c98a48SJohn Johansen dbg_printk("\n"); 96893c98a48SJohn Johansen } 969993b3ab0SKees Cook bprm->secureexec = 1; 970898127c3SJohn Johansen } 97193c98a48SJohn Johansen 97293c98a48SJohn Johansen if (label->proxy != new->proxy) { 97393c98a48SJohn Johansen /* when transitioning clear unsafe personality bits */ 97493c98a48SJohn Johansen if (DEBUG_ON) { 97593c98a48SJohn Johansen dbg_printk("apparmor: clearing unsafe personality " 97693c98a48SJohn Johansen "bits. %s label=", bprm->filename); 9778ac2ca32SSebastian Andrzej Siewior aa_label_printk(new, GFP_KERNEL); 97893c98a48SJohn Johansen dbg_printk("\n"); 97993c98a48SJohn Johansen } 980898127c3SJohn Johansen bprm->per_clear |= PER_CLEAR_ON_SETID; 98193c98a48SJohn Johansen } 982d9087c49SJohn Johansen aa_put_label(cred_label(bprm->cred)); 983d9087c49SJohn Johansen /* transfer reference, released when cred is freed */ 98469b5a44aSCasey Schaufler set_cred_label(bprm->cred, new); 985898127c3SJohn Johansen 98693c98a48SJohn Johansen done: 987637f688dSJohn Johansen aa_put_label(label); 988df323337SSebastian Andrzej Siewior aa_put_buffer(buffer); 989898127c3SJohn Johansen 990898127c3SJohn Johansen return error; 99193c98a48SJohn Johansen 99293c98a48SJohn Johansen audit: 99393c98a48SJohn Johansen error = fn_for_each(label, profile, 99490c436a6SJohn Johansen aa_audit_file(current_cred(), profile, &nullperms, 99590c436a6SJohn Johansen OP_EXEC, MAY_EXEC, 99693c98a48SJohn Johansen bprm->filename, NULL, new, 9975e26a01eSChristian Brauner vfsuid_into_kuid(vfsuid), info, error)); 99893c98a48SJohn Johansen aa_put_label(new); 99993c98a48SJohn Johansen goto done; 1000898127c3SJohn Johansen } 1001898127c3SJohn Johansen 1002898127c3SJohn Johansen /* 1003898127c3SJohn Johansen * Functions for self directed profile change 1004898127c3SJohn Johansen */ 1005898127c3SJohn Johansen 100689dbf196SJohn Johansen 100789dbf196SJohn Johansen /* helper fn for change_hat 1008898127c3SJohn Johansen * 100989dbf196SJohn Johansen * Returns: label for hat transition OR ERR_PTR. Does NOT return NULL 1010898127c3SJohn Johansen */ 101190c436a6SJohn Johansen static struct aa_label *build_change_hat(const struct cred *subj_cred, 101290c436a6SJohn Johansen struct aa_profile *profile, 101389dbf196SJohn Johansen const char *name, bool sibling) 1014898127c3SJohn Johansen { 101589dbf196SJohn Johansen struct aa_profile *root, *hat = NULL; 101689dbf196SJohn Johansen const char *info = NULL; 101789dbf196SJohn Johansen int error = 0; 101889dbf196SJohn Johansen 101989dbf196SJohn Johansen if (sibling && PROFILE_IS_HAT(profile)) { 102089dbf196SJohn Johansen root = aa_get_profile_rcu(&profile->parent); 102189dbf196SJohn Johansen } else if (!sibling && !PROFILE_IS_HAT(profile)) { 102289dbf196SJohn Johansen root = aa_get_profile(profile); 102389dbf196SJohn Johansen } else { 102489dbf196SJohn Johansen info = "conflicting target types"; 102589dbf196SJohn Johansen error = -EPERM; 102689dbf196SJohn Johansen goto audit; 102789dbf196SJohn Johansen } 102889dbf196SJohn Johansen 102989dbf196SJohn Johansen hat = aa_find_child(root, name); 103089dbf196SJohn Johansen if (!hat) { 103189dbf196SJohn Johansen error = -ENOENT; 103289dbf196SJohn Johansen if (COMPLAIN_MODE(profile)) { 103358f89ce5SJohn Johansen hat = aa_new_learning_profile(profile, true, name, 103489dbf196SJohn Johansen GFP_KERNEL); 103589dbf196SJohn Johansen if (!hat) { 103689dbf196SJohn Johansen info = "failed null profile create"; 103789dbf196SJohn Johansen error = -ENOMEM; 103889dbf196SJohn Johansen } 103989dbf196SJohn Johansen } 104089dbf196SJohn Johansen } 104189dbf196SJohn Johansen aa_put_profile(root); 104289dbf196SJohn Johansen 104389dbf196SJohn Johansen audit: 104490c436a6SJohn Johansen aa_audit_file(subj_cred, profile, &nullperms, OP_CHANGE_HAT, 104590c436a6SJohn Johansen AA_MAY_CHANGEHAT, 104689dbf196SJohn Johansen name, hat ? hat->base.hname : NULL, 104724b87a16SJohn Johansen hat ? &hat->label : NULL, GLOBAL_ROOT_UID, info, 104889dbf196SJohn Johansen error); 104989dbf196SJohn Johansen if (!hat || (error && error != -ENOENT)) 105089dbf196SJohn Johansen return ERR_PTR(error); 105189dbf196SJohn Johansen /* if hat && error - complain mode, already audited and we adjust for 105289dbf196SJohn Johansen * complain mode allow by returning hat->label 105389dbf196SJohn Johansen */ 105489dbf196SJohn Johansen return &hat->label; 105589dbf196SJohn Johansen } 105689dbf196SJohn Johansen 105789dbf196SJohn Johansen /* helper fn for changing into a hat 105889dbf196SJohn Johansen * 105989dbf196SJohn Johansen * Returns: label for hat transition or ERR_PTR. Does not return NULL 106089dbf196SJohn Johansen */ 106190c436a6SJohn Johansen static struct aa_label *change_hat(const struct cred *subj_cred, 106290c436a6SJohn Johansen struct aa_label *label, const char *hats[], 106389dbf196SJohn Johansen int count, int flags) 106489dbf196SJohn Johansen { 106589dbf196SJohn Johansen struct aa_profile *profile, *root, *hat = NULL; 106689dbf196SJohn Johansen struct aa_label *new; 106789dbf196SJohn Johansen struct label_it it; 106889dbf196SJohn Johansen bool sibling = false; 106989dbf196SJohn Johansen const char *name, *info = NULL; 107089dbf196SJohn Johansen int i, error; 107189dbf196SJohn Johansen 107289dbf196SJohn Johansen AA_BUG(!label); 107389dbf196SJohn Johansen AA_BUG(!hats); 107489dbf196SJohn Johansen AA_BUG(count < 1); 107589dbf196SJohn Johansen 107689dbf196SJohn Johansen if (PROFILE_IS_HAT(labels_profile(label))) 107789dbf196SJohn Johansen sibling = true; 107889dbf196SJohn Johansen 107989dbf196SJohn Johansen /*find first matching hat */ 108089dbf196SJohn Johansen for (i = 0; i < count && !hat; i++) { 108189dbf196SJohn Johansen name = hats[i]; 108289dbf196SJohn Johansen label_for_each_in_ns(it, labels_ns(label), label, profile) { 108389dbf196SJohn Johansen if (sibling && PROFILE_IS_HAT(profile)) { 108489dbf196SJohn Johansen root = aa_get_profile_rcu(&profile->parent); 108589dbf196SJohn Johansen } else if (!sibling && !PROFILE_IS_HAT(profile)) { 108689dbf196SJohn Johansen root = aa_get_profile(profile); 108789dbf196SJohn Johansen } else { /* conflicting change type */ 108889dbf196SJohn Johansen info = "conflicting targets types"; 108989dbf196SJohn Johansen error = -EPERM; 109089dbf196SJohn Johansen goto fail; 109189dbf196SJohn Johansen } 109289dbf196SJohn Johansen hat = aa_find_child(root, name); 109389dbf196SJohn Johansen aa_put_profile(root); 109489dbf196SJohn Johansen if (!hat) { 109589dbf196SJohn Johansen if (!COMPLAIN_MODE(profile)) 109689dbf196SJohn Johansen goto outer_continue; 109789dbf196SJohn Johansen /* complain mode succeed as if hat */ 109889dbf196SJohn Johansen } else if (!PROFILE_IS_HAT(hat)) { 109989dbf196SJohn Johansen info = "target not hat"; 110089dbf196SJohn Johansen error = -EPERM; 110189dbf196SJohn Johansen aa_put_profile(hat); 110289dbf196SJohn Johansen goto fail; 110389dbf196SJohn Johansen } 110489dbf196SJohn Johansen aa_put_profile(hat); 110589dbf196SJohn Johansen } 110689dbf196SJohn Johansen /* found a hat for all profiles in ns */ 110789dbf196SJohn Johansen goto build; 110889dbf196SJohn Johansen outer_continue: 110989dbf196SJohn Johansen ; 111089dbf196SJohn Johansen } 111189dbf196SJohn Johansen /* no hats that match, find appropriate error 111289dbf196SJohn Johansen * 111389dbf196SJohn Johansen * In complain mode audit of the failure is based off of the first 111489dbf196SJohn Johansen * hat supplied. This is done due how userspace interacts with 111589dbf196SJohn Johansen * change_hat. 111689dbf196SJohn Johansen */ 111789dbf196SJohn Johansen name = NULL; 111889dbf196SJohn Johansen label_for_each_in_ns(it, labels_ns(label), label, profile) { 111989dbf196SJohn Johansen if (!list_empty(&profile->base.profiles)) { 112089dbf196SJohn Johansen info = "hat not found"; 112189dbf196SJohn Johansen error = -ENOENT; 112289dbf196SJohn Johansen goto fail; 112389dbf196SJohn Johansen } 112489dbf196SJohn Johansen } 112589dbf196SJohn Johansen info = "no hats defined"; 112689dbf196SJohn Johansen error = -ECHILD; 112789dbf196SJohn Johansen 112889dbf196SJohn Johansen fail: 112989dbf196SJohn Johansen label_for_each_in_ns(it, labels_ns(label), label, profile) { 113089dbf196SJohn Johansen /* 113189dbf196SJohn Johansen * no target as it has failed to be found or built 113289dbf196SJohn Johansen * 113389dbf196SJohn Johansen * change_hat uses probing and should not log failures 113489dbf196SJohn Johansen * related to missing hats 113589dbf196SJohn Johansen */ 113689dbf196SJohn Johansen /* TODO: get rid of GLOBAL_ROOT_UID */ 113789dbf196SJohn Johansen if (count > 1 || COMPLAIN_MODE(profile)) { 113890c436a6SJohn Johansen aa_audit_file(subj_cred, profile, &nullperms, 113990c436a6SJohn Johansen OP_CHANGE_HAT, 114089dbf196SJohn Johansen AA_MAY_CHANGEHAT, name, NULL, NULL, 114189dbf196SJohn Johansen GLOBAL_ROOT_UID, info, error); 114289dbf196SJohn Johansen } 114389dbf196SJohn Johansen } 114489dbf196SJohn Johansen return ERR_PTR(error); 114589dbf196SJohn Johansen 114689dbf196SJohn Johansen build: 114789dbf196SJohn Johansen new = fn_label_build_in_ns(label, profile, GFP_KERNEL, 114890c436a6SJohn Johansen build_change_hat(subj_cred, profile, name, 114990c436a6SJohn Johansen sibling), 115089dbf196SJohn Johansen aa_get_label(&profile->label)); 115189dbf196SJohn Johansen if (!new) { 115289dbf196SJohn Johansen info = "label build failed"; 115389dbf196SJohn Johansen error = -ENOMEM; 115489dbf196SJohn Johansen goto fail; 115589dbf196SJohn Johansen } /* else if (IS_ERR) build_change_hat has logged error so return new */ 115689dbf196SJohn Johansen 115789dbf196SJohn Johansen return new; 1158898127c3SJohn Johansen } 1159898127c3SJohn Johansen 1160898127c3SJohn Johansen /** 1161898127c3SJohn Johansen * aa_change_hat - change hat to/from subprofile 1162898127c3SJohn Johansen * @hats: vector of hat names to try changing into (MAYBE NULL if @count == 0) 1163898127c3SJohn Johansen * @count: number of hat names in @hats 1164898127c3SJohn Johansen * @token: magic value to validate the hat change 1165df8073c6SJohn Johansen * @flags: flags affecting behavior of the change 1166898127c3SJohn Johansen * 116789dbf196SJohn Johansen * Returns %0 on success, error otherwise. 116889dbf196SJohn Johansen * 1169898127c3SJohn Johansen * Change to the first profile specified in @hats that exists, and store 1170898127c3SJohn Johansen * the @hat_magic in the current task context. If the count == 0 and the 1171898127c3SJohn Johansen * @token matches that stored in the current task context, return to the 1172898127c3SJohn Johansen * top level profile. 1173898127c3SJohn Johansen * 117489dbf196SJohn Johansen * change_hat only applies to profiles in the current ns, and each profile 117589dbf196SJohn Johansen * in the ns must make the same transition otherwise change_hat will fail. 1176898127c3SJohn Johansen */ 1177df8073c6SJohn Johansen int aa_change_hat(const char *hats[], int count, u64 token, int flags) 1178898127c3SJohn Johansen { 117990c436a6SJohn Johansen const struct cred *subj_cred; 11809fcf78ccSJohn Johansen struct aa_task_ctx *ctx = task_ctx(current); 118189dbf196SJohn Johansen struct aa_label *label, *previous, *new = NULL, *target = NULL; 118289dbf196SJohn Johansen struct aa_profile *profile; 11832d679f3cSJohn Johansen struct aa_perms perms = {}; 118489dbf196SJohn Johansen const char *info = NULL; 1185898127c3SJohn Johansen int error = 0; 1186898127c3SJohn Johansen 1187898127c3SJohn Johansen /* released below */ 118890c436a6SJohn Johansen subj_cred = get_current_cred(); 118990c436a6SJohn Johansen label = aa_get_newest_cred_label(subj_cred); 1190f175221aSJohn Johansen previous = aa_get_newest_label(ctx->previous); 1191898127c3SJohn Johansen 11929fcf78ccSJohn Johansen /* 11939fcf78ccSJohn Johansen * Detect no new privs being set, and store the label it 11949fcf78ccSJohn Johansen * occurred under. Ideally this would happen when nnp 11959fcf78ccSJohn Johansen * is set but there isn't a good way to do that yet. 11969fcf78ccSJohn Johansen * 11979fcf78ccSJohn Johansen * Testing for unconfined must be done before the subset test 11989fcf78ccSJohn Johansen */ 11999fcf78ccSJohn Johansen if (task_no_new_privs(current) && !unconfined(label) && !ctx->nnp) 12009fcf78ccSJohn Johansen ctx->nnp = aa_get_label(label); 12019fcf78ccSJohn Johansen 1202637f688dSJohn Johansen if (unconfined(label)) { 120389dbf196SJohn Johansen info = "unconfined can not change_hat"; 1204898127c3SJohn Johansen error = -EPERM; 120589dbf196SJohn Johansen goto fail; 1206898127c3SJohn Johansen } 1207898127c3SJohn Johansen 1208898127c3SJohn Johansen if (count) { 120990c436a6SJohn Johansen new = change_hat(subj_cred, label, hats, count, flags); 121089dbf196SJohn Johansen AA_BUG(!new); 121189dbf196SJohn Johansen if (IS_ERR(new)) { 121289dbf196SJohn Johansen error = PTR_ERR(new); 121389dbf196SJohn Johansen new = NULL; 121489dbf196SJohn Johansen /* already audited */ 1215898127c3SJohn Johansen goto out; 1216898127c3SJohn Johansen } 1217898127c3SJohn Johansen 121890c436a6SJohn Johansen /* target cred is the same as current except new label */ 121990c436a6SJohn Johansen error = may_change_ptraced_domain(subj_cred, new, &info); 122089dbf196SJohn Johansen if (error) 122189dbf196SJohn Johansen goto fail; 1222898127c3SJohn Johansen 12239fcf78ccSJohn Johansen /* 12249fcf78ccSJohn Johansen * no new privs prevents domain transitions that would 12259fcf78ccSJohn Johansen * reduce restrictions. 12269fcf78ccSJohn Johansen */ 12279fcf78ccSJohn Johansen if (task_no_new_privs(current) && !unconfined(label) && 12283ed4aaa9SJohn Johansen !aa_label_is_unconfined_subset(new, ctx->nnp)) { 12299fcf78ccSJohn Johansen /* not an apparmor denial per se, so don't log it */ 12309fcf78ccSJohn Johansen AA_DEBUG("no_new_privs - change_hat denied"); 12319fcf78ccSJohn Johansen error = -EPERM; 12329fcf78ccSJohn Johansen goto out; 12339fcf78ccSJohn Johansen } 12349fcf78ccSJohn Johansen 123589dbf196SJohn Johansen if (flags & AA_CHANGE_TEST) 123689dbf196SJohn Johansen goto out; 1237898127c3SJohn Johansen 123889dbf196SJohn Johansen target = new; 123989dbf196SJohn Johansen error = aa_set_current_hat(new, token); 1240898127c3SJohn Johansen if (error == -EACCES) 1241898127c3SJohn Johansen /* kill task in case of brute force attacks */ 124289dbf196SJohn Johansen goto kill; 124389dbf196SJohn Johansen } else if (previous && !(flags & AA_CHANGE_TEST)) { 12449fcf78ccSJohn Johansen /* 12459fcf78ccSJohn Johansen * no new privs prevents domain transitions that would 12469fcf78ccSJohn Johansen * reduce restrictions. 12479fcf78ccSJohn Johansen */ 12489fcf78ccSJohn Johansen if (task_no_new_privs(current) && !unconfined(label) && 12493ed4aaa9SJohn Johansen !aa_label_is_unconfined_subset(previous, ctx->nnp)) { 12509fcf78ccSJohn Johansen /* not an apparmor denial per se, so don't log it */ 12519fcf78ccSJohn Johansen AA_DEBUG("no_new_privs - change_hat denied"); 12529fcf78ccSJohn Johansen error = -EPERM; 12539fcf78ccSJohn Johansen goto out; 12549fcf78ccSJohn Johansen } 12559fcf78ccSJohn Johansen 125689dbf196SJohn Johansen /* Return to saved label. Kill task if restore fails 1257898127c3SJohn Johansen * to avoid brute force attacks 1258898127c3SJohn Johansen */ 125989dbf196SJohn Johansen target = previous; 1260637f688dSJohn Johansen error = aa_restore_previous_label(token); 126189dbf196SJohn Johansen if (error) { 126289dbf196SJohn Johansen if (error == -EACCES) 126389dbf196SJohn Johansen goto kill; 126489dbf196SJohn Johansen goto fail; 126589dbf196SJohn Johansen } 126689dbf196SJohn Johansen } /* else ignore @flags && restores when there is no saved profile */ 1267898127c3SJohn Johansen 1268898127c3SJohn Johansen out: 126989dbf196SJohn Johansen aa_put_label(new); 127089dbf196SJohn Johansen aa_put_label(previous); 1271637f688dSJohn Johansen aa_put_label(label); 127290c436a6SJohn Johansen put_cred(subj_cred); 1273898127c3SJohn Johansen 1274898127c3SJohn Johansen return error; 127589dbf196SJohn Johansen 127689dbf196SJohn Johansen kill: 127789dbf196SJohn Johansen info = "failed token match"; 127889dbf196SJohn Johansen perms.kill = AA_MAY_CHANGEHAT; 127989dbf196SJohn Johansen 128089dbf196SJohn Johansen fail: 128189dbf196SJohn Johansen fn_for_each_in_ns(label, profile, 128290c436a6SJohn Johansen aa_audit_file(subj_cred, profile, &perms, OP_CHANGE_HAT, 128389dbf196SJohn Johansen AA_MAY_CHANGEHAT, NULL, NULL, target, 128489dbf196SJohn Johansen GLOBAL_ROOT_UID, info, error)); 128589dbf196SJohn Johansen 128689dbf196SJohn Johansen goto out; 1287898127c3SJohn Johansen } 1288898127c3SJohn Johansen 128989dbf196SJohn Johansen 1290e00b02bbSJohn Johansen static int change_profile_perms_wrapper(const char *op, const char *name, 129190c436a6SJohn Johansen const struct cred *subj_cred, 1292e00b02bbSJohn Johansen struct aa_profile *profile, 1293e00b02bbSJohn Johansen struct aa_label *target, bool stack, 1294e00b02bbSJohn Johansen u32 request, struct aa_perms *perms) 1295e00b02bbSJohn Johansen { 12961ad22fccSJohn Johansen struct aa_ruleset *rules = list_first_entry(&profile->rules, 12971ad22fccSJohn Johansen typeof(*rules), list); 1298e00b02bbSJohn Johansen const char *info = NULL; 1299e00b02bbSJohn Johansen int error = 0; 1300e00b02bbSJohn Johansen 1301e00b02bbSJohn Johansen if (!error) 1302e00b02bbSJohn Johansen error = change_profile_perms(profile, target, stack, request, 1303*98b824ffSJohn Johansen rules->file->start[AA_CLASS_FILE], 130453bdc46fSJohn Johansen perms); 1305e00b02bbSJohn Johansen if (error) 130690c436a6SJohn Johansen error = aa_audit_file(subj_cred, profile, perms, op, request, 130790c436a6SJohn Johansen name, 1308e00b02bbSJohn Johansen NULL, target, GLOBAL_ROOT_UID, info, 1309e00b02bbSJohn Johansen error); 1310e00b02bbSJohn Johansen 1311e00b02bbSJohn Johansen return error; 1312e00b02bbSJohn Johansen } 131389dbf196SJohn Johansen 1314898127c3SJohn Johansen /** 1315898127c3SJohn Johansen * aa_change_profile - perform a one-way profile transition 1316aa9a39adSJohn Johansen * @fqname: name of profile may include namespace (NOT NULL) 1317df8073c6SJohn Johansen * @flags: flags affecting change behavior 1318898127c3SJohn Johansen * 1319898127c3SJohn Johansen * Change to new profile @name. Unlike with hats, there is no way 1320898127c3SJohn Johansen * to change back. If @name isn't specified the current profile name is 1321898127c3SJohn Johansen * used. 1322898127c3SJohn Johansen * If @onexec then the transition is delayed until 1323898127c3SJohn Johansen * the next exec. 1324898127c3SJohn Johansen * 1325898127c3SJohn Johansen * Returns %0 on success, error otherwise. 1326898127c3SJohn Johansen */ 1327df8073c6SJohn Johansen int aa_change_profile(const char *fqname, int flags) 1328898127c3SJohn Johansen { 1329e00b02bbSJohn Johansen struct aa_label *label, *new = NULL, *target = NULL; 1330e00b02bbSJohn Johansen struct aa_profile *profile; 13312d679f3cSJohn Johansen struct aa_perms perms = {}; 1332e00b02bbSJohn Johansen const char *info = NULL; 1333e00b02bbSJohn Johansen const char *auditname = fqname; /* retain leading & if stack */ 1334e00b02bbSJohn Johansen bool stack = flags & AA_CHANGE_STACK; 13359fcf78ccSJohn Johansen struct aa_task_ctx *ctx = task_ctx(current); 133690c436a6SJohn Johansen const struct cred *subj_cred = get_current_cred(); 133747f6e5ccSJohn Johansen int error = 0; 1338e00b02bbSJohn Johansen char *op; 1339898127c3SJohn Johansen u32 request; 1340898127c3SJohn Johansen 13419fcf78ccSJohn Johansen label = aa_get_current_label(); 13429fcf78ccSJohn Johansen 13439fcf78ccSJohn Johansen /* 13449fcf78ccSJohn Johansen * Detect no new privs being set, and store the label it 13459fcf78ccSJohn Johansen * occurred under. Ideally this would happen when nnp 13469fcf78ccSJohn Johansen * is set but there isn't a good way to do that yet. 13479fcf78ccSJohn Johansen * 13489fcf78ccSJohn Johansen * Testing for unconfined must be done before the subset test 13499fcf78ccSJohn Johansen */ 13509fcf78ccSJohn Johansen if (task_no_new_privs(current) && !unconfined(label) && !ctx->nnp) 13519fcf78ccSJohn Johansen ctx->nnp = aa_get_label(label); 13529fcf78ccSJohn Johansen 1353aa9a39adSJohn Johansen if (!fqname || !*fqname) { 1354a0b845ffSXiyu Yang aa_put_label(label); 1355aa9a39adSJohn Johansen AA_DEBUG("no profile name"); 1356898127c3SJohn Johansen return -EINVAL; 1357aa9a39adSJohn Johansen } 1358898127c3SJohn Johansen 1359df8073c6SJohn Johansen if (flags & AA_CHANGE_ONEXEC) { 1360898127c3SJohn Johansen request = AA_MAY_ONEXEC; 1361e00b02bbSJohn Johansen if (stack) 1362e00b02bbSJohn Johansen op = OP_STACK_ONEXEC; 1363e00b02bbSJohn Johansen else 1364898127c3SJohn Johansen op = OP_CHANGE_ONEXEC; 1365898127c3SJohn Johansen } else { 1366898127c3SJohn Johansen request = AA_MAY_CHANGE_PROFILE; 1367e00b02bbSJohn Johansen if (stack) 1368e00b02bbSJohn Johansen op = OP_STACK; 1369e00b02bbSJohn Johansen else 1370898127c3SJohn Johansen op = OP_CHANGE_PROFILE; 1371898127c3SJohn Johansen } 1372898127c3SJohn Johansen 1373e00b02bbSJohn Johansen if (*fqname == '&') { 1374e00b02bbSJohn Johansen stack = true; 1375e00b02bbSJohn Johansen /* don't have label_parse() do stacking */ 1376e00b02bbSJohn Johansen fqname++; 1377c29bceb3SJohn Johansen } 1378e00b02bbSJohn Johansen target = aa_label_parse(label, fqname, GFP_KERNEL, true, false); 1379e00b02bbSJohn Johansen if (IS_ERR(target)) { 1380e00b02bbSJohn Johansen struct aa_profile *tprofile; 1381c29bceb3SJohn Johansen 1382e00b02bbSJohn Johansen info = "label not found"; 1383e00b02bbSJohn Johansen error = PTR_ERR(target); 1384e00b02bbSJohn Johansen target = NULL; 1385e00b02bbSJohn Johansen /* 1386e00b02bbSJohn Johansen * TODO: fixme using labels_profile is not right - do profile 1387e00b02bbSJohn Johansen * per complain profile 1388e00b02bbSJohn Johansen */ 1389df8073c6SJohn Johansen if ((flags & AA_CHANGE_TEST) || 1390e00b02bbSJohn Johansen !COMPLAIN_MODE(labels_profile(label))) 1391898127c3SJohn Johansen goto audit; 1392898127c3SJohn Johansen /* released below */ 139358f89ce5SJohn Johansen tprofile = aa_new_learning_profile(labels_profile(label), false, 1394e00b02bbSJohn Johansen fqname, GFP_KERNEL); 1395e00b02bbSJohn Johansen if (!tprofile) { 1396898127c3SJohn Johansen info = "failed null profile create"; 1397898127c3SJohn Johansen error = -ENOMEM; 1398898127c3SJohn Johansen goto audit; 1399898127c3SJohn Johansen } 1400e00b02bbSJohn Johansen target = &tprofile->label; 1401e00b02bbSJohn Johansen goto check; 1402898127c3SJohn Johansen } 1403898127c3SJohn Johansen 1404e00b02bbSJohn Johansen /* 1405e00b02bbSJohn Johansen * self directed transitions only apply to current policy ns 1406e00b02bbSJohn Johansen * TODO: currently requiring perms for stacking and straight change 1407e00b02bbSJohn Johansen * stacking doesn't strictly need this. Determine how much 1408e00b02bbSJohn Johansen * we want to loosen this restriction for stacking 1409e00b02bbSJohn Johansen * 1410e00b02bbSJohn Johansen * if (!stack) { 1411e00b02bbSJohn Johansen */ 1412e00b02bbSJohn Johansen error = fn_for_each_in_ns(label, profile, 1413e00b02bbSJohn Johansen change_profile_perms_wrapper(op, auditname, 141490c436a6SJohn Johansen subj_cred, 1415e00b02bbSJohn Johansen profile, target, stack, 1416e00b02bbSJohn Johansen request, &perms)); 1417e00b02bbSJohn Johansen if (error) 1418e00b02bbSJohn Johansen /* auditing done in change_profile_perms_wrapper */ 1419e00b02bbSJohn Johansen goto out; 1420aa9a39adSJohn Johansen 1421e00b02bbSJohn Johansen /* } */ 1422e00b02bbSJohn Johansen 1423e00b02bbSJohn Johansen check: 1424898127c3SJohn Johansen /* check if tracing task is allowed to trace target domain */ 142590c436a6SJohn Johansen error = may_change_ptraced_domain(subj_cred, target, &info); 1426e00b02bbSJohn Johansen if (error && !fn_for_each_in_ns(label, profile, 1427e00b02bbSJohn Johansen COMPLAIN_MODE(profile))) 1428e00b02bbSJohn Johansen goto audit; 1429e00b02bbSJohn Johansen 1430e00b02bbSJohn Johansen /* TODO: add permission check to allow this 1431e00b02bbSJohn Johansen * if ((flags & AA_CHANGE_ONEXEC) && !current_is_single_threaded()) { 1432e00b02bbSJohn Johansen * info = "not a single threaded task"; 1433e00b02bbSJohn Johansen * error = -EACCES; 1434e00b02bbSJohn Johansen * goto audit; 1435e00b02bbSJohn Johansen * } 1436e00b02bbSJohn Johansen */ 1437e00b02bbSJohn Johansen if (flags & AA_CHANGE_TEST) 1438e00b02bbSJohn Johansen goto out; 1439e00b02bbSJohn Johansen 14409fcf78ccSJohn Johansen /* stacking is always a subset, so only check the nonstack case */ 14419fcf78ccSJohn Johansen if (!stack) { 14429fcf78ccSJohn Johansen new = fn_label_build_in_ns(label, profile, GFP_KERNEL, 14439fcf78ccSJohn Johansen aa_get_label(target), 14449fcf78ccSJohn Johansen aa_get_label(&profile->label)); 14459fcf78ccSJohn Johansen /* 14469fcf78ccSJohn Johansen * no new privs prevents domain transitions that would 14479fcf78ccSJohn Johansen * reduce restrictions. 14489fcf78ccSJohn Johansen */ 14499fcf78ccSJohn Johansen if (task_no_new_privs(current) && !unconfined(label) && 14503ed4aaa9SJohn Johansen !aa_label_is_unconfined_subset(new, ctx->nnp)) { 14519fcf78ccSJohn Johansen /* not an apparmor denial per se, so don't log it */ 14529fcf78ccSJohn Johansen AA_DEBUG("no_new_privs - change_hat denied"); 14539fcf78ccSJohn Johansen error = -EPERM; 14549fcf78ccSJohn Johansen goto out; 14559fcf78ccSJohn Johansen } 14569fcf78ccSJohn Johansen } 14579fcf78ccSJohn Johansen 1458e00b02bbSJohn Johansen if (!(flags & AA_CHANGE_ONEXEC)) { 1459e00b02bbSJohn Johansen /* only transition profiles in the current ns */ 1460e00b02bbSJohn Johansen if (stack) 1461e00b02bbSJohn Johansen new = aa_label_merge(label, target, GFP_KERNEL); 1462e00b02bbSJohn Johansen if (IS_ERR_OR_NULL(new)) { 1463e00b02bbSJohn Johansen info = "failed to build target label"; 1464d6d478aeSJohn Johansen if (!new) 1465d6d478aeSJohn Johansen error = -ENOMEM; 1466d6d478aeSJohn Johansen else 1467e00b02bbSJohn Johansen error = PTR_ERR(new); 1468e00b02bbSJohn Johansen new = NULL; 1469e00b02bbSJohn Johansen perms.allow = 0; 1470898127c3SJohn Johansen goto audit; 1471898127c3SJohn Johansen } 1472e00b02bbSJohn Johansen error = aa_replace_current_label(new); 14739fcf78ccSJohn Johansen } else { 14749fcf78ccSJohn Johansen if (new) { 14759fcf78ccSJohn Johansen aa_put_label(new); 14769fcf78ccSJohn Johansen new = NULL; 14779fcf78ccSJohn Johansen } 14789fcf78ccSJohn Johansen 1479e00b02bbSJohn Johansen /* full transition will be built in exec path */ 14800897fcb1SQuanfa Fu aa_set_current_onexec(target, stack); 14819fcf78ccSJohn Johansen } 1482898127c3SJohn Johansen 1483898127c3SJohn Johansen audit: 1484e00b02bbSJohn Johansen error = fn_for_each_in_ns(label, profile, 148590c436a6SJohn Johansen aa_audit_file(subj_cred, 148690c436a6SJohn Johansen profile, &perms, op, request, auditname, 1487e00b02bbSJohn Johansen NULL, new ? new : target, 1488e00b02bbSJohn Johansen GLOBAL_ROOT_UID, info, error)); 1489898127c3SJohn Johansen 1490e00b02bbSJohn Johansen out: 1491e00b02bbSJohn Johansen aa_put_label(new); 1492e00b02bbSJohn Johansen aa_put_label(target); 1493637f688dSJohn Johansen aa_put_label(label); 149490c436a6SJohn Johansen put_cred(subj_cred); 1495898127c3SJohn Johansen 1496898127c3SJohn Johansen return error; 1497898127c3SJohn Johansen } 1498