Hey,
I don't think this is correct. At least I don't see the leakage.
On Mon, 2026-07-06 at 14:36 -0700, Ryan Lee via AppArmor wrote:
> 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);
In apparmor_sk_free_security we put the label on both peer and
peer_lastupdate, so this would cause 2 puts for 1 get, which isn't
right.
> 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);
I don't think the put above was meant to address the "get" done in
update_sk_ctx, I believe it was meant for the plabel =
aa_get_newest_label(pctx->label);
And the gets done in update_sk_ctx would be addressed by
apparmor_sk_free_security.
> + 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;
> }