On Wed, Nov 6, 2024 at 10:55 AM Ondrej Mosnacek <omosnace@xxxxxxxxxx> 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@xxxxxxxxxx> > --- > 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