apparmor: allow restricting unprivileged change_profile

unprivileged unconfined can use change_profile to alter the confinement
set by the mac admin.

Allow restricting unprivileged unconfined by still allowing change_profile
but stacking the change against unconfined. This allows unconfined to
still apply system policy but allows the task to enter the new confinement.

If unprivileged unconfined is required a sysctl is provided to switch
to the previous behavior.

Reviewed-by: Georgia Garcia <georgia.garcia@canonical.com>
Signed-off-by: John Johansen <john.johansen@canonical.com>
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c
index 87dfa0e..ed4a13d 100644
--- a/security/apparmor/domain.c
+++ b/security/apparmor/domain.c
@@ -1311,6 +1311,8 @@ static int change_profile_perms_wrapper(const char *op, const char *name,
 	return error;
 }
 
+const char *stack_msg = "change_profile unprivileged unconfined converted to stacking";
+
 /**
  * aa_change_profile - perform a one-way profile transition
  * @fqname: name of profile may include namespace (NOT NULL)
@@ -1370,6 +1372,28 @@ int aa_change_profile(const char *fqname, int flags)
 			op = OP_CHANGE_PROFILE;
 	}
 
+	/* This should move to a per profile test. Requires pushing build
+	 * into callback
+	 */
+	if (!stack && unconfined(label) &&
+	    label == &labels_ns(label)->unconfined->label &&
+	    aa_unprivileged_unconfined_restricted &&
+	    /* TODO: refactor so this check is a fn */
+	    cap_capable(current_cred(), &init_user_ns, CAP_MAC_OVERRIDE,
+			CAP_OPT_NOAUDIT)) {
+		/* regardless of the request in this case apparmor
+		 * stacks against unconfined so admin set policy can't be
+		 * by-passed
+		 */
+		stack = true;
+		perms.audit = request;
+		(void) fn_for_each_in_ns(label, profile,
+				aa_audit_file(subj_cred, profile, &perms, op,
+					      request, auditname, NULL, target,
+					      GLOBAL_ROOT_UID, stack_msg, 0));
+		perms.audit = 0;
+	}
+
 	if (*fqname == '&') {
 		stack = true;
 		/* don't have label_parse() do stacking */