summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-07-06 14:53:35 +0100
committerMark Brown <broonie@kernel.org>2026-07-06 14:53:35 +0100
commitaf08992ee167ececa094117817ef6baad8dd1c90 (patch)
treef4cf0c8387a8694668456908826437296779b84c
parent0c795db2b766fd808a1c4ee3f2f9f4d866805b5f (diff)
parentdda61023f976d7ab3bc7c8b46d26d6d23424f890 (diff)
downloadlinux-next-af08992ee167ececa094117817ef6baad8dd1c90.tar.gz
linux-next-af08992ee167ececa094117817ef6baad8dd1c90.zip
Merge branch 'apparmor-next' of https://git.kernel.org/pub/scm/linux/kernel/git/jj/linux-apparmor
-rw-r--r--security/apparmor/af_unix.c1
-rw-r--r--security/apparmor/apparmorfs.c129
-rw-r--r--security/apparmor/domain.c1
-rw-r--r--security/apparmor/include/apparmorfs.h3
-rw-r--r--security/apparmor/include/capability.h1
-rw-r--r--security/apparmor/include/path.h3
-rw-r--r--security/apparmor/include/policy.h4
-rw-r--r--security/apparmor/include/policy_unpack.h4
-rw-r--r--security/apparmor/include/procattr.h2
-rw-r--r--security/apparmor/include/task.h5
-rw-r--r--security/apparmor/policy.c8
-rw-r--r--security/apparmor/policy_unpack.c24
12 files changed, 174 insertions, 11 deletions
diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c
index b9b22edae202..9ca9d1b890ba 100644
--- a/security/apparmor/af_unix.c
+++ b/security/apparmor/af_unix.c
@@ -20,6 +20,7 @@
#include "include/apparmor.h"
#include "include/file.h"
#include "include/label.h"
+#include "include/net.h"
#include "include/path.h"
#include "include/policy.h"
#include "include/cred.h"
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
index 56155d7d5b2f..2ae9ab94a5a9 100644
--- a/security/apparmor/apparmorfs.c
+++ b/security/apparmor/apparmorfs.c
@@ -23,6 +23,7 @@
#include <linux/fs_context.h>
#include <linux/poll.h>
#include <linux/zstd.h>
+#include <linux/string.h>
#include <uapi/linux/major.h>
#include <uapi/linux/magic.h>
@@ -33,6 +34,7 @@
#include "include/crypto.h"
#include "include/ipc.h"
#include "include/label.h"
+#include "include/net.h"
#include "include/lib.h"
#include "include/policy.h"
#include "include/policy_ns.h"
@@ -481,6 +483,78 @@ static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf,
return data;
}
+static int decompress_zstd(char *src, size_t slen, char *dst, size_t dlen);
+/**
+ * aa_get_data_from_compressed - common routine for getting compressed policy
+ * from user and get both compressed and uncompressed version.
+ * @userbuf: user buffer to copy data from (NOT NULL)
+ * @buffer_size: size of user buffer
+ * @pos: position write is at in the file (NOT NULL)
+ * @compressed_data Ptr on compressed data. *compressed_data is allocated there
+ *
+ * Returns: kernel buffer containing copy of user buffer data or an
+ * ERR_PTR on failure.
+ */
+
+static struct aa_loaddata *aa_get_data_from_compressed(const char __user *userbuf,
+ size_t buffer_size,
+ loff_t *pos,
+ char **compressed_data)
+{
+ struct aa_loaddata *data;
+ zstd_frame_header header;
+ int error;
+
+ if (!userbuf || !pos)
+ return ERR_PTR(-EINVAL);
+ if (*pos)
+ return ERR_PTR(-ESPIPE);
+
+ *compressed_data = kvmalloc(buffer_size, GFP_KERNEL);
+ if (!*compressed_data)
+ return ERR_PTR(-ENOMEM);
+ error = copy_from_user(*compressed_data, userbuf, buffer_size);
+ if (error)
+ goto fail;
+
+ error = zstd_get_frame_header(&header, *compressed_data, buffer_size);
+ if (error || header.frameContentSize == ZSTD_CONTENTSIZE_UNKNOWN ||
+ header.frameContentSize == ZSTD_CONTENTSIZE_ERROR) {
+ error = -EINVAL;
+ goto fail;
+ }
+
+ data = aa_loaddata_alloc(header.frameContentSize);
+ if (IS_ERR(data)) {
+ error = PTR_ERR(data);
+ goto fail;
+ }
+
+ // We then decompress the data
+ error = decompress_zstd(*compressed_data, buffer_size, data->data,
+ header.frameContentSize);
+ if (error)
+ goto fail_decompress;
+
+ data->size = header.frameContentSize;
+ return data;
+
+fail_decompress:
+ aa_put_i_loaddata(data);
+fail:
+ kvfree(*compressed_data);
+ return ERR_PTR(error);
+
+}
+
+struct aa_user_hdr {
+ uint8_t version;
+ uint8_t compress_level;
+ uint8_t padding[6]; /* force 8-byte alignment */
+} __packed __aligned(8);
+
+#define aa_hdr_magic "\x04\x08\x00\x76\x65\x72\x73\x69\x6f\x6e\x00\x02"
+#define aa_hdr_magic_size 12
static ssize_t policy_update(u32 mask, const char __user *buf, size_t size,
loff_t *pos, struct aa_ns *ns,
@@ -489,6 +563,10 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size,
struct aa_loaddata *data;
struct aa_label *label;
ssize_t error;
+ char *compressed_data = NULL;
+ __le32 magic_le;
+ bool is_compressed;
+ u8 aahdr[aa_hdr_magic_size];
label = begin_current_label_crit_section();
@@ -499,10 +577,52 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size,
if (error)
goto end_section;
- data = aa_simple_write_to_buffer(buf, size, size, pos);
- error = PTR_ERR(data);
+ /* If the policy is userspace compressed we start by decompressing it
+ * to make the required checks (computing hash, verifying profile, ...)
+ *
+ * Getting a userspace-compressed version then decompressing it in the
+ * kernel actually makes sense since zstd decompression is ~3.5x faster
+ * than compression. This also allow to increase the compression level.
+ */
+
+ if (size >= sizeof(__le32) &&
+ !copy_from_user(&magic_le, buf, sizeof(magic_le)) &&
+ le32_to_cpu(magic_le) == ZSTD_MAGICNUMBER) {
+ is_compressed = true;
+ } else if (size >= sizeof(struct aa_user_hdr) + sizeof(__le32) &&
+ !copy_from_user(&magic_le,
+ buf + sizeof(struct aa_user_hdr),
+ sizeof(magic_le)) &&
+ le32_to_cpu(magic_le) == ZSTD_MAGICNUMBER) {
+ is_compressed = true;
+ /* skip the userspace header if present */
+ buf += sizeof(struct aa_user_hdr);
+ size -= sizeof(struct aa_user_hdr);
+ } else if (size >= sizeof(struct aa_user_hdr) +
+ aa_hdr_magic_size &&
+ !copy_from_user(&aahdr,
+ buf + sizeof(struct aa_user_hdr),
+ aa_hdr_magic_size) &&
+ memcmp(&aahdr, aa_hdr_magic, aa_hdr_magic_size) == 0) {
+ /* uncompressed blob with user hdr */
+ buf += sizeof(struct aa_user_hdr);
+ size -= sizeof(struct aa_user_hdr);
+ is_compressed = false;
+ } else {
+ is_compressed = false;
+ }
+ if (is_compressed) {
+
+ data = aa_get_data_from_compressed(buf, size, pos, &compressed_data);
+ error = PTR_ERR(data);
+ } else {
+ data = aa_simple_write_to_buffer(buf, size, size, pos);
+ error = PTR_ERR(data);
+ }
+
if (!IS_ERR(data)) {
- error = aa_replace_profiles(ns, label, mask, data);
+ error = aa_replace_profiles(ns, label, mask, data,
+ compressed_data, size);
/* put pcount, which will put count and free if no
* profiles referencing it.
*/
@@ -549,6 +669,7 @@ static const struct file_operations aa_fs_profile_replace = {
.llseek = default_llseek,
};
+
/* .remove file hook fn to remove loaded policy */
static ssize_t profile_remove(struct file *f, const char __user *buf,
size_t size, loff_t *pos)
@@ -2483,6 +2604,8 @@ static struct aa_sfs_entry aa_sfs_entry_policy[] = {
AA_SFS_FILE_STRING("permstable32", PERMS32STR),
AA_SFS_FILE_U64("state32", 1),
AA_SFS_DIR("unconfined_restrictions", aa_sfs_entry_unconfined),
+ AA_SFS_FILE_BOOLEAN("compressed_load", 1),
+ AA_SFS_FILE_BOOLEAN("extended_policy_header", 1),
{ }
};
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c
index d6958eb00e30..4154964cdac5 100644
--- a/security/apparmor/domain.c
+++ b/security/apparmor/domain.c
@@ -24,6 +24,7 @@
#include "include/domain.h"
#include "include/file.h"
#include "include/ipc.h"
+#include "include/lib.h"
#include "include/match.h"
#include "include/path.h"
#include "include/policy.h"
diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h
index 33243d11fd10..c6c8fcde728f 100644
--- a/security/apparmor/include/apparmorfs.h
+++ b/security/apparmor/include/apparmorfs.h
@@ -11,6 +11,9 @@
#ifndef __AA_APPARMORFS_H
#define __AA_APPARMORFS_H
+#include <linux/init.h>
+#include <linux/types.h>
+
extern struct path aa_null;
enum aa_sfs_type {
diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h
index 1ddcec2d1160..3fb8b8f2182a 100644
--- a/security/apparmor/include/capability.h
+++ b/security/apparmor/include/capability.h
@@ -11,6 +11,7 @@
#ifndef __AA_CAPABILITY_H
#define __AA_CAPABILITY_H
+#include <linux/capability.h>
#include <linux/sched.h>
#include "apparmorfs.h"
diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h
index 8bb915d48dc7..250812a77e11 100644
--- a/security/apparmor/include/path.h
+++ b/security/apparmor/include/path.h
@@ -11,6 +11,9 @@
#ifndef __AA_PATH_H
#define __AA_PATH_H
+#include <linux/path.h>
+#include <linux/types.h>
+
enum path_flags {
PATH_IS_DIR = 0x1, /* path is a directory */
PATH_SOCK_COND = 0x2,
diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h
index 3895f8774a3f..240b2eba7687 100644
--- a/security/apparmor/include/policy.h
+++ b/security/apparmor/include/policy.h
@@ -26,7 +26,6 @@
#include "file.h"
#include "lib.h"
#include "label.h"
-#include "net.h"
#include "perms.h"
#include "resource.h"
@@ -305,7 +304,8 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_label *base,
const char *fqname, size_t n);
ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_label *label,
- u32 mask, struct aa_loaddata *udata);
+ u32 mask, struct aa_loaddata *udata,
+ char *compressed_profile, size_t compressed_size);
ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_label *label,
char *name, size_t size);
void __aa_profile_list_release(struct list_head *head);
diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h
index 4ea9b6479a3e..c01f6885dbe3 100644
--- a/security/apparmor/include/policy_unpack.h
+++ b/security/apparmor/include/policy_unpack.h
@@ -16,6 +16,7 @@
#include <linux/dcache.h>
#include <linux/workqueue.h>
+#include "lib.h"
struct aa_load_ent {
struct list_head list;
@@ -128,7 +129,8 @@ struct aa_loaddata {
char *data;
};
-int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns);
+int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns,
+ char *compressed_data, size_t compressed_size);
/**
* aa_get_i_loaddata - get a reference count from a counted data reference
diff --git a/security/apparmor/include/procattr.h b/security/apparmor/include/procattr.h
index 03dbfdb2f2c0..56acd1bdb634 100644
--- a/security/apparmor/include/procattr.h
+++ b/security/apparmor/include/procattr.h
@@ -11,6 +11,8 @@
#ifndef __AA_PROCATTR_H
#define __AA_PROCATTR_H
+#include "label.h"
+
int aa_getprocattr(struct aa_label *label, char **string, bool newline);
int aa_setprocattr_changehat(char *args, size_t size, int flags);
diff --git a/security/apparmor/include/task.h b/security/apparmor/include/task.h
index b1aaaf60fa8b..017d8b06b8f2 100644
--- a/security/apparmor/include/task.h
+++ b/security/apparmor/include/task.h
@@ -10,6 +10,11 @@
#ifndef __AA_TASK_H
#define __AA_TASK_H
+#include <linux/sched.h>
+
+#include "audit.h"
+#include "label.h"
+
static inline struct aa_task_ctx *task_ctx(struct task_struct *task)
{
return task->security + apparmor_blob_sizes.lbs_task;
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c
index 94b4a7e727cc..f6f1b72d7c3d 100644
--- a/security/apparmor/policy.c
+++ b/security/apparmor/policy.c
@@ -81,6 +81,7 @@
#include "include/file.h"
#include "include/ipc.h"
#include "include/match.h"
+#include "include/net.h"
#include "include/path.h"
#include "include/policy.h"
#include "include/policy_ns.h"
@@ -1158,6 +1159,8 @@ static struct aa_profile *update_to_newest_parent(struct aa_profile *new)
* @label: label that is attempting to load/replace policy
* @mask: permission mask
* @udata: serialized data stream (NOT NULL)
+ * @compressed_profile: The userspace-provided compressed profile. May be NULL
+ * @compressed_size: If compressed_data is not NULL, the compressed data size
*
* unpack and replace a profile on the profile list and uses of that profile
* by any task creds via invalidating the old version of the profile, which
@@ -1167,7 +1170,8 @@ static struct aa_profile *update_to_newest_parent(struct aa_profile *new)
* Returns: size of data consumed else error code on failure.
*/
ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
- u32 mask, struct aa_loaddata *udata)
+ u32 mask, struct aa_loaddata *udata,
+ char *compressed_profile, size_t compressed_size)
{
const char *ns_name = NULL, *info = NULL;
struct aa_ns *ns = NULL;
@@ -1180,7 +1184,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
op = mask & AA_MAY_REPLACE_POLICY ? OP_PROF_REPL : OP_PROF_LOAD;
aa_get_profile_loaddata(udata);
/* released below */
- error = aa_unpack(udata, &lh, &ns_name);
+ error = aa_unpack(udata, &lh, &ns_name, compressed_profile, compressed_size);
if (error)
goto out;
diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c
index d9dcff167c48..5969d78f16af 100644
--- a/security/apparmor/policy_unpack.c
+++ b/security/apparmor/policy_unpack.c
@@ -25,6 +25,7 @@
#include "include/crypto.h"
#include "include/file.h"
#include "include/match.h"
+#include "include/net.h"
#include "include/path.h"
#include "include/policy.h"
#include "include/policy_unpack.h"
@@ -1717,6 +1718,8 @@ static int compress_loaddata(struct aa_loaddata *data)
* @udata: user data copied to kmem (NOT NULL)
* @lh: list to place unpacked profiles in a aa_repl_ws
* @ns: Returns namespace profile is in if specified else NULL (NOT NULL)
+ * @compressed_data: The userspace-provided compressed data. May be NULL
+ * @compressed_size: If compressed_data is not NULL, the compressed data size
*
* Unpack user data and return refcounted allocated profile(s) stored in
* @lh in order of discovery, with the list chain stored in base.list
@@ -1725,12 +1728,12 @@ static int compress_loaddata(struct aa_loaddata *data)
* Returns: profile(s) on @lh else error pointer if fails to unpack
*/
int aa_unpack(struct aa_loaddata *udata, struct list_head *lh,
- const char **ns)
+ const char **ns, char *compressed_data, size_t compressed_size)
{
struct aa_load_ent *tmp, *ent;
struct aa_profile *profile = NULL;
char *ns_name = NULL;
- int error;
+ int error = 0;
struct aa_ext e = {
.start = udata->data,
.end = udata->data + udata->size,
@@ -1783,10 +1786,23 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh,
}
if (aa_g_export_binary) {
- error = compress_loaddata(udata);
+ /* Do we have userspace-compressed data? */
+ if (compressed_data) {
+ kvfree(udata->data);
+ udata->data = compressed_data;
+ udata->compressed_size = compressed_size;
+ compressed_data = NULL; /* consumed */
+
+ } else
+ error = compress_loaddata(udata);
+
if (error)
goto fail;
+ } else if (compressed_data) {
+ kvfree(compressed_data);
+ compressed_data = NULL;
}
+
return 0;
fail_profile:
@@ -1794,6 +1810,8 @@ fail_profile:
aa_put_profile(profile);
fail:
+ if (compressed_data)
+ kvfree(compressed_data);
list_for_each_entry_safe(ent, tmp, lh, list) {
list_del_init(&ent->list);
aa_load_ent_free(ent);