|
@@ -301,26 +301,6 @@ static int change_profile_perms(struct aa_profile *profile,
|
|
|
return label_match(profile, target, stack, start, true, request, perms);
|
|
|
}
|
|
|
|
|
|
-static struct aa_perms change_profile_perms_wrapper(struct aa_profile *profile,
|
|
|
- struct aa_profile *target,
|
|
|
- u32 request,
|
|
|
- unsigned int start)
|
|
|
-{
|
|
|
- struct aa_perms perms;
|
|
|
-
|
|
|
- if (profile_unconfined(profile)) {
|
|
|
- perms.allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC;
|
|
|
- perms.audit = perms.quiet = perms.kill = 0;
|
|
|
- return perms;
|
|
|
- }
|
|
|
-
|
|
|
- if (change_profile_perms(profile, &target->label, false, request,
|
|
|
- start, &perms))
|
|
|
- return nullperms;
|
|
|
-
|
|
|
- return perms;
|
|
|
-}
|
|
|
-
|
|
|
/**
|
|
|
* __attach_match_ - find an attachment match
|
|
|
* @name - to match against (NOT NULL)
|
|
@@ -1140,6 +1120,39 @@ fail:
|
|
|
}
|
|
|
|
|
|
|
|
|
+static int change_profile_perms_wrapper(const char *op, const char *name,
|
|
|
+ struct aa_profile *profile,
|
|
|
+ struct aa_label *target, bool stack,
|
|
|
+ u32 request, struct aa_perms *perms)
|
|
|
+{
|
|
|
+ const char *info = NULL;
|
|
|
+ int error = 0;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Fail explicitly requested domain transitions when no_new_privs
|
|
|
+ * and not unconfined OR the transition results in a stack on
|
|
|
+ * the current label.
|
|
|
+ * Stacking domain transitions and transitions from unconfined are
|
|
|
+ * allowed even when no_new_privs is set because this aways results
|
|
|
+ * in a reduction of permissions.
|
|
|
+ */
|
|
|
+ if (task_no_new_privs(current) && !stack &&
|
|
|
+ !profile_unconfined(profile) &&
|
|
|
+ !aa_label_is_subset(target, &profile->label)) {
|
|
|
+ info = "no new privs";
|
|
|
+ error = -EPERM;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!error)
|
|
|
+ error = change_profile_perms(profile, target, stack, request,
|
|
|
+ profile->file.start, perms);
|
|
|
+ if (error)
|
|
|
+ error = aa_audit_file(profile, perms, op, request, name,
|
|
|
+ NULL, target, GLOBAL_ROOT_UID, info,
|
|
|
+ error);
|
|
|
+
|
|
|
+ return error;
|
|
|
+}
|
|
|
|
|
|
/**
|
|
|
* aa_change_profile - perform a one-way profile transition
|
|
@@ -1157,12 +1170,14 @@ fail:
|
|
|
*/
|
|
|
int aa_change_profile(const char *fqname, int flags)
|
|
|
{
|
|
|
- const struct cred *cred;
|
|
|
- struct aa_label *label;
|
|
|
- struct aa_profile *profile, *target = NULL;
|
|
|
+ struct aa_label *label, *new = NULL, *target = NULL;
|
|
|
+ struct aa_profile *profile;
|
|
|
struct aa_perms perms = {};
|
|
|
- const char *info = NULL, *op;
|
|
|
+ const char *info = NULL;
|
|
|
+ const char *auditname = fqname; /* retain leading & if stack */
|
|
|
+ bool stack = flags & AA_CHANGE_STACK;
|
|
|
int error = 0;
|
|
|
+ char *op;
|
|
|
u32 request;
|
|
|
|
|
|
if (!fqname || !*fqname) {
|
|
@@ -1172,76 +1187,116 @@ int aa_change_profile(const char *fqname, int flags)
|
|
|
|
|
|
if (flags & AA_CHANGE_ONEXEC) {
|
|
|
request = AA_MAY_ONEXEC;
|
|
|
- op = OP_CHANGE_ONEXEC;
|
|
|
+ if (stack)
|
|
|
+ op = OP_STACK_ONEXEC;
|
|
|
+ else
|
|
|
+ op = OP_CHANGE_ONEXEC;
|
|
|
} else {
|
|
|
request = AA_MAY_CHANGE_PROFILE;
|
|
|
- op = OP_CHANGE_PROFILE;
|
|
|
+ if (stack)
|
|
|
+ op = OP_STACK;
|
|
|
+ else
|
|
|
+ op = OP_CHANGE_PROFILE;
|
|
|
}
|
|
|
|
|
|
- cred = get_current_cred();
|
|
|
- label = aa_get_newest_cred_label(cred);
|
|
|
- profile = labels_profile(label);
|
|
|
+ label = aa_get_current_label();
|
|
|
|
|
|
- /*
|
|
|
- * Fail explicitly requested domain transitions if no_new_privs
|
|
|
- * and not unconfined.
|
|
|
- * Domain transitions from unconfined are allowed even when
|
|
|
- * no_new_privs is set because this aways results in a reduction
|
|
|
- * of permissions.
|
|
|
- */
|
|
|
- if (task_no_new_privs(current) && !profile_unconfined(profile)) {
|
|
|
- put_cred(cred);
|
|
|
- return -EPERM;
|
|
|
+ if (*fqname == '&') {
|
|
|
+ stack = true;
|
|
|
+ /* don't have label_parse() do stacking */
|
|
|
+ fqname++;
|
|
|
}
|
|
|
+ target = aa_label_parse(label, fqname, GFP_KERNEL, true, false);
|
|
|
+ if (IS_ERR(target)) {
|
|
|
+ struct aa_profile *tprofile;
|
|
|
|
|
|
- target = aa_fqlookupn_profile(label, fqname, strlen(fqname));
|
|
|
- if (!target) {
|
|
|
- info = "profile not found";
|
|
|
- error = -ENOENT;
|
|
|
+ info = "label not found";
|
|
|
+ error = PTR_ERR(target);
|
|
|
+ target = NULL;
|
|
|
+ /*
|
|
|
+ * TODO: fixme using labels_profile is not right - do profile
|
|
|
+ * per complain profile
|
|
|
+ */
|
|
|
if ((flags & AA_CHANGE_TEST) ||
|
|
|
- !COMPLAIN_MODE(profile))
|
|
|
+ !COMPLAIN_MODE(labels_profile(label)))
|
|
|
goto audit;
|
|
|
/* released below */
|
|
|
- target = aa_new_null_profile(profile, false, fqname,
|
|
|
- GFP_KERNEL);
|
|
|
- if (!target) {
|
|
|
+ tprofile = aa_new_null_profile(labels_profile(label), false,
|
|
|
+ fqname, GFP_KERNEL);
|
|
|
+ if (!tprofile) {
|
|
|
info = "failed null profile create";
|
|
|
error = -ENOMEM;
|
|
|
goto audit;
|
|
|
}
|
|
|
+ target = &tprofile->label;
|
|
|
+ goto check;
|
|
|
}
|
|
|
|
|
|
- perms = change_profile_perms_wrapper(profile, target, request,
|
|
|
- profile->file.start);
|
|
|
- if (!(perms.allow & request)) {
|
|
|
- error = -EACCES;
|
|
|
- goto audit;
|
|
|
- }
|
|
|
+ /*
|
|
|
+ * self directed transitions only apply to current policy ns
|
|
|
+ * TODO: currently requiring perms for stacking and straight change
|
|
|
+ * stacking doesn't strictly need this. Determine how much
|
|
|
+ * we want to loosen this restriction for stacking
|
|
|
+ *
|
|
|
+ * if (!stack) {
|
|
|
+ */
|
|
|
+ error = fn_for_each_in_ns(label, profile,
|
|
|
+ change_profile_perms_wrapper(op, auditname,
|
|
|
+ profile, target, stack,
|
|
|
+ request, &perms));
|
|
|
+ if (error)
|
|
|
+ /* auditing done in change_profile_perms_wrapper */
|
|
|
+ goto out;
|
|
|
|
|
|
+ /* } */
|
|
|
+
|
|
|
+check:
|
|
|
/* check if tracing task is allowed to trace target domain */
|
|
|
- error = may_change_ptraced_domain(&target->label, &info);
|
|
|
- if (error) {
|
|
|
- info = "ptrace prevents transition";
|
|
|
+ error = may_change_ptraced_domain(target, &info);
|
|
|
+ if (error && !fn_for_each_in_ns(label, profile,
|
|
|
+ COMPLAIN_MODE(profile)))
|
|
|
goto audit;
|
|
|
- }
|
|
|
|
|
|
+ /* TODO: add permission check to allow this
|
|
|
+ * if ((flags & AA_CHANGE_ONEXEC) && !current_is_single_threaded()) {
|
|
|
+ * info = "not a single threaded task";
|
|
|
+ * error = -EACCES;
|
|
|
+ * goto audit;
|
|
|
+ * }
|
|
|
+ */
|
|
|
if (flags & AA_CHANGE_TEST)
|
|
|
- goto audit;
|
|
|
+ goto out;
|
|
|
|
|
|
- if (flags & AA_CHANGE_ONEXEC)
|
|
|
- error = aa_set_current_onexec(&target->label, 0);
|
|
|
- else
|
|
|
- error = aa_replace_current_label(&target->label);
|
|
|
+ if (!(flags & AA_CHANGE_ONEXEC)) {
|
|
|
+ /* only transition profiles in the current ns */
|
|
|
+ if (stack)
|
|
|
+ new = aa_label_merge(label, target, GFP_KERNEL);
|
|
|
+ else
|
|
|
+ new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
|
|
|
+ aa_get_label(target),
|
|
|
+ aa_get_label(&profile->label));
|
|
|
+ if (IS_ERR_OR_NULL(new)) {
|
|
|
+ info = "failed to build target label";
|
|
|
+ error = PTR_ERR(new);
|
|
|
+ new = NULL;
|
|
|
+ perms.allow = 0;
|
|
|
+ goto audit;
|
|
|
+ }
|
|
|
+ error = aa_replace_current_label(new);
|
|
|
+ } else
|
|
|
+ /* full transition will be built in exec path */
|
|
|
+ error = aa_set_current_onexec(target, stack);
|
|
|
|
|
|
audit:
|
|
|
- if (!(flags & AA_CHANGE_TEST))
|
|
|
- error = aa_audit_file(profile, &perms, op, request, NULL,
|
|
|
- fqname, NULL, GLOBAL_ROOT_UID, info,
|
|
|
- error);
|
|
|
+ error = fn_for_each_in_ns(label, profile,
|
|
|
+ aa_audit_file(profile, &perms, op, request, auditname,
|
|
|
+ NULL, new ? new : target,
|
|
|
+ GLOBAL_ROOT_UID, info, error));
|
|
|
|
|
|
- aa_put_profile(target);
|
|
|
+out:
|
|
|
+ aa_put_label(new);
|
|
|
+ aa_put_label(target);
|
|
|
aa_put_label(label);
|
|
|
- put_cred(cred);
|
|
|
|
|
|
return error;
|
|
|
}
|