[PATCH] selinux,xfrm: fix dangling refcount on deferred skb free
Paul Moore
paul at paul-moore.com
Fri Nov 8 00:08:57 UTC 2024
On Wed, Nov 6, 2024 at 10:55 AM Ondrej Mosnacek <omosnace at redhat.com> wrote:
>
> SELinux tracks the number of allocated xfrm_state/xfrm_policy objects
> (via the selinux_xfrm_refcount variable) as an input in deciding if peer
> labeling should be used.
>
> However, as a result of commits f35f821935d8 ("tcp: defer skb freeing
> after socket lock is released") and 68822bdf76f1 ("net: generalize skb
> freeing deferral to per-cpu lists"), freeing of a sk_buff object, which
> may hold a reference to an xfrm_state object, can be deferred for
> processing on another CPU core, so even after xfrm_state is deleted from
> the configuration by userspace, the refcount isn't decremented until the
> deferred freeing of relevant sk_buffs happens. On a system with many
> cores this can take a very long time (even minutes or more if the system
> is not very active), leading to peer labeling being enabled for much
> longer than expected.
>
> Fix this by moving the selinux_xfrm_refcount decrementing to just after
> the actual deletion of the xfrm objects rather than waiting for the
> freeing to happen. For xfrm_policy it currently doesn't seem to be
> necessary, but let's do the same there for consistency and
> future-proofing.
>
> We hit this issue on a specific aarch64 256-core system, where the
> sequence of unix_socket/test and inet_socket/tcp/test from
> selinux-testsuite [1] would quite reliably trigger this scenario, and a
> subsequent sctp/test run would then stumble because the policy for that
> test misses some rules that would make it work under peer labeling
> enabled (namely it was getting the netif::egress permission denied in
> some of the test cases).
>
> [1] https://github.com/SELinuxProject/selinux-testsuite/
>
> Fixes: f35f821935d8 ("tcp: defer skb freeing after socket lock is released")
> Fixes: 68822bdf76f1 ("net: generalize skb freeing deferral to per-cpu lists")
> Signed-off-by: Ondrej Mosnacek <omosnace at redhat.com>
> ---
> include/linux/lsm_hook_defs.h | 2 ++
> include/linux/security.h | 10 ++++++++++
> net/xfrm/xfrm_policy.c | 1 +
> net/xfrm/xfrm_state.c | 2 ++
> security/security.c | 26 ++++++++++++++++++++++++++
> security/selinux/hooks.c | 2 ++
> security/selinux/include/xfrm.h | 2 ++
> security/selinux/xfrm.c | 17 ++++++++++++++++-
> 8 files changed, 61 insertions(+), 1 deletion(-)
...
> diff --git a/security/security.c b/security/security.c
> index 6875eb4a59fcc..f6a985417f6f8 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -5295,6 +5295,19 @@ int security_xfrm_policy_delete(struct xfrm_sec_ctx *ctx)
> return call_int_hook(xfrm_policy_delete_security, ctx);
> }
>
> +/**
> + * security_xfrm_policy_deleted() - Handle deletion of xfrm policy
> + * @ctx: xfrm security context
> + *
> + * Handle deletion of xfrm policy. This is called on the actual deletion
> + * of the policy from the xfrm system. References to the policy may be
> + * still held elsewhere, so resources should not be freed yet.
> + */
> +void security_xfrm_policy_deleted(struct xfrm_sec_ctx *ctx)
> +{
> + call_void_hook(xfrm_policy_deleted, ctx);
> +}
> +
> /**
> * security_xfrm_state_alloc() - Allocate a xfrm state LSM blob
> * @x: xfrm state being added to the SAD
> @@ -5345,6 +5358,19 @@ int security_xfrm_state_delete(struct xfrm_state *x)
> }
> EXPORT_SYMBOL(security_xfrm_state_delete);
>
> +/**
> + * security_xfrm_state_deleted() - Handle deletion of xfrm state
> + * @x: xfrm state
> + *
> + * Handle deletion of xfrm state. This is called on the actual deletion
> + * of the state from the xfrm system. References to the state may be
> + * still held elsewhere, so resources should not be freed yet.
> + */
> +void security_xfrm_state_deleted(struct xfrm_state *x)
> +{
> + call_void_hook(xfrm_state_deleted, x);
> +}
I'm not really liking the names or the descriptions above. The
"_deleted" hooks are named a little too close to the existing
"_delete" hooks for comfort; I can see people mistakenly calling the
wrong hooks and causing problems in the future. I'd also suggest a
change in the hooks description to clarify the "actual deletion"
aspect as that is confusing when you later talk about how references
still may exist. The xfrm hooks have a "_delete" to authorize the
deletion of the object, and a "_free" to finally release any LSM state
associated with the object once all the references are gone; using
"_put" may be problematic without an associated "_get", but how about
something along the lines of "_release" or "_drop" with a description
that talks about all references to the xfrm object being released or
dropped?
> diff --git a/security/selinux/xfrm.c b/security/selinux/xfrm.c
> index 90ec4ef1b082f..35372bdba7279 100644
> --- a/security/selinux/xfrm.c
> +++ b/security/selinux/xfrm.c
> @@ -321,6 +320,14 @@ int selinux_xfrm_policy_delete(struct xfrm_sec_ctx *ctx)
> return selinux_xfrm_delete(ctx);
> }
>
> +/*
> + * LSM hook implementation that handles deletion of labeled SAs.
> + */
> +void selinux_xfrm_policy_deleted(struct xfrm_sec_ctx *ctx)
> +{
> + atomic_dec(&selinux_xfrm_refcount);
> +}
> +
> /*
> * LSM hook implementation that allocates a xfrm_sec_state, populates it using
> * the supplied security context, and assigns it to the xfrm_state.
> @@ -389,6 +396,14 @@ int selinux_xfrm_state_delete(struct xfrm_state *x)
> return selinux_xfrm_delete(x->security);
> }
>
> +/*
> + * LSM hook implementation that handles deletion of labeled SAs.
> + */
> +void selinux_xfrm_state_deleted(struct xfrm_state *x)
> +{
> + atomic_dec(&selinux_xfrm_refcount);
> +}
> +
> /*
> * LSM hook that controls access to unlabelled packets. If
> * a xfrm_state is authorizable (defined by macro) then it was
> --
> 2.47.0
--
paul-moore.com
More information about the Linux-security-module-archive
mailing list