summaryrefslogtreecommitdiff
path: root/security/apparmor
diff options
context:
space:
mode:
Diffstat (limited to 'security/apparmor')
-rw-r--r--security/apparmor/af_unix.c67
-rw-r--r--security/apparmor/apparmorfs.c119
-rw-r--r--security/apparmor/domain.c97
-rw-r--r--security/apparmor/file.c12
-rw-r--r--security/apparmor/include/apparmorfs.h12
-rw-r--r--security/apparmor/include/label.h32
-rw-r--r--security/apparmor/include/lib.h21
-rw-r--r--security/apparmor/include/policy_unpack.h21
-rw-r--r--security/apparmor/label.c26
-rw-r--r--security/apparmor/lsm.c20
-rw-r--r--security/apparmor/match.c22
-rw-r--r--security/apparmor/mount.c17
-rw-r--r--security/apparmor/net.c3
-rw-r--r--security/apparmor/policy.c35
-rw-r--r--security/apparmor/policy_unpack.c6
-rw-r--r--security/apparmor/procattr.c2
-rw-r--r--security/apparmor/task.c2
17 files changed, 364 insertions, 150 deletions
diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c
index fdb4a9f212c3..b9b22edae202 100644
--- a/security/apparmor/af_unix.c
+++ b/security/apparmor/af_unix.c
@@ -615,7 +615,7 @@ static int unix_peer_perm(const struct cred *subj_cred,
peer_label, &ad));
}
-/**
+/*
*
* Requires: lock held on both @sk and @peer_sk
* called by unix_stream_connect, unix_may_send
@@ -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);
+ 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;
- 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);
- 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);
+ 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_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,
+ 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..56155d7d5b2f 100644
--- a/security/apparmor/apparmorfs.c
+++ b/security/apparmor/apparmorfs.c
@@ -9,6 +9,7 @@
*/
#include <linux/ctype.h>
+#include <linux/slab.h>
#include <linux/security.h>
#include <linux/vmalloc.h>
#include <linux/init.h>
@@ -71,10 +72,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 +175,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 +183,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)
{
@@ -904,7 +907,7 @@ static void multi_transaction_kref(struct kref *kref)
struct multi_transaction *t;
t = container_of(kref, struct multi_transaction, count);
- free_page((unsigned long) t);
+ kfree(t);
}
static struct multi_transaction *
@@ -947,7 +950,7 @@ static struct multi_transaction *multi_transaction_new(struct file *file,
if (size > MULTI_TRANSACTION_LIMIT - 1)
return ERR_PTR(-EFBIG);
- t = (struct multi_transaction *)get_zeroed_page(GFP_KERNEL);
+ t = kzalloc(PAGE_SIZE, GFP_KERNEL);
if (!t)
return ERR_PTR(-ENOMEM);
kref_init(&t->count);
@@ -1434,7 +1437,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 +1470,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 +1758,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 +1907,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 +1976,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)
@@ -2422,6 +2476,7 @@ static struct aa_sfs_entry aa_sfs_entry_versions[] = {
static struct aa_sfs_entry aa_sfs_entry_policy[] = {
AA_SFS_DIR("versions", aa_sfs_entry_versions),
AA_SFS_FILE_BOOLEAN("set_load", 1),
+ AA_SFS_FILE_BOOLEAN("diff-encode", 1),
/* number of out of band transitions supported */
AA_SFS_FILE_U64("outofband", MAX_OOB_SUPPORTED),
AA_SFS_FILE_U64("permstable32_version", 3),
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c
index f02bf770f638..d6958eb00e30 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>
@@ -135,7 +136,7 @@ static int label_compound_match(struct aa_profile *profile,
struct label_it i;
struct path_cond cond = { };
- /* find first subcomponent that is in view and going to be interated with */
+ /* find first subcomponent that is in view and going to be interacted with */
label_for_each(i, label, tp) {
if (!aa_ns_visible(profile->ns, tp->ns, inview))
continue;
@@ -863,6 +864,15 @@ audit:
}
/* ensure none ns domain transitions are correctly applied with onexec */
+static struct aa_label *label_merge_wrap(struct aa_label *a, struct aa_label *b,
+ gfp_t gfp)
+{
+ struct aa_label *label = aa_label_merge(a, b, gfp);
+
+ if (!label)
+ return ERR_PTR(-ENOMEM);
+ return label;
+}
static struct aa_label *handle_onexec(const struct cred *subj_cred,
struct aa_label *label,
@@ -890,12 +900,13 @@ static struct aa_label *handle_onexec(const struct cred *subj_cred,
return ERR_PTR(error);
new = fn_label_build_in_scope(label, profile, GFP_KERNEL,
- stack ? aa_label_merge(&profile->label, onexec,
- GFP_KERNEL)
+ stack ? label_merge_wrap(&profile->label, onexec,
+ GFP_KERNEL)
: aa_get_newest_label(onexec),
profile_transition(subj_cred, profile, bprm,
buffer, cond, unsafe));
- if (new)
+ AA_BUG(!new);
+ if (!IS_ERR(new))
return new;
/* TODO: get rid of GLOBAL_ROOT_UID */
@@ -904,7 +915,8 @@ static struct aa_label *handle_onexec(const struct cred *subj_cred,
OP_CHANGE_ONEXEC,
AA_MAY_ONEXEC, bprm->filename, NULL,
onexec, GLOBAL_ROOT_UID,
- "failed to build target label", -ENOMEM));
+ "failed to build target label",
+ PTR_ERR(new)));
return ERR_PTR(error);
}
@@ -967,14 +979,10 @@ int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm)
profile_transition(subj_cred, profile, bprm,
buffer,
&cond, &unsafe));
-
AA_BUG(!new);
if (IS_ERR(new)) {
error = PTR_ERR(new);
goto done;
- } else if (!new) {
- error = -ENOMEM;
- goto done;
}
/* Policy has specified a domain transitions. If no_new_privs and
@@ -1109,6 +1117,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 +1128,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 +1162,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 +1222,7 @@ fail:
GLOBAL_ROOT_UID, info, error);
}
}
+ mutex_unlock(&ns->lock);
return ERR_PTR(error);
build:
@@ -1194,11 +1230,9 @@ build:
build_change_hat(subj_cred, profile, name,
sibling),
aa_get_label(&profile->label));
- if (!new) {
- info = "label build failed";
- error = -ENOMEM;
- goto fail;
- } /* else if (IS_ERR) build_change_hat has logged error so return new */
+ mutex_unlock(&ns->lock);
+ AA_BUG(!new);
+ /* return new label or error ptr */
return new;
}
@@ -1527,6 +1561,9 @@ check:
new = fn_label_build_in_scope(label, profile, GFP_KERNEL,
aa_get_label(target),
aa_get_label(&profile->label));
+ AA_BUG(!new);
+ if (IS_ERR(new))
+ goto build_fail;
/*
* no new privs prevents domain transitions that would
* reduce restrictions.
@@ -1545,27 +1582,29 @@ 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) {
- aa_put_label(new);
- new = NULL;
- }
+ /* new will be recomputed so at exec time. So discard */
+ aa_put_label(new);
+ new = NULL;
/* full transition will be built in exec path */
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/include/label.h b/security/apparmor/include/label.h
index 335f21930702..b5a722a47fd2 100644
--- a/security/apparmor/include/label.h
+++ b/security/apparmor/include/label.h
@@ -423,6 +423,38 @@ static inline struct aa_label *aa_get_newest_label(struct aa_label *l)
return aa_get_label(l);
}
+/**
+ * aa_get_newest_label_condref - find the newest version of @l
+ * @l: the label to check for newer versions of
+ * @needput: returns whether the reference needs put
+ *
+ * Returns: refcounted newest version of @l taking into account
+ * replacement, renames and removals
+ * return @l.
+ */
+static inline struct aa_label *aa_get_newest_label_condref(struct aa_label *l,
+ bool *needput)
+{
+ if (l && unlikely(label_is_stale(l))) {
+ struct aa_label *tmp;
+
+ AA_BUG(!l->proxy);
+ AA_BUG(!l->proxy->label);
+ /* BUG: only way this can happen is @l ref count and its
+ * replacement count have gone to 0 and are on their way
+ * to destruction. ie. we have a refcounting error
+ */
+ tmp = aa_get_label_rcu(&l->proxy->label);
+ AA_BUG(!tmp);
+
+ *needput = true;
+ return tmp;
+ }
+
+ *needput = false;
+ return l;
+}
+
static inline void aa_put_label(struct aa_label *l)
{
if (l)
diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h
index 8c6ce8484552..e3c8cb044a90 100644
--- a/security/apparmor/include/lib.h
+++ b/security/apparmor/include/lib.h
@@ -281,15 +281,15 @@ void aa_policy_destroy(struct aa_policy *policy);
* @FN: fn to call for each profile transition. @P is set to the profile
*
* Returns: new label on success
+ * NULL if all callbacks decline to specify a transition
* ERR_PTR if build @FN fails
- * NULL if label_build fails due to low memory conditions
*
- * @FN must return a label or ERR_PTR on failure. NULL is not allowed
+ * @FN must return a label or ERR_PTR on failure.
*/
#define fn_label_build(L, P, GFP, FN) \
({ \
__label__ __do_cleanup, __done; \
- struct aa_label *__new_; \
+ struct aa_label *__new_ = NULL; \
\
if ((L)->size > 1) { \
/* TODO: add cache of transitions already done */ \
@@ -298,17 +298,21 @@ void aa_policy_destroy(struct aa_policy *policy);
DEFINE_VEC(label, __lvec); \
DEFINE_VEC(profile, __pvec); \
if (vec_setup(label, __lvec, (L)->size, (GFP))) { \
- __new_ = NULL; \
+ __new_ = ERR_PTR(-ENOMEM); \
goto __done; \
} \
__j = 0; \
label_for_each(__i, (L), (P)) { \
__new_ = (FN); \
- AA_BUG(!__new_); \
+ if (!__new_) \
+ continue; \
if (IS_ERR(__new_)) \
goto __do_cleanup; \
__lvec[__j++] = __new_; \
} \
+ if (__j == 0) \
+ /* no components adding to build */ \
+ goto __do_cleanup; \
for (__j = __count = 0; __j < (L)->size; __j++) \
__count += __lvec[__j]->size; \
if (!vec_setup(profile, __pvec, __count, (GFP))) { \
@@ -320,14 +324,13 @@ void aa_policy_destroy(struct aa_policy *policy);
if (__count > 1) { \
__new_ = aa_vec_find_or_create_label(__pvec,\
__count, (GFP)); \
- /* only fails if out of Mem */ \
if (!__new_) \
- __new_ = NULL; \
+ __new_ = ERR_PTR(-ENOMEM); \
} else \
__new_ = aa_get_label(&__pvec[0]->label); \
vec_cleanup(profile, __pvec, __count); \
} else \
- __new_ = NULL; \
+ __new_ = ERR_PTR(-ENOMEM); \
__do_cleanup: \
vec_cleanup(label, __lvec, (L)->size); \
} else { \
@@ -335,7 +338,7 @@ __do_cleanup: \
__new_ = (FN); \
} \
__done: \
- if (!__new_) \
+ if (PTR_ERR(__new_)) \
AA_DEBUG(DEBUG_LABEL, "label build failed\n"); \
(__new_); \
})
diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h
index e5a95dc4da1f..4ea9b6479a3e 100644
--- a/security/apparmor/include/policy_unpack.h
+++ b/security/apparmor/include/policy_unpack.h
@@ -131,7 +131,7 @@ struct aa_loaddata {
int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns);
/**
- * aa_get_loaddata - get a reference count from a counted data reference
+ * aa_get_i_loaddata - get a reference count from a counted data reference
* @data: reference to get a count on
*
* Returns: pointer to reference
@@ -163,6 +163,25 @@ aa_get_profile_loaddata(struct aa_loaddata *data)
return data;
}
+/**
+ * aa_get_profile_loaddata_not0 - get a profile reference count if not zero
+ * @data: reference to get a count on
+ *
+ * Like aa_get_profile_loaddata(), but safe to call on an entry that may
+ * be on a list (e.g. ns->rawdata_list) where the last pcount has already
+ * dropped and the deferred cleanup has not yet run.
+ *
+ * Returns: pointer to reference, or %NULL if @data is NULL or its
+ * profile refcount has already reached zero.
+ */
+static inline struct aa_loaddata *
+aa_get_profile_loaddata_not0(struct aa_loaddata *data)
+{
+ if (data && kref_get_unless_zero(&data->pcount))
+ return data;
+ return NULL;
+}
+
void __aa_loaddata_update(struct aa_loaddata *data, long revision);
bool aa_rawdata_eq(struct aa_loaddata *l, struct aa_loaddata *r);
void aa_loaddata_kref(struct kref *kref);
diff --git a/security/apparmor/label.c b/security/apparmor/label.c
index 3a721fdf1833..3fd384d8c41a 100644
--- a/security/apparmor/label.c
+++ b/security/apparmor/label.c
@@ -83,7 +83,7 @@ void __aa_proxy_redirect(struct aa_label *orig, struct aa_label *new)
tmp = rcu_dereference_protected(orig->proxy->label,
&labels_ns(orig)->lock);
rcu_assign_pointer(orig->proxy->label, aa_get_label(new));
- orig->flags |= FLAG_STALE;
+ __label_make_stale(orig);
aa_put_label(tmp);
}
@@ -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;
}
@@ -1176,22 +1176,21 @@ static struct aa_label *__label_find_merge(struct aa_labelset *ls,
struct aa_label *aa_label_find_merge(struct aa_label *a, struct aa_label *b)
{
struct aa_labelset *ls;
- struct aa_label *label, *ar = NULL, *br = NULL;
+ struct aa_label *label;
unsigned long flags;
+ bool a_needput, b_needput;
AA_BUG(!a);
AA_BUG(!b);
- if (label_is_stale(a))
- a = ar = aa_get_newest_label(a);
- if (label_is_stale(b))
- b = br = aa_get_newest_label(b);
+ a = aa_get_newest_label_condref(a, &a_needput);
+ b = aa_get_newest_label_condref(b, &b_needput);
ls = labelset_of_merge(a, b);
read_lock_irqsave(&ls->lock, flags);
label = __label_find_merge(ls, a, b);
read_unlock_irqrestore(&ls->lock, flags);
- aa_put_label(ar);
- aa_put_label(br);
+ aa_put_label_condref(a, a_needput);
+ aa_put_label_condref(b, b_needput);
return label;
}
@@ -1228,9 +1227,10 @@ struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b,
if (!label) {
struct aa_label *new;
+ bool a_needput, b_needput;
- a = aa_get_newest_label(a);
- b = aa_get_newest_label(b);
+ a = aa_get_newest_label_condref(a, &a_needput);
+ b = aa_get_newest_label_condref(b, &b_needput);
/* could use label_merge_len(a, b), but requires double
* comparison for small savings
@@ -1242,8 +1242,8 @@ struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b,
label = label_merge_insert(new, a, b);
label_free_or_put_new(label, new);
out:
- aa_put_label(a);
- aa_put_label(b);
+ aa_put_label_condref(a, a_needput);
+ aa_put_label_condref(b, b_needput);
}
return label;
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index 3491e9f60194..88d12e89d115 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -1422,7 +1422,21 @@ static int aa_sock_msg_perm(const char *op, u32 request, struct socket *sock,
static int apparmor_socket_sendmsg(struct socket *sock,
struct msghdr *msg, int size)
{
- return aa_sock_msg_perm(OP_SENDMSG, AA_MAY_SEND, sock, msg, size);
+ int error = aa_sock_msg_perm(OP_SENDMSG, AA_MAY_SEND, sock, msg, size);
+
+ if (error)
+ return error;
+
+ /* TCP fast open carries connect() semantics in sendmsg(); mediate
+ * the implicit connect so it cannot bypass the connect permission.
+ */
+ if ((msg->msg_flags & MSG_FASTOPEN) && msg->msg_name &&
+ (sk_is_tcp(sock->sk) ||
+ (sk_is_inet(sock->sk) && sock->sk->sk_type == SOCK_STREAM &&
+ sock->sk->sk_protocol == IPPROTO_MPTCP)))
+ error = aa_sk_perm(OP_CONNECT, AA_MAY_CONNECT, sock->sk);
+
+ return error;
}
static int apparmor_socket_recvmsg(struct socket *sock,
@@ -1493,7 +1507,7 @@ static int apparmor_socket_shutdown(struct socket *sock, int how)
*
* Note: can not sleep may be called with locks held
*
- * dont want protocol specific in __skb_recv_datagram()
+ * don't want protocol specific in __skb_recv_datagram()
* to deny an incoming connection socket_sock_rcv_skb()
*/
static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
@@ -2129,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/match.c b/security/apparmor/match.c
index 3a2c6cf02b3c..d43ff34d705c 100644
--- a/security/apparmor/match.c
+++ b/security/apparmor/match.c
@@ -27,13 +27,13 @@
* @blob: data to unpack (NOT NULL)
* @bsize: size of blob
*
- * Returns: pointer to table else NULL on failure
+ * Returns: pointer to table else ERR_PTR on failure
*
* NOTE: must be freed by kvfree (not kfree)
*/
static struct table_header *unpack_table(char *blob, size_t bsize)
{
- struct table_header *table = NULL;
+ struct table_header *table = ERR_PTR(-EPROTO);
struct table_header th;
size_t tsize;
@@ -74,20 +74,21 @@ static struct table_header *unpack_table(char *blob, size_t bsize)
else if (th.td_flags == YYTD_DATA32)
UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
u32, __be32, get_unaligned_be32);
- else
- goto fail;
+ else {
+ kvfree(table);
+ table = ERR_PTR(-EPROTO);
+ goto out;
+ }
/* if table was vmalloced make sure the page tables are synced
* before it is used, as it goes live to all cpus.
*/
if (is_vmalloc_addr(table))
vm_unmap_aliases();
- }
+ } else
+ table = ERR_PTR(-ENOMEM);
out:
return table;
-fail:
- kvfree(table);
- return NULL;
}
/**
@@ -359,8 +360,11 @@ struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags)
while (size > 0) {
table = unpack_table(data, size);
- if (!table)
+ if (IS_ERR(table)) {
+ error = PTR_ERR(table);
+ table = NULL;
goto fail;
+ }
switch (table->td_id) {
case YYTD_ID_ACCEPT:
diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c
index 523570aa1a5a..2f5d918832c1 100644
--- a/security/apparmor/mount.c
+++ b/security/apparmor/mount.c
@@ -735,17 +735,11 @@ int aa_pivotroot(const struct cred *subj_cred, struct aa_label *label,
build_pivotroot(subj_cred, profile, new_path,
new_buffer,
old_path, old_buffer));
- if (!target) {
- info = "label build failed";
- error = -ENOMEM;
- goto fail;
- } else if (!IS_ERR(target)) {
+ AA_BUG(!target);
+ if (!IS_ERR(target)) {
error = aa_replace_current_label(target);
- if (error) {
- /* TODO: audit target */
- aa_put_label(target);
- goto out;
- }
+ if (error)
+ goto fail;
aa_put_label(target);
} else
/* already audited error */
@@ -763,7 +757,8 @@ fail:
NULL /*new_name */,
NULL /* old_name */,
NULL, NULL,
- 0, NULL, AA_MAY_PIVOTROOT, &nullperms, info,
+ 0, target->hname, AA_MAY_PIVOTROOT, &nullperms, info,
error));
+ aa_put_label(target);
goto out;
}
diff --git a/security/apparmor/net.c b/security/apparmor/net.c
index 44c04102062f..cf590dd08540 100644
--- a/security/apparmor/net.c
+++ b/security/apparmor/net.c
@@ -22,12 +22,14 @@
struct aa_sfs_entry aa_sfs_entry_network[] = {
AA_SFS_FILE_STRING("af_mask", AA_SFS_AF_MASK),
+ AA_SFS_FILE_BOOLEAN("tcp-fast-open", 1),
{ }
};
struct aa_sfs_entry aa_sfs_entry_networkv9[] = {
AA_SFS_FILE_STRING("af_mask", AA_SFS_AF_MASK),
AA_SFS_FILE_BOOLEAN("af_unix", 1),
+ AA_SFS_FILE_BOOLEAN("tcp-fast-open", 1),
{ }
};
@@ -354,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 b6a5eb4021db..94b4a7e727cc 100644
--- a/security/apparmor/policy.c
+++ b/security/apparmor/policy.c
@@ -232,6 +232,13 @@ static void __remove_profile(struct aa_profile *profile)
aa_label_remove(&profile->label);
__aafs_profile_rmdir(profile);
__list_remove_profile(profile);
+ /* rawdata is only ever referenced by fs lookup, that is no
+ * longer possible here, so put the reference to it. This will
+ * enable the rawdata to be freed if for some reason the profile
+ * is pinned and going to live for a while.
+ */
+ aa_put_profile_loaddata(profile->rawdata);
+ profile->rawdata = NULL;
}
/**
@@ -1223,8 +1230,12 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
if (aa_rawdata_eq(rawdata_ent, udata)) {
struct aa_loaddata *tmp;
- tmp = aa_get_profile_loaddata(rawdata_ent);
- /* check we didn't fail the race */
+ /*
+ * Entries remain on rawdata_list with
+ * pcount == 0 until do_ploaddata_rmfs()
+ * runs; only take a live profile ref.
+ */
+ tmp = aa_get_profile_loaddata_not0(rawdata_ent);
if (tmp) {
aa_put_profile_loaddata(udata);
udata = tmp;
@@ -1342,6 +1353,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
@@ -1352,6 +1373,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;
@@ -1372,12 +1398,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);