Commit 219d7660 authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag 'apparmor-pr-2026-02-18' of...

Merge tag 'apparmor-pr-2026-02-18' of git://git.kernel.org/pub/scm/linux/kernel/git/jj/linux-apparmor

Pull AppArmor updates from John Johansen:
 "Features:
   - add .kunitconfig
   - audit execpath in userns mediation
   - add support loading per permission tagging

  Cleanups:
   - remove unused percpu critical sections in buffer management
   - document the buffer hold, add an overflow guard
   - split xxx_in_ns into its two separate semantic use cases
   - remove apply_modes_to_perms from label_match
   - refactor/cleanup cred helper fns.
   - guard against free attachment/data routines being called with NULL
   - drop in_atomic flag in common_mmap, common_file_perm, and cleanup
   - make str table more generic and be able to have multiple entries
   - Replace deprecated strcpy with memcpy in gen_symlink_name
   - Replace deprecated strcpy in d_namespace_path
   - Replace sprintf/strcpy with scnprintf/strscpy in aa_policy_init
   - replace sprintf with snprintf in aa_new_learning_profile

  Bug Fixes:
   - fix cast in format string DEBUG statement
   - fix make aa_labelmatch return consistent
   - fix fmt string type error in process_strs_entry
   - fix kernel-doc comments for inview
   - fix invalid deref of rawdata when export_binary is unset
   - avoid per-cpu hold underflow in aa_get_buffer
   - fix fast path cache check for unix sockets
   - fix rlimit for posix cpu timers
   - fix label and profile debug macros
   - move check for aa_null file to cover all cases
   - return -ENOMEM in unpack_perms_table upon alloc failure
   - fix boolean argument in apparmor_mmap_file
   - Fix & Optimize table creation from possibly unaligned memory
   - Allow apparmor to handle unaligned dfa tables
   - fix NULL deref in aa_sock_file_perm
   - fix NULL pointer dereference in __unix_needs_revalidation
   - fix signedness bug in unpack_tags()"

* tag 'apparmor-pr-2026-02-18' of git://git.kernel.org/pub/scm/linux/kernel/git/jj/linux-apparmor: (34 commits)
  apparmor: fix signedness bug in unpack_tags()
  apparmor: fix cast in format string DEBUG statement
  apparmor: fix aa_label to return state from compount and component match
  apparmor: fix fmt string type error in process_strs_entry
  apparmor: fix kernel-doc comments for inview
  apparmor: fix invalid deref of rawdata when export_binary is unset
  apparmor: add .kunitconfig
  apparmor: cleanup remove unused percpu critical sections in buffer management
  apparmor: document the buffer hold, add an overflow guard
  apparmor: avoid per-cpu hold underflow in aa_get_buffer
  apparmor: split xxx_in_ns into its two separate semantic use cases
  apparmor: make label_match return a consistent value
  apparmor: remove apply_modes_to_perms from label_match
  apparmor: fix fast path cache check for unix sockets
  apparmor: fix rlimit for posix cpu timers
  apparmor: refactor/cleanup cred helper fns.
  apparmor: fix label and profile debug macros
  apparmor: move check for aa_null file to cover all cases
  apparmor: guard against free routines being called with a NULL
  apparmor: return -ENOMEM in unpack_perms_table upon alloc failure
  ...
parents 43257b2e 08020dbe
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
CONFIG_KUNIT=y
CONFIG_NET=y
CONFIG_SECURITY=y
CONFIG_SECURITY_APPARMOR=y
CONFIG_SECURITY_APPARMOR_KUNIT_TEST=y
+1 −1
Original line number Diff line number Diff line
@@ -416,7 +416,7 @@ static int profile_peer_perm(struct aa_profile *profile, u32 request,
				      unix_sk(sk),
				      peer_addr, peer_addrlen, &p, &ad->info);

		return fn_for_each_in_ns(peer_label, peerp,
		return fn_for_each_in_scope(peer_label, peerp,
				match_label(profile, rules, state, request,
					    peerp, p, ad));
	}
