From nobody Tue Sep 29 13:57:44 2026 Received: from mail-pl1-f175.google.com (mail-pl1-f175.google.com [209.85.214.175]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 097D2263F44 for ; Fri, 7 Aug 2026 01:01:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.175 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786064510; cv=none; b=OSXZAWSdCKqCa2yzb1zHDHP+YFwFstsTaCpRYFT5zURsDYaGA06tGl3bym5OrIWpcjF+XfqrPLi6LRsMvr0LfLJqzUjQliJ6hCTxIbPnVcN1qWMNTYZ0bSbyMswuUHW/F6Rv7YdT5o8GEK/Mu8N2ezASGf7Rs+9VOOdNifkyD/w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786064510; c=relaxed/simple; bh=5MlwxZW6swiSLCczpbu3RjCTjMOtxNuISAYVxZ1MH5c=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=pdfZ1drwZbxXLYyvgRNd2nMirc5l5WLdhCQyfae2bFl/P+mcbEt/IKxhP1QctBRp9+fXiV56pAkmiIu0j7MzqSLJ2O+x1dg286Dk24leF9SFmZk+FJ3Jp4sOmAQEm151AwakOSKJbmZyftQAUYmXqZbUSZ9C+eM5bhzYAeRRmfU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=V2IV0H0P; arc=none smtp.client-ip=209.85.214.175 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="V2IV0H0P" Received: by mail-pl1-f175.google.com with SMTP id d9443c01a7336-2d032846c95so35756465ad.1 for ; Thu, 06 Aug 2026 18:01:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786064508; x=1786669308; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=pwP36o17QkWQ0PjVr8FVFT3lgFW/hOoyQfxT67LFwVA=; b=V2IV0H0PzOybZ0GHI1Pl2oDhaq2aOW5BGTTCsC/EqpT0DXh81WAXwG7Km1IXOJBudc FCy0WT6CjIB1bJC4tJmpekFmiafPfoJHBY60Uw1fbrXu3jJNAO8YD8ioAL9iimzc8/rB 2pDZQx8foZZPpeXPXk8Wly7Y9b9D62pCP9oMc8i1AZq1C+HEDCHzAGeYig5EIVdlDKRS Ojz+4zlnYTNDA2A39qhL90NaVuL4HAfWkBZgQzcS6fn8IfRdYTGSeKjMkgkJmZcYEwK9 idbtKwwrjlckukTGXtdGf80ziBXp2/aqlM1jhE/eAhVkQQ8K9WhD8MzaeIhFODeWLMjK TYwg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786064508; x=1786669308; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=pwP36o17QkWQ0PjVr8FVFT3lgFW/hOoyQfxT67LFwVA=; b=jt7m0RBEOI1W9eBw+7T3psSQ8UsNk56q0IQNsmsplRvjW6diLIZLBAw+qQWWEMWvxQ KC8MSGj/TQtoOcc2NMM+l+/WyFoS7bjU0HO04tnnKpPJbC3++K9gskXAlzIUiU4h0FcZ f4gkM081Pr9ilVNd3LtUMSvPJPnqeNlyQLXmlN/HswgshVWJGM8X/hyeVFcEFEJ+v2+F 6Fj8DmMA2hOlR0mpzovr3Ywr3wureWD2LMUfyhEnTTjvcV7CWRFsLzuj0jlS4gvGnMcq XGEk3Xm+M1m4QgwIUQ3Z8+HfN/Ex5JMrVH5tuZ0/1hfZxCK6DzUcLoam3bvpWLZMjOmw l4yA== X-Forwarded-Encrypted: i=1; AHgh+RpmgHO4/Zxi1y1gtOpGUrkKGInoO5Dl1CA16y3pAfACSeoEhJMcmZFgwcG17Rf93LYX8pAhHWeAh8aSOIk=@vger.kernel.org X-Gm-Message-State: AOJu0YwLetxe0rH4Q2T1HwX4bsm6hh1dcYLbYNBQgm4mdVgkVah+VqLs gATgw3U1uZje5yPEfCr/MdJdo1mXPNxOdN51krMT1rH4hybCKSlUboWV X-Gm-Gg: AR+sD10bO+PQSfHLZ+7wkc1TEsGY7Kh6BJQ/Sf75L3k7aeVvu3emXRU12HGfbXxvmri EuG9OKa8VNyUfL/Ko7qySvFI5FaPJ1rva8etSDba6yazGzmMoL2AGgM4lFyGFtiG0LfOsXN1vyf NXrIDBHWwzjJN21Pv3LZN8NaJhX2ujNTZM7FrUhSyynScgYDGOqJgtp/aql1q4gGXE41z06TaoA c7U5yibUbJzMuQFmexhFE2iagiXUa3AXjKClbnCgZBQ/Hw0AWP9ys5y3IN4Pvnl0jVbVrOG3AWz FuDKZw6zFDiaLMT4OGsUdbX2jyLBCdod5ZqYDSCOl8bXwxWkFtxc64WZYPIV03UhHmRCka+R0tf Z9rtGB+wIolNPmD4V3FlwhN70h4OLrQfdI4UnGCpQQZ8w71vZbNQwaesPK/4HjiTLTP66WUi4Mo Ecd/u3oWiTmUrLOSJbI0rrhu1aghawUDCr2l1n9hDWPHxIKWWKXVu/ycCEUyhN375sC673JJPMN lXpD/+uYmIIaghHrurI0iGSI7sFwkIfnQg= X-Received: by 2002:a17:902:dac1:b0:2c0:e5ee:f554 with SMTP id d9443c01a7336-2d0ca71b3d7mr209247785ad.8.1786064507867; Thu, 06 Aug 2026 18:01:47 -0700 (PDT) Received: from [192.168.0.160] (c-98-225-44-182.hsd1.wa.comcast.net. [98.225.44.182]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2d14d7684f8sm1527725ad.33.2026.08.06.18.01.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 06 Aug 2026 18:01:47 -0700 (PDT) From: Stanislav Kinsburskii Date: Thu, 06 Aug 2026 18:01:19 -0700 Subject: [PATCH 1/3] selftests/audit: Add syscall overhead benchmark Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260806-audit-v1-1-ddd0d94ff0b6@gmail.com> References: <20260806-audit-v1-0-ddd0d94ff0b6@gmail.com> In-Reply-To: <20260806-audit-v1-0-ddd0d94ff0b6@gmail.com> To: Shuah Khan , Paul Moore , Eric Paris , Al Viro , Amy Griffis Cc: Stanislav Kinsburskii , Frank Hofmann , Noah Orlando , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, audit@vger.kernel.org X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1786064505; l=10426; i=skinsburskii@gmail.com; s=20260722; h=from:subject:message-id; bh=5MlwxZW6swiSLCczpbu3RjCTjMOtxNuISAYVxZ1MH5c=; b=0u1iAu0ZGuodPxXfccaUhAWuBCzIPPS+0TUNC592bEADuck1TGFnAu/MqVhBTREhuWBytf8g0 OcxVotg5x4MBJI99zEQy5ehUXt5V5UrgG4+qkm2H6nKDrE8HNHdXFTs X-Developer-Key: i=skinsburskii@gmail.com; a=ed25519; pk=bDpriHBYgeTdkIDweZDCemxsU93neJBOCn3YLIuJpnE= Add a microbenchmark which repeatedly invokes getpid(2) and reports the per-operation latency across multiple repetitions. The workload avoids filesystem and other syscall-specific work so the fixed audit syscall overhead remains visible. The benchmark deliberately leaves audit policy management to the caller. This permits comparisons with increasing numbers of unrelated exit rules and with automatically removed watch or tree rules without modifying an existing policy unexpectedly. Signed-off-by: Stanislav Kinsburskii --- MAINTAINERS | 1 + tools/testing/selftests/Makefile | 1 + tools/testing/selftests/audit/.gitignore | 2 + tools/testing/selftests/audit/Makefile | 9 ++ tools/testing/selftests/audit/README | 30 ++++ tools/testing/selftests/audit/audit_bench.c | 227 ++++++++++++++++++++++++= ++++ 6 files changed, 270 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index d52c224eabaf..6d87387de0cf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4365,6 +4365,7 @@ F: include/linux/audit_arch.h F: include/uapi/linux/audit.h F: kernel/audit* F: lib/*audit.c +F: tools/testing/selftests/audit/ K: \baudit_[a-z_0-9]\+\b =20 AUTOFDO BUILD diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Mak= efile index 84343fd1e354..fb2dae9d4018 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 TARGETS +=3D acct +TARGETS +=3D audit TARGETS +=3D alloc_tag TARGETS +=3D alsa TARGETS +=3D amd-pstate diff --git a/tools/testing/selftests/audit/.gitignore b/tools/testing/selft= ests/audit/.gitignore new file mode 100644 index 000000000000..1138c94bdce5 --- /dev/null +++ b/tools/testing/selftests/audit/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +audit_bench diff --git a/tools/testing/selftests/audit/Makefile b/tools/testing/selftes= ts/audit/Makefile new file mode 100644 index 000000000000..ce7e06725fd0 --- /dev/null +++ b/tools/testing/selftests/audit/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 + +CFLAGS +=3D -O2 -Wall -Wextra +LDLIBS +=3D -lm + +TEST_GEN_PROGS_EXTENDED :=3D audit_bench +TEST_FILES :=3D README + +include ../lib.mk diff --git a/tools/testing/selftests/audit/README b/tools/testing/selftests= /audit/README new file mode 100644 index 000000000000..b40155808dcf --- /dev/null +++ b/tools/testing/selftests/audit/README @@ -0,0 +1,30 @@ +Audit syscall overhead benchmark +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D + +Build it with: + + make -C tools/testing/selftests/audit + +The benchmark repeatedly invokes getpid(2). It does not install or remove +audit rules. Configure the policy explicitly with auditctl, then run the s= ame +workload for each policy. + +For example: + + sudo auditctl -a always,exit -F arch=3Db64 -S openat + sudo ./tools/testing/selftests/audit/audit_bench + sudo auditctl -d always,exit -F arch=3Db64 -S openat + +The getpid workload exposes the fixed per-syscall audit overhead without +adding filesystem work. Useful comparisons are no rules, increasing numbers +of unrelated syscall rules, and a clean state versus one where a watch or +tree rule was removed automatically. Keep the machine idle, pin with --cpu +when possible, and collect profiles with perf stat and perf record. Report +the kernel commit, CPU model, audit status, policy and auditd state with e= very +comparison. + +After printing each repetition, the benchmark reports the median, arithmet= ic +mean, sample standard deviation, coefficient of variation and observed ran= ge +of per-operation latency across repetitions. The coefficient of variation +makes noisy runs easy to identify; increase the iteration count or investi= gate +system noise when it is high. diff --git a/tools/testing/selftests/audit/audit_bench.c b/tools/testing/se= lftests/audit/audit_bench.c new file mode 100644 index 000000000000..89c19c03817c --- /dev/null +++ b/tools/testing/selftests/audit/audit_bench.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Microbenchmark for Linux audit syscall overhead. + * + * This program does not configure audit. Install rules manually to compa= re + * the same workload under different policies. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_ITERATIONS 10000000ULL +#define DEFAULT_REPETITIONS 10 + +static int compare_double(const void *left, const void *right) +{ + const double a =3D *(const double *)left; + const double b =3D *(const double *)right; + + return (a > b) - (a < b); +} + +static void print_summary(double *samples, unsigned int repetitions) +{ + double mean =3D 0.0; + double squared_deviations =3D 0.0; + double median; + double stddev; + unsigned int i; + + for (i =3D 0; i < repetitions; i++) + mean +=3D samples[i]; + mean /=3D repetitions; + + for (i =3D 0; i < repetitions; i++) { + double deviation =3D samples[i] - mean; + + squared_deviations +=3D deviation * deviation; + } + stddev =3D repetitions > 1 ? + sqrt(squared_deviations / (repetitions - 1)) : 0.0; + + qsort(samples, repetitions, sizeof(*samples), compare_double); + if (repetitions % 2) + median =3D samples[repetitions / 2]; + else + median =3D (samples[repetitions / 2 - 1] + + samples[repetitions / 2]) / 2.0; + + printf("=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D\n"); + printf("summary (ns/op): median=3D%.f", median); + printf(" mean=3D%.f", mean); + printf(" stddev=3D%.f (%.f%%)", stddev, + mean ? stddev * 100.0 / mean : 0.0); + printf(" range=3D%.f..%.f\n", + samples[0], samples[repetitions - 1]); +} + +static void usage(const char *program) +{ + printf("Usage: %s [OPTIONS]\n", program); + printf("\n"); + printf("Options:\n"); + printf(" -c, --cpu CPU pin the benchmark to CPU\n"); + printf(" -h, --help show this help\n"); + printf(" -n, --iterations N measured operations per repetition\n"); + printf(" (default: %llu)\n", + DEFAULT_ITERATIONS); + printf(" -r, --repetitions N number of measured repetitions\n"); + printf(" (default: %d)\n", + DEFAULT_REPETITIONS); + printf(" -w, --warmup N warm-up operations (default N/10)\n"); +} + +static uint64_t parse_u64(const char *value, const char *name) +{ + uint64_t parsed; + char *end; + + if (*value < '0' || *value > '9') + errx(EXIT_FAILURE, "invalid %s: %s", name, value); + + errno =3D 0; + parsed =3D strtoull(value, &end, 0); + if (errno || *value =3D=3D '\0' || *end !=3D '\0') + errx(EXIT_FAILURE, "invalid %s: %s", name, value); + + return parsed; +} + +static unsigned int parse_uint(const char *value, const char *name) +{ + uint64_t parsed =3D parse_u64(value, name); + + if (parsed > UINT_MAX) + errx(EXIT_FAILURE, "%s is too large: %s", name, value); + + return parsed; +} + +static void pin_to_cpu(unsigned int cpu) +{ + cpu_set_t set; + + if (cpu >=3D CPU_SETSIZE) + errx(EXIT_FAILURE, "CPU must be less than %d", CPU_SETSIZE); + + CPU_ZERO(&set); + CPU_SET(cpu, &set); + if (sched_setaffinity(0, sizeof(set), &set)) + err(EXIT_FAILURE, "sched_setaffinity(%u)", cpu); +} + +static uint64_t elapsed_ns(const struct timespec *start, + const struct timespec *end) +{ + return (end->tv_sec - start->tv_sec) * 1000000000ULL + + end->tv_nsec - start->tv_nsec; +} + +static void run_getpid(uint64_t iterations) +{ + uint64_t i; + + for (i =3D 0; i < iterations; i++) + syscall(SYS_getpid); +} + +int main(int argc, char **argv) +{ + static const struct option options[] =3D { + { "cpu", required_argument, NULL, 'c' }, + { "help", no_argument, NULL, 'h' }, + { "iterations", required_argument, NULL, 'n' }, + { "repetitions", required_argument, NULL, 'r' }, + { "warmup", required_argument, NULL, 'w' }, + { } + }; + uint64_t iterations =3D DEFAULT_ITERATIONS; + uint64_t warmup =3D 0; + unsigned int repetitions =3D DEFAULT_REPETITIONS; + unsigned int cpu =3D 0; + bool warmup_set =3D false; + bool cpu_set =3D false; + double *samples; + int option; + unsigned int repetition; + + while ((option =3D getopt_long(argc, argv, "c:hn:r:w:", options, + NULL)) !=3D -1) { + switch (option) { + case 'c': + cpu =3D parse_uint(optarg, "CPU"); + cpu_set =3D true; + break; + case 'h': + usage(argv[0]); + return EXIT_SUCCESS; + case 'n': + iterations =3D parse_u64(optarg, "iteration count"); + break; + case 'r': + repetitions =3D parse_uint(optarg, "repetition count"); + break; + case 'w': + warmup =3D parse_u64(optarg, "warm-up count"); + warmup_set =3D true; + break; + default: + usage(argv[0]); + return EXIT_FAILURE; + } + } + + if (optind !=3D argc) + errx(EXIT_FAILURE, "unexpected positional argument: %s", + argv[optind]); + if (!iterations || !repetitions) + errx(EXIT_FAILURE, "iterations and repetitions must be nonzero"); + if (!warmup_set) + warmup =3D iterations / 10; + if (cpu_set) + pin_to_cpu(cpu); + + samples =3D calloc(repetitions, sizeof(*samples)); + if (!samples) + err(EXIT_FAILURE, "calloc(samples)"); + + printf("getpid iterations=3D%" PRIu64 " warmup=3D%" PRIu64 + " repetitions=3D%u\n", iterations, warmup, repetitions); + + run_getpid(warmup); + for (repetition =3D 0; repetition < repetitions; repetition++) { + struct timespec start, end; + uint64_t duration; + double ns_per_operation; + + if (clock_gettime(CLOCK_MONOTONIC_RAW, &start)) + err(EXIT_FAILURE, "clock_gettime(start)"); + run_getpid(iterations); + if (clock_gettime(CLOCK_MONOTONIC_RAW, &end)) + err(EXIT_FAILURE, "clock_gettime(end)"); + + duration =3D elapsed_ns(&start, &end); + ns_per_operation =3D (double)duration / iterations; + samples[repetition] =3D ns_per_operation; + printf("%u: %.2f ns/op\n", repetition + 1, + ns_per_operation); + } + + print_summary(samples, repetitions); + free(samples); + return EXIT_SUCCESS; +} --=20 2.43.0 From nobody Tue Sep 29 13:57:44 2026 Received: from mail-pl1-f177.google.com (mail-pl1-f177.google.com [209.85.214.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D96BC263F34 for ; Fri, 7 Aug 2026 01:01:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.177 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786064511; cv=none; b=q44M1pq9cePFtFWDiSsZbn+PlcaiFrAkgl3psP1nYaOmn5nZNN7dH++zlGgazBV5lFpq9WGiO82uMiGeStAhZ7zb8OK1Amu1NM62kpu1HXRC/2BaTr0aDO0Ki0bn4O5ulaeB6zZFh9KiIo0U/JODveZmcJvs9Rov13Nbf1YVpWM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786064511; c=relaxed/simple; bh=hO2zIVXsanarVAstUrUEC/wI+YDEQjhFgSc8QifAcHk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=TnEEIKFhS8tNVh32KzXJoOaV6f/l85QTbVJ4QsUGNb44M2VlbaCGp3CUjjZi94V75OaOvpCD6F7QX7NHE2jP4J2jEMya6mLiEojslbPAFulPcBsh4H4tmJf+452a1cqKYNbEnLrbkuh0VRte6zIUuFQ3f+ZSOjg8xx3XPstQUCo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=dBt78xqn; arc=none smtp.client-ip=209.85.214.177 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="dBt78xqn" Received: by mail-pl1-f177.google.com with SMTP id d9443c01a7336-2d049069377so30348935ad.0 for ; Thu, 06 Aug 2026 18:01:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786064509; x=1786669309; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=VOrYwMwMcKhjd1hcBSf/T5DT9YmmM3v7iOs2AT3Kchg=; b=dBt78xqnqIcbqDZ/NjKVmuDs70mtOjIy6/VfAcyUFkbbjznXITD8jdI/hcv+MLcSzs KgWOctpzuWRiy4zT4FQrB4y4YvdVAkBhEm+2Oqf4GMpoyF/O3ejLFAKuMUgmTcPtrPhC BCD3/qddHGW4vcLOs7+QDOJoti+t84g1lxXYNbLg4tqTYMb42IqxHJv/hValfdm5dst/ LzAnmaMAYBibUzJXjZ7KzuSODLhZMf52YWCmiQH7FByp3C85NeDxuDar29kdyZqu8ut0 oiLrnzDXoPlSX4JTMwbFqSFMMw6wimkCQhCz3mgxe0MjopYjGnjwmu80yyimDvO4xVcr OM2A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786064509; x=1786669309; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=VOrYwMwMcKhjd1hcBSf/T5DT9YmmM3v7iOs2AT3Kchg=; b=NJzrzWQ2rY7bxQGlV7sPQLkQYyIDTUIbpXDyYn/9eDYCIkEKo2Z+ALjspRmxl5UXuE 3FZ/i9ErUnjK1sJmjtOd7rs9LzqUyRRUajGXYrDf7iwemgm9FBSS7N6L6pxksrteaG+I ioz7WdMGKL48oH7dsG2N1Uv2sP2tZTrzKkpGRekR9ZaTneCqp73P4J05agzIJAWUNrb7 rs1hl1cri3ZUg4c5usSUUlqkt+zbNSfua3t/cBiwopGXL6KuaJjLs3BcZRlXAWRv/NrH P0fAiA2BG01yiIzP+uU0d2AVVMvLndneAYSPQwaCvPl0g9wYN4ETTZsgR85cDLILgQ/Z 3utA== X-Forwarded-Encrypted: i=1; AHgh+RrEHMW5YA8JpCcr1FqYw46oSfqGD/oOKFdgo5efxH+lXqMYMTwTLlCF/q37emBfAq40Ygwv8w+f3pHl360=@vger.kernel.org X-Gm-Message-State: AOJu0YzeoNHHAEWEYK5mxXOGVOikusdxoQuL6hPfKzzkQxI1IRqUPG1A DP4TrZn3B2rzK9yxC5SGd0/vEnD07hjrZHGEXB8/jf17Qm9aqGehHduMfW+/mQ== X-Gm-Gg: AR+sD11JyrkCotd2TpwIhPcDn0ajBNQV2zr1TMlnZ2UZQlfsVsYbLAP61OIWMBe/eQv Kvf3pLqhHbtTYG5CZXHzHwH2DxIun4HsMk9YxPnENevpGSwe4b/hgWgYk1cYid+yOYxeGgx1/Av hK+pLsEEwoRj7Im+2DbTUO3WCMPnwq8rPk6qX+GoOBFM4tuw+aYHKG7VBEQoCU74Y6uTEIBes2A 8z/rN98iDdYkXiVb+/0zmhK1v1OShp4ge4zRdmC6gfR6TobYOgkVfdUwHN65EfOMcDG+4NbeHl1 wQLMY5o9qCO0r4srMufPEXwLWiMKD6FIRziZnnASsWKBlgB3K9zSDs3VriMTWMSV5tBqZlQZ3B7 5z+u6rpdS/xxvE48uAfOulTmGo7HYknoqJ7GI3HtYct9+r13ge6L/uko0pjLkkfgC1ArlVQeyII 5fPU1Jjlao8rdu35Nqm1Qojsb8IiXuiQw/Y2Fjn1JbVqNid7eRLtJoRHuUmsebMAtfZJxK040uT hib8XNl7shx/DhlhXnmJ5u3IHr+P1Db6fv9pQbacm/iCQ== X-Received: by 2002:a17:902:e787:b0:2c9:c083:cd50 with SMTP id d9443c01a7336-2d106e333f0mr65082035ad.17.1786064509061; Thu, 06 Aug 2026 18:01:49 -0700 (PDT) Received: from [192.168.0.160] (c-98-225-44-182.hsd1.wa.comcast.net. [98.225.44.182]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2d14d7684f8sm1527725ad.33.2026.08.06.18.01.48 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 06 Aug 2026 18:01:48 -0700 (PDT) From: Stanislav Kinsburskii Date: Thu, 06 Aug 2026 18:01:20 -0700 Subject: [PATCH 2/3] audit: Fix filter rule accounting after automatic removal Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260806-audit-v1-2-ddd0d94ff0b6@gmail.com> References: <20260806-audit-v1-0-ddd0d94ff0b6@gmail.com> In-Reply-To: <20260806-audit-v1-0-ddd0d94ff0b6@gmail.com> To: Shuah Khan , Paul Moore , Eric Paris , Al Viro , Amy Griffis Cc: Stanislav Kinsburskii , Frank Hofmann , Noah Orlando , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, audit@vger.kernel.org X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1786064505; l=9360; i=skinsburskii@gmail.com; s=20260722; h=from:subject:message-id; bh=hO2zIVXsanarVAstUrUEC/wI+YDEQjhFgSc8QifAcHk=; b=O3j92f4AyhUHfjs2ASYG+yoDK37hQWIocAmIfCo2js5SaETWgqyzfitgG2HpDJQAoElEjgood YhHt6m9Jo6SAUnZvxXlApSixj4OmoJ86MXNL4ikRI3vzbTqgPhaOl3S X-Developer-Key: i=skinsburskii@gmail.com; a=ed25519; pk=bDpriHBYgeTdkIDweZDCemxsU93neJBOCn3YLIuJpnE= The audit_n_rules and audit_signals counters are incremented when filter rules are installed and decremented by the explicit rule deletion path. Rules can also disappear when a watch or tree is removed, or when an LSM rule cannot be reconstructed, but those paths do not update the counters. As a result, audit_n_rules can remain nonzero after the last applicable rule has gone away, causing subsequent syscalls to allocate non-dummy audit contexts unnecessarily. A stale audit_signals value similarly causes unnecessary signal auditing work. This can be reproduced for an inode watch with: mkdir /tmp/audit-n-rules-bench touch /tmp/audit-n-rules-bench/watched auditctl -w /tmp/audit-n-rules-bench/watched -p r \ -k audit_n_rules_bench rm /tmp/audit-n-rules-bench/watched rmdir /tmp/audit-n-rules-bench The rm updates the watch after its inode disappears, and the rmdir causes audit_remove_parent_watches() to remove the rule. For an audit tree, the kill_rules() path can be reproduced with: mkdir /tmp/audit-kill-rules auditctl -a always,exit -F arch=3Db64 \ -F dir=3D/tmp/audit-kill-rules -F perm=3Dr \ -k audit_kill_rules_test rmdir /tmp/audit-kill-rules In both cases, auditctl -l reports no rules after the directory is removed. Run the following before installing the rule and again after it has disappeared: audit_bench --iterations 10000000 --repetitions 10 For the inode watch, the same VM produced: no rules: median=3D38 mean=3D39 stddev=3D4 (10%) range=3D38..53 ns/op automatically removed, before this fix: median=3D55 mean=3D56 stddev=3D3 (5%) range=3D55..65 ns/op automatically removed, with this fix: median=3D38 mean=3D39 stddev=3D4 (10%) range=3D38..52 ns/op For the audit tree, it produced: no rules: median=3D38 mean=3D39 stddev=3D4 (9%) range=3D38..52 ns/op automatically removed, before this fix: median=3D59 mean=3D60 stddev=3D2 (3%) range=3D59..67 ns/op automatically removed, with this fix: median=3D38 mean=3D39 stddev=3D4 (9%) range=3D38..52 ns/op Reboot between the unpatched and patched tests because an already stale counter cannot be repaired by deleting rules which are no longer present. Factor the existing counter updates into common rule insertion and removal helpers and call the removal helper from every automatic removal path. All of these updates remain serialized by audit_filter_mutex. Fixes: 471a5c7c8391 ("[PATCH] introduce audit rules counter") Fixes: e54dc2431d74 ("[PATCH] audit signal recipients") Signed-off-by: Stanislav Kinsburskii --- kernel/audit.h | 5 +++ kernel/audit_tree.c | 1 + kernel/audit_watch.c | 2 ++ kernel/auditfilter.c | 86 +++++++++++++++++++++++++-----------------------= ---- 4 files changed, 50 insertions(+), 44 deletions(-) diff --git a/kernel/audit.h b/kernel/audit.h index 92d5e723d570..3176da464843 100644 --- a/kernel/audit.h +++ b/kernel/audit.h @@ -272,6 +272,9 @@ extern void audit_put_tty(struct tty_struct *tty); /* audit watch/mark/tree functions */ extern unsigned int audit_serial(void); #ifdef CONFIG_AUDITSYSCALL +void audit_rule_account(const struct audit_krule *rule); +void audit_rule_unaccount(const struct audit_krule *rule); + extern int auditsc_get_stamp(struct audit_context *ctx, struct audit_stamp *stamp); =20 @@ -315,6 +318,8 @@ extern void audit_filter_inodes(struct task_struct *tsk, struct audit_context *ctx); extern struct list_head *audit_killed_trees(void); #else /* CONFIG_AUDITSYSCALL */ +#define audit_rule_account(...) do { } while (0) +#define audit_rule_unaccount(...) do { } while (0) #define auditsc_get_stamp(c, s) 0 #define audit_put_watch(w) do { } while (0) #define audit_get_watch(w) do { } while (0) diff --git a/kernel/audit_tree.c b/kernel/audit_tree.c index 1ed19b775912..2d68d2ec2b2a 100644 --- a/kernel/audit_tree.c +++ b/kernel/audit_tree.c @@ -558,6 +558,7 @@ static void kill_rules(struct audit_context *context, s= truct audit_tree *tree) rule->tree =3D NULL; list_del_rcu(&entry->list); list_del(&entry->rule.list); + audit_rule_unaccount(rule); call_rcu(&entry->rcu, audit_free_rule_rcu); } } diff --git a/kernel/audit_watch.c b/kernel/audit_watch.c index 4ac8a91e9ba8..28fab822ca0c 100644 --- a/kernel/audit_watch.c +++ b/kernel/audit_watch.c @@ -284,6 +284,7 @@ static void audit_update_watch(struct audit_parent *par= ent, nentry =3D audit_dupe_rule(&oentry->rule, ctx); if (IS_ERR(nentry)) { list_del(&oentry->rule.list); + audit_rule_unaccount(r); audit_panic("error updating watch, removing"); } else { int h =3D audit_hash_ino(ino); @@ -336,6 +337,7 @@ static void audit_remove_parent_watches(struct audit_pa= rent *parent) list_del(&r->rlist); list_del(&r->list); list_del_rcu(&e->list); + audit_rule_unaccount(r); call_rcu(&e->rcu, audit_free_rule_rcu); } audit_remove_watch(w); diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c index 7f791afe5791..38a56278ae0b 100644 --- a/kernel/auditfilter.c +++ b/kernel/auditfilter.c @@ -196,7 +196,7 @@ int audit_match_class(int class, unsigned int syscall) } =20 #ifdef CONFIG_AUDITSYSCALL -static inline int audit_match_class_bits(int class, u32 *mask) +static inline int audit_match_class_bits(int class, const u32 *mask) { int i; =20 @@ -208,30 +208,62 @@ static inline int audit_match_class_bits(int class, u= 32 *mask) return 1; } =20 -static int audit_match_signal(struct audit_entry *entry) +static int audit_match_signal(const struct audit_krule *rule) { - struct audit_field *arch =3D entry->rule.arch_f; + struct audit_field *arch =3D rule->arch_f; =20 if (!arch) { /* When arch is unspecified, we must check both masks on biarch * as syscall number alone is ambiguous. */ return (audit_match_class_bits(AUDIT_CLASS_SIGNAL, - entry->rule.mask) && + rule->mask) && audit_match_class_bits(AUDIT_CLASS_SIGNAL_32, - entry->rule.mask)); + rule->mask)); } =20 switch (audit_classify_arch(arch->val)) { case 0: /* native */ return (audit_match_class_bits(AUDIT_CLASS_SIGNAL, - entry->rule.mask)); + rule->mask)); case 1: /* 32bit on biarch */ return (audit_match_class_bits(AUDIT_CLASS_SIGNAL_32, - entry->rule.mask)); + rule->mask)); default: return 1; } } + +static bool audit_rule_counts_syscalls(const struct audit_krule *rule) +{ + switch (rule->listnr) { + case AUDIT_FILTER_USER: + case AUDIT_FILTER_EXCLUDE: + case AUDIT_FILTER_FS: + return false; + default: + return true; + } +} + +void audit_rule_account(const struct audit_krule *rule) +{ + lockdep_assert_held(&audit_filter_mutex); + + if (audit_rule_counts_syscalls(rule)) + audit_n_rules++; + if (!audit_match_signal(rule)) + audit_signals++; +} + +void audit_rule_unaccount(const struct audit_krule *rule) +{ + lockdep_assert_held(&audit_filter_mutex); + + if (audit_rule_counts_syscalls(rule)) + audit_n_rules--; + if (!audit_match_signal(rule)) + audit_signals--; +} #endif =20 /* Common user-space to kernel rule translation. */ @@ -943,17 +975,6 @@ static inline int audit_add_rule(struct audit_entry *e= ntry) struct audit_tree *tree =3D entry->rule.tree; struct list_head *list; int err =3D 0; -#ifdef CONFIG_AUDITSYSCALL - int dont_count =3D 0; - - /* If any of these, don't count towards total */ - switch (entry->rule.listnr) { - case AUDIT_FILTER_USER: - case AUDIT_FILTER_EXCLUDE: - case AUDIT_FILTER_FS: - dont_count =3D 1; - } -#endif =20 mutex_lock(&audit_filter_mutex); e =3D audit_find_rule(entry, &list); @@ -1007,13 +1028,7 @@ static inline int audit_add_rule(struct audit_entry = *entry) &audit_rules_list[entry->rule.listnr]); list_add_tail_rcu(&entry->list, list); } -#ifdef CONFIG_AUDITSYSCALL - if (!dont_count) - audit_n_rules++; - - if (!audit_match_signal(entry)) - audit_signals++; -#endif + audit_rule_account(&entry->rule); mutex_unlock(&audit_filter_mutex); =20 return err; @@ -1026,17 +1041,6 @@ int audit_del_rule(struct audit_entry *entry) struct audit_tree *tree =3D entry->rule.tree; struct list_head *list; int ret =3D 0; -#ifdef CONFIG_AUDITSYSCALL - int dont_count =3D 0; - - /* If any of these, don't count towards total */ - switch (entry->rule.listnr) { - case AUDIT_FILTER_USER: - case AUDIT_FILTER_EXCLUDE: - case AUDIT_FILTER_FS: - dont_count =3D 1; - } -#endif =20 mutex_lock(&audit_filter_mutex); e =3D audit_find_rule(entry, &list); @@ -1058,14 +1062,7 @@ int audit_del_rule(struct audit_entry *entry) if (e->rule.exe) audit_remove_mark_rule(&e->rule); =20 -#ifdef CONFIG_AUDITSYSCALL - if (!dont_count) - audit_n_rules--; - - if (!audit_match_signal(entry)) - audit_signals--; -#endif - + audit_rule_unaccount(&e->rule); call_rcu(&e->rcu, audit_free_rule_rcu); =20 out: @@ -1429,6 +1426,7 @@ static int update_lsm_rule(struct audit_krule *r) list_del(&r->rlist); list_del_rcu(&entry->list); list_del(&r->list); + audit_rule_unaccount(r); } else { if (r->watch || r->tree) list_replace_init(&r->rlist, &nentry->rule.rlist); --=20 2.43.0 From nobody Tue Sep 29 13:57:44 2026 Received: from mail-pl1-f174.google.com (mail-pl1-f174.google.com [209.85.214.174]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 19FBF70808 for ; Fri, 7 Aug 2026 01:01:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786064513; cv=none; b=ZPeGwZhw51JM9pv0U7KJ/K5yQLPt4+v/wuPsQx2bJbznJLrURrVQwEiHR7aNAGhGVPoKZmbatq4bCxn2TX2nnCZcZox210sxjP7+9kOWcKmZrKMcjO42mbFsyuuBsHElDMXvcBVcdg4Xn9xAXz5H4sCSqOtMTiMqPzIU+sxnqfE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786064513; c=relaxed/simple; bh=e3tkQE+YfKX/7hZDZzcdQK5NFYbWS59J0EjRsKa+l6A=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=kPAVMnr0CkU13lv78kvHa0qODz8yWd/j7fHl33Ux8JPX+al5WEUknoiYndBVQN+48GFQJYGw1XoyumGQmLZtmRxH45Q+D9W5o3+vo2o/8Xyy1cbnXEqLSNf1gY/DVE3evDpVrUaEAso/TMb2960nrnjs9SaBCdtz+I3pmRUPebk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=h+eey9fw; arc=none smtp.client-ip=209.85.214.174 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="h+eey9fw" Received: by mail-pl1-f174.google.com with SMTP id d9443c01a7336-2cc97653887so33491255ad.1 for ; Thu, 06 Aug 2026 18:01:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786064510; x=1786669310; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=/GuggX/pXpjezOWD83mhX4lKeZfHohXlOyI+wXNFtAk=; b=h+eey9fwxi+V93EUzrsV4bTAv59EmiMKeTb+hauLbQMLlbxJFjQlo8NKDCaU1qhtK8 MxvWnTi2wRJr3awiP4BODR3nBZmBESGX43Qvx4VfYDn4b2ezhnHKhc2DSZlThEibiczk E9mppli2ooyF8w/iONpnZ/CoaC80tlcpLPLeJIuPBRfaD0LRbD0EDZsXyGRTvsZmItwp 0qCqLd/gYeQpD7ep7zivA9KaL0GZYgx3lm2rhKm4F/hVvPMtLSoYfLWwNwRahfLasrW1 PbZ07mxi+r4+tmvUrRvGAOsilMEsLvbjOCsJooxquO2Ff5KrEZ9oAmEjCrvjnnJ7fMB/ Oh/w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786064510; x=1786669310; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=/GuggX/pXpjezOWD83mhX4lKeZfHohXlOyI+wXNFtAk=; b=K1WpTIkWxjcx6AYRosk2ul78cBftgFTCEfOMUk5frGHh3Irv1wJ7Oixdx8ESzf2Q7C /x8gHq6mf/8w2+N0hGRX2WXsoruCI0aTL0qn2KoPJLcujo1IVbmWkZRhZuXTdTrzfZug CDY/2kptZlMoWeRlZ/Is712UtAeJZpElIqAaBG32SnmEmq097WF9gIsAa7F8MIGcfOob cejGa9ZRafVpgEc5Qsfo1xCQyhwRsaMtL0zb/PqkOI1n/GQrei8E8PgWFZpFAxqkhOoh p7yDDoExOvMUQ4PQLlcjU3l2d4mEZvd+m53nCmOvHxmYRieACGS9BjSZfVG3gUQlB7no fyWw== X-Forwarded-Encrypted: i=1; AHgh+RrPMsspWZDqSdPpRX6M9HYEU+0/klpF7zeelT4mdCGwsLzRTSuSyojyEuV4dxNbusFiRDM6gi4JNqFlryE=@vger.kernel.org X-Gm-Message-State: AOJu0YzT6QT3OmR3oPKKVQvSD9L59oCTw7R3g7zsAQAFoLwXQRunwkid ucVXwPc0s0cCUOiiFBQTkfSPWTiOj/09DEIdX5zYHdp0PvMqOfJRFxmD X-Gm-Gg: AR+sD116D2IwqvJE5jTNJE6LHA4ABP4dTjJDi3txyrrE1QGJt8UgMOXUc0JqhRK+Xy2 BSRNm6lTDldkFZ6bXvUGrpgJhDm93uaNNQWy2q694kn40URA8bD2keDC592YResuNsqsta3XLuK 7zmLRebpBomAVksa4ajeMV2Ze5c6d8wKPt6ESmGHsYt5G9qFxrBSfGIIDRR5I9yzoyWJtOVtbJz 7B+YP5/uK31LinRznfs25Vk5NRkVOJbKKsKmgBCbBhI6A3e6re52/y/c/ZiO74B56JzhoMO0DCj RmeitQ6g1O3Scw6rTFahFnhhjGYrf7L0pIpZGWJBdLuNKHAAgl5qYPT2ayBHrBlJceuSIDllrPc O3pcUtKWN1iHCqwjwsmvOxV/6fhQDjhUaWPERlIhamQPULnm0Xp04SoiOQPF8VeEzYRkOg/ziWb uQWHUpaVtL6OSEok6xbG9d327GlvX67YpHbujavtIG7y40AHyLffOfSXv8jrzbQMhvjYDwWMrxK /qt7Phl0Fz2UfF3d6F3I7V7S621+1qY0Hk= X-Received: by 2002:a17:903:3903:b0:2ca:6c8:abd8 with SMTP id d9443c01a7336-2d0ca751829mr238604445ad.12.1786064510270; Thu, 06 Aug 2026 18:01:50 -0700 (PDT) Received: from [192.168.0.160] (c-98-225-44-182.hsd1.wa.comcast.net. [98.225.44.182]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2d14d7684f8sm1527725ad.33.2026.08.06.18.01.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 06 Aug 2026 18:01:49 -0700 (PDT) From: Stanislav Kinsburskii Date: Thu, 06 Aug 2026 18:01:21 -0700 Subject: [PATCH 3/3] audit: Skip exit filtering for syscalls without rules Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260806-audit-v1-3-ddd0d94ff0b6@gmail.com> References: <20260806-audit-v1-0-ddd0d94ff0b6@gmail.com> In-Reply-To: <20260806-audit-v1-0-ddd0d94ff0b6@gmail.com> To: Shuah Khan , Paul Moore , Eric Paris , Al Viro , Amy Griffis Cc: Stanislav Kinsburskii , Frank Hofmann , Noah Orlando , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, audit@vger.kernel.org X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1786064505; l=6780; i=skinsburskii@gmail.com; s=20260722; h=from:subject:message-id; bh=e3tkQE+YfKX/7hZDZzcdQK5NFYbWS59J0EjRsKa+l6A=; b=ZMDRwz8bBqyYWgD3Q0qo1HzxvjISmiQACD7mFn+6I5wORtD8tvXa3RCk0xAGAEnnvk8T1PzpM +mRyyD6NR7eDDTiB5KRKHplILEfKVW5OGb/MzUHPSpz67XvgfkvIORj X-Developer-Key: i=skinsburskii@gmail.com; a=ed25519; pk=bDpriHBYgeTdkIDweZDCemxsU93neJBOCn3YLIuJpnE= Audit walks every exit filter rule for each audited syscall, even when no rule contains the current syscall number. Policies with many unrelated rules therefore add linear overhead to otherwise uninteresting syscalls. Maintain a reference count for each syscall bit present in exit filter rules and derive an aggregate interest mask. Update the mask through the centralized rule lifecycle helpers, which cover explicit and automatic rule removal. Use the mask as a lockless rejection test before entering the exit filter RCU traversal. The mask is architecture-independent. Syscall number overlap between architectures can cause an unnecessary scan but cannot suppress a match. The aggregate bit must be set before list_add_rcu() publishes a new rule. Otherwise, a reader could observe the rule after publication while the aggregate mask still rejects its syscall. Move audit_rule_account() before the list insertion to provide this ordering. Rule removal already uses the inverse safe ordering: it unlinks the rule before clearing the aggregate bit, so a concurrent reader can only perform an unnecessary scan, not miss a rule. To measure the effect, install increasing numbers of distinct statx rules in a disposable VM and benchmark the unrelated getpid syscall after each set is installed: for nr_rules in 1 32 128 256; do auditctl -D for uid in $(seq 1 $nr_rules); do auditctl -a always,exit -F arch=3Db64 -S statx \ -F uid=3D$uid done audit_bench done Without this change, the same unpinned VM produced: 1 rule: median=3D55 ns/op 32 rules: median=3D71 ns/op 128 rules: median=3D428 ns/op 256 rules: median=3D791 ns/op With this change, it produced: 1 rule: median=3D55 ns/op 32 rules: median=3D55 ns/op 128 rules: median=3D55 ns/op 256 rules: median=3D55 ns/op Signed-off-by: Stanislav Kinsburskii --- kernel/audit.h | 2 ++ kernel/auditfilter.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++= +++- kernel/auditsc.c | 13 ++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/kernel/audit.h b/kernel/audit.h index 3176da464843..afcbdecc917c 100644 --- a/kernel/audit.h +++ b/kernel/audit.h @@ -272,6 +272,8 @@ extern void audit_put_tty(struct tty_struct *tty); /* audit watch/mark/tree functions */ extern unsigned int audit_serial(void); #ifdef CONFIG_AUDITSYSCALL +extern u32 audit_exit_filter_mask[AUDIT_BITMASK_SIZE]; + void audit_rule_account(const struct audit_krule *rule); void audit_rule_unaccount(const struct audit_krule *rule); =20 diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c index 38a56278ae0b..55ab9d05fafd 100644 --- a/kernel/auditfilter.c +++ b/kernel/auditfilter.c @@ -196,6 +196,54 @@ int audit_match_class(int class, unsigned int syscall) } =20 #ifdef CONFIG_AUDITSYSCALL +/* + * The mask provides a quick rejection test for syscalls which cannot matc= h an + * exit filter rule. The counters and mask updates are protected by + * audit_filter_mutex; the mask is read locklessly in the syscall exit pat= h. + * + * The mask is intentionally architecture-independent. Syscall number + * overlap between architectures can only cause an unnecessary filter scan. + */ +u32 audit_exit_filter_mask[AUDIT_BITMASK_SIZE] __read_mostly; +static unsigned int audit_exit_filter_count[AUDIT_BITMASK_SIZE * 32]; + +static void audit_exit_mask_update(const struct audit_krule *rule, bool ad= d) +{ + unsigned int bit, index, word; + u32 mask, rule_mask; + + lockdep_assert_held(&audit_filter_mutex); + + for (word =3D 0; word < AUDIT_BITMASK_SIZE; word++) { + mask =3D READ_ONCE(audit_exit_filter_mask[word]); + rule_mask =3D rule->mask[word]; + if (!rule_mask) + continue; + while (rule_mask) { + bit =3D __ffs(rule_mask); + index =3D word * 32 + bit; + if (add) { + if (!audit_exit_filter_count[index]++) + mask |=3D BIT(bit); + } else if (!--audit_exit_filter_count[index]) { + mask &=3D ~BIT(bit); + } + rule_mask &=3D ~BIT(bit); + } + WRITE_ONCE(audit_exit_filter_mask[word], mask); + } +} + +static void audit_exit_mask_add(const struct audit_krule *rule) +{ + audit_exit_mask_update(rule, true); +} + +static void audit_exit_mask_remove(const struct audit_krule *rule) +{ + audit_exit_mask_update(rule, false); +} + static inline int audit_match_class_bits(int class, const u32 *mask) { int i; @@ -249,6 +297,9 @@ void audit_rule_account(const struct audit_krule *rule) { lockdep_assert_held(&audit_filter_mutex); =20 + if (rule->listnr =3D=3D AUDIT_FILTER_EXIT) + audit_exit_mask_add(rule); + if (audit_rule_counts_syscalls(rule)) audit_n_rules++; if (!audit_match_signal(rule)) @@ -259,6 +310,9 @@ void audit_rule_unaccount(const struct audit_krule *rul= e) { lockdep_assert_held(&audit_filter_mutex); =20 + if (rule->listnr =3D=3D AUDIT_FILTER_EXIT) + audit_exit_mask_remove(rule); + if (audit_rule_counts_syscalls(rule)) audit_n_rules--; if (!audit_match_signal(rule)) @@ -1018,6 +1072,7 @@ static inline int audit_add_rule(struct audit_entry *= entry) entry->rule.prio =3D --prio_low; } =20 + audit_rule_account(&entry->rule); if (entry->rule.flags & AUDIT_FILTER_PREPEND) { list_add(&entry->rule.list, &audit_rules_list[entry->rule.listnr]); @@ -1028,7 +1083,6 @@ static inline int audit_add_rule(struct audit_entry *= entry) &audit_rules_list[entry->rule.listnr]); list_add_tail_rcu(&entry->list, list); } - audit_rule_account(&entry->rule); mutex_unlock(&audit_filter_mutex); =20 return err; diff --git a/kernel/auditsc.c b/kernel/auditsc.c index 2b9ce0b52511..ff1809df63df 100644 --- a/kernel/auditsc.c +++ b/kernel/auditsc.c @@ -861,6 +861,16 @@ static void audit_filter_uring(struct task_struct *tsk, rcu_read_unlock(); } =20 +static inline bool audit_exit_filter_may_match(unsigned long syscall) +{ + u32 word; + + if (syscall >=3D AUDIT_BITMASK_SIZE * 32) + return false; + word =3D AUDIT_WORD(syscall); + return READ_ONCE(audit_exit_filter_mask[word]) & AUDIT_BIT(syscall); +} + /* At syscall exit time, this filter is called if the audit_state is * not low enough that auditing cannot take place, but is also not * high enough that we already know we have to write an audit record @@ -872,6 +882,9 @@ static void audit_filter_syscall(struct task_struct *ts= k, if (auditd_test_task(tsk)) return; =20 + if (!audit_exit_filter_may_match(ctx->major)) + return; + rcu_read_lock(); __audit_filter_op(tsk, ctx, &audit_filter_list[AUDIT_FILTER_EXIT], NULL, ctx->major); --=20 2.43.0