Commit 29caf07e authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag 'apparmor-pr-2024-11-27' of...

Merge tag 'apparmor-pr-2024-11-27' of git://git.kernel.org/pub/scm/linux/kernel/git/jj/linux-apparmor

Pull apparmor updates from John Johansen:
 "Features:
   - extend next/check table to add support for 2^24 states to the state
     machine.
   - rework capability audit cache to use broader cred information
     instead of just the profile. Also add a time stamp so old entries
     can be aged out of the cache.

  Bug Fixes:
   - fix 'Do simple duplicate message elimination' to clear previous
     state when updating in capability audit cache
   - Fix memory leak for aa_unpack_strdup()
   - properly handle cx/px lookup failure when in complain mode
   - allocate xmatch for nullpdb inside aa_alloc_null fixing a NULL ptr
     deref of tracking profiles in when in complain mode

  Cleanups:
   - Remove everything being reported as deadcode
   - replace misleading 'scrubbing environment' phrase in debug print
   - Remove unnecessary NULL check before kvfree()
   - clean up duplicated parts of handle_onexec()
   - Use IS_ERR_OR_NULL() helper function
   - move new_profile declaration to top of block instead immediately
     after label to remove C23 extension warning

  Documentation:
   - add comment to document capability.c:profile_capable ad ptr
     parameter can not be NULL
   - add comment to document first entry is in packed perms struct is
     reserved for future planned expansion.
   - Update LSM/apparmor.rst add blurb for DEFAULT_SECURITY_APPARMOR"

* tag 'apparmor-pr-2024-11-27' of git://git.kernel.org/pub/scm/linux/kernel/git/jj/linux-apparmor:
  apparmor: lift new_profile declaration to remove C23 extension warning
  apparmor: replace misleading 'scrubbing environment' phrase in debug print
  parser: drop dead code for XXX_comb macros
  apparmor: Remove unused parameter L1 in macro next_comb
  Docs: Update LSM/apparmor.rst
  apparmor: audit_cap dedup based on subj_cred instead of profile
  apparmor: add a cache entry expiration time aging out capability audit cache
  apparmor: document capability.c:profile_capable ad ptr not being NULL
  apparmor: fix 'Do simple duplicate message elimination'
  apparmor: document first entry is in packed perms struct is reserved
  apparmor: test: Fix memory leak for aa_unpack_strdup()
  apparmor: Remove deadcode
  apparmor: Remove unnecessary NULL check before kvfree()
  apparmor: domain: clean up duplicated parts of handle_onexec()
  apparmor: Use IS_ERR_OR_NULL() helper function
  apparmor: add support for 2^24 states to the dfa state machine.
  apparmor: properly handle cx/px lookup failure for complain
  apparmor: allocate xmatch for nullpdb inside aa_alloc_null
parents 509f806f 04b5f0a5
Loading
Loading
Loading
Loading
+5 −2
Original line number Diff line number Diff line
@@ -18,8 +18,11 @@ set ``CONFIG_SECURITY_APPARMOR=y``

If AppArmor should be selected as the default security module then set::

   CONFIG_DEFAULT_SECURITY="apparmor"
   CONFIG_SECURITY_APPARMOR_BOOTPARAM_VALUE=1
   CONFIG_DEFAULT_SECURITY_APPARMOR=y

The CONFIG_LSM parameter manages the order and selection of LSMs.
Specify apparmor as the first "major" module (e.g. AppArmor, SELinux, Smack)
in the list.

Build the kernel

+1 −0
Original line number Diff line number Diff line
@@ -2366,6 +2366,7 @@ static struct aa_sfs_entry aa_sfs_entry_policy[] = {
	AA_SFS_FILE_U64("outofband",		MAX_OOB_SUPPORTED),
	AA_SFS_FILE_U64("permstable32_version",	1),
	AA_SFS_FILE_STRING("permstable32", PERMS32STR),
	AA_SFS_FILE_U64("state32",	1),
	AA_SFS_DIR("unconfined_restrictions",   aa_sfs_entry_unconfined),
	{ }
};
+12 −7
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@
#include <linux/errno.h>
#include <linux/gfp.h>
#include <linux/security.h>
#include <linux/timekeeping.h>

#include "include/apparmor.h"
#include "include/capability.h"
@@ -30,8 +31,9 @@ struct aa_sfs_entry aa_sfs_entry_caps[] = {
};

