From: Josef Bacik <josef@toxicpanda.com>
To: Christian Brauner <brauner@kernel.org>,
Paul Moore <paul@paul-moore.com>
Cc: Serge Hallyn <sergeh@kernel.org>,
linux-fsdevel@vger.kernel.org,
linux-security-module@vger.kernel.org,
Josef Bacik <josef@toxicpanda.com>
Subject: [PATCH 02/13] cred: add KUnit tests for the id rules
Date: Sun, 18 Oct 2026 17:31:44 +0000 [thread overview]
Message-ID: <20261018-b4-cred-ids-v1-2-7bdf2db4ea9f@toxicpanda.com> (raw)
In-Reply-To: <20261018-b4-cred-ids-v1-0-7bdf2db4ea9f@toxicpanda.com>
Hold each of the new functions against the expression it is about to
replace, word for word as it stands where it is used, for every
combination of a few ids, 0 among them: 729 combinations of three uids
each for subject and object, and 4096 of the twelve ids that
cred_ids_allow_control() can look at.
That the conversions which follow change nothing then does not have to be
taken on trust.
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
MAINTAINERS | 1 +
kernel/Makefile | 1 +
kernel/cred_kunit.c | 218 ++++++++++++++++++++++++++++++++++++++++++++++++++++
lib/Kconfig.debug | 13 ++++
4 files changed, 233 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 62744835f86f..a0eaeed0319d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6941,6 +6941,7 @@ S: Supported
T: git https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/lsm.git
F: include/linux/cred.h
F: kernel/cred.c
+F: kernel/cred_kunit.c
F: rust/kernel/cred.rs
F: Documentation/security/credentials.rst
diff --git a/kernel/Makefile b/kernel/Makefile
index 2a64282749b8..8f7e2fb7b389 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -140,6 +140,7 @@ obj-$(CONFIG_RSEQ) += rseq.o
obj-$(CONFIG_WATCH_QUEUE) += watch_queue.o
obj-$(CONFIG_RESOURCE_KUNIT_TEST) += resource_kunit.o
+obj-$(CONFIG_CRED_KUNIT_TEST) += cred_kunit.o
obj-$(CONFIG_SYSCTL_KUNIT_TEST) += sysctl-test.o
obj-$(CONFIG_USER_NS_MAP_KUNIT_TEST) += tests/user_ns_map_kunit.o
diff --git a/kernel/cred_kunit.c b/kernel/cred_kunit.c
new file mode 100644
index 000000000000..b3a54e8c565f
--- /dev/null
+++ b/kernel/cred_kunit.c
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit tests for the rules by which the ids of one credential allow it to act
+ * on another, see "One task acting on another" in kernel/cred.c.
+ *
+ * Each rule is held against the expression it replaced, word for word, as it
+ * stood where it was used, for every combination of a few ids.
+ */
+#include <kunit/test.h>
+#include <linux/cred.h>
+#include <linux/uidgid.h>
+
+/* Root, since two of the rules know it, and two others. */
+static const uid_t ids[] = { 0, 1000, 1001 };
+#define NR_IDS ARRAY_SIZE(ids)
+
+static void set_uids(struct cred *cred, unsigned int *n)
+{
+ cred->uid = KUIDT_INIT(ids[*n % NR_IDS]);
+ *n /= NR_IDS;
+ cred->euid = KUIDT_INIT(ids[*n % NR_IDS]);
+ *n /= NR_IDS;
+ cred->suid = KUIDT_INIT(ids[*n % NR_IDS]);
+ *n /= NR_IDS;
+}
+
+/* NR_IDS to the power of six: three uids each for the subject and the object. */
+#define NR_UID_COMBINATIONS (NR_IDS * NR_IDS * NR_IDS * NR_IDS * NR_IDS * NR_IDS)
+
+static void uid_combination(unsigned int n, struct cred *cred, struct cred *tcred)
+{
+ memset(cred, 0, sizeof(*cred));
+ memset(tcred, 0, sizeof(*tcred));
+ set_uids(cred, &n);
+ set_uids(tcred, &n);
+}
+
+/* kill_ok_by_cred() and kill_as_cred_perm() */
+static bool old_signal(const struct cred *cred, const struct cred *tcred)
+{
+ return uid_eq(cred->euid, tcred->suid) ||
+ uid_eq(cred->euid, tcred->uid) ||
+ uid_eq(cred->uid, tcred->suid) ||
+ uid_eq(cred->uid, tcred->uid);
+}
+
+/* sigio_perm(), which had the uid and the euid of the owner in @fown */
+static bool old_sigio(const struct cred *fown, const struct cred *cred)
+{
+ return (uid_eq(fown->euid, GLOBAL_ROOT_UID) ||
+ uid_eq(fown->euid, cred->suid) || uid_eq(fown->euid, cred->uid) ||
+ uid_eq(fown->uid, cred->suid) || uid_eq(fown->uid, cred->uid));
+}
+
+/* check_same_owner(), twice */
+static bool old_sched(const struct cred *cred, const struct cred *pcred)
+{
+ return (uid_eq(cred->euid, pcred->euid) ||
+ uid_eq(cred->euid, pcred->uid));
+}
+
+/* set_one_prio_perm() */
+static bool old_nice(const struct cred *cred, const struct cred *pcred)
+{
+ if (uid_eq(pcred->uid, cred->euid) ||
+ uid_eq(pcred->euid, cred->euid))
+ return true;
+ return false;
+}
+
+/* set_task_ioprio() */
+static bool old_ioprio(const struct cred *cred, const struct cred *tcred)
+{
+ if (!uid_eq(tcred->uid, cred->euid) &&
+ !uid_eq(tcred->uid, cred->uid))
+ return false;
+ return true;
+}
+
+/* __cgroup1_procs_write() and rdtgroup_task_write_permission() */
+static bool old_move(const struct cred *cred, const struct cred *tcred)
+{
+ if (!uid_eq(cred->euid, GLOBAL_ROOT_UID) &&
+ !uid_eq(cred->euid, tcred->uid) &&
+ !uid_eq(cred->euid, tcred->suid))
+ return false;
+ return true;
+}
+
+static void cred_ids_uid_rules(struct kunit *test)
+{
+ struct cred *cred, *tcred;
+ unsigned int n;
+
+ cred = kunit_kzalloc(test, sizeof(*cred), GFP_KERNEL);
+ tcred = kunit_kzalloc(test, sizeof(*tcred), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, cred);
+ KUNIT_ASSERT_NOT_NULL(test, tcred);
+
+ for (n = 0; n < NR_UID_COMBINATIONS; n++) {
+ uid_combination(n, cred, tcred);
+
+ KUNIT_EXPECT_EQ_MSG(test, cred_ids_allow_signal(cred, tcred),
+ old_signal(cred, tcred), "combination %u", n);
+ KUNIT_EXPECT_EQ_MSG(test, cred_ids_allow_sigio(cred, tcred),
+ old_sigio(cred, tcred), "combination %u", n);
+ KUNIT_EXPECT_EQ_MSG(test, cred_ids_allow_sched(cred, tcred),
+ old_sched(cred, tcred), "combination %u", n);
+ KUNIT_EXPECT_EQ_MSG(test, cred_ids_allow_sched(cred, tcred),
+ old_nice(cred, tcred), "combination %u", n);
+ KUNIT_EXPECT_EQ_MSG(test, cred_ids_allow_ioprio(cred, tcred),
+ old_ioprio(cred, tcred), "combination %u", n);
+ KUNIT_EXPECT_EQ_MSG(test, cred_ids_allow_move(cred, tcred),
+ old_move(cred, tcred), "combination %u", n);
+ }
+}
+
+/* __ptrace_may_access(), with the ids it picked by its mode */
+static bool old_ptrace(kuid_t caller_uid, kgid_t caller_gid,
+ const struct cred *tcred)
+{
+ if (uid_eq(caller_uid, tcred->euid) &&
+ uid_eq(caller_uid, tcred->suid) &&
+ uid_eq(caller_uid, tcred->uid) &&
+ gid_eq(caller_gid, tcred->egid) &&
+ gid_eq(caller_gid, tcred->sgid) &&
+ gid_eq(caller_gid, tcred->gid))
+ return true;
+ return false;
+}
+
+/* check_prlimit_permission() */
+static bool old_prlimit(const struct cred *cred, const struct cred *tcred)
+{
+ return (uid_eq(cred->uid, tcred->euid) &&
+ uid_eq(cred->uid, tcred->suid) &&
+ uid_eq(cred->uid, tcred->uid) &&
+ gid_eq(cred->gid, tcred->egid) &&
+ gid_eq(cred->gid, tcred->sgid) &&
+ gid_eq(cred->gid, tcred->gid));
+}
+
+/* keyctl_session_to_parent() */
+static bool old_session_to_parent(const struct cred *mycred,
+ const struct cred *pcred)
+{
+ if (!uid_eq(pcred->uid, mycred->euid) ||
+ !uid_eq(pcred->euid, mycred->euid) ||
+ !uid_eq(pcred->suid, mycred->euid) ||
+ !gid_eq(pcred->gid, mycred->egid) ||
+ !gid_eq(pcred->egid, mycred->egid) ||
+ !gid_eq(pcred->sgid, mycred->egid))
+ return false;
+ return true;
+}
+
+/*
+ * Two values for each of twelve ids: uid, euid, fsuid and their gids for the
+ * subject, uid, euid, suid and their gids for the object.
+ */
+#define NR_CONTROL_COMBINATIONS (1U << 12)
+
+static void cred_ids_control_rule(struct kunit *test)
+{
+ struct cred *cred, *tcred;
+ unsigned int n;
+
+ cred = kunit_kzalloc(test, sizeof(*cred), GFP_KERNEL);
+ tcred = kunit_kzalloc(test, sizeof(*tcred), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, cred);
+ KUNIT_ASSERT_NOT_NULL(test, tcred);
+
+ for (n = 0; n < NR_CONTROL_COMBINATIONS; n++) {
+ cred->uid = KUIDT_INIT(1000 + !!(n & BIT(0)));
+ cred->euid = KUIDT_INIT(1000 + !!(n & BIT(1)));
+ cred->fsuid = KUIDT_INIT(1000 + !!(n & BIT(2)));
+ cred->gid = KGIDT_INIT(1000 + !!(n & BIT(3)));
+ cred->egid = KGIDT_INIT(1000 + !!(n & BIT(4)));
+ cred->fsgid = KGIDT_INIT(1000 + !!(n & BIT(5)));
+ tcred->uid = KUIDT_INIT(1000 + !!(n & BIT(6)));
+ tcred->euid = KUIDT_INIT(1000 + !!(n & BIT(7)));
+ tcred->suid = KUIDT_INIT(1000 + !!(n & BIT(8)));
+ tcred->gid = KGIDT_INIT(1000 + !!(n & BIT(9)));
+ tcred->egid = KGIDT_INIT(1000 + !!(n & BIT(10)));
+ tcred->sgid = KGIDT_INIT(1000 + !!(n & BIT(11)));
+
+ KUNIT_EXPECT_EQ_MSG(test,
+ cred_ids_allow_control(cred, CRED_IDS_FS, tcred),
+ old_ptrace(cred->fsuid, cred->fsgid, tcred),
+ "combination %u", n);
+ KUNIT_EXPECT_EQ_MSG(test,
+ cred_ids_allow_control(cred, CRED_IDS_REAL, tcred),
+ old_ptrace(cred->uid, cred->gid, tcred),
+ "combination %u", n);
+ KUNIT_EXPECT_EQ_MSG(test,
+ cred_ids_allow_control(cred, CRED_IDS_REAL, tcred),
+ old_prlimit(cred, tcred), "combination %u", n);
+ KUNIT_EXPECT_EQ_MSG(test,
+ cred_ids_allow_control(cred, CRED_IDS_EFFECTIVE, tcred),
+ old_session_to_parent(cred, tcred),
+ "combination %u", n);
+ }
+}
+
+static struct kunit_case cred_ids_cases[] = {
+ KUNIT_CASE(cred_ids_uid_rules),
+ KUNIT_CASE(cred_ids_control_rule),
+ {}
+};
+
+static struct kunit_suite cred_ids_suite = {
+ .name = "cred_ids",
+ .test_cases = cred_ids_cases,
+};
+kunit_test_suite(cred_ids_suite);
+
+MODULE_DESCRIPTION("KUnit tests for the id rules of credentials");
+MODULE_LICENSE("GPL");
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 6b24a2c89b84..778d8ea50bda 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2772,6 +2772,19 @@ config HASH_KUNIT_TEST
This is intended to help people writing architecture-specific
optimized versions. If unsure, say N.
+config CRED_KUNIT_TEST
+ bool "KUnit test for the id rules of credentials" if !KUNIT_ALL_TESTS
+ depends on KUNIT=y
+ default KUNIT_ALL_TESTS
+ help
+ This builds the unit test for the rules by which the ids of one
+ credential allow it to act on another, cred_ids_allow_signal() and
+ friends. Each rule is held against the expression it replaced.
+ For more information on KUnit and unit tests in general please refer
+ to the KUnit documentation in Documentation/dev-tools/kunit/.
+
+ If unsure, say N.
+
config RESOURCE_KUNIT_TEST
tristate "KUnit test for resource API" if !KUNIT_ALL_TESTS
depends on KUNIT
--
2.55.0
next prev parent reply other threads:[~2026-10-18 17:32 UTC|newest]
Thread overview: 27+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-18 17:31 [PATCH 00/13] cred: gather the "same uid" checks between tasks in one place Josef Bacik
2026-10-18 17:31 ` [PATCH 01/13] cred: gather the rules for acting on a task by uid Josef Bacik
2026-10-18 17:35 ` sashiko-bot
2026-10-18 17:31 ` Josef Bacik [this message]
2026-10-18 17:34 ` [PATCH 02/13] cred: add KUnit tests for the id rules sashiko-bot
2026-10-18 17:31 ` [PATCH 03/13] signal: use cred_ids_allow_signal() Josef Bacik
2026-10-18 17:34 ` sashiko-bot
2026-10-18 17:31 ` [PATCH 04/13] fcntl: use cred_ids_allow_sigio() Josef Bacik
2026-10-18 17:37 ` sashiko-bot
2026-10-18 17:31 ` [PATCH 05/13] sched: use cred_ids_allow_sched() Josef Bacik
2026-10-18 17:34 ` sashiko-bot
2026-10-18 17:31 ` [PATCH 06/13] MIPS: " Josef Bacik
2026-10-18 17:34 ` sashiko-bot
2026-10-18 17:31 ` [PATCH 07/13] sys: use cred_ids_allow_sched() for setpriority() Josef Bacik
2026-10-18 17:34 ` sashiko-bot
2026-10-18 17:31 ` [PATCH 08/13] sys: use cred_ids_allow_control() for prlimit() Josef Bacik
2026-10-18 17:34 ` sashiko-bot
2026-10-18 17:31 ` [PATCH 09/13] block: use cred_ids_allow_ioprio() Josef Bacik
2026-10-18 17:34 ` sashiko-bot
2026-10-18 17:31 ` [PATCH 10/13] cgroup/v1: use cred_ids_allow_move() Josef Bacik
2026-10-18 17:34 ` sashiko-bot
2026-10-18 17:31 ` [PATCH 11/13] fs/resctrl: " Josef Bacik
2026-10-18 17:34 ` sashiko-bot
2026-10-18 17:31 ` [PATCH 12/13] ptrace: use cred_ids_allow_control() Josef Bacik
2026-10-18 17:34 ` sashiko-bot
2026-10-18 17:31 ` [PATCH 13/13] keys: " Josef Bacik
2026-10-18 17:34 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261018-b4-cred-ids-v1-2-7bdf2db4ea9f@toxicpanda.com \
--to=josef@toxicpanda.com \
--cc=brauner@kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-security-module@vger.kernel.org \
--cc=paul@paul-moore.com \
--cc=sergeh@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox