Commit
6d25e7b47616cb2db43351210929c8f19dc305a3 ("apparmor: fix refcount leak when
updating the sk_ctx")
fixes a refcount leak by unconditionally getting and putting a reference
to plabel in update_sk_ctx. However, update_sk_ctx can instead inform its
caller (aa_unix_file_perm) whether or not it persists a reference, and
then conditionally put the reference if it is not used. Semantically, this
involves update_sk_ctx now conditionally transferring the refcount, and
informing aa_unix_file_perm whether the refcount was used (and thus
whether it should be put as part of cleanups).
Signed-off-by: Ryan Lee <[email protected]>
---
security/apparmor/af_unix.c | 30 +++++++++++++++++++++---------
1 file changed, 21 insertions(+), 9 deletions(-)
diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c
index 9ca9d1b890ba..2730f4ad08d3 100644
--- a/security/apparmor/af_unix.c
+++ b/security/apparmor/af_unix.c
@@ -643,13 +643,16 @@ int aa_unix_peer_perm(const struct cred *subj_cred,
peer_label);
}
-/* sk_plabel for comparison only */
-static void update_sk_ctx(struct sock *sk, struct aa_label *label,
+/* sk_plabel for comparison only
+ * Returns whether plabel was assigned to a pointer
+ */
+static bool update_sk_ctx(struct sock *sk, struct aa_label *label,
struct aa_label *plabel)
{
struct aa_label *l, *old;
struct aa_sk_ctx *ctx = aa_sock(sk);
bool update_sk;
+ bool plabel_used = false;
rcu_read_lock();
update_sk = (plabel &&
@@ -658,7 +661,7 @@ static void update_sk_ctx(struct sock *sk, struct aa_label
*label,
!__aa_subj_label_is_cached(label, rcu_dereference(ctx->label));
rcu_read_unlock();
if (!update_sk)
- return;
+ return false;
spin_lock(&unix_sk(sk)->lock);
old = rcu_dereference_protected(ctx->label,
@@ -675,16 +678,17 @@ 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,
- aa_get_label(plabel));
+ rcu_assign_pointer(ctx->peer_lastupdate, plabel);
+ plabel_used = true;
} else if (aa_label_is_subset(plabel, old)) {
- rcu_assign_pointer(ctx->peer_lastupdate,
- aa_get_label(plabel));
+ rcu_assign_pointer(ctx->peer_lastupdate, plabel);
rcu_assign_pointer(ctx->peer, aa_get_label(plabel));
aa_put_label(old);
+ plabel_used = true;
} /* else race or a subset - don't update */
}
spin_unlock(&unix_sk(sk)->lock);
+ return plabel_used;
}
static void update_peer_ctx(struct sock *sk, struct aa_sk_ctx *ctx,
@@ -721,6 +725,7 @@ int aa_unix_file_perm(const struct cred *subj_cred, struct
aa_label *label,
struct path path;
bool is_sk_fs;
int error = 0;
+ bool plabel_owner_transfer = false;
AA_BUG(!label);
AA_BUG(!sock);
@@ -799,8 +804,15 @@ int aa_unix_file_perm(const struct cred *subj_cred, struct
aa_label *label,
/* update peer cache to latest successful perm check */
if (error == 0)
- update_sk_ctx(sock->sk, label, plabel);
- aa_put_label(plabel);
+ plabel_owner_transfer = update_sk_ctx(sock->sk, label, plabel);
+
+ /* If plabel ownership was not transferred, plabel can be either null
+ * (we never got a ref) or non-null (we got a ref and nobody else will
+ * use it, so we need to put it). Either way, aa_put_label will do
+ * the right thing.
+ */
+ if (!plabel_owner_transfer)
+ aa_put_label(plabel);
return error;
}
--
2.43.0