diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index c09cfdf1b62c..be6c3293c9e0 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -2627,6 +2627,7 @@ static struct aa_sfs_entry aa_sfs_entry_ns[] = { AA_SFS_FILE_BOOLEAN("profile", 1), AA_SFS_FILE_BOOLEAN("pivot_root", 0), AA_SFS_FILE_STRING("mask", "userns_create"), + AA_SFS_FILE_STRING("userns_create", "pciu&"), { } }; diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index f2e8b34ae094..3b40128b37b4 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -153,6 +153,9 @@ struct apparmor_audit_data { kuid_t fsuid; kuid_t ouid; } mq; + struct { + const char *target; + } ns; }; }; struct { diff --git a/security/apparmor/include/task.h b/security/apparmor/include/task.h index b1aaaf60fa8b..ff8bed8f60b2 100644 --- a/security/apparmor/include/task.h +++ b/security/apparmor/include/task.h @@ -99,7 +99,8 @@ int aa_may_ptrace(const struct cred *tracer_cred, struct aa_label *tracer, #define AA_USERNS_CREATE 8 -int aa_profile_ns_perm(struct aa_profile *profile, - struct apparmor_audit_data *ad, u32 request); +struct aa_label *aa_profile_ns_perm(struct aa_profile *profile, + struct apparmor_audit_data *ad, + u32 request); #endif /* __AA_TASK_H */ diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 55504a519682..38ae09393f85 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -1242,7 +1242,7 @@ static int apparmor_task_kill(struct task_struct *target, struct kernel_siginfo return error; } -static int apparmor_userns_create(const struct cred *cred) +static int apparmor_userns_create(const struct cred *new_cred) { struct aa_label *label; struct aa_profile *profile; @@ -1252,13 +1252,22 @@ static int apparmor_userns_create(const struct cred *cred) /* remove unprivileged_userns_restricted check when unconfined is updated */ if (aa_unprivileged_userns_restricted || label_mediates(label, AA_CLASS_NS)) { + struct aa_label *new; DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_TASK, AA_CLASS_NS, OP_USERNS_CREATE); ad.subj_cred = current_cred(); - error = fn_for_each(label, profile, - aa_profile_ns_perm(profile, &ad, AA_USERNS_CREATE)); - end_current_label_crit_section(label); + new = fn_label_build(label, profile, GFP_KERNEL, + aa_profile_ns_perm(profile, &ad, + AA_USERNS_CREATE)); + if (IS_ERR(new)) { + error = PTR_ERR(new); + } else if (new && cred_label(new_cred) != new) { + aa_put_label(cred_label(new_cred)); + set_cred_label(new_cred, new); + } else { + aa_put_label(new); + } } end_current_label_crit_section(label); diff --git a/security/apparmor/task.c b/security/apparmor/task.c index 70ab89faddcd..56f7d7686c14 100644 --- a/security/apparmor/task.c +++ b/security/apparmor/task.c @@ -308,19 +308,106 @@ static void audit_ns_cb(struct audit_buffer *ab, void *va) if (ad->denied & AA_USERNS_CREATE) audit_log_format(ab, " denied=\"userns_create\""); + + if (ad->peer) { + audit_log_format(ab, " target="); + aa_label_xaudit(ab, labels_ns(ad->subj_label), ad->peer, + FLAG_VIEW_SUBNS, GFP_KERNEL); + } else if (ad->ns.target) { + audit_log_format(ab, " target="); + audit_log_untrustedstring(ab, ad->ns.target); + } + } -int aa_profile_ns_perm(struct aa_profile *profile, - struct apparmor_audit_data *ad, - u32 request) +/* + * Returns: refcounted label to change to, even if no change + * PTR_ERR on failure + */ +static struct aa_label *ns_x_to_label(struct aa_profile *profile, + u32 xindex, const char **lookupname, + const char **info) { struct aa_ruleset *rules = list_first_entry(&profile->rules, typeof(*rules), list); + struct aa_label *new = NULL; + u32 xtype = xindex & AA_X_TYPE_MASK; + struct aa_label *stack = NULL; + + /* must be none or table */ + switch (xtype) { + case AA_X_NONE: + /* default not failure */ + *lookupname = NULL; + return NULL; + break; + case AA_X_TABLE: + /* TODO: fix when perm mapping done at unload */ + /* released by caller + * if null for both stack and direct want to try fallback + */ + new = x_table_lookup(profile, xindex, lookupname); + if (!new) { + *info = "failed to find transition profile"; + return ERR_PTR(-ENOMEM); + } + if (**lookupname == '&') { + stack = new; + new = NULL; + } + break; + default: + *info = "invalid profile transition type"; + return ERR_PTR(-EINVAL); + break; + } + + /* stack is true if !new */ + if (!new) { + if (xindex & AA_X_UNCONFINED) { + new = aa_get_newest_label(ns_unconfined(profile->ns)); + *info = "ux fallback"; + } else { + if (xindex & AA_X_INHERIT) { + /* (p|c|n)ix - don't change profile but do + * use the newest version + */ + *info = "ix fallback"; + /* no profile && no error */ + } /* else, stack is implicitly against current */ + new = aa_get_newest_label(&profile->label); + } + } + + if (stack) { + /* base the stack on post domain transition */ + struct aa_label *base = new; + + new = aa_label_merge(base, stack, GFP_KERNEL); + /* null on error */ + aa_put_label(base); + aa_put_label(stack); + if (!new) + return ERR_PTR(-ENOMEM); + } + + /* released by caller */ + return new; +} + +struct aa_label *aa_profile_ns_perm(struct aa_profile *profile, + struct apparmor_audit_data *ad, + u32 request) +{ + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); + struct aa_label *new; struct aa_perms perms = { }; aa_state_t state; ad->subj_label = &profile->label; ad->request = request; + int error; /* TODO: rework unconfined profile/dfa to mediate user ns, then * we can drop the unconfined test @@ -335,19 +422,57 @@ int aa_profile_ns_perm(struct aa_profile *profile, if (!aa_unprivileged_userns_restricted || ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN)) - return 0; + return aa_get_newest_label(&profile->label); ad->info = "User namespace creation restricted"; /* unconfined unprivileged user */ /* don't just return: allow complain mode to override */ +// hardcode unconfined transition for now + new = aa_label_parse(&profile->label, + "unprivileged_userns", GFP_KERNEL, + true, false); + if (IS_ERR(new)) { + ad->info = "Userns create restricted - failed to find unprivileged_userns profile"; + ad->error = PTR_ERR(new); + ad->ns.target = "unprivileged_userns"; + new = NULL; + perms.deny |= request; + goto hard_coded; + } + ad->info = "Userns create - transitioning profile"; + perms.audit = request; + perms.allow = request; + goto hard_coded; +// once we have special unconfined profile, jump to ns_x_to_label() +// end hardcode } else if (!aa_unprivileged_userns_restricted_force) { - return 0; + return aa_get_newest_label(&profile->label); } /* continue to mediation */ } + perms = *aa_lookup_perms(rules->policy, state); + new = ns_x_to_label(profile, perms.xindex, &ad->ns.target, &ad->info); + if (IS_ERR(new)) { + ad->error = PTR_ERR(new); + new = NULL; + perms.deny |= request; + } else if (!new) { + /* no transition - not done in x_to_label so we can track */ + new = aa_get_label(&profile->label); + } else { +hard_coded: + ad->peer = new; + } if (aa_unprivileged_userns_restricted_complain) perms.complain = ALL_PERMS_MASK; + // TODO: nnp + // TODO: complain mode support for transitions aa_apply_modes_to_perms(profile, &perms); - return aa_check_perms(profile, &perms, request, ad, audit_ns_cb); + error = aa_check_perms(profile, &perms, request, ad, audit_ns_cb); + if (error) { + aa_put_label(new); + return ERR_PTR(error); + } + return new; }