+18 −5
Original line number Diff line number Diff line
@@ -801,7 +801,7 @@ static ssize_t query_label(char *buf, size_t buf_len,

	perms = allperms;
	if (view_only) {
		label_for_each_in_ns(i, labels_ns(label), label, profile) {
		label_for_each_in_scope(i, labels_ns(label), label, profile) {
			profile_query_cb(profile, &perms, match_str, match_len);
		}
	} else {
@@ -1607,16 +1607,20 @@ static char *gen_symlink_name(int depth, const char *dirname, const char *fname)
{
	char *buffer, *s;
	int error;
	int size = depth * 6 + strlen(dirname) + strlen(fname) + 11;
	const char *path = "../../";
	size_t path_len = strlen(path);
	int size;

	/* Extra 11 bytes: "raw_data" (9) + two slashes "//" (2) */
	size = depth * path_len + strlen(dirname) + strlen(fname) + 11;
	s = buffer = kmalloc(size, GFP_KERNEL);
	if (!buffer)
		return ERR_PTR(-ENOMEM);

	for (; depth > 0; depth--) {
		strcpy(s, "../../");
		s += 6;
		size -= 6;
		memcpy(s, path, path_len);
		s += path_len;
		size -= path_len;
	}

	error = snprintf(s, size, "raw_data/%s/%s", dirname, fname);
@@ -1644,6 +1648,15 @@ static const char *rawdata_get_link_base(struct dentry *dentry,

	label = aa_get_label_rcu(&proxy->label);
	profile = labels_profile(label);

	/* rawdata can be null when aa_g_export_binary is unset during
	 * runtime and a profile is replaced
	 */
	if (!profile->rawdata) {
		aa_put_label(label);
		return ERR_PTR(-ENOENT);
	}

	depth = profile_depth(profile);
	target = gen_symlink_name(depth, profile->rawdata->name, name);
	aa_put_label(label);
+31 −29
Original line number Diff line number Diff line
@@ -115,7 +115,7 @@ static inline aa_state_t match_component(struct aa_profile *profile,
 * @label: label to check access permissions for
 * @stack: whether this is a stacking request
 * @state: state to start match in
 * @subns: whether to do permission checks on components in a subns
 * @inview: whether to match labels in view or only in scope
 * @request: permissions to request
 * @perms: perms struct to set
 *
@@ -127,7 +127,7 @@ static inline aa_state_t match_component(struct aa_profile *profile,
 */
static int label_compound_match(struct aa_profile *profile,
				struct aa_label *label, bool stack,
				aa_state_t state, bool subns, u32 request,
				aa_state_t state, bool inview, u32 request,
				struct aa_perms *perms)
{
	struct aa_ruleset *rules = profile->label.rules[0];
@@ -135,9 +135,9 @@ static int label_compound_match(struct aa_profile *profile,
	struct label_it i;
	struct path_cond cond = { };

	/* find first subcomponent that is visible */
	/* find first subcomponent that is in view and going to be interated with */
	label_for_each(i, label, tp) {
		if (!aa_ns_visible(profile->ns, tp->ns, subns))
		if (!aa_ns_visible(profile->ns, tp->ns, inview))
			continue;
		state = match_component(profile, tp, stack, state);
		if (!state)
@@ -151,7 +151,7 @@ static int label_compound_match(struct aa_profile *profile,

next:
	label_for_each_cont(i, label, tp) {
		if (!aa_ns_visible(profile->ns, tp->ns, subns))
		if (!aa_ns_visible(profile->ns, tp->ns, inview))
			continue;
		state = aa_dfa_match(rules->file->dfa, state, "//&");
		state = match_component(profile, tp, false, state);
@@ -177,7 +177,7 @@ static int label_compound_match(struct aa_profile *profile,
 * @label: label to check access permissions for
 * @stack: whether this is a stacking request
 * @start: state to start match in
 * @subns: whether to do permission checks on components in a subns
 * @inview: whether to match labels in view or only in scope
 * @request: permissions to request
 * @perms: an initialized perms struct to add accumulation to
 *
@@ -189,7 +189,7 @@ static int label_compound_match(struct aa_profile *profile,
 */
static int label_components_match(struct aa_profile *profile,
				  struct aa_label *label, bool stack,
				  aa_state_t start, bool subns, u32 request,
				  aa_state_t start, bool inview, u32 request,
				  struct aa_perms *perms)
{
	struct aa_ruleset *rules = profile->label.rules[0];
@@ -201,7 +201,7 @@ static int label_components_match(struct aa_profile *profile,

	/* find first subcomponent to test */
	label_for_each(i, label, tp) {
		if (!aa_ns_visible(profile->ns, tp->ns, subns))
		if (!aa_ns_visible(profile->ns, tp->ns, inview))
			continue;
		state = match_component(profile, tp, stack, start);
		if (!state)
@@ -218,7 +218,7 @@ static int label_components_match(struct aa_profile *profile,
	aa_apply_modes_to_perms(profile, &tmp);
	aa_perms_accum(perms, &tmp);
	label_for_each_cont(i, label, tp) {
		if (!aa_ns_visible(profile->ns, tp->ns, subns))
		if (!aa_ns_visible(profile->ns, tp->ns, inview))
			continue;
		state = match_component(profile, tp, stack, start);
		if (!state)
@@ -245,26 +245,26 @@ static int label_components_match(struct aa_profile *profile,
 * @label: label to match (NOT NULL)
 * @stack: whether this is a stacking request
 * @state: state to start in
 * @subns: whether to match subns components
 * @inview: whether to match labels in view or only in scope
 * @request: permission request
 * @perms: Returns computed perms (NOT NULL)
 *
 * Returns: the state the match finished in, may be the none matching state
 */
static int label_match(struct aa_profile *profile, struct aa_label *label,
		       bool stack, aa_state_t state, bool subns, u32 request,
		       bool stack, aa_state_t state, bool inview, u32 request,
		       struct aa_perms *perms)
{
	int error;

	*perms = nullperms;
	error = label_compound_match(profile, label, stack, state, subns,
	error = label_compound_match(profile, label, stack, state, inview,
				     request, perms);
	if (!error)
		return error;

	*perms = allperms;
	return label_components_match(profile, label, stack, state, subns,
	return label_components_match(profile, label, stack, state, inview,
				      request, perms);
}

@@ -529,7 +529,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
	/* TODO: move lookup parsing to unpack time so this is a straight
	 *       index into the resultant label
	 */
	for (next = rules->file->trans.table[index]; next;
	for (next = rules->file->trans.table[index].strs; next;
	     next = next_name(xtype, next)) {
		const char *lookup = (*next == '&') ? next + 1 : next;
		*name = next;
@@ -880,14 +880,16 @@ static struct aa_label *handle_onexec(const struct cred *subj_cred,
	AA_BUG(!bprm);
	AA_BUG(!buffer);

	/* TODO: determine how much we want to loosen this */
	error = fn_for_each_in_ns(label, profile,
	/* TODO: determine how much we want to loosen this
	 * only check profiles in scope for permission to change at exec
	 */
	error = fn_for_each_in_scope(label, profile,
			profile_onexec(subj_cred, profile, onexec, stack,
				       bprm, buffer, cond, unsafe));
	if (error)
		return ERR_PTR(error);

	new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
	new = fn_label_build_in_scope(label, profile, GFP_KERNEL,
			stack ? aa_label_merge(&profile->label, onexec,
					       GFP_KERNEL)
			      : aa_get_newest_label(onexec),
@@ -897,7 +899,7 @@ static struct aa_label *handle_onexec(const struct cred *subj_cred,
		return new;

	/* TODO: get rid of GLOBAL_ROOT_UID */
	error = fn_for_each_in_ns(label, profile,
	error = fn_for_each_in_scope(label, profile,
			aa_audit_file(subj_cred, profile, &nullperms,
				      OP_CHANGE_ONEXEC,
				      AA_MAY_ONEXEC, bprm->filename, NULL,
@@ -1123,7 +1125,7 @@ static struct aa_label *change_hat(const struct cred *subj_cred,
	/*find first matching hat */
	for (i = 0; i < count && !hat; i++) {
		name = hats[i];
		label_for_each_in_ns(it, labels_ns(label), label, profile) {
		label_for_each_in_scope(it, labels_ns(label), label, profile) {
			if (sibling && PROFILE_IS_HAT(profile)) {
				root = aa_get_profile_rcu(&profile->parent);
			} else if (!sibling && !PROFILE_IS_HAT(profile)) {
@@ -1159,7 +1161,7 @@ static struct aa_label *change_hat(const struct cred *subj_cred,
	 * change_hat.
	 */
	name = NULL;
	label_for_each_in_ns(it, labels_ns(label), label, profile) {
	label_for_each_in_scope(it, labels_ns(label), label, profile) {
		if (!list_empty(&profile->base.profiles)) {
			info = "hat not found";
			error = -ENOENT;
@@ -1170,7 +1172,7 @@ static struct aa_label *change_hat(const struct cred *subj_cred,
	error = -ECHILD;

fail:
	label_for_each_in_ns(it, labels_ns(label), label, profile) {
	label_for_each_in_scope(it, labels_ns(label), label, profile) {
		/*
		 * no target as it has failed to be found or built
		 *
@@ -1188,7 +1190,7 @@ static struct aa_label *change_hat(const struct cred *subj_cred,
	return ERR_PTR(error);

build:
	new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
	new = fn_label_build_in_scope(label, profile, GFP_KERNEL,
				   build_change_hat(subj_cred, profile, name,
						    sibling),
				   aa_get_label(&profile->label));
@@ -1251,7 +1253,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags)
		bool empty = true;

		rcu_read_lock();
		label_for_each_in_ns(i, labels_ns(label), label, profile) {
		label_for_each_in_scope(i, labels_ns(label), label, profile) {
			empty &= list_empty(&profile->base.profiles);
		}
		rcu_read_unlock();
@@ -1338,7 +1340,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags)
	perms.kill = AA_MAY_CHANGEHAT;

fail:
	fn_for_each_in_ns(label, profile,
	fn_for_each_in_scope(label, profile,
		aa_audit_file(subj_cred, profile, &perms, OP_CHANGE_HAT,
			      AA_MAY_CHANGEHAT, NULL, NULL, target,
			      GLOBAL_ROOT_UID, info, error));
@@ -1446,7 +1448,7 @@ int aa_change_profile(const char *fqname, int flags)
		 */
		stack = true;
		perms.audit = request;
		(void) fn_for_each_in_ns(label, profile,
		(void) fn_for_each_in_scope(label, profile,
				aa_audit_file(subj_cred, profile, &perms, op,
					      request, auditname, NULL, target,
					      GLOBAL_ROOT_UID, stack_msg, 0));
@@ -1492,7 +1494,7 @@ int aa_change_profile(const char *fqname, int flags)
	 *
	 * if (!stack) {
	 */
	error = fn_for_each_in_ns(label, profile,
	error = fn_for_each_in_scope(label, profile,
			change_profile_perms_wrapper(op, auditname,
						     subj_cred,
						     profile, target, stack,
@@ -1506,7 +1508,7 @@ int aa_change_profile(const char *fqname, int flags)
check:
	/* check if tracing task is allowed to trace target domain */
	error = may_change_ptraced_domain(subj_cred, target, &info);
	if (error && !fn_for_each_in_ns(label, profile,
	if (error && !fn_for_each_in_scope(label, profile,
					COMPLAIN_MODE(profile)))
		goto audit;

@@ -1522,7 +1524,7 @@ int aa_change_profile(const char *fqname, int flags)

	/* stacking is always a subset, so only check the nonstack case */
	if (!stack) {
		new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
		new = fn_label_build_in_scope(label, profile, GFP_KERNEL,
					   aa_get_label(target),
					   aa_get_label(&profile->label));
		/*
@@ -1565,7 +1567,7 @@ int aa_change_profile(const char *fqname, int flags)
	}

audit:
	error = fn_for_each_in_ns(label, profile,
	error = fn_for_each_in_scope(label, profile,
			aa_audit_file(subj_cred,
				      profile, &perms, op, request, auditname,
				      NULL, new ? new : target,
+34 −15
Original line number Diff line number Diff line
@@ -103,6 +103,7 @@ int aa_audit_file(const struct cred *subj_cred,

	ad.subj_cred = subj_cred;
	ad.request = request;
	ad.tags = perms->tag;
	ad.name = name;
	ad.fs.target = target;
	ad.peer = tlabel;
@@ -154,6 +155,10 @@ static int path_name(const char *op, const struct cred *subj_cred,
	const char *info = NULL;
	int error;

	/* don't reaudit files closed during inheritance */
	if (unlikely(path->dentry == aa_null.dentry))
		error = -EACCES;
	else
		error = aa_path_name(path, flags, buffer, name, &info,
				     labels_profile(label)->disconnected);
	if (error) {
@@ -567,8 +572,7 @@ static bool __file_is_delegated(struct aa_label *obj_label)
	return unconfined(obj_label);
}

static bool __unix_needs_revalidation(struct file *file, struct aa_label *label,
				      u32 request)
static bool __is_unix_file(struct file *file)
{
	struct socket *sock = (struct socket *) file->private_data;

@@ -576,20 +580,31 @@ static bool __unix_needs_revalidation(struct file *file, struct aa_label *label,

	if (!S_ISSOCK(file_inode(file)->i_mode))
		return false;
	if (request & NET_PEER_MASK)
	/* sock and sock->sk can be NULL for sockets being set up or torn down */
	if (!sock || !sock->sk)
		return false;
	if (sock->sk->sk_family == PF_UNIX) {
		struct aa_sk_ctx *ctx = aa_sock(sock->sk);

		if (rcu_access_pointer(ctx->peer) !=
		    rcu_access_pointer(ctx->peer_lastupdate))
	if (sock->sk->sk_family == PF_UNIX)
		return true;
		return !__aa_subj_label_is_cached(rcu_dereference(ctx->label),
						  label);
	}
	return false;
}

static bool __unix_needs_revalidation(struct file *file, struct aa_label *label,
				      u32 request)
{
	struct socket *sock = (struct socket *) file->private_data;

	AA_BUG(!__is_unix_file(file));
	lockdep_assert_in_rcu_read_lock();

	struct aa_sk_ctx *skctx = aa_sock(sock->sk);

	if (rcu_access_pointer(skctx->peer) !=
	    rcu_access_pointer(skctx->peer_lastupdate))
		return true;

	return !__aa_subj_label_is_cached(rcu_dereference(skctx->label), label);
}

/**
 * aa_file_perm - do permission revalidation check & audit for @file
 * @op: operation being checked
@@ -613,6 +628,10 @@ int aa_file_perm(const char *op, const struct cred *subj_cred,
	AA_BUG(!label);
	AA_BUG(!file);

	/* don't reaudit files closed during inheritance */
	if (unlikely(file->f_path.dentry == aa_null.dentry))
		return -EACCES;

	fctx = file_ctx(file);

	rcu_read_lock();
@@ -628,7 +647,7 @@ int aa_file_perm(const char *op, const struct cred *subj_cred,
	 */
	denied = request & ~fctx->allow;
	if (unconfined(label) || __file_is_delegated(flabel) ||
	    __unix_needs_revalidation(file, label, request) ||
	    (!denied && __is_unix_file(file) && !__unix_needs_revalidation(file, label, request)) ||
	    (!denied && __aa_subj_label_is_cached(label, flabel))) {
		rcu_read_unlock();
		goto done;
Loading