Commit 93761c93 authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag 'apparmor-pr-2022-12-14' of...

Merge tag 'apparmor-pr-2022-12-14' of git://git.kernel.org/pub/scm/linux/kernel/git/jj/linux-apparmor

Pull apparmor updates from John Johansen:
 "Features:
   - switch to zstd compression for profile raw data

  Cleanups:
   - simplify obtaining the newest label on a cred
   - remove useless static inline functions
   - compute permission conversion on policy unpack
   - refactor code to share common permissins
   - refactor unpack to group policy backwards compatiblity code
   - add __init annotation to aa_{setup/teardown}_dfa_engine()

  Bug Fixes:
   - fix a memleak in
       - multi_transaction_new()
       - free_ruleset()
       - unpack_profile()
       - alloc_ns()
   - fix lockdep warning when removing a namespace
   - fix regression in stacking due to label flags
   - fix loading of child before parent
   - fix kernel-doc comments that differ from fns
   - fix spelling errors in comments
   - store return value of unpack_perms_table() to signed variable"

* tag 'apparmor-pr-2022-12-14' of git://git.kernel.org/pub/scm/linux/kernel/git/jj/linux-apparmor: (64 commits)
  apparmor: Fix uninitialized symbol 'array_size' in policy_unpack_test.c
  apparmor: Add __init annotation to aa_{setup/teardown}_dfa_engine()
  apparmor: Fix memleak in alloc_ns()
  apparmor: Fix memleak issue in unpack_profile()
  apparmor: fix a memleak in free_ruleset()
  apparmor: Fix spelling of function name in comment block
  apparmor: Use pointer to struct aa_label for lbs_cred
  AppArmor: Fix kernel-doc
  LSM: Fix kernel-doc
  AppArmor: Fix kernel-doc
  apparmor: Fix loading of child before parent
  apparmor: refactor code that alloc null profiles
  apparmor: fix obsoleted comments for aa_getprocattr() and audit_resource()
  apparmor: remove useless static inline functions
  apparmor: Fix unpack_profile() warn: passing zero to 'ERR_PTR'
  apparmor: fix uninitialize table variable in error in unpack_trans_table
  apparmor: store return value of unpack_perms_table() to signed variable
  apparmor: Fix kunit test for out of bounds array
  apparmor: Fix decompression of rawdata for read back to userspace
  apparmor: Fix undefined references to zstd_ symbols
  ...
parents 64e7003c 4295c60b
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -85,8 +85,8 @@ config SECURITY_APPARMOR_HASH_DEFAULT
config SECURITY_APPARMOR_EXPORT_BINARY
	bool "Allow exporting the raw binary policy"
	depends on SECURITY_APPARMOR_INTROSPECT_POLICY
	select ZLIB_INFLATE
	select ZLIB_DEFLATE
	select ZSTD_COMPRESS
	select ZSTD_DECOMPRESS
	default y
	help
	  This option allows reading back binary policy as it was loaded.
+2 −1
Original line number Diff line number Diff line
@@ -5,7 +5,8 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o

apparmor-y := apparmorfs.o audit.o capability.o task.o ipc.o lib.o match.o \
              path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \
              resource.o secid.o file.o policy_ns.o label.o mount.o net.o
              resource.o secid.o file.o policy_ns.o label.o mount.o net.o \
              policy_compat.o
apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o

obj-$(CONFIG_SECURITY_APPARMOR_KUNIT_TEST) += apparmor_policy_unpack_test.o
+63 −52
Original line number Diff line number Diff line
@@ -21,7 +21,7 @@
#include <linux/fs.h>
#include <linux/fs_context.h>
#include <linux/poll.h>
#include <linux/zlib.h>
#include <linux/zstd.h>
#include <uapi/linux/major.h>
#include <uapi/linux/magic.h>

