On Wed, Jul 22, 2026 at 8:22 AM Georgia Garcia
<[email protected]> wrote:
>
> Hey,
>
> I don't think this is correct. At least I don't see the leakage.
This commit is not fixing a leak, but is instead making a previous fix
for a refcount leak more efficient.
>
> 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);
This was also my understanding: that this aa_put_label call is meant
to pair with the aa_get_newest_label call earlier in this function.
>
> And the gets done in update_sk_ctx would be addressed by
> apparmor_sk_free_security.
I have a further explanation at the bottom of this email explaining
how I traced the refcount changes.
>
>
> > + 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;
> > }
>
To sketch out how I view the refcounts in the control flow of
aa_unix_file_perm (all this assuming plabel is not null):
Before this patch:
plabel is obtained as aa_get_newest_label(pctx->label) (+1)
unix_peer_perm and __aa_subj_label_is_cached leave the plabel refcount
as they were before
if (error != 0), plabel is not used again and is put, leaving the
refcount balanced (+1 -1 = 0)
if (error == 0), update_sk_ctx is called:
- if ctx->peer_lastupdate != plabel:
-- compare ctx->peer to plabel. If same:
--- update only ctx->peer_lastupdate, taking a new refcount to plabel
(+1 +1 = 2)
-- else:
--- update both ctx->peer_lastupdate and ctx->peer, taking new
refcounts for each (+1 +1 +1 = 3)
-- *Another potential issue spotted: do we need to put the refcount
for whatever used to be in ctx->peer_lastupdate?*
- After update_sk_ctx, plabel in aa_unix_file_perm gets put, with
final refcount change being (+1 +1 -1 = 1) or (+1 +1 +1 -1 = 2)
apparmor_sk_free_security frees the refcounts taken in update_sk_ctx,
bringing the net refcount change back to 0
After this patch:
plabel is obtained as aa_get_newest_label(pctx->label) (+1)
unix_peer_perm and __aa_subj_label_is_cached leave the plabel refcount
as they were before
if (error != 0), plabel is not used again and is put, leaving the
refcount balanced (+1 -1 = 0)
if (error == 0), update_sk_ctx is called:
- if ctx->peer_lastupdate != plabel:
-- compare ctx->peer to plabel. If same:
--- update only ctx->peer_lastupdate, reusing the existing refcount to
plabel (+1 +0 = 1)
-- else:
--- update both ctx->peer_lastupdate and ctx->peer, reusing the
existing refcount and taking only one new reference (+1 +0 +1 = 2)
-- *This patch does not address the potential issue newly identified.*
- After update_sk_ctx, plabel in aa_unix_file_perm gets conditionally
put depending on whether the existing refcount was reused, with the
final refcount change being (+1 +0 -0 = 1) or (+1 +0 +1 -0 = 2)
apparmor_sk_free_security frees the refcounts taken in update_sk_ctx,
bringing the net refcount change back to 0
Thus, the advantage of this patch is that it removes one get/put pair,
reducing the number of atomic operations needed on the refcount
member.
Please let me know if you think I messed up in my analysis somewhere.