diff options
| author | Mark Brown <broonie@kernel.org> | 2026-07-06 14:53:35 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-07-06 14:53:35 +0100 |
| commit | af08992ee167ececa094117817ef6baad8dd1c90 (patch) | |
| tree | f4cf0c8387a8694668456908826437296779b84c | |
| parent | 0c795db2b766fd808a1c4ee3f2f9f4d866805b5f (diff) | |
| parent | dda61023f976d7ab3bc7c8b46d26d6d23424f890 (diff) | |
| download | linux-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.c | 1 | ||||
| -rw-r--r-- | security/apparmor/apparmorfs.c | 129 | ||||
| -rw-r--r-- | security/apparmor/domain.c | 1 | ||||
| -rw-r--r-- | security/apparmor/include/apparmorfs.h | 3 | ||||
| -rw-r--r-- | security/apparmor/include/capability.h | 1 | ||||
| -rw-r--r-- | security/apparmor/include/path.h | 3 | ||||
| -rw-r--r-- | security/apparmor/include/policy.h | 4 | ||||
| -rw-r--r-- | security/apparmor/include/policy_unpack.h | 4 | ||||
| -rw-r--r-- | security/apparmor/include/procattr.h | 2 | ||||
| -rw-r--r-- | security/apparmor/include/task.h | 5 | ||||
| -rw-r--r-- | security/apparmor/policy.c | 8 | ||||
| -rw-r--r-- | security/apparmor/policy_unpack.c | 24 |
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); |