@@ -611,29 +611,30 @@ static const struct file_operations aa_fs_ns_revision_fops = {
static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms,
			     const char *match_str, size_t match_len)
{
	struct aa_ruleset *rules = list_first_entry(&profile->rules,
						    typeof(*rules), list);
	struct aa_perms tmp = { };
	struct aa_dfa *dfa;
	unsigned int state = 0;
	aa_state_t state = DFA_NOMATCH;

	if (profile_unconfined(profile))
		return;
	if (profile->file.dfa && *match_str == AA_CLASS_FILE) {
		dfa = profile->file.dfa;
		state = aa_dfa_match_len(dfa, profile->file.start,
	if (rules->file.dfa && *match_str == AA_CLASS_FILE) {
		state = aa_dfa_match_len(rules->file.dfa,
					 rules->file.start[AA_CLASS_FILE],
					 match_str + 1, match_len - 1);
		if (state) {
			struct path_cond cond = { };

			tmp = aa_compute_fperms(dfa, state, &cond);
			tmp = *(aa_lookup_fperms(&(rules->file), state, &cond));
		}
	} else if (profile->policy.dfa) {
		if (!PROFILE_MEDIATES(profile, *match_str))
	} else if (rules->policy.dfa) {
		if (!RULE_MEDIATES(rules, *match_str))
			return;	/* no change to current perms */
		dfa = profile->policy.dfa;
		state = aa_dfa_match_len(dfa, profile->policy.start[0],
		state = aa_dfa_match_len(rules->policy.dfa,
					 rules->policy.start[0],
					 match_str, match_len);
		if (state)
			aa_compute_perms(dfa, state, &tmp);
			tmp = *aa_lookup_perms(&rules->policy, state);
	}
	aa_apply_modes_to_perms(profile, &tmp);
	aa_perms_accum_raw(perms, &tmp);
@@ -868,8 +869,10 @@ static struct multi_transaction *multi_transaction_new(struct file *file,
	if (!t)
		return ERR_PTR(-ENOMEM);
	kref_init(&t->count);
	if (copy_from_user(t->data, buf, size))
	if (copy_from_user(t->data, buf, size)) {
		put_multi_transaction(t);
		return ERR_PTR(-EFAULT);
	}

	return t;
}
@@ -1090,9 +1093,9 @@ static int seq_profile_attach_show(struct seq_file *seq, void *v)
	struct aa_proxy *proxy = seq->private;
	struct aa_label *label = aa_get_label_rcu(&proxy->label);
	struct aa_profile *profile = labels_profile(label);
	if (profile->attach)
		seq_printf(seq, "%s\n", profile->attach);
	else if (profile->xmatch)
	if (profile->attach.xmatch_str)
		seq_printf(seq, "%s\n", profile->attach.xmatch_str);
	else if (profile->attach.xmatch.dfa)
		seq_puts(seq, "<unknown>\n");
	else
		seq_printf(seq, "%s\n", profile->base.name);
@@ -1197,10 +1200,24 @@ static int seq_ns_name_show(struct seq_file *seq, void *v)
	return 0;
}

static int seq_ns_compress_min_show(struct seq_file *seq, void *v)
{
	seq_printf(seq, "%d\n", AA_MIN_CLEVEL);
	return 0;
}

static int seq_ns_compress_max_show(struct seq_file *seq, void *v)
{
	seq_printf(seq, "%d\n", AA_MAX_CLEVEL);
	return 0;
}

SEQ_NS_FOPS(stacked);
SEQ_NS_FOPS(nsstacked);
SEQ_NS_FOPS(level);
SEQ_NS_FOPS(name);
SEQ_NS_FOPS(compress_min);
SEQ_NS_FOPS(compress_max);


/* policy/raw_data/ * file ops */
@@ -1295,42 +1312,34 @@ SEQ_RAWDATA_FOPS(revision);
SEQ_RAWDATA_FOPS(hash);
SEQ_RAWDATA_FOPS(compressed_size);

static int deflate_decompress(char *src, size_t slen, char *dst, size_t dlen)
static int decompress_zstd(char *src, size_t slen, char *dst, size_t dlen)
{
#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
	if (aa_g_rawdata_compression_level != 0) {
		int error = 0;
		struct z_stream_s strm;

		memset(&strm, 0, sizeof(strm));

		strm.workspace = kvzalloc(zlib_inflate_workspacesize(), GFP_KERNEL);
		if (!strm.workspace)
			return -ENOMEM;

		strm.next_in = src;
		strm.avail_in = slen;

		error = zlib_inflateInit(&strm);
		if (error != Z_OK) {
			error = -ENOMEM;
			goto fail_inflate_init;
		}

		strm.next_out = dst;
		strm.avail_out = dlen;

		error = zlib_inflate(&strm, Z_FINISH);
		if (error != Z_STREAM_END)
			error = -EINVAL;
		else
			error = 0;

		zlib_inflateEnd(&strm);
fail_inflate_init:
		kvfree(strm.workspace);

		return error;
	if (slen < dlen) {
		const size_t wksp_len = zstd_dctx_workspace_bound();
		zstd_dctx *ctx;
		void *wksp;
		size_t out_len;
		int ret = 0;

		wksp = kvzalloc(wksp_len, GFP_KERNEL);
		if (!wksp) {
			ret = -ENOMEM;
			goto cleanup;
		}
		ctx = zstd_init_dctx(wksp, wksp_len);
		if (ctx == NULL) {
			ret = -ENOMEM;
			goto cleanup;
		}
		out_len = zstd_decompress_dctx(ctx, dst, dlen, src, slen);
		if (zstd_is_error(out_len)) {
			ret = -EINVAL;
			goto cleanup;
		}
cleanup:
		kvfree(wksp);
		return ret;
	}
#endif

@@ -1379,7 +1388,7 @@ static int rawdata_open(struct inode *inode, struct file *file)

	private->loaddata = loaddata;

	error = deflate_decompress(loaddata->data, loaddata->compressed_size,
	error = decompress_zstd(loaddata->data, loaddata->compressed_size,
				RAWDATA_F_DATA_BUF(private),
				loaddata->size);
	if (error)
@@ -2392,6 +2401,8 @@ static struct aa_sfs_entry aa_sfs_entry_apparmor[] = {
	AA_SFS_FILE_FOPS(".ns_level", 0444, &seq_ns_level_fops),
	AA_SFS_FILE_FOPS(".ns_name", 0444, &seq_ns_name_fops),
	AA_SFS_FILE_FOPS("profiles", 0444, &aa_sfs_profiles_fops),
	AA_SFS_FILE_FOPS("raw_data_compression_level_min", 0444, &seq_ns_compress_min_fops),
	AA_SFS_FILE_FOPS("raw_data_compression_level_max", 0444, &seq_ns_compress_max_fops),
	AA_SFS_DIR("features", aa_sfs_entry_features),
	{ }
};
+44 −1
Original line number Diff line number Diff line
@@ -36,6 +36,43 @@ static const char *const aa_audit_type[] = {
	"AUTO"
};

static const char *const aa_class_names[] = {
	"none",
	"unknown",
	"file",
	"cap",
	"net",
	"rlimits",
	"domain",
	"mount",
	"unknown",
	"ptrace",
	"signal",
	"xmatch",
	"unknown",
	"unknown",
	"net",
	"unknown",
	"label",
	"posix_mqueue",
	"io_uring",
	"module",
	"lsm",
	"unknown",
	"unknown",
	"unknown",
	"unknown",
	"unknown",
	"unknown",
	"unknown",
	"unknown",
	"unknown",
	"unknown",
	"X",
	"dbus",
};


/*
 * Currently AppArmor auditing is fed straight into the audit framework.
 *
@@ -46,7 +83,7 @@ static const char *const aa_audit_type[] = {
 */

/**
 * audit_base - core AppArmor function.
 * audit_pre() - core AppArmor function.
 * @ab: audit buffer to fill (NOT NULL)
 * @ca: audit structure containing data to audit (NOT NULL)
 *
@@ -65,6 +102,12 @@ static void audit_pre(struct audit_buffer *ab, void *ca)
		audit_log_format(ab, " operation=\"%s\"", aad(sa)->op);
	}

	if (aad(sa)->class)
		audit_log_format(ab, " class=\"%s\"",
				 aad(sa)->class <= AA_CLASS_LAST ?
				 aa_class_names[aad(sa)->class] :
				 "unknown");

	if (aad(sa)->info) {
		audit_log_format(ab, " info=\"%s\"", aad(sa)->info);
		if (aad(sa)->error)
+10 −6
Original line number Diff line number Diff line
@@ -64,6 +64,8 @@ static void audit_cb(struct audit_buffer *ab, void *va)
static int audit_caps(struct common_audit_data *sa, struct aa_profile *profile,
		      int cap, int error)
{
	struct aa_ruleset *rules = list_first_entry(&profile->rules,
						    typeof(*rules), list);
	struct audit_cache *ent;
	int type = AUDIT_APPARMOR_AUTO;

@@ -72,13 +74,13 @@ static int audit_caps(struct common_audit_data *sa, struct aa_profile *profile,
	if (likely(!error)) {
		/* test if auditing is being forced */
		if (likely((AUDIT_MODE(profile) != AUDIT_ALL) &&
			   !cap_raised(profile->caps.audit, cap)))
			   !cap_raised(rules->caps.audit, cap)))
			return 0;
		type = AUDIT_APPARMOR_AUDIT;
	} else if (KILL_MODE(profile) ||
		   cap_raised(profile->caps.kill, cap)) {
		   cap_raised(rules->caps.kill, cap)) {
		type = AUDIT_APPARMOR_KILL;
	} else if (cap_raised(profile->caps.quiet, cap) &&
	} else if (cap_raised(rules->caps.quiet, cap) &&
		   AUDIT_MODE(profile) != AUDIT_NOQUIET &&
		   AUDIT_MODE(profile) != AUDIT_ALL) {
		/* quiet auditing */
@@ -114,10 +116,12 @@ static int audit_caps(struct common_audit_data *sa, struct aa_profile *profile,
static int profile_capable(struct aa_profile *profile, int cap,
			   unsigned int opts, struct common_audit_data *sa)
{
	struct aa_ruleset *rules = list_first_entry(&profile->rules,
						    typeof(*rules), list);
	int error;

	if (cap_raised(profile->caps.allow, cap) &&
	    !cap_raised(profile->caps.denied, cap))
	if (cap_raised(rules->caps.allow, cap) &&
	    !cap_raised(rules->caps.denied, cap))
		error = 0;
	else
		error = -EPERM;
@@ -148,7 +152,7 @@ int aa_capable(struct aa_label *label, int cap, unsigned int opts)
{
	struct aa_profile *profile;
	int error = 0;
	DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_CAP, OP_CAPABLE);
	DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_CAP, AA_CLASS_CAP, OP_CAPABLE);

	sa.u.cap = cap;
	error = fn_for_each_confined(label, profile,
Loading