Subject: [REGRESSION] selinux: ~90% throughput drop in System V IPC (msg) since commit 7edea6e8c8e8

Jiri Vozar <[email protected]> Tue, 4 Aug 2026 02:16:14 +0200
Newsgroups org.kernel.vger.linux-kernel,org.kernel.vger.linux-security-module,org.kernel.vger.selinux
Message-ID <CAMgFczCi2Z011dNf84Amc0Q-qnTt0+VUjWY+Y7zPyXdaH35Jvw@mail.gmail.com>
Hi,

Commit 7edea6e8c8e8 ("selinux: beef up isvalid checks") introduces a new
loop in mls_level_isvalid() that causes ~89-94% throughput regression in
System V IPC message queue operations (msgsnd/msgrcv).

The regression affects all architectures (x86_64, aarch64, ppc64le) and
is independent of SELinux enforcement mode (enforcing vs permissive).

Upstream commit:
https://github.com/torvalds/linux/commit/7edea6e8c8e8944e060da6b0b81d66db8d=
5fffcc
Merge commit:    https://github.com/torvalds/linux/commit/231e9d447ea9
(selinux-pr-20260615)
First affected:  v7.2-rc1

## Regression data

Measured with stress-ng msg stressor across multiple machines and
architectures. The percentage is throughput change vs a v6.12 baseline:

  NUMA config | Max drop | Avg drop | Hosts affected
  ------------|----------|----------|-------------------
  1 NUMA      |  -94%    |  -91.5%  | aarch64 5/5, ppc64le 3/3, x86_64 6/21
  2 NUMA      |  -94%    |  -92%    | x86_64 5 hosts
  4 NUMA      |  -94%    |  -90%    | x86_64 4 hosts
  8 NUMA      |  -89%    |  -89%    | x86_64 1 host

Architecture-independent =E2=80=94 confirms the regression is in the kernel=
 code
path, not hardware-specific.

## Root cause

The new loop in security/selinux/ss/mls.c mls_level_isvalid():

    ebitmap_for_each_positive_bit(&levdatum->level.cat, node, bit) {
        if (!sym_name(p, SYM_CATS, bit))
            return false;
    }

iterates over every set bit in the category bitmap (~1024 bits in
standard MCS policy with c0.c1023 range). Each iteration calls
_find_next_bit() + sym_name() (array dereference into
p->sym_val_to_name[SYM_CATS]).

The function is called on every IPC operation via:

    msgsnd() -> security_msg_msg_alloc() -> selinux_msg_msg_alloc_security(=
)
      -> security_context_to_sid_core() -> mls_context_isvalid()
        -> mls_range_isvalid() -> mls_level_isvalid() (x2 per range)

This adds ~2048 iterations of bitmap scanning + array lookups per IPC
operation. SELinux permissive mode does not help (~0.6% difference)
because the overhead is in the validation path, not the enforcement
decision.

## Perf profile evidence

Single-threaded stress-ng --msg 1 -t 60 on x86_64:
  - Baseline (v6.12): ~67,000 msg ops/sec
  - Regressed (v7.2-rc4): ~7,400 msg ops/sec (-89%)

perf top on the regressed kernel shows _find_next_bit and sym_name
dominating kernel CPU time during the msg workload.

## How to reproduce

On any machine with kernel >=3D v7.2 and SELinux enabled:

    # Install stress-ng
    dnf install -y stress-ng

    # Run the msg stressor
    stress-ng --msg 1 -t 60 --metrics-brief

    # Compare with SELinux disabled (requires reboot with selinux=3D0):
    stress-ng --msg 1 -t 60 --metrics-brief

Expected: ~67,000 bogo-ops/sec (as on v6.12)
Actual:   ~7,400 bogo-ops/sec (on v7.2) =E2=80=94 approximately 89% drop

The regression affects any workload that uses System V IPC message queues
at high frequency. Other IPC stressors (sem, shm) may also be affected
to a lesser degree.

## Suggested fix

The ebitmap_for_each_positive_bit loop validates that every category bit
has a corresponding name in the policy symbol table. This is a property
of the policy itself and does not change at runtime. The validation
should be moved to policy load time in
security/selinux/ss/policydb_validate.c (e.g. in validate_level()) and
removed from the hot path in mls_level_isvalid().

Thanks,
Jiri Vozar