struct audit_cache {
	struct aa_profile *profile;
	kernel_cap_t caps;
	const struct cred *ad_subj_cred;
	/* Capabilities go from 0 to CAP_LAST_CAP */
	u64 ktime_ns_expiration[CAP_LAST_CAP+1];
};

static DEFINE_PER_CPU(struct audit_cache, audit_cache);
@@ -64,6 +66,8 @@ static void audit_cb(struct audit_buffer *ab, void *va)
static int audit_caps(struct apparmor_audit_data *ad, struct aa_profile *profile,
		      int cap, int error)
{
	const u64 AUDIT_CACHE_TIMEOUT_NS = 1000*1000*1000; /* 1 second */

	struct aa_ruleset *rules = list_first_entry(&profile->rules,
						    typeof(*rules), list);
	struct audit_cache *ent;
@@ -89,15 +93,16 @@ static int audit_caps(struct apparmor_audit_data *ad, struct aa_profile *profile

	/* Do simple duplicate message elimination */
	ent = &get_cpu_var(audit_cache);
	if (profile == ent->profile && cap_raised(ent->caps, cap)) {
	/* If the capability was never raised the timestamp check would also catch that */
	if (ad->subj_cred == ent->ad_subj_cred && ktime_get_ns() <= ent->ktime_ns_expiration[cap]) {
		put_cpu_var(audit_cache);
		if (COMPLAIN_MODE(profile))
			return complain_error(error);
		return error;
	} else {
		aa_put_profile(ent->profile);
		ent->profile = aa_get_profile(profile);
		cap_raise(ent->caps, cap);
		put_cred(ent->ad_subj_cred);
		ent->ad_subj_cred = get_cred(ad->subj_cred);
		ent->ktime_ns_expiration[cap] = ktime_get_ns() + AUDIT_CACHE_TIMEOUT_NS;
	}
	put_cpu_var(audit_cache);

@@ -109,7 +114,7 @@ static int audit_caps(struct apparmor_audit_data *ad, struct aa_profile *profile
 * @profile: profile being enforced    (NOT NULL, NOT unconfined)
 * @cap: capability to test if allowed
 * @opts: CAP_OPT_NOAUDIT bit determines whether audit record is generated
 * @ad: audit data (MAY BE NULL indicating no auditing)
 * @ad: audit data (NOT NULL)
 *
 * Returns: 0 if allowed else -EPERM
 */
+28 −38
Original line number Diff line number Diff line
@@ -636,6 +636,7 @@ static struct aa_label *profile_transition(const struct cred *subj_cred,
	struct aa_ruleset *rules = list_first_entry(&profile->rules,
						    typeof(*rules), list);
	struct aa_label *new = NULL;
	struct aa_profile *new_profile = NULL;
	const char *info = NULL, *name = NULL, *target = NULL;
	aa_state_t state = rules->file->start[AA_CLASS_FILE];
	struct aa_perms perms = {};
@@ -680,15 +681,18 @@ static struct aa_label *profile_transition(const struct cred *subj_cred,
			/* hack ix fallback - improve how this is detected */
			goto audit;
		} else if (!new) {
			error = -EACCES;
			info = "profile transition not found";
			/* remove MAY_EXEC to audit as failure */
			/* remove MAY_EXEC to audit as failure or complaint */
			perms.allow &= ~MAY_EXEC;
			if (COMPLAIN_MODE(profile)) {
				/* create null profile instead of failing */
				goto create_learning_profile;
			}
			error = -EACCES;
		}
	} else if (COMPLAIN_MODE(profile)) {
create_learning_profile:
		/* no exec permission - learning mode */
		struct aa_profile *new_profile = NULL;

		new_profile = aa_new_learning_profile(profile, false, name,
						      GFP_KERNEL);
		if (!new_profile) {
@@ -709,8 +713,8 @@ static struct aa_label *profile_transition(const struct cred *subj_cred,

	if (!(perms.xindex & AA_X_UNSAFE)) {
		if (DEBUG_ON) {
			dbg_printk("apparmor: scrubbing environment variables"
				   " for %s profile=", name);
			dbg_printk("apparmor: setting AT_SECURE for %s profile=",
				   name);
			aa_label_printk(new, GFP_KERNEL);
			dbg_printk("\n");
		}
@@ -789,8 +793,8 @@ static int profile_onexec(const struct cred *subj_cred,

	if (!(perms.xindex & AA_X_UNSAFE)) {
		if (DEBUG_ON) {
			dbg_printk("apparmor: scrubbing environment "
				   "variables for %s label=", xname);
			dbg_printk("apparmor: setting AT_SECURE for %s label=",
				   xname);
			aa_label_printk(onexec, GFP_KERNEL);
			dbg_printk("\n");
		}
@@ -821,33 +825,19 @@ static struct aa_label *handle_onexec(const struct cred *subj_cred,
	AA_BUG(!bprm);
	AA_BUG(!buffer);

	if (!stack) {
	/* TODO: determine how much we want to loosen this */
	error = fn_for_each_in_ns(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,
				aa_get_newest_label(onexec),
				profile_transition(subj_cred, profile, bprm,
						   buffer,
						   cond, unsafe));

	} else {
		/* TODO: determine how much we want to loosen this */
		error = fn_for_each_in_ns(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,
				aa_label_merge(&profile->label, onexec,
					       GFP_KERNEL),
			stack ? aa_label_merge(&profile->label, onexec,
					       GFP_KERNEL)
			      : aa_get_newest_label(onexec),
			profile_transition(subj_cred, profile, bprm,
						   buffer,
						   cond, unsafe));
	}

					   buffer, cond, unsafe));
	if (new)
		return new;

@@ -960,8 +950,8 @@ int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm)

	if (unsafe) {
		if (DEBUG_ON) {
			dbg_printk("scrubbing environment variables for %s "
				   "label=", bprm->filename);
			dbg_printk("setting AT_SECURE for %s label=",
				   bprm->filename);
			aa_label_printk(new, GFP_KERNEL);
			dbg_printk("\n");
		}
@@ -971,8 +961,8 @@ int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm)
	if (label->proxy != new->proxy) {
		/* when transitioning clear unsafe personality bits */
		if (DEBUG_ON) {
			dbg_printk("apparmor: clearing unsafe personality "
				   "bits. %s label=", bprm->filename);
			dbg_printk("apparmor: clearing unsafe personality bits. %s label=",
				   bprm->filename);
			aa_label_printk(new, GFP_KERNEL);
			dbg_printk("\n");
		}
+0 −28
Original line number Diff line number Diff line
@@ -160,31 +160,7 @@ int aa_label_next_confined(struct aa_label *l, int i);
#define label_for_each_cont(I, L, P)					\
	for (++((I).i); ((P) = (L)->vec[(I).i]); ++((I).i))

#define next_comb(I, L1, L2)						\
do {									\
	(I).j++;							\
	if ((I).j >= (L2)->size) {					\
		(I).i++;						\
		(I).j = 0;						\
	}								\
} while (0)


/* for each combination of P1 in L1, and P2 in L2 */
#define label_for_each_comb(I, L1, L2, P1, P2)				\
for ((I).i = (I).j = 0;							\
	((P1) = (L1)->vec[(I).i]) && ((P2) = (L2)->vec[(I).j]);		\
	(I) = next_comb(I, L1, L2))

#define fn_for_each_comb(L1, L2, P1, P2, FN)				\
({									\
	struct label_it i;						\
	int __E = 0;							\
	label_for_each_comb(i, (L1), (L2), (P1), (P2)) {		\
		last_error(__E, (FN));					\
	}								\
	__E;								\
})

/* for each profile that is enforcing confinement in a label */
#define label_for_each_confined(I, L, P)				\
@@ -291,8 +267,6 @@ bool aa_label_replace(struct aa_label *old, struct aa_label *new);
bool aa_label_make_newest(struct aa_labelset *ls, struct aa_label *old,
			  struct aa_label *new);

struct aa_label *aa_label_find(struct aa_label *l);

struct aa_profile *aa_label_next_in_merge(struct label_it *I,
					  struct aa_label *a,
					  struct aa_label *b);
@@ -320,8 +294,6 @@ void aa_label_seq_xprint(struct seq_file *f, struct aa_ns *ns,
			 struct aa_label *label, int flags, gfp_t gfp);
void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags,
		      gfp_t gfp);
void aa_label_audit(struct audit_buffer *ab, struct aa_label *label, gfp_t gfp);
void aa_label_seq_print(struct seq_file *f, struct aa_label *label, gfp_t gfp);
void aa_label_printk(struct aa_label *label, gfp_t gfp);

struct aa_label *aa_label_strn_parse(struct aa_label *base, const char *str,
Loading