summaryrefslogtreecommitdiff
path: root/security/apparmor
diff options
context:
space:
mode:
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>2026-07-24 16:21:27 +0200
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2026-07-24 16:21:27 +0200
commit8f9aa2c90530ab92301a82231ae44f3722becd93 (patch)
treefb282e955b0a880b07131a135257fe3ec764e928 /security/apparmor
parent93467b31bec6da512b51544e5e4584f2745e995e (diff)
parent155b42bec9cbb6b8cdc47dd9bd09503a81fbe493 (diff)
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'security/apparmor')
-rw-r--r--security/apparmor/af_unix.c71
-rw-r--r--security/apparmor/apparmorfs.c113
-rw-r--r--security/apparmor/domain.c58
-rw-r--r--security/apparmor/file.c12
-rw-r--r--security/apparmor/include/apparmorfs.h12
-rw-r--r--security/apparmor/label.c2
-rw-r--r--security/apparmor/lsm.c2
-rw-r--r--security/apparmor/net.c1
-rw-r--r--security/apparmor/policy.c20
-rw-r--r--security/apparmor/policy_unpack.c6
-rw-r--r--security/apparmor/procattr.c2
-rw-r--r--security/apparmor/task.c2
12 files changed, 214 insertions, 87 deletions
diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c
index fdb4a9f212c3..834a3b1c2f0a 100644
--- a/security/apparmor/af_unix.c
+++ b/security/apparmor/af_unix.c
@@ -674,9 +674,11 @@ static void update_sk_ctx(struct sock *sk, struct aa_label *label,
old = rcu_dereference_protected(ctx->peer, lockdep_is_held(&unix_sk(sk)->lock));
if (old == plabel) {
- rcu_assign_pointer(ctx->peer_lastupdate, plabel);
+ rcu_assign_pointer(ctx->peer_lastupdate,
+ aa_get_label(plabel));
} else if (aa_label_is_subset(plabel, old)) {
- rcu_assign_pointer(ctx->peer_lastupdate, plabel);
+ rcu_assign_pointer(ctx->peer_lastupdate,
+ aa_get_label(plabel));
rcu_assign_pointer(ctx->peer, aa_get_label(plabel));
aa_put_label(old);
} /* else race or a subset - don't update */
@@ -748,42 +750,47 @@ int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label,
if (!peer_sk)
goto out;
- peer_addr = aa_sunaddr(unix_sk(peer_sk), &peer_addrlen);
-
- struct path peer_path;
-
- peer_path = unix_sk(peer_sk)->path;
- if (!is_sk_fs && is_unix_fs(peer_sk)) {
- last_error(error,
- unix_fs_perm(op, request, subj_cred, label,
- is_unix_fs(peer_sk) ? &peer_path : NULL));
- } else if (!is_sk_fs) {
- struct aa_label *plabel;
- struct aa_sk_ctx *pctx = aa_sock(peer_sk);
-
- rcu_read_lock();
- plabel = aa_get_label_rcu(&pctx->label);
- rcu_read_unlock();
- /* no fs check of aa_unix_peer_perm because conditions above
- * ensure they will never be done
- */
- last_error(error,
- xcheck(unix_peer_perm(subj_cred, label, op,
+ if (!is_sk_fs) {
+ bool is_peer_fs = is_unix_fs(peer_sk);
+
+ peer_addr = aa_sunaddr(unix_sk(peer_sk), &peer_addrlen);
+ if (is_peer_fs) {
+ struct path peer_path;
+
+ unix_state_lock(peer_sk);
+ peer_path = unix_sk(peer_sk)->path;
+ if (peer_path.dentry)
+ path_get(&peer_path);
+ unix_state_unlock(peer_sk);
+
+ last_error(error,
+ unix_fs_perm(op, request, subj_cred, label,
+ &peer_path));
+ if (peer_path.dentry)
+ path_put(&peer_path);
+ } else {
+ struct aa_sk_ctx *pctx = aa_sock(peer_sk);
+
+ rcu_read_lock();
+ plabel = aa_get_newest_label(pctx->label);
+ rcu_read_unlock();
+ /* no fs check of aa_unix_peer_perm because conditions
+ * above ensure they will never be done
+ */
+ last_error(error,
+ xcheck(unix_peer_perm(subj_cred, label, op,
MAY_READ | MAY_WRITE, sock->sk,
is_sk_fs ? &path : NULL,
peer_addr, peer_addrlen,
- is_unix_fs(peer_sk) ?
- &peer_path : NULL,
- plabel),
- unix_peer_perm(file->f_cred, plabel, op,
+ NULL, plabel),
+ unix_peer_perm(file->f_cred, plabel, op,
MAY_READ | MAY_WRITE, peer_sk,
- is_unix_fs(peer_sk) ?
- &peer_path : NULL,
- addr, addrlen,
+ NULL, addr, addrlen,
is_sk_fs ? &path : NULL,
label)));
- if (!error && !__aa_subj_label_is_cached(plabel, label))
- update_peer_ctx(peer_sk, pctx, label);
+ if (!error && !__aa_subj_label_is_cached(plabel, label))
+ update_peer_ctx(peer_sk, pctx, label);
+ }
}
sock_put(peer_sk);
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
index ededaf46f3ca..95deb867a76b 100644
--- a/security/apparmor/apparmorfs.c
+++ b/security/apparmor/apparmorfs.c
@@ -71,10 +71,10 @@
struct rawdata_f_data {
struct aa_loaddata *loaddata;
+ DECLARE_FLEX_ARRAY(char, data);
};
#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
-#define RAWDATA_F_DATA_BUF(p) (char *)(p + 1)
static void rawdata_f_data_free(struct rawdata_f_data *private)
{
@@ -174,6 +174,7 @@ static struct aa_proxy *get_proxy_common_ref(struct aa_common_ref *ref)
return NULL;
}
+#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
static struct aa_loaddata *get_loaddata_common_ref(struct aa_common_ref *ref)
{
if (ref)
@@ -181,6 +182,7 @@ static struct aa_loaddata *get_loaddata_common_ref(struct aa_common_ref *ref)
count));
return NULL;
}
+#endif
static void aa_put_common_ref(struct aa_common_ref *ref)
{
@@ -1434,7 +1436,7 @@ static ssize_t rawdata_read(struct file *file, char __user *buf, size_t size,
struct rawdata_f_data *private = file->private_data;
return simple_read_from_buffer(buf, size, ppos,
- RAWDATA_F_DATA_BUF(private),
+ private->data,
private->loaddata->size);
}
@@ -1467,8 +1469,7 @@ static int rawdata_open(struct inode *inode, struct file *file)
private->loaddata = loaddata;
error = decompress_zstd(loaddata->data, loaddata->compressed_size,
- RAWDATA_F_DATA_BUF(private),
- loaddata->size);
+ private->data, loaddata->size);
if (error)
goto fail_decompress;
@@ -1756,6 +1757,80 @@ static const struct inode_operations rawdata_link_abi_iops = {
static const struct inode_operations rawdata_link_data_iops = {
.get_link = rawdata_get_link_data,
};
+
+/*
+ * Requires: @profile->ns->lock held
+ */
+void __aa_remove_rawdata_symlink_dents(struct aa_profile *profile)
+{
+ aafs_remove(profile->dents[AAFS_PROF_RAW_HASH]);
+ profile->dents[AAFS_PROF_RAW_HASH] = NULL;
+ aafs_remove(profile->dents[AAFS_PROF_RAW_ABI]);
+ profile->dents[AAFS_PROF_RAW_ABI] = NULL;
+ aafs_remove(profile->dents[AAFS_PROF_RAW_DATA]);
+ profile->dents[AAFS_PROF_RAW_DATA] = NULL;
+}
+
+static inline int create_symlink_dent(struct aa_profile *profile,
+ const char *name,
+ enum aafs_prof_type type,
+ const struct inode_operations *iops)
+{
+ struct dentry *dent = NULL;
+ struct dentry *dir = prof_dir(profile);
+
+ if (profile->dents[type])
+ return 0;
+
+ dent = aafs_create(name, S_IFLNK | 0444, dir,
+ &profile->label.proxy->count, NULL, NULL, iops);
+ if (IS_ERR(dent))
+ return PTR_ERR(dent);
+
+ profile->dents[type] = dent;
+ return 0;
+}
+
+/*
+ * Requires: @profile->ns->lock held
+ */
+int __aa_create_rawdata_symlink_dents(struct aa_profile *profile)
+{
+ int error;
+
+ if (!profile ||
+ (profile->dents[AAFS_PROF_RAW_HASH] &&
+ profile->dents[AAFS_PROF_RAW_ABI] &&
+ profile->dents[AAFS_PROF_RAW_DATA]))
+ return 0;
+
+ if (!profile->rawdata)
+ return 0;
+
+ if (aa_g_hash_policy) {
+ error = create_symlink_dent(profile, "raw_sha256",
+ AAFS_PROF_RAW_HASH,
+ &rawdata_link_sha256_iops);
+ if (error)
+ return error;
+ }
+
+ error = create_symlink_dent(profile, "raw_abi",
+ AAFS_PROF_RAW_ABI,
+ &rawdata_link_abi_iops);
+ if (error)
+ return error;
+
+
+ error = create_symlink_dent(profile, "raw_data",
+ AAFS_PROF_RAW_DATA,
+ &rawdata_link_data_iops);
+ if (error)
+ return error;
+
+ return 0;
+}
+
#endif /* CONFIG_SECURITY_APPARMOR_EXPORT_BINARY */
/*
@@ -1831,31 +1906,9 @@ int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent)
profile->dents[AAFS_PROF_HASH] = dent;
}
-#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
- if (profile->rawdata) {
- if (aa_g_hash_policy) {
- dent = aafs_create("raw_sha256", S_IFLNK | 0444, dir,
- &profile->label.proxy->count, NULL,
- NULL, &rawdata_link_sha256_iops);
- if (IS_ERR(dent))
- goto fail;
- profile->dents[AAFS_PROF_RAW_HASH] = dent;
- }
- dent = aafs_create("raw_abi", S_IFLNK | 0444, dir,
- &profile->label.proxy->count, NULL, NULL,
- &rawdata_link_abi_iops);
- if (IS_ERR(dent))
- goto fail;
- profile->dents[AAFS_PROF_RAW_ABI] = dent;
-
- dent = aafs_create("raw_data", S_IFLNK | 0444, dir,
- &profile->label.proxy->count, NULL, NULL,
- &rawdata_link_data_iops);
- if (IS_ERR(dent))
- goto fail;
- profile->dents[AAFS_PROF_RAW_DATA] = dent;
- }
-#endif /*CONFIG_SECURITY_APPARMOR_EXPORT_BINARY */
+ error = __aa_create_rawdata_symlink_dents(profile);
+ if (error)
+ goto fail2;
list_for_each_entry(child, &profile->base.profiles, base.list) {
error = __aafs_profile_mkdir(child, prof_child_dir(profile));
@@ -1922,7 +1975,7 @@ out:
mutex_unlock(&parent->lock);
aa_put_ns(parent);
- return ERR_PTR(error);
+ return error ? ERR_PTR(error) : NULL;
}
static int ns_rmdir_op(struct inode *dir, struct dentry *dentry)
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c
index f02bf770f638..c2652165a588 100644
--- a/security/apparmor/domain.c
+++ b/security/apparmor/domain.c
@@ -12,6 +12,7 @@
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/mount.h>
+#include <linux/mutex.h>
#include <linux/syscalls.h>
#include <linux/personality.h>
#include <linux/xattr.h>
@@ -1109,6 +1110,7 @@ static struct aa_label *change_hat(const struct cred *subj_cred,
int count, int flags)
{
struct aa_profile *profile, *root, *hat = NULL;
+ struct aa_ns *ns, *new_ns;
struct aa_label *new;
struct label_it it;
bool sibling = false;
@@ -1119,6 +1121,32 @@ static struct aa_label *change_hat(const struct cred *subj_cred,
AA_BUG(!hats);
AA_BUG(count < 1);
+ /*
+ * Acquire the newest label and then hold the lock until we choose a
+ * hat, so that profile replacement doesn't atomically truncate the
+ * list of potential hats. Because we are getting the namespaces from
+ * the profiles and label, we can rely on the namespaces being live
+ * and avoid incrementing their refcounts while grabbing the lock.
+ */
+ label = aa_get_label(label);
+ ns = labels_ns(label);
+
+retry:
+ mutex_lock_nested(&ns->lock, ns->level);
+ if (label_is_stale(label)) {
+ new = aa_get_newest_label(label);
+ new_ns = labels_ns(new);
+ if (new_ns != ns) {
+ aa_put_label(new);
+ mutex_unlock(&ns->lock);
+ ns = new_ns;
+ label = new;
+ goto retry;
+ }
+ aa_put_label(label);
+ label = new;
+ }
+
if (PROFILE_IS_HAT(labels_profile(label)))
sibling = true;
@@ -1127,7 +1155,7 @@ static struct aa_label *change_hat(const struct cred *subj_cred,
name = hats[i];
label_for_each_in_scope(it, labels_ns(label), label, profile) {
if (sibling && PROFILE_IS_HAT(profile)) {
- root = aa_get_profile_rcu(&profile->parent);
+ root = aa_get_profile(profile->parent);
} else if (!sibling && !PROFILE_IS_HAT(profile)) {
root = aa_get_profile(profile);
} else { /* conflicting change type */
@@ -1187,6 +1215,7 @@ fail:
GLOBAL_ROOT_UID, info, error);
}
}
+ mutex_unlock(&ns->lock);
return ERR_PTR(error);
build:
@@ -1199,7 +1228,7 @@ build:
error = -ENOMEM;
goto fail;
} /* else if (IS_ERR) build_change_hat has logged error so return new */
-
+ mutex_unlock(&ns->lock);
return new;
}
@@ -1527,6 +1556,8 @@ check:
new = fn_label_build_in_scope(label, profile, GFP_KERNEL,
aa_get_label(target),
aa_get_label(&profile->label));
+ if (IS_ERR_OR_NULL(new))
+ goto build_fail;
/*
* no new privs prevents domain transitions that would
* reduce restrictions.
@@ -1545,16 +1576,8 @@ check:
/* only transition profiles in the current ns */
if (stack)
new = aa_label_merge(label, target, GFP_KERNEL);
- if (IS_ERR_OR_NULL(new)) {
- info = "failed to build target label";
- if (!new)
- error = -ENOMEM;
- else
- error = PTR_ERR(new);
- new = NULL;
- perms.allow = 0;
- goto audit;
- }
+ if (IS_ERR_OR_NULL(new))
+ goto build_fail;
error = aa_replace_current_label(new);
} else {
if (new) {
@@ -1566,6 +1589,17 @@ check:
aa_set_current_onexec(target, stack);
}
+ goto audit;
+
+build_fail:
+ info = "failed to build target label";
+ if (!new)
+ error = -ENOMEM;
+ else
+ error = PTR_ERR(new);
+ new = NULL;
+ perms.allow = 0;
+
audit:
error = fn_for_each_in_scope(label, profile,
aa_audit_file(subj_cred,
diff --git a/security/apparmor/file.c b/security/apparmor/file.c
index 694e157149e8..c9d55fe1086f 100644
--- a/security/apparmor/file.c
+++ b/security/apparmor/file.c
@@ -157,10 +157,10 @@ static int path_name(const char *op, const struct cred *subj_cred,
/* 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);
+ return -EACCES;
+
+ error = aa_path_name(path, flags, buffer, name, &info,
+ labels_profile(label)->disconnected);
if (error) {
fn_for_each_confined(label, profile,
aa_audit_file(subj_cred,
@@ -250,7 +250,7 @@ static int profile_path_perm(const char *op, const struct cred *subj_cred,
struct path_cond *cond, int flags,
struct aa_perms *perms)
{
- const char *name;
+ const char *name = NULL;
int error;
if (profile_unconfined(profile))
@@ -328,7 +328,7 @@ static int profile_path_link(const struct cred *subj_cred,
struct path_cond *cond)
{
struct aa_ruleset *rules = profile->label.rules[0];
- const char *lname, *tname = NULL;
+ const char *lname = NULL, *tname = NULL;
struct aa_perms lperms = {}, perms;
const char *info = NULL;
u32 request = AA_MAY_LINK;
diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h
index dd580594dfb7..33243d11fd10 100644
--- a/security/apparmor/include/apparmorfs.h
+++ b/security/apparmor/include/apparmorfs.h
@@ -120,6 +120,8 @@ struct aa_loaddata;
#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
void __aa_fs_remove_rawdata(struct aa_loaddata *rawdata);
int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata);
+void __aa_remove_rawdata_symlink_dents(struct aa_profile *profile);
+int __aa_create_rawdata_symlink_dents(struct aa_profile *profile);
#else
static inline void __aa_fs_remove_rawdata(struct aa_loaddata *rawdata)
{
@@ -131,6 +133,16 @@ static inline int __aa_fs_create_rawdata(struct aa_ns *ns,
{
return 0;
}
+
+static inline void __aa_remove_rawdata_symlink_dents(struct aa_profile *profile)
+{
+ /* empty stub */
+}
+
+static inline int __aa_create_rawdata_symlink_dents(struct aa_profile *profile)
+{
+ return 0;
+}
#endif /* CONFIG_SECURITY_APPARMOR_EXPORT_BINARY */
#endif /* __AA_APPARMORFS_H */
diff --git a/security/apparmor/label.c b/security/apparmor/label.c
index 3a721fdf1833..c6a96355e8d9 100644
--- a/security/apparmor/label.c
+++ b/security/apparmor/label.c
@@ -458,7 +458,7 @@ struct aa_label *aa_label_alloc(int size, struct aa_proxy *proxy, gfp_t gfp)
return new;
fail:
- kfree(new);
+ aa_label_free(new);
return NULL;
}
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index e01efdf50efa..1f2bbc3175d2 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -2143,7 +2143,7 @@ static int param_set_mode(const char *val, const struct kernel_param *kp)
*/
static void cache_hold_inc(unsigned int *hold)
{
- if (*hold > MAX_HOLD_COUNT)
+ if (*hold < MAX_HOLD_COUNT)
(*hold)++;
}
diff --git a/security/apparmor/net.c b/security/apparmor/net.c
index 1fc6145ccbb8..cf590dd08540 100644
--- a/security/apparmor/net.c
+++ b/security/apparmor/net.c
@@ -356,6 +356,7 @@ static int apparmor_secmark_init(struct aa_secmark *secmark)
return PTR_ERR(label);
secmark->secid = label->secid;
+ aa_put_label(label);
return 0;
}
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c
index e103cce6f4af..567f6a1c2d65 100644
--- a/security/apparmor/policy.c
+++ b/security/apparmor/policy.c
@@ -1346,6 +1346,16 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
goto skip;
}
+ if (!aa_g_export_binary) {
+ if (ent->old && ent->old->rawdata &&
+ ent->old->dents[AAFS_LOADDATA_DIR]) {
+ /* remove rawdata symlinks because the symlink
+ * target will be removed
+ */
+ __aa_remove_rawdata_symlink_dents(ent->old);
+ }
+ }
+
/*
* TODO: finer dedup based on profile range in data. Load set
* can differ but profile may remain unchanged
@@ -1356,6 +1366,11 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
if (ent->old) {
share_name(ent->old, ent->new);
__replace_profile(ent->old, ent->new);
+ if (aa_g_export_binary) {
+ /* recreate rawdata symlinks */
+ if (!ent->old->rawdata)
+ __aa_create_rawdata_symlink_dents(ent->new);
+ }
} else {
struct list_head *lh;
@@ -1376,12 +1391,15 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
out:
aa_put_ns(ns);
+
+ ssize_t udata_sz = udata->size;
+
aa_put_profile_loaddata(udata);
kfree(ns_name);
if (error)
return error;
- return udata->size;
+ return udata_sz;
fail_lock:
mutex_unlock(&ns->lock);
diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c
index 9f45d5513d2c..d9dcff167c48 100644
--- a/security/apparmor/policy_unpack.c
+++ b/security/apparmor/policy_unpack.c
@@ -1045,7 +1045,7 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy,
}
/* accept2 is in some cases being allocated, even with perms */
- if (pdb->perms && !pdb->dfa->tables[YYTD_ID_ACCEPT2]) {
+ if (pdb->dfa && pdb->perms && !pdb->dfa->tables[YYTD_ID_ACCEPT2]) {
/* add dfa flags table missing in v2 */
u32 noents = pdb->dfa->tables[YYTD_ID_ACCEPT]->td_lolen;
u16 tdflags = pdb->dfa->tables[YYTD_ID_ACCEPT]->td_flags;
@@ -1054,7 +1054,8 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy,
pdb->dfa->tables[YYTD_ID_ACCEPT2] = kvzalloc(tsize, GFP_KERNEL);
if (!pdb->dfa->tables[YYTD_ID_ACCEPT2]) {
*info = "failed to alloc dfa flags table";
- goto out;
+ error = -ENOMEM;
+ goto fail;
}
pdb->dfa->tables[YYTD_ID_ACCEPT2]->td_lolen = noents;
pdb->dfa->tables[YYTD_ID_ACCEPT2]->td_flags = tdflags;
@@ -1079,7 +1080,6 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy,
* - move free of unneeded trans table here, has to be done
* after perm mapping.
*/
-out:
*policy = pdb;
return 0;
diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c
index ce40f15d4952..c07b6e8fd9c9 100644
--- a/security/apparmor/procattr.c
+++ b/security/apparmor/procattr.c
@@ -54,6 +54,8 @@ int aa_getprocattr(struct aa_label *label, char **string, bool newline)
FLAG_SHOW_MODE | FLAG_VIEW_SUBNS |
FLAG_HIDDEN_UNCONFINED);
if (len < 0) {
+ kfree(*string);
+ *string = NULL;
aa_put_ns(current_ns);
return len;
}
diff --git a/security/apparmor/task.c b/security/apparmor/task.c
index 0db0e81b4600..b9fb3738124e 100644
--- a/security/apparmor/task.c
+++ b/security/apparmor/task.c
@@ -314,7 +314,7 @@ static const char *get_current_exe_path(char *buffer, int buffer_size)
path_get(&p);
if (aa_path_name(&p, FLAG_VIEW_SUBNS, buffer, &path_str, NULL, NULL))
- return ERR_PTR(-ENOMEM);
+ path_str = ERR_PTR(-ENOMEM);
fput(exe_file);
path_put(&p);