* [PATCH v5 08/34] lsm: replace the name field with a pointer to the lsm_id struct
From: Paul Moore @ 2025-10-17 20:24 UTC (permalink / raw)
To: linux-security-module, linux-integrity, selinux
Cc: John Johansen, Mimi Zohar, Roberto Sassu, Fan Wu,
Mickaël Salaün, Günther Noack, Kees Cook,
Micah Morton, Casey Schaufler, Tetsuo Handa, Nicolas Bouchinet,
Xiu Jianfeng
In-Reply-To: <20251017202456.484010-36-paul@paul-moore.com>
Reduce the duplication between the lsm_id struct and the DEFINE_LSM()
definition by linking the lsm_id struct directly into the individual
LSM's DEFINE_LSM() instance.
Linking the lsm_id into the LSM definition also allows us to simplify
the security_add_hooks() function by removing the code which populates
the lsm_idlist[] array and moving it into the normal LSM startup code
where the LSM list is parsed and the individual LSMs are enabled,
making for a cleaner implementation with less overhead at boot.
Reviewed-by: Kees Cook <kees@kernel.org>
Reviewed-by: John Johansen <john.johansen@canonical.com>
Reviewed-by: Casey Schaufler <casey@schaufler-ca.com>
Reviewed-by: Mimi Zohar <zohar@linux.ibm.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
---
include/linux/lsm_hooks.h | 2 +-
security/apparmor/lsm.c | 2 +-
security/bpf/hooks.c | 2 +-
security/commoncap.c | 2 +-
security/integrity/evm/evm_main.c | 2 +-
security/integrity/ima/ima_main.c | 2 +-
security/ipe/ipe.c | 2 +-
security/landlock/setup.c | 2 +-
security/loadpin/loadpin.c | 2 +-
security/lockdown/lockdown.c | 2 +-
security/lsm_init.c | 45 +++++++++++++------------------
security/safesetid/lsm.c | 2 +-
security/selinux/hooks.c | 2 +-
security/smack/smack_lsm.c | 2 +-
security/tomoyo/tomoyo.c | 2 +-
security/yama/yama_lsm.c | 2 +-
16 files changed, 33 insertions(+), 42 deletions(-)
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index 0112926ed923..7343dd60b1d5 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -152,7 +152,7 @@ enum lsm_order {
};
struct lsm_info {
- const char *name; /* Required. */
+ const struct lsm_id *id;
enum lsm_order order; /* Optional: default is LSM_ORDER_MUTABLE */
unsigned long flags; /* Optional: flags describing LSM */
int *enabled; /* Optional: controlled by CONFIG_LSM */
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index b3f7a3258a2c..f6798144234b 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -2555,7 +2555,7 @@ static int __init apparmor_init(void)
}
DEFINE_LSM(apparmor) = {
- .name = "apparmor",
+ .id = &apparmor_lsmid,
.flags = LSM_FLAG_LEGACY_MAJOR | LSM_FLAG_EXCLUSIVE,
.enabled = &apparmor_enabled,
.blobs = &apparmor_blob_sizes,
diff --git a/security/bpf/hooks.c b/security/bpf/hooks.c
index db759025abe1..40efde233f3a 100644
--- a/security/bpf/hooks.c
+++ b/security/bpf/hooks.c
@@ -33,7 +33,7 @@ struct lsm_blob_sizes bpf_lsm_blob_sizes __ro_after_init = {
};
DEFINE_LSM(bpf) = {
- .name = "bpf",
+ .id = &bpf_lsmid,
.init = bpf_lsm_init,
.blobs = &bpf_lsm_blob_sizes
};
diff --git a/security/commoncap.c b/security/commoncap.c
index 6bd4adeb4795..b50479bd0286 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -1505,7 +1505,7 @@ static int __init capability_init(void)
}
DEFINE_LSM(capability) = {
- .name = "capability",
+ .id = &capability_lsmid,
.order = LSM_ORDER_FIRST,
.init = capability_init,
};
diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c
index 0add782e73ba..db8e324ed4e6 100644
--- a/security/integrity/evm/evm_main.c
+++ b/security/integrity/evm/evm_main.c
@@ -1175,7 +1175,7 @@ struct lsm_blob_sizes evm_blob_sizes __ro_after_init = {
};
DEFINE_LSM(evm) = {
- .name = "evm",
+ .id = &evm_lsmid,
.init = init_evm_lsm,
.order = LSM_ORDER_LAST,
.blobs = &evm_blob_sizes,
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index cdd225f65a62..eade8e1e3cb1 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -1279,7 +1279,7 @@ struct lsm_blob_sizes ima_blob_sizes __ro_after_init = {
};
DEFINE_LSM(ima) = {
- .name = "ima",
+ .id = &ima_lsmid,
.init = init_ima_lsm,
.order = LSM_ORDER_LAST,
.blobs = &ima_blob_sizes,
diff --git a/security/ipe/ipe.c b/security/ipe/ipe.c
index 4317134cb0da..2426441181dc 100644
--- a/security/ipe/ipe.c
+++ b/security/ipe/ipe.c
@@ -92,7 +92,7 @@ static int __init ipe_init(void)
}
DEFINE_LSM(ipe) = {
- .name = "ipe",
+ .id = &ipe_lsmid,
.init = ipe_init,
.blobs = &ipe_blobs,
};
diff --git a/security/landlock/setup.c b/security/landlock/setup.c
index bd53c7a56ab9..47dac1736f10 100644
--- a/security/landlock/setup.c
+++ b/security/landlock/setup.c
@@ -75,7 +75,7 @@ static int __init landlock_init(void)
}
DEFINE_LSM(LANDLOCK_NAME) = {
- .name = LANDLOCK_NAME,
+ .id = &landlock_lsmid,
.init = landlock_init,
.blobs = &landlock_blob_sizes,
};
diff --git a/security/loadpin/loadpin.c b/security/loadpin/loadpin.c
index 68252452b66c..b9ddf05c5c16 100644
--- a/security/loadpin/loadpin.c
+++ b/security/loadpin/loadpin.c
@@ -271,7 +271,7 @@ static int __init loadpin_init(void)
}
DEFINE_LSM(loadpin) = {
- .name = "loadpin",
+ .id = &loadpin_lsmid,
.init = loadpin_init,
};
diff --git a/security/lockdown/lockdown.c b/security/lockdown/lockdown.c
index cf83afa1d879..4813f168ff93 100644
--- a/security/lockdown/lockdown.c
+++ b/security/lockdown/lockdown.c
@@ -168,6 +168,6 @@ DEFINE_EARLY_LSM(lockdown) = {
#else
DEFINE_LSM(lockdown) = {
#endif
- .name = "lockdown",
+ .id = &lockdown_lsmid,
.init = lockdown_lsm_init,
};
diff --git a/security/lsm_init.c b/security/lsm_init.c
index 9249d5f37ae9..692d61a2ea10 100644
--- a/security/lsm_init.c
+++ b/security/lsm_init.c
@@ -127,9 +127,10 @@ static void __init append_ordered_lsm(struct lsm_info *lsm, const char *from)
/* Enable this LSM, if it is not already set. */
if (!lsm->enabled)
lsm->enabled = &lsm_enabled_true;
- ordered_lsms[last_lsm++] = lsm;
+ ordered_lsms[last_lsm] = lsm;
+ lsm_idlist[last_lsm++] = lsm->id;
- init_debug("%s ordered: %s (%s)\n", from, lsm->name,
+ init_debug("%s ordered: %s (%s)\n", from, lsm->id->name,
is_enabled(lsm) ? "enabled" : "disabled");
}
@@ -157,7 +158,7 @@ static void __init lsm_prepare(struct lsm_info *lsm)
set_enabled(lsm, false);
return;
} else if ((lsm->flags & LSM_FLAG_EXCLUSIVE) && exclusive) {
- init_debug("exclusive disabled: %s\n", lsm->name);
+ init_debug("exclusive disabled: %s\n", lsm->id->name);
set_enabled(lsm, false);
return;
}
@@ -165,7 +166,7 @@ static void __init lsm_prepare(struct lsm_info *lsm)
/* Mark the LSM as enabled. */
set_enabled(lsm, true);
if ((lsm->flags & LSM_FLAG_EXCLUSIVE) && !exclusive) {
- init_debug("exclusive chosen: %s\n", lsm->name);
+ init_debug("exclusive chosen: %s\n", lsm->id->name);
exclusive = lsm;
}
@@ -200,9 +201,9 @@ static void __init initialize_lsm(struct lsm_info *lsm)
if (is_enabled(lsm)) {
int ret;
- init_debug("initializing %s\n", lsm->name);
+ init_debug("initializing %s\n", lsm->id->name);
ret = lsm->init();
- WARN(ret, "%s failed to initialize: %d\n", lsm->name, ret);
+ WARN(ret, "%s failed to initialize: %d\n", lsm->id->name, ret);
}
}
@@ -236,10 +237,10 @@ static void __init ordered_lsm_parse(const char *order, const char *origin)
*/
lsm_for_each_raw(major) {
if ((major->flags & LSM_FLAG_LEGACY_MAJOR) &&
- strcmp(major->name, chosen_major_lsm) != 0) {
+ strcmp(major->id->name, chosen_major_lsm) != 0) {
set_enabled(major, false);
init_debug("security=%s disabled: %s (only one legacy major LSM)\n",
- chosen_major_lsm, major->name);
+ chosen_major_lsm, major->id->name);
}
}
}
@@ -251,7 +252,7 @@ static void __init ordered_lsm_parse(const char *order, const char *origin)
bool found = false;
lsm_for_each_raw(lsm) {
- if (strcmp(lsm->name, name) == 0) {
+ if (strcmp(lsm->id->name, name) == 0) {
if (lsm->order == LSM_ORDER_MUTABLE)
append_ordered_lsm(lsm, origin);
found = true;
@@ -268,7 +269,7 @@ static void __init ordered_lsm_parse(const char *order, const char *origin)
lsm_for_each_raw(lsm) {
if (exists_ordered_lsm(lsm))
continue;
- if (strcmp(lsm->name, chosen_major_lsm) == 0)
+ if (strcmp(lsm->id->name, chosen_major_lsm) == 0)
append_ordered_lsm(lsm, "security=");
}
}
@@ -285,7 +286,7 @@ static void __init ordered_lsm_parse(const char *order, const char *origin)
continue;
set_enabled(lsm, false);
init_debug("%s skipped: %s (not in requested order)\n",
- origin, lsm->name);
+ origin, lsm->id->name);
}
kfree(sep);
@@ -317,11 +318,13 @@ static void __init lsm_init_ordered(void)
pr_info("initializing lsm=");
lsm_early_for_each_raw(early) {
if (is_enabled(early))
- pr_cont("%s%s", first++ == 0 ? "" : ",", early->name);
+ pr_cont("%s%s",
+ first++ == 0 ? "" : ",", early->id->name);
}
lsm_order_for_each(lsm) {
if (is_enabled(*lsm))
- pr_cont("%s%s", first++ == 0 ? "" : ",", (*lsm)->name);
+ pr_cont("%s%s",
+ first++ == 0 ? "" : ",", (*lsm)->id->name);
}
pr_cont("\n");
@@ -432,18 +435,6 @@ void __init security_add_hooks(struct security_hook_list *hooks, int count,
{
int i;
- /*
- * A security module may call security_add_hooks() more
- * than once during initialization, and LSM initialization
- * is serialized. Landlock is one such case.
- * Look at the previous entry, if there is one, for duplication.
- */
- if (lsm_active_cnt == 0 || lsm_idlist[lsm_active_cnt - 1] != lsmid) {
- if (lsm_active_cnt >= MAX_LSM_COUNT)
- panic("%s Too many LSMs registered.\n", __func__);
- lsm_idlist[lsm_active_cnt++] = lsmid;
- }
-
for (i = 0; i < count; i++) {
hooks[i].lsmid = lsmid;
lsm_static_call_init(&hooks[i]);
@@ -491,10 +482,10 @@ int __init security_init(void)
* available
*/
lsm_early_for_each_raw(lsm) {
- init_debug(" early started: %s (%s)\n", lsm->name,
+ init_debug(" early started: %s (%s)\n", lsm->id->name,
is_enabled(lsm) ? "enabled" : "disabled");
if (lsm->enabled)
- lsm_append(lsm->name, &lsm_names);
+ lsm_append(lsm->id->name, &lsm_names);
}
/* Load LSMs in specified order. */
diff --git a/security/safesetid/lsm.c b/security/safesetid/lsm.c
index 1ba564f097f5..9a7c68d4e642 100644
--- a/security/safesetid/lsm.c
+++ b/security/safesetid/lsm.c
@@ -287,6 +287,6 @@ static int __init safesetid_security_init(void)
}
DEFINE_LSM(safesetid_security_init) = {
+ .id = &safesetid_lsmid,
.init = safesetid_security_init,
- .name = "safesetid",
};
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index dfc22da42f30..299b656ac007 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -7639,7 +7639,7 @@ void selinux_complete_init(void)
/* SELinux requires early initialization in order to label
all processes and objects when they are created. */
DEFINE_LSM(selinux) = {
- .name = "selinux",
+ .id = &selinux_lsmid,
.flags = LSM_FLAG_LEGACY_MAJOR | LSM_FLAG_EXCLUSIVE,
.enabled = &selinux_enabled_boot,
.blobs = &selinux_blob_sizes,
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index af986587841d..392698e41120 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -5280,7 +5280,7 @@ static __init int smack_init(void)
* all processes and objects when they are created.
*/
DEFINE_LSM(smack) = {
- .name = "smack",
+ .id = &smack_lsmid,
.flags = LSM_FLAG_LEGACY_MAJOR | LSM_FLAG_EXCLUSIVE,
.blobs = &smack_blob_sizes,
.init = smack_init,
diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c
index 48fc59d38ab2..cb003c460dc2 100644
--- a/security/tomoyo/tomoyo.c
+++ b/security/tomoyo/tomoyo.c
@@ -612,7 +612,7 @@ static int __init tomoyo_init(void)
}
DEFINE_LSM(tomoyo) = {
- .name = "tomoyo",
+ .id = &tomoyo_lsmid,
.enabled = &tomoyo_enabled,
.flags = LSM_FLAG_LEGACY_MAJOR,
.blobs = &tomoyo_blob_sizes,
diff --git a/security/yama/yama_lsm.c b/security/yama/yama_lsm.c
index 3d064dd4e03f..38b21ee0c560 100644
--- a/security/yama/yama_lsm.c
+++ b/security/yama/yama_lsm.c
@@ -476,6 +476,6 @@ static int __init yama_init(void)
}
DEFINE_LSM(yama) = {
- .name = "yama",
+ .id = &yama_lsmid,
.init = yama_init,
};
--
2.51.1.dirty
^ permalink raw reply related
* [PATCH v5 09/34] lsm: rename the lsm order variables for consistency
From: Paul Moore @ 2025-10-17 20:24 UTC (permalink / raw)
To: linux-security-module, linux-integrity, selinux
Cc: John Johansen, Mimi Zohar, Roberto Sassu, Fan Wu,
Mickaël Salaün, Günther Noack, Kees Cook,
Micah Morton, Casey Schaufler, Tetsuo Handa, Nicolas Bouchinet,
Xiu Jianfeng
In-Reply-To: <20251017202456.484010-36-paul@paul-moore.com>
Rename the builtin_lsm_order variable to lsm_order_builtin,
chosen_lsm_order to lsm_order_cmdline, chosen_major_lsm to
lsm_order_legacy, ordered_lsms[] to lsm_order[], and exclusive
to lsm_exclusive.
This patch also renames the associated kernel command line parsing
functions and adds some basic function comment blocks. The parsing
function choose_major_lsm() was renamed to lsm_choose_security(),
choose_lsm_order() to lsm_choose_lsm(), and enable_debug() to
lsm_debug_enable().
Reviewed-by: Kees Cook <kees@kernel.org>
Reviewed-by: John Johansen <john.johansen@canonical.com>
Reviewed-by: Casey Schaufler <casey@schaufler-ca.com>
Reviewed-by: Mimi Zohar <zohar@linux.ibm.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
---
security/lsm_init.c | 86 +++++++++++++++++++++++++--------------------
1 file changed, 48 insertions(+), 38 deletions(-)
diff --git a/security/lsm_init.c b/security/lsm_init.c
index 692d61a2ea10..a0785ca081c7 100644
--- a/security/lsm_init.c
+++ b/security/lsm_init.c
@@ -16,14 +16,14 @@ char *lsm_names;
extern struct lsm_info __start_lsm_info[], __end_lsm_info[];
extern struct lsm_info __start_early_lsm_info[], __end_early_lsm_info[];
-/* Boot-time LSM user choice */
-static __initconst const char *const builtin_lsm_order = CONFIG_LSM;
-static __initdata const char *chosen_lsm_order;
-static __initdata const char *chosen_major_lsm;
+/* Build and boot-time LSM ordering. */
+static __initconst const char *const lsm_order_builtin = CONFIG_LSM;
+static __initdata const char *lsm_order_cmdline;
+static __initdata const char *lsm_order_legacy;
/* Ordered list of LSMs to initialize. */
-static __initdata struct lsm_info *ordered_lsms[MAX_LSM_COUNT + 1];
-static __initdata struct lsm_info *exclusive;
+static __initdata struct lsm_info *lsm_order[MAX_LSM_COUNT + 1];
+static __initdata struct lsm_info *lsm_exclusive;
static __initdata bool debug;
#define init_debug(...) \
@@ -33,7 +33,7 @@ static __initdata bool debug;
} while (0)
#define lsm_order_for_each(iter) \
- for ((iter) = ordered_lsms; *(iter); (iter)++)
+ for ((iter) = lsm_order; *(iter); (iter)++)
#define lsm_for_each_raw(iter) \
for ((iter) = __start_lsm_info; \
(iter) < __end_lsm_info; (iter)++)
@@ -41,31 +41,41 @@ static __initdata bool debug;
for ((iter) = __start_early_lsm_info; \
(iter) < __end_early_lsm_info; (iter)++)
-static int lsm_append(const char *new, char **result);
-
-/* Save user chosen LSM */
-static int __init choose_major_lsm(char *str)
+/**
+ * lsm_choose_security - Legacy "major" LSM selection
+ * @str: kernel command line parameter
+ */
+static int __init lsm_choose_security(char *str)
{
- chosen_major_lsm = str;
+ lsm_order_legacy = str;
return 1;
}
-__setup("security=", choose_major_lsm);
+__setup("security=", lsm_choose_security);
-/* Explicitly choose LSM initialization order. */
-static int __init choose_lsm_order(char *str)
+/**
+ * lsm_choose_lsm - Modern LSM selection
+ * @str: kernel command line parameter
+ */
+static int __init lsm_choose_lsm(char *str)
{
- chosen_lsm_order = str;
+ lsm_order_cmdline = str;
return 1;
}
-__setup("lsm=", choose_lsm_order);
+__setup("lsm=", lsm_choose_lsm);
-/* Enable LSM order debugging. */
-static int __init enable_debug(char *str)
+/**
+ * lsm_debug_enable - Enable LSM framework debugging
+ * @str: kernel command line parameter
+ *
+ * Currently we only provide debug info during LSM initialization, but we may
+ * want to expand this in the future.
+ */
+static int __init lsm_debug_enable(char *str)
{
debug = true;
return 1;
}
-__setup("lsm.debug", enable_debug);
+__setup("lsm.debug", lsm_debug_enable);
/* Mark an LSM's enabled flag. */
static int lsm_enabled_true __initdata = 1;
@@ -127,7 +137,7 @@ static void __init append_ordered_lsm(struct lsm_info *lsm, const char *from)
/* Enable this LSM, if it is not already set. */
if (!lsm->enabled)
lsm->enabled = &lsm_enabled_true;
- ordered_lsms[last_lsm] = lsm;
+ lsm_order[last_lsm] = lsm;
lsm_idlist[last_lsm++] = lsm->id;
init_debug("%s ordered: %s (%s)\n", from, lsm->id->name,
@@ -157,7 +167,7 @@ static void __init lsm_prepare(struct lsm_info *lsm)
if (!is_enabled(lsm)) {
set_enabled(lsm, false);
return;
- } else if ((lsm->flags & LSM_FLAG_EXCLUSIVE) && exclusive) {
+ } else if ((lsm->flags & LSM_FLAG_EXCLUSIVE) && lsm_exclusive) {
init_debug("exclusive disabled: %s\n", lsm->id->name);
set_enabled(lsm, false);
return;
@@ -165,9 +175,9 @@ static void __init lsm_prepare(struct lsm_info *lsm)
/* Mark the LSM as enabled. */
set_enabled(lsm, true);
- if ((lsm->flags & LSM_FLAG_EXCLUSIVE) && !exclusive) {
+ if ((lsm->flags & LSM_FLAG_EXCLUSIVE) && !lsm_exclusive) {
init_debug("exclusive chosen: %s\n", lsm->id->name);
- exclusive = lsm;
+ lsm_exclusive = lsm;
}
/* Register the LSM blob sizes. */
@@ -226,7 +236,7 @@ static void __init ordered_lsm_parse(const char *order, const char *origin)
}
/* Process "security=", if given. */
- if (chosen_major_lsm) {
+ if (lsm_order_legacy) {
struct lsm_info *major;
/*
@@ -237,10 +247,10 @@ static void __init ordered_lsm_parse(const char *order, const char *origin)
*/
lsm_for_each_raw(major) {
if ((major->flags & LSM_FLAG_LEGACY_MAJOR) &&
- strcmp(major->id->name, chosen_major_lsm) != 0) {
+ strcmp(major->id->name, lsm_order_legacy) != 0) {
set_enabled(major, false);
init_debug("security=%s disabled: %s (only one legacy major LSM)\n",
- chosen_major_lsm, major->id->name);
+ lsm_order_legacy, major->id->name);
}
}
}
@@ -265,11 +275,11 @@ static void __init ordered_lsm_parse(const char *order, const char *origin)
}
/* Process "security=", if given. */
- if (chosen_major_lsm) {
+ if (lsm_order_legacy) {
lsm_for_each_raw(lsm) {
if (exists_ordered_lsm(lsm))
continue;
- if (strcmp(lsm->id->name, chosen_major_lsm) == 0)
+ if (strcmp(lsm->id->name, lsm_order_legacy) == 0)
append_ordered_lsm(lsm, "security=");
}
}
@@ -301,15 +311,15 @@ static void __init lsm_init_ordered(void)
struct lsm_info **lsm;
struct lsm_info *early;
- if (chosen_lsm_order) {
- if (chosen_major_lsm) {
+ if (lsm_order_cmdline) {
+ if (lsm_order_legacy) {
pr_warn("security=%s is ignored because it is superseded by lsm=%s\n",
- chosen_major_lsm, chosen_lsm_order);
- chosen_major_lsm = NULL;
+ lsm_order_legacy, lsm_order_cmdline);
+ lsm_order_legacy = NULL;
}
- ordered_lsm_parse(chosen_lsm_order, "cmdline");
+ ordered_lsm_parse(lsm_order_cmdline, "cmdline");
} else
- ordered_lsm_parse(builtin_lsm_order, "builtin");
+ ordered_lsm_parse(lsm_order_builtin, "builtin");
lsm_order_for_each(lsm) {
lsm_prepare(*lsm);
@@ -473,9 +483,9 @@ int __init security_init(void)
{
struct lsm_info *lsm;
- init_debug("legacy security=%s\n", chosen_major_lsm ? : " *unspecified*");
- init_debug(" CONFIG_LSM=%s\n", builtin_lsm_order);
- init_debug("boot arg lsm=%s\n", chosen_lsm_order ? : " *unspecified*");
+ init_debug("legacy security=%s\n", lsm_order_legacy ? : " *unspecified*");
+ init_debug(" CONFIG_LSM=%s\n", lsm_order_builtin);
+ init_debug("boot arg lsm=%s\n", lsm_order_cmdline ? : " *unspecified*");
/*
* Append the names of the early LSM modules now that kmalloc() is
--
2.51.1.dirty
^ permalink raw reply related
* [PATCH v5 07/34] lsm: rename ordered_lsm_init() to lsm_init_ordered()
From: Paul Moore @ 2025-10-17 20:24 UTC (permalink / raw)
To: linux-security-module, linux-integrity, selinux
Cc: John Johansen, Mimi Zohar, Roberto Sassu, Fan Wu,
Mickaël Salaün, Günther Noack, Kees Cook,
Micah Morton, Casey Schaufler, Tetsuo Handa, Nicolas Bouchinet,
Xiu Jianfeng
In-Reply-To: <20251017202456.484010-36-paul@paul-moore.com>
The new name more closely fits the rest of the naming scheme in
security/lsm_init.c. This patch also adds a trivial comment block to
the top of the function.
Reviewed-by: Casey Schaufler <casey@schaufler-ca.com>
Reviewed-by: John Johansen <john.johhansen@canonical.com>
Reviewed-by: Mimi Zohar <zohar@linux.ibm.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
---
security/lsm_init.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/security/lsm_init.c b/security/lsm_init.c
index 8bb473aca113..9249d5f37ae9 100644
--- a/security/lsm_init.c
+++ b/security/lsm_init.c
@@ -291,7 +291,10 @@ static void __init ordered_lsm_parse(const char *order, const char *origin)
kfree(sep);
}
-static void __init ordered_lsm_init(void)
+/**
+ * lsm_init_ordered - Initialize the ordered LSMs
+ */
+static void __init lsm_init_ordered(void)
{
unsigned int first = 0;
struct lsm_info **lsm;
@@ -342,9 +345,6 @@ static void __init ordered_lsm_init(void)
init_debug("bpf prog blob size = %d\n", blob_sizes.lbs_bpf_prog);
init_debug("bpf token blob size = %d\n", blob_sizes.lbs_bpf_token);
- /*
- * Create any kmem_caches needed for blobs
- */
if (blob_sizes.lbs_file)
lsm_file_cache = kmem_cache_create("lsm_file_cache",
blob_sizes.lbs_file, 0,
@@ -498,7 +498,7 @@ int __init security_init(void)
}
/* Load LSMs in specified order. */
- ordered_lsm_init();
+ lsm_init_ordered();
return 0;
}
--
2.51.1.dirty
^ permalink raw reply related
* [PATCH v5 06/34] lsm: integrate lsm_early_cred() and lsm_early_task() into caller
From: Paul Moore @ 2025-10-17 20:24 UTC (permalink / raw)
To: linux-security-module, linux-integrity, selinux
Cc: John Johansen, Mimi Zohar, Roberto Sassu, Fan Wu,
Mickaël Salaün, Günther Noack, Kees Cook,
Micah Morton, Casey Schaufler, Tetsuo Handa, Nicolas Bouchinet,
Xiu Jianfeng
In-Reply-To: <20251017202456.484010-36-paul@paul-moore.com>
With only one caller of lsm_early_cred() and lsm_early_task(), insert
the functions' code directly into the caller and ger rid of the two
functions.
Reviewed-by: Casey Schaufler <casey@schaufler-ca.com>
Reviewed-by: John Johansen <john.johhansen@canonical.com>
Reviewed-by: Mimi Zohar <zohar@linux.ibm.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
---
security/lsm_init.c | 35 +++++------------------------------
1 file changed, 5 insertions(+), 30 deletions(-)
diff --git a/security/lsm_init.c b/security/lsm_init.c
index 09afa7ad719e..8bb473aca113 100644
--- a/security/lsm_init.c
+++ b/security/lsm_init.c
@@ -291,34 +291,6 @@ static void __init ordered_lsm_parse(const char *order, const char *origin)
kfree(sep);
}
-/**
- * lsm_early_cred - during initialization allocate a composite cred blob
- * @cred: the cred that needs a blob
- *
- * Allocate the cred blob for all the modules
- */
-static void __init lsm_early_cred(struct cred *cred)
-{
- int rc = lsm_cred_alloc(cred, GFP_KERNEL);
-
- if (rc)
- panic("%s: Early cred alloc failed.\n", __func__);
-}
-
-/**
- * lsm_early_task - during initialization allocate a composite task blob
- * @task: the task that needs a blob
- *
- * Allocate the task blob for all the modules
- */
-static void __init lsm_early_task(struct task_struct *task)
-{
- int rc = lsm_task_alloc(task);
-
- if (rc)
- panic("%s: Early task alloc failed.\n", __func__);
-}
-
static void __init ordered_lsm_init(void)
{
unsigned int first = 0;
@@ -382,8 +354,11 @@ static void __init ordered_lsm_init(void)
blob_sizes.lbs_inode, 0,
SLAB_PANIC, NULL);
- lsm_early_cred((struct cred *) current->cred);
- lsm_early_task(current);
+ if (lsm_cred_alloc((struct cred __rcu *)current->cred, GFP_KERNEL))
+ panic("%s: early cred alloc failed.\n", __func__);
+ if (lsm_task_alloc(current))
+ panic("%s: early task alloc failed.\n", __func__);
+
lsm_order_for_each(lsm) {
initialize_lsm(*lsm);
}
--
2.51.1.dirty
^ permalink raw reply related
* [PATCH v5 05/34] lsm: integrate report_lsm_order() code into caller
From: Paul Moore @ 2025-10-17 20:24 UTC (permalink / raw)
To: linux-security-module, linux-integrity, selinux
Cc: John Johansen, Mimi Zohar, Roberto Sassu, Fan Wu,
Mickaël Salaün, Günther Noack, Kees Cook,
Micah Morton, Casey Schaufler, Tetsuo Handa, Nicolas Bouchinet,
Xiu Jianfeng
In-Reply-To: <20251017202456.484010-36-paul@paul-moore.com>
With only one caller of report_lsm_order(), insert the function's code
directly into the caller and ger rid of report_lsm_order().
Reviewed-by: Casey Schaufler <casey@schaufler-ca.com>
Reviewed-by: John Johansen <john.johhansen@canonical.com>
Reviewed-by: Mimi Zohar <zohar@linux.ibm.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
---
security/lsm_init.c | 33 ++++++++++++---------------------
1 file changed, 12 insertions(+), 21 deletions(-)
diff --git a/security/lsm_init.c b/security/lsm_init.c
index 18828a65c364..09afa7ad719e 100644
--- a/security/lsm_init.c
+++ b/security/lsm_init.c
@@ -291,26 +291,6 @@ static void __init ordered_lsm_parse(const char *order, const char *origin)
kfree(sep);
}
-static void __init report_lsm_order(void)
-{
- struct lsm_info **lsm, *early;
- int first = 0;
-
- pr_info("initializing lsm=");
-
- /* Report each enabled LSM name, comma separated. */
- lsm_early_for_each_raw(early) {
- if (is_enabled(early))
- pr_cont("%s%s", first++ == 0 ? "" : ",", early->name);
- }
- lsm_order_for_each(lsm) {
- if (is_enabled(*lsm))
- pr_cont("%s%s", first++ == 0 ? "" : ",", (*lsm)->name);
- }
-
- pr_cont("\n");
-}
-
/**
* lsm_early_cred - during initialization allocate a composite cred blob
* @cred: the cred that needs a blob
@@ -341,7 +321,9 @@ static void __init lsm_early_task(struct task_struct *task)
static void __init ordered_lsm_init(void)
{
+ unsigned int first = 0;
struct lsm_info **lsm;
+ struct lsm_info *early;
if (chosen_lsm_order) {
if (chosen_major_lsm) {
@@ -357,7 +339,16 @@ static void __init ordered_lsm_init(void)
lsm_prepare(*lsm);
}
- report_lsm_order();
+ pr_info("initializing lsm=");
+ lsm_early_for_each_raw(early) {
+ if (is_enabled(early))
+ pr_cont("%s%s", first++ == 0 ? "" : ",", early->name);
+ }
+ lsm_order_for_each(lsm) {
+ if (is_enabled(*lsm))
+ pr_cont("%s%s", first++ == 0 ? "" : ",", (*lsm)->name);
+ }
+ pr_cont("\n");
init_debug("cred blob size = %d\n", blob_sizes.lbs_cred);
init_debug("file blob size = %d\n", blob_sizes.lbs_file);
--
2.51.1.dirty
^ permalink raw reply related
* [PATCH v5 04/34] lsm: introduce looping macros for the initialization code
From: Paul Moore @ 2025-10-17 20:24 UTC (permalink / raw)
To: linux-security-module, linux-integrity, selinux
Cc: John Johansen, Mimi Zohar, Roberto Sassu, Fan Wu,
Mickaël Salaün, Günther Noack, Kees Cook,
Micah Morton, Casey Schaufler, Tetsuo Handa, Nicolas Bouchinet,
Xiu Jianfeng
In-Reply-To: <20251017202456.484010-36-paul@paul-moore.com>
There are three common for loop patterns in the LSM initialization code
to loop through the ordered LSM list and the registered "early" LSMs.
This patch implements these loop patterns as macros to help simplify the
code and reduce the chance for errors.
Reviewed-by: Casey Schaufler <casey@schaufler-ca.com>
Reviewed-by: John Johansen <john.johhansen@canonical.com>
Reviewed-by: Mimi Zohar <zohar@linux.ibm.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
---
security/lsm_init.c | 42 +++++++++++++++++++++++++++---------------
1 file changed, 27 insertions(+), 15 deletions(-)
diff --git a/security/lsm_init.c b/security/lsm_init.c
index 6f40ab1d2f54..18828a65c364 100644
--- a/security/lsm_init.c
+++ b/security/lsm_init.c
@@ -32,6 +32,15 @@ static __initdata bool debug;
pr_info(__VA_ARGS__); \
} while (0)
+#define lsm_order_for_each(iter) \
+ for ((iter) = ordered_lsms; *(iter); (iter)++)
+#define lsm_for_each_raw(iter) \
+ for ((iter) = __start_lsm_info; \
+ (iter) < __end_lsm_info; (iter)++)
+#define lsm_early_for_each_raw(iter) \
+ for ((iter) = __start_early_lsm_info; \
+ (iter) < __end_early_lsm_info; (iter)++)
+
static int lsm_append(const char *new, char **result);
/* Save user chosen LSM */
@@ -96,9 +105,10 @@ static bool __init exists_ordered_lsm(struct lsm_info *lsm)
{
struct lsm_info **check;
- for (check = ordered_lsms; *check; check++)
+ lsm_order_for_each(check) {
if (*check == lsm)
return true;
+ }
return false;
}
@@ -209,7 +219,7 @@ static void __init ordered_lsm_parse(const char *order, const char *origin)
char *sep, *name, *next;
/* LSM_ORDER_FIRST is always first. */
- for (lsm = __start_lsm_info; lsm < __end_lsm_info; lsm++) {
+ lsm_for_each_raw(lsm) {
if (lsm->order == LSM_ORDER_FIRST)
append_ordered_lsm(lsm, " first");
}
@@ -224,8 +234,7 @@ static void __init ordered_lsm_parse(const char *order, const char *origin)
* if the selected one was separately disabled: disable
* all non-matching Legacy Major LSMs.
*/
- for (major = __start_lsm_info; major < __end_lsm_info;
- major++) {
+ lsm_for_each_raw(major) {
if ((major->flags & LSM_FLAG_LEGACY_MAJOR) &&
strcmp(major->name, chosen_major_lsm) != 0) {
set_enabled(major, false);
@@ -241,7 +250,7 @@ static void __init ordered_lsm_parse(const char *order, const char *origin)
while ((name = strsep(&next, ",")) != NULL) {
bool found = false;
- for (lsm = __start_lsm_info; lsm < __end_lsm_info; lsm++) {
+ lsm_for_each_raw(lsm) {
if (strcmp(lsm->name, name) == 0) {
if (lsm->order == LSM_ORDER_MUTABLE)
append_ordered_lsm(lsm, origin);
@@ -256,7 +265,7 @@ static void __init ordered_lsm_parse(const char *order, const char *origin)
/* Process "security=", if given. */
if (chosen_major_lsm) {
- for (lsm = __start_lsm_info; lsm < __end_lsm_info; lsm++) {
+ lsm_for_each_raw(lsm) {
if (exists_ordered_lsm(lsm))
continue;
if (strcmp(lsm->name, chosen_major_lsm) == 0)
@@ -265,13 +274,13 @@ static void __init ordered_lsm_parse(const char *order, const char *origin)
}
/* LSM_ORDER_LAST is always last. */
- for (lsm = __start_lsm_info; lsm < __end_lsm_info; lsm++) {
+ lsm_for_each_raw(lsm) {
if (lsm->order == LSM_ORDER_LAST)
append_ordered_lsm(lsm, " last");
}
/* Disable all LSMs not in the ordered list. */
- for (lsm = __start_lsm_info; lsm < __end_lsm_info; lsm++) {
+ lsm_for_each_raw(lsm) {
if (exists_ordered_lsm(lsm))
continue;
set_enabled(lsm, false);
@@ -290,13 +299,14 @@ static void __init report_lsm_order(void)
pr_info("initializing lsm=");
/* Report each enabled LSM name, comma separated. */
- for (early = __start_early_lsm_info;
- early < __end_early_lsm_info; early++)
+ lsm_early_for_each_raw(early) {
if (is_enabled(early))
pr_cont("%s%s", first++ == 0 ? "" : ",", early->name);
- for (lsm = ordered_lsms; *lsm; lsm++)
+ }
+ lsm_order_for_each(lsm) {
if (is_enabled(*lsm))
pr_cont("%s%s", first++ == 0 ? "" : ",", (*lsm)->name);
+ }
pr_cont("\n");
}
@@ -343,8 +353,9 @@ static void __init ordered_lsm_init(void)
} else
ordered_lsm_parse(builtin_lsm_order, "builtin");
- for (lsm = ordered_lsms; *lsm; lsm++)
+ lsm_order_for_each(lsm) {
lsm_prepare(*lsm);
+ }
report_lsm_order();
@@ -382,8 +393,9 @@ static void __init ordered_lsm_init(void)
lsm_early_cred((struct cred *) current->cred);
lsm_early_task(current);
- for (lsm = ordered_lsms; *lsm; lsm++)
+ lsm_order_for_each(lsm) {
initialize_lsm(*lsm);
+ }
}
static bool match_last_lsm(const char *list, const char *lsm)
@@ -485,7 +497,7 @@ int __init early_security_init(void)
{
struct lsm_info *lsm;
- for (lsm = __start_early_lsm_info; lsm < __end_early_lsm_info; lsm++) {
+ lsm_early_for_each_raw(lsm) {
if (!lsm->enabled)
lsm->enabled = &lsm_enabled_true;
lsm_prepare(lsm);
@@ -512,7 +524,7 @@ int __init security_init(void)
* Append the names of the early LSM modules now that kmalloc() is
* available
*/
- for (lsm = __start_early_lsm_info; lsm < __end_early_lsm_info; lsm++) {
+ lsm_early_for_each_raw(lsm) {
init_debug(" early started: %s (%s)\n", lsm->name,
is_enabled(lsm) ? "enabled" : "disabled");
if (lsm->enabled)
--
2.51.1.dirty
^ permalink raw reply related
* [PATCH v5 03/34] lsm: consolidate lsm_allowed() and prepare_lsm() into lsm_prepare()
From: Paul Moore @ 2025-10-17 20:24 UTC (permalink / raw)
To: linux-security-module, linux-integrity, selinux
Cc: John Johansen, Mimi Zohar, Roberto Sassu, Fan Wu,
Mickaël Salaün, Günther Noack, Kees Cook,
Micah Morton, Casey Schaufler, Tetsuo Handa, Nicolas Bouchinet,
Xiu Jianfeng
In-Reply-To: <20251017202456.484010-36-paul@paul-moore.com>
Simplify and consolidate the lsm_allowed() and prepare_lsm() functions
into a new function, lsm_prepare().
Reviewed-by: Casey Schaufler <casey@schaufler-ca.com>
Reviewed-by: John Johansen <john.johhansen@canonical.com>
Reviewed-by: Mimi Zohar <zohar@linux.ibm.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
---
security/lsm_init.c | 109 +++++++++++++++++++-------------------------
1 file changed, 46 insertions(+), 63 deletions(-)
diff --git a/security/lsm_init.c b/security/lsm_init.c
index 124213b906af..6f40ab1d2f54 100644
--- a/security/lsm_init.c
+++ b/security/lsm_init.c
@@ -123,22 +123,6 @@ static void __init append_ordered_lsm(struct lsm_info *lsm, const char *from)
is_enabled(lsm) ? "enabled" : "disabled");
}
-/* Is an LSM allowed to be initialized? */
-static bool __init lsm_allowed(struct lsm_info *lsm)
-{
- /* Skip if the LSM is disabled. */
- if (!is_enabled(lsm))
- return false;
-
- /* Not allowed if another exclusive LSM already initialized. */
- if ((lsm->flags & LSM_FLAG_EXCLUSIVE) && exclusive) {
- init_debug("exclusive disabled: %s\n", lsm->name);
- return false;
- }
-
- return true;
-}
-
static void __init lsm_set_blob_size(int *need, int *lbs)
{
int offset;
@@ -151,54 +135,53 @@ static void __init lsm_set_blob_size(int *need, int *lbs)
*need = offset;
}
-static void __init lsm_set_blob_sizes(struct lsm_blob_sizes *needed)
+/**
+ * lsm_prepare - Prepare the LSM framework for a new LSM
+ * @lsm: LSM definition
+ */
+static void __init lsm_prepare(struct lsm_info *lsm)
{
- if (!needed)
+ struct lsm_blob_sizes *blobs;
+
+ if (!is_enabled(lsm)) {
+ set_enabled(lsm, false);
+ return;
+ } else if ((lsm->flags & LSM_FLAG_EXCLUSIVE) && exclusive) {
+ init_debug("exclusive disabled: %s\n", lsm->name);
+ set_enabled(lsm, false);
return;
-
- lsm_set_blob_size(&needed->lbs_cred, &blob_sizes.lbs_cred);
- lsm_set_blob_size(&needed->lbs_file, &blob_sizes.lbs_file);
- lsm_set_blob_size(&needed->lbs_ib, &blob_sizes.lbs_ib);
- /*
- * The inode blob gets an rcu_head in addition to
- * what the modules might need.
- */
- if (needed->lbs_inode && blob_sizes.lbs_inode == 0)
- blob_sizes.lbs_inode = sizeof(struct rcu_head);
- lsm_set_blob_size(&needed->lbs_inode, &blob_sizes.lbs_inode);
- lsm_set_blob_size(&needed->lbs_ipc, &blob_sizes.lbs_ipc);
- lsm_set_blob_size(&needed->lbs_key, &blob_sizes.lbs_key);
- lsm_set_blob_size(&needed->lbs_msg_msg, &blob_sizes.lbs_msg_msg);
- lsm_set_blob_size(&needed->lbs_perf_event, &blob_sizes.lbs_perf_event);
- lsm_set_blob_size(&needed->lbs_sock, &blob_sizes.lbs_sock);
- lsm_set_blob_size(&needed->lbs_superblock, &blob_sizes.lbs_superblock);
- lsm_set_blob_size(&needed->lbs_task, &blob_sizes.lbs_task);
- lsm_set_blob_size(&needed->lbs_tun_dev, &blob_sizes.lbs_tun_dev);
- lsm_set_blob_size(&needed->lbs_xattr_count,
- &blob_sizes.lbs_xattr_count);
- lsm_set_blob_size(&needed->lbs_bdev, &blob_sizes.lbs_bdev);
- lsm_set_blob_size(&needed->lbs_bpf_map, &blob_sizes.lbs_bpf_map);
- lsm_set_blob_size(&needed->lbs_bpf_prog, &blob_sizes.lbs_bpf_prog);
- lsm_set_blob_size(&needed->lbs_bpf_token, &blob_sizes.lbs_bpf_token);
-}
-
-/* Prepare LSM for initialization. */
-static void __init prepare_lsm(struct lsm_info *lsm)
-{
- int enabled = lsm_allowed(lsm);
-
- /* Record enablement (to handle any following exclusive LSMs). */
- set_enabled(lsm, enabled);
-
- /* If enabled, do pre-initialization work. */
- if (enabled) {
- if ((lsm->flags & LSM_FLAG_EXCLUSIVE) && !exclusive) {
- exclusive = lsm;
- init_debug("exclusive chosen: %s\n", lsm->name);
- }
-
- lsm_set_blob_sizes(lsm->blobs);
}
+
+ /* Mark the LSM as enabled. */
+ set_enabled(lsm, true);
+ if ((lsm->flags & LSM_FLAG_EXCLUSIVE) && !exclusive) {
+ init_debug("exclusive chosen: %s\n", lsm->name);
+ exclusive = lsm;
+ }
+
+ /* Register the LSM blob sizes. */
+ blobs = lsm->blobs;
+ lsm_set_blob_size(&blobs->lbs_cred, &blob_sizes.lbs_cred);
+ lsm_set_blob_size(&blobs->lbs_file, &blob_sizes.lbs_file);
+ lsm_set_blob_size(&blobs->lbs_ib, &blob_sizes.lbs_ib);
+ /* inode blob gets an rcu_head in addition to LSM blobs. */
+ if (blobs->lbs_inode && blob_sizes.lbs_inode == 0)
+ blob_sizes.lbs_inode = sizeof(struct rcu_head);
+ lsm_set_blob_size(&blobs->lbs_inode, &blob_sizes.lbs_inode);
+ lsm_set_blob_size(&blobs->lbs_ipc, &blob_sizes.lbs_ipc);
+ lsm_set_blob_size(&blobs->lbs_key, &blob_sizes.lbs_key);
+ lsm_set_blob_size(&blobs->lbs_msg_msg, &blob_sizes.lbs_msg_msg);
+ lsm_set_blob_size(&blobs->lbs_perf_event, &blob_sizes.lbs_perf_event);
+ lsm_set_blob_size(&blobs->lbs_sock, &blob_sizes.lbs_sock);
+ lsm_set_blob_size(&blobs->lbs_superblock, &blob_sizes.lbs_superblock);
+ lsm_set_blob_size(&blobs->lbs_task, &blob_sizes.lbs_task);
+ lsm_set_blob_size(&blobs->lbs_tun_dev, &blob_sizes.lbs_tun_dev);
+ lsm_set_blob_size(&blobs->lbs_xattr_count,
+ &blob_sizes.lbs_xattr_count);
+ lsm_set_blob_size(&blobs->lbs_bdev, &blob_sizes.lbs_bdev);
+ lsm_set_blob_size(&blobs->lbs_bpf_map, &blob_sizes.lbs_bpf_map);
+ lsm_set_blob_size(&blobs->lbs_bpf_prog, &blob_sizes.lbs_bpf_prog);
+ lsm_set_blob_size(&blobs->lbs_bpf_token, &blob_sizes.lbs_bpf_token);
}
/* Initialize a given LSM, if it is enabled. */
@@ -361,7 +344,7 @@ static void __init ordered_lsm_init(void)
ordered_lsm_parse(builtin_lsm_order, "builtin");
for (lsm = ordered_lsms; *lsm; lsm++)
- prepare_lsm(*lsm);
+ lsm_prepare(*lsm);
report_lsm_order();
@@ -505,7 +488,7 @@ int __init early_security_init(void)
for (lsm = __start_early_lsm_info; lsm < __end_early_lsm_info; lsm++) {
if (!lsm->enabled)
lsm->enabled = &lsm_enabled_true;
- prepare_lsm(lsm);
+ lsm_prepare(lsm);
initialize_lsm(lsm);
}
--
2.51.1.dirty
^ permalink raw reply related
* [PATCH v5 02/34] lsm: split the init code out into lsm_init.c
From: Paul Moore @ 2025-10-17 20:24 UTC (permalink / raw)
To: linux-security-module, linux-integrity, selinux
Cc: John Johansen, Mimi Zohar, Roberto Sassu, Fan Wu,
Mickaël Salaün, Günther Noack, Kees Cook,
Micah Morton, Casey Schaufler, Tetsuo Handa, Nicolas Bouchinet,
Xiu Jianfeng
In-Reply-To: <20251017202456.484010-36-paul@paul-moore.com>
Continue to pull code out of security/security.c to help improve
readability by pulling all of the LSM framework initialization
code out into a new file.
No code changes.
Reviewed-by: Kees Cook <kees@kernel.org>
Reviewed-by: John Johansen <john.johansen@canonical.com>
Reviewed-by: Casey Schaufler <casey@schaufler-ca.com>
Reviewed-by: Mimi Zohar <zohar@linux.ibm.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
---
include/linux/lsm_hooks.h | 3 +-
security/Makefile | 2 +-
security/lsm.h | 22 ++
security/lsm_init.c | 543 ++++++++++++++++++++++++++++++++++
security/security.c | 597 +++-----------------------------------
5 files changed, 601 insertions(+), 566 deletions(-)
create mode 100644 security/lsm.h
create mode 100644 security/lsm_init.c
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index 79ec5a2bdcca..0112926ed923 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -170,11 +170,10 @@ struct lsm_info {
__used __section(".early_lsm_info.init") \
__aligned(sizeof(unsigned long))
+
/* DO NOT tamper with these variables outside of the LSM framework */
extern char *lsm_names;
extern struct lsm_static_calls_table static_calls_table __ro_after_init;
-extern struct lsm_info __start_lsm_info[], __end_lsm_info[];
-extern struct lsm_info __start_early_lsm_info[], __end_early_lsm_info[];
/**
* lsm_get_xattr_slot - Return the next available slot and increment the index
diff --git a/security/Makefile b/security/Makefile
index 14d87847bce8..4601230ba442 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -11,7 +11,7 @@ obj-$(CONFIG_SECURITY) += lsm_syscalls.o
obj-$(CONFIG_MMU) += min_addr.o
# Object file lists
-obj-$(CONFIG_SECURITY) += security.o lsm_notifier.o
+obj-$(CONFIG_SECURITY) += security.o lsm_notifier.o lsm_init.o
obj-$(CONFIG_SECURITYFS) += inode.o
obj-$(CONFIG_SECURITY_SELINUX) += selinux/
obj-$(CONFIG_SECURITY_SMACK) += smack/
diff --git a/security/lsm.h b/security/lsm.h
new file mode 100644
index 000000000000..0e1731bad4a7
--- /dev/null
+++ b/security/lsm.h
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * LSM functions
+ */
+
+#ifndef _LSM_H_
+#define _LSM_H_
+
+#include <linux/lsm_hooks.h>
+
+/* LSM blob configuration */
+extern struct lsm_blob_sizes blob_sizes;
+
+/* LSM blob caches */
+extern struct kmem_cache *lsm_file_cache;
+extern struct kmem_cache *lsm_inode_cache;
+
+/* LSM blob allocators */
+int lsm_cred_alloc(struct cred *cred, gfp_t gfp);
+int lsm_task_alloc(struct task_struct *task);
+
+#endif /* _LSM_H_ */
diff --git a/security/lsm_init.c b/security/lsm_init.c
new file mode 100644
index 000000000000..124213b906af
--- /dev/null
+++ b/security/lsm_init.c
@@ -0,0 +1,543 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * LSM initialization functions
+ */
+
+#define pr_fmt(fmt) "LSM: " fmt
+
+#include <linux/init.h>
+#include <linux/lsm_hooks.h>
+
+#include "lsm.h"
+
+char *lsm_names;
+
+/* Pointers to LSM sections defined in include/asm-generic/vmlinux.lds.h */
+extern struct lsm_info __start_lsm_info[], __end_lsm_info[];
+extern struct lsm_info __start_early_lsm_info[], __end_early_lsm_info[];
+
+/* Boot-time LSM user choice */
+static __initconst const char *const builtin_lsm_order = CONFIG_LSM;
+static __initdata const char *chosen_lsm_order;
+static __initdata const char *chosen_major_lsm;
+
+/* Ordered list of LSMs to initialize. */
+static __initdata struct lsm_info *ordered_lsms[MAX_LSM_COUNT + 1];
+static __initdata struct lsm_info *exclusive;
+
+static __initdata bool debug;
+#define init_debug(...) \
+ do { \
+ if (debug) \
+ pr_info(__VA_ARGS__); \
+ } while (0)
+
+static int lsm_append(const char *new, char **result);
+
+/* Save user chosen LSM */
+static int __init choose_major_lsm(char *str)
+{
+ chosen_major_lsm = str;
+ return 1;
+}
+__setup("security=", choose_major_lsm);
+
+/* Explicitly choose LSM initialization order. */
+static int __init choose_lsm_order(char *str)
+{
+ chosen_lsm_order = str;
+ return 1;
+}
+__setup("lsm=", choose_lsm_order);
+
+/* Enable LSM order debugging. */
+static int __init enable_debug(char *str)
+{
+ debug = true;
+ return 1;
+}
+__setup("lsm.debug", enable_debug);
+
+/* Mark an LSM's enabled flag. */
+static int lsm_enabled_true __initdata = 1;
+static int lsm_enabled_false __initdata = 0;
+static void __init set_enabled(struct lsm_info *lsm, bool enabled)
+{
+ /*
+ * When an LSM hasn't configured an enable variable, we can use
+ * a hard-coded location for storing the default enabled state.
+ */
+ if (!lsm->enabled) {
+ if (enabled)
+ lsm->enabled = &lsm_enabled_true;
+ else
+ lsm->enabled = &lsm_enabled_false;
+ } else if (lsm->enabled == &lsm_enabled_true) {
+ if (!enabled)
+ lsm->enabled = &lsm_enabled_false;
+ } else if (lsm->enabled == &lsm_enabled_false) {
+ if (enabled)
+ lsm->enabled = &lsm_enabled_true;
+ } else {
+ *lsm->enabled = enabled;
+ }
+}
+
+static inline bool is_enabled(struct lsm_info *lsm)
+{
+ if (!lsm->enabled)
+ return false;
+
+ return *lsm->enabled;
+}
+
+/* Is an LSM already listed in the ordered LSMs list? */
+static bool __init exists_ordered_lsm(struct lsm_info *lsm)
+{
+ struct lsm_info **check;
+
+ for (check = ordered_lsms; *check; check++)
+ if (*check == lsm)
+ return true;
+
+ return false;
+}
+
+/* Append an LSM to the list of ordered LSMs to initialize. */
+static int last_lsm __initdata;
+static void __init append_ordered_lsm(struct lsm_info *lsm, const char *from)
+{
+ /* Ignore duplicate selections. */
+ if (exists_ordered_lsm(lsm))
+ return;
+
+ if (WARN(last_lsm == MAX_LSM_COUNT, "%s: out of LSM static calls!?\n", from))
+ return;
+
+ /* Enable this LSM, if it is not already set. */
+ if (!lsm->enabled)
+ lsm->enabled = &lsm_enabled_true;
+ ordered_lsms[last_lsm++] = lsm;
+
+ init_debug("%s ordered: %s (%s)\n", from, lsm->name,
+ is_enabled(lsm) ? "enabled" : "disabled");
+}
+
+/* Is an LSM allowed to be initialized? */
+static bool __init lsm_allowed(struct lsm_info *lsm)
+{
+ /* Skip if the LSM is disabled. */
+ if (!is_enabled(lsm))
+ return false;
+
+ /* Not allowed if another exclusive LSM already initialized. */
+ if ((lsm->flags & LSM_FLAG_EXCLUSIVE) && exclusive) {
+ init_debug("exclusive disabled: %s\n", lsm->name);
+ return false;
+ }
+
+ return true;
+}
+
+static void __init lsm_set_blob_size(int *need, int *lbs)
+{
+ int offset;
+
+ if (*need <= 0)
+ return;
+
+ offset = ALIGN(*lbs, sizeof(void *));
+ *lbs = offset + *need;
+ *need = offset;
+}
+
+static void __init lsm_set_blob_sizes(struct lsm_blob_sizes *needed)
+{
+ if (!needed)
+ return;
+
+ lsm_set_blob_size(&needed->lbs_cred, &blob_sizes.lbs_cred);
+ lsm_set_blob_size(&needed->lbs_file, &blob_sizes.lbs_file);
+ lsm_set_blob_size(&needed->lbs_ib, &blob_sizes.lbs_ib);
+ /*
+ * The inode blob gets an rcu_head in addition to
+ * what the modules might need.
+ */
+ if (needed->lbs_inode && blob_sizes.lbs_inode == 0)
+ blob_sizes.lbs_inode = sizeof(struct rcu_head);
+ lsm_set_blob_size(&needed->lbs_inode, &blob_sizes.lbs_inode);
+ lsm_set_blob_size(&needed->lbs_ipc, &blob_sizes.lbs_ipc);
+ lsm_set_blob_size(&needed->lbs_key, &blob_sizes.lbs_key);
+ lsm_set_blob_size(&needed->lbs_msg_msg, &blob_sizes.lbs_msg_msg);
+ lsm_set_blob_size(&needed->lbs_perf_event, &blob_sizes.lbs_perf_event);
+ lsm_set_blob_size(&needed->lbs_sock, &blob_sizes.lbs_sock);
+ lsm_set_blob_size(&needed->lbs_superblock, &blob_sizes.lbs_superblock);
+ lsm_set_blob_size(&needed->lbs_task, &blob_sizes.lbs_task);
+ lsm_set_blob_size(&needed->lbs_tun_dev, &blob_sizes.lbs_tun_dev);
+ lsm_set_blob_size(&needed->lbs_xattr_count,
+ &blob_sizes.lbs_xattr_count);
+ lsm_set_blob_size(&needed->lbs_bdev, &blob_sizes.lbs_bdev);
+ lsm_set_blob_size(&needed->lbs_bpf_map, &blob_sizes.lbs_bpf_map);
+ lsm_set_blob_size(&needed->lbs_bpf_prog, &blob_sizes.lbs_bpf_prog);
+ lsm_set_blob_size(&needed->lbs_bpf_token, &blob_sizes.lbs_bpf_token);
+}
+
+/* Prepare LSM for initialization. */
+static void __init prepare_lsm(struct lsm_info *lsm)
+{
+ int enabled = lsm_allowed(lsm);
+
+ /* Record enablement (to handle any following exclusive LSMs). */
+ set_enabled(lsm, enabled);
+
+ /* If enabled, do pre-initialization work. */
+ if (enabled) {
+ if ((lsm->flags & LSM_FLAG_EXCLUSIVE) && !exclusive) {
+ exclusive = lsm;
+ init_debug("exclusive chosen: %s\n", lsm->name);
+ }
+
+ lsm_set_blob_sizes(lsm->blobs);
+ }
+}
+
+/* Initialize a given LSM, if it is enabled. */
+static void __init initialize_lsm(struct lsm_info *lsm)
+{
+ if (is_enabled(lsm)) {
+ int ret;
+
+ init_debug("initializing %s\n", lsm->name);
+ ret = lsm->init();
+ WARN(ret, "%s failed to initialize: %d\n", lsm->name, ret);
+ }
+}
+
+/*
+ * Current index to use while initializing the lsm id list.
+ */
+u32 lsm_active_cnt __ro_after_init;
+const struct lsm_id *lsm_idlist[MAX_LSM_COUNT];
+
+/* Populate ordered LSMs list from comma-separated LSM name list. */
+static void __init ordered_lsm_parse(const char *order, const char *origin)
+{
+ struct lsm_info *lsm;
+ char *sep, *name, *next;
+
+ /* LSM_ORDER_FIRST is always first. */
+ for (lsm = __start_lsm_info; lsm < __end_lsm_info; lsm++) {
+ if (lsm->order == LSM_ORDER_FIRST)
+ append_ordered_lsm(lsm, " first");
+ }
+
+ /* Process "security=", if given. */
+ if (chosen_major_lsm) {
+ struct lsm_info *major;
+
+ /*
+ * To match the original "security=" behavior, this
+ * explicitly does NOT fallback to another Legacy Major
+ * if the selected one was separately disabled: disable
+ * all non-matching Legacy Major LSMs.
+ */
+ for (major = __start_lsm_info; major < __end_lsm_info;
+ major++) {
+ if ((major->flags & LSM_FLAG_LEGACY_MAJOR) &&
+ strcmp(major->name, chosen_major_lsm) != 0) {
+ set_enabled(major, false);
+ init_debug("security=%s disabled: %s (only one legacy major LSM)\n",
+ chosen_major_lsm, major->name);
+ }
+ }
+ }
+
+ sep = kstrdup(order, GFP_KERNEL);
+ next = sep;
+ /* Walk the list, looking for matching LSMs. */
+ while ((name = strsep(&next, ",")) != NULL) {
+ bool found = false;
+
+ for (lsm = __start_lsm_info; lsm < __end_lsm_info; lsm++) {
+ if (strcmp(lsm->name, name) == 0) {
+ if (lsm->order == LSM_ORDER_MUTABLE)
+ append_ordered_lsm(lsm, origin);
+ found = true;
+ }
+ }
+
+ if (!found)
+ init_debug("%s ignored: %s (not built into kernel)\n",
+ origin, name);
+ }
+
+ /* Process "security=", if given. */
+ if (chosen_major_lsm) {
+ for (lsm = __start_lsm_info; lsm < __end_lsm_info; lsm++) {
+ if (exists_ordered_lsm(lsm))
+ continue;
+ if (strcmp(lsm->name, chosen_major_lsm) == 0)
+ append_ordered_lsm(lsm, "security=");
+ }
+ }
+
+ /* LSM_ORDER_LAST is always last. */
+ for (lsm = __start_lsm_info; lsm < __end_lsm_info; lsm++) {
+ if (lsm->order == LSM_ORDER_LAST)
+ append_ordered_lsm(lsm, " last");
+ }
+
+ /* Disable all LSMs not in the ordered list. */
+ for (lsm = __start_lsm_info; lsm < __end_lsm_info; lsm++) {
+ if (exists_ordered_lsm(lsm))
+ continue;
+ set_enabled(lsm, false);
+ init_debug("%s skipped: %s (not in requested order)\n",
+ origin, lsm->name);
+ }
+
+ kfree(sep);
+}
+
+static void __init report_lsm_order(void)
+{
+ struct lsm_info **lsm, *early;
+ int first = 0;
+
+ pr_info("initializing lsm=");
+
+ /* Report each enabled LSM name, comma separated. */
+ for (early = __start_early_lsm_info;
+ early < __end_early_lsm_info; early++)
+ if (is_enabled(early))
+ pr_cont("%s%s", first++ == 0 ? "" : ",", early->name);
+ for (lsm = ordered_lsms; *lsm; lsm++)
+ if (is_enabled(*lsm))
+ pr_cont("%s%s", first++ == 0 ? "" : ",", (*lsm)->name);
+
+ pr_cont("\n");
+}
+
+/**
+ * lsm_early_cred - during initialization allocate a composite cred blob
+ * @cred: the cred that needs a blob
+ *
+ * Allocate the cred blob for all the modules
+ */
+static void __init lsm_early_cred(struct cred *cred)
+{
+ int rc = lsm_cred_alloc(cred, GFP_KERNEL);
+
+ if (rc)
+ panic("%s: Early cred alloc failed.\n", __func__);
+}
+
+/**
+ * lsm_early_task - during initialization allocate a composite task blob
+ * @task: the task that needs a blob
+ *
+ * Allocate the task blob for all the modules
+ */
+static void __init lsm_early_task(struct task_struct *task)
+{
+ int rc = lsm_task_alloc(task);
+
+ if (rc)
+ panic("%s: Early task alloc failed.\n", __func__);
+}
+
+static void __init ordered_lsm_init(void)
+{
+ struct lsm_info **lsm;
+
+ if (chosen_lsm_order) {
+ if (chosen_major_lsm) {
+ pr_warn("security=%s is ignored because it is superseded by lsm=%s\n",
+ chosen_major_lsm, chosen_lsm_order);
+ chosen_major_lsm = NULL;
+ }
+ ordered_lsm_parse(chosen_lsm_order, "cmdline");
+ } else
+ ordered_lsm_parse(builtin_lsm_order, "builtin");
+
+ for (lsm = ordered_lsms; *lsm; lsm++)
+ prepare_lsm(*lsm);
+
+ report_lsm_order();
+
+ init_debug("cred blob size = %d\n", blob_sizes.lbs_cred);
+ init_debug("file blob size = %d\n", blob_sizes.lbs_file);
+ init_debug("ib blob size = %d\n", blob_sizes.lbs_ib);
+ init_debug("inode blob size = %d\n", blob_sizes.lbs_inode);
+ init_debug("ipc blob size = %d\n", blob_sizes.lbs_ipc);
+#ifdef CONFIG_KEYS
+ init_debug("key blob size = %d\n", blob_sizes.lbs_key);
+#endif /* CONFIG_KEYS */
+ init_debug("msg_msg blob size = %d\n", blob_sizes.lbs_msg_msg);
+ init_debug("sock blob size = %d\n", blob_sizes.lbs_sock);
+ init_debug("superblock blob size = %d\n", blob_sizes.lbs_superblock);
+ init_debug("perf event blob size = %d\n", blob_sizes.lbs_perf_event);
+ init_debug("task blob size = %d\n", blob_sizes.lbs_task);
+ init_debug("tun device blob size = %d\n", blob_sizes.lbs_tun_dev);
+ init_debug("xattr slots = %d\n", blob_sizes.lbs_xattr_count);
+ init_debug("bdev blob size = %d\n", blob_sizes.lbs_bdev);
+ init_debug("bpf map blob size = %d\n", blob_sizes.lbs_bpf_map);
+ init_debug("bpf prog blob size = %d\n", blob_sizes.lbs_bpf_prog);
+ init_debug("bpf token blob size = %d\n", blob_sizes.lbs_bpf_token);
+
+ /*
+ * Create any kmem_caches needed for blobs
+ */
+ if (blob_sizes.lbs_file)
+ lsm_file_cache = kmem_cache_create("lsm_file_cache",
+ blob_sizes.lbs_file, 0,
+ SLAB_PANIC, NULL);
+ if (blob_sizes.lbs_inode)
+ lsm_inode_cache = kmem_cache_create("lsm_inode_cache",
+ blob_sizes.lbs_inode, 0,
+ SLAB_PANIC, NULL);
+
+ lsm_early_cred((struct cred *) current->cred);
+ lsm_early_task(current);
+ for (lsm = ordered_lsms; *lsm; lsm++)
+ initialize_lsm(*lsm);
+}
+
+static bool match_last_lsm(const char *list, const char *lsm)
+{
+ const char *last;
+
+ if (WARN_ON(!list || !lsm))
+ return false;
+ last = strrchr(list, ',');
+ if (last)
+ /* Pass the comma, strcmp() will check for '\0' */
+ last++;
+ else
+ last = list;
+ return !strcmp(last, lsm);
+}
+
+static int lsm_append(const char *new, char **result)
+{
+ char *cp;
+
+ if (*result == NULL) {
+ *result = kstrdup(new, GFP_KERNEL);
+ if (*result == NULL)
+ return -ENOMEM;
+ } else {
+ /* Check if it is the last registered name */
+ if (match_last_lsm(*result, new))
+ return 0;
+ cp = kasprintf(GFP_KERNEL, "%s,%s", *result, new);
+ if (cp == NULL)
+ return -ENOMEM;
+ kfree(*result);
+ *result = cp;
+ }
+ return 0;
+}
+
+static void __init lsm_static_call_init(struct security_hook_list *hl)
+{
+ struct lsm_static_call *scall = hl->scalls;
+ int i;
+
+ for (i = 0; i < MAX_LSM_COUNT; i++) {
+ /* Update the first static call that is not used yet */
+ if (!scall->hl) {
+ __static_call_update(scall->key, scall->trampoline,
+ hl->hook.lsm_func_addr);
+ scall->hl = hl;
+ static_branch_enable(scall->active);
+ return;
+ }
+ scall++;
+ }
+ panic("%s - Ran out of static slots.\n", __func__);
+}
+
+/**
+ * security_add_hooks - Add a modules hooks to the hook lists.
+ * @hooks: the hooks to add
+ * @count: the number of hooks to add
+ * @lsmid: the identification information for the security module
+ *
+ * Each LSM has to register its hooks with the infrastructure.
+ */
+void __init security_add_hooks(struct security_hook_list *hooks, int count,
+ const struct lsm_id *lsmid)
+{
+ int i;
+
+ /*
+ * A security module may call security_add_hooks() more
+ * than once during initialization, and LSM initialization
+ * is serialized. Landlock is one such case.
+ * Look at the previous entry, if there is one, for duplication.
+ */
+ if (lsm_active_cnt == 0 || lsm_idlist[lsm_active_cnt - 1] != lsmid) {
+ if (lsm_active_cnt >= MAX_LSM_COUNT)
+ panic("%s Too many LSMs registered.\n", __func__);
+ lsm_idlist[lsm_active_cnt++] = lsmid;
+ }
+
+ for (i = 0; i < count; i++) {
+ hooks[i].lsmid = lsmid;
+ lsm_static_call_init(&hooks[i]);
+ }
+
+ /*
+ * Don't try to append during early_security_init(), we'll come back
+ * and fix this up afterwards.
+ */
+ if (slab_is_available()) {
+ if (lsm_append(lsmid->name, &lsm_names) < 0)
+ panic("%s - Cannot get early memory.\n", __func__);
+ }
+}
+
+int __init early_security_init(void)
+{
+ struct lsm_info *lsm;
+
+ for (lsm = __start_early_lsm_info; lsm < __end_early_lsm_info; lsm++) {
+ if (!lsm->enabled)
+ lsm->enabled = &lsm_enabled_true;
+ prepare_lsm(lsm);
+ initialize_lsm(lsm);
+ }
+
+ return 0;
+}
+
+/**
+ * security_init - initializes the security framework
+ *
+ * This should be called early in the kernel initialization sequence.
+ */
+int __init security_init(void)
+{
+ struct lsm_info *lsm;
+
+ init_debug("legacy security=%s\n", chosen_major_lsm ? : " *unspecified*");
+ init_debug(" CONFIG_LSM=%s\n", builtin_lsm_order);
+ init_debug("boot arg lsm=%s\n", chosen_lsm_order ? : " *unspecified*");
+
+ /*
+ * Append the names of the early LSM modules now that kmalloc() is
+ * available
+ */
+ for (lsm = __start_early_lsm_info; lsm < __end_early_lsm_info; lsm++) {
+ init_debug(" early started: %s (%s)\n", lsm->name,
+ is_enabled(lsm) ? "enabled" : "disabled");
+ if (lsm->enabled)
+ lsm_append(lsm->name, &lsm_names);
+ }
+
+ /* Load LSMs in specified order. */
+ ordered_lsm_init();
+
+ return 0;
+}
diff --git a/security/security.c b/security/security.c
index 667479c2e82f..dc9734f0d45c 100644
--- a/security/security.c
+++ b/security/security.c
@@ -32,24 +32,7 @@
#include <net/flow.h>
#include <net/sock.h>
-#define SECURITY_HOOK_ACTIVE_KEY(HOOK, IDX) security_hook_active_##HOOK##_##IDX
-
-/*
- * Identifier for the LSM static calls.
- * HOOK is an LSM hook as defined in linux/lsm_hookdefs.h
- * IDX is the index of the static call. 0 <= NUM < MAX_LSM_COUNT
- */
-#define LSM_STATIC_CALL(HOOK, IDX) lsm_static_call_##HOOK##_##IDX
-
-/*
- * Call the macro M for each LSM hook MAX_LSM_COUNT times.
- */
-#define LSM_LOOP_UNROLL(M, ...) \
-do { \
- UNROLL(MAX_LSM_COUNT, M, __VA_ARGS__) \
-} while (0)
-
-#define LSM_DEFINE_UNROLL(M, ...) UNROLL(MAX_LSM_COUNT, M, __VA_ARGS__)
+#include "lsm.h"
/*
* These are descriptions of the reasons that can be passed to the
@@ -90,21 +73,29 @@ const char *const lockdown_reasons[LOCKDOWN_CONFIDENTIALITY_MAX + 1] = {
[LOCKDOWN_CONFIDENTIALITY_MAX] = "confidentiality",
};
-static struct kmem_cache *lsm_file_cache;
-static struct kmem_cache *lsm_inode_cache;
+struct lsm_blob_sizes blob_sizes;
-char *lsm_names;
-static struct lsm_blob_sizes blob_sizes __ro_after_init;
+struct kmem_cache *lsm_file_cache;
+struct kmem_cache *lsm_inode_cache;
-/* Boot-time LSM user choice */
-static __initdata const char *chosen_lsm_order;
-static __initdata const char *chosen_major_lsm;
+#define SECURITY_HOOK_ACTIVE_KEY(HOOK, IDX) security_hook_active_##HOOK##_##IDX
-static __initconst const char *const builtin_lsm_order = CONFIG_LSM;
+/*
+ * Identifier for the LSM static calls.
+ * HOOK is an LSM hook as defined in linux/lsm_hookdefs.h
+ * IDX is the index of the static call. 0 <= NUM < MAX_LSM_COUNT
+ */
+#define LSM_STATIC_CALL(HOOK, IDX) lsm_static_call_##HOOK##_##IDX
-/* Ordered list of LSMs to initialize. */
-static __initdata struct lsm_info *ordered_lsms[MAX_LSM_COUNT + 1];
-static __initdata struct lsm_info *exclusive;
+/*
+ * Call the macro M for each LSM hook MAX_LSM_COUNT times.
+ */
+#define LSM_LOOP_UNROLL(M, ...) \
+do { \
+ UNROLL(MAX_LSM_COUNT, M, __VA_ARGS__) \
+} while (0)
+
+#define LSM_DEFINE_UNROLL(M, ...) UNROLL(MAX_LSM_COUNT, M, __VA_ARGS__)
#ifdef CONFIG_HAVE_STATIC_CALL
#define LSM_HOOK_TRAMP(NAME, NUM) \
@@ -155,496 +146,25 @@ struct lsm_static_calls_table
#undef INIT_LSM_STATIC_CALL
};
-static __initdata bool debug;
-#define init_debug(...) \
- do { \
- if (debug) \
- pr_info(__VA_ARGS__); \
- } while (0)
-
-static bool __init is_enabled(struct lsm_info *lsm)
-{
- if (!lsm->enabled)
- return false;
-
- return *lsm->enabled;
-}
-
-/* Mark an LSM's enabled flag. */
-static int lsm_enabled_true __initdata = 1;
-static int lsm_enabled_false __initdata = 0;
-static void __init set_enabled(struct lsm_info *lsm, bool enabled)
-{
- /*
- * When an LSM hasn't configured an enable variable, we can use
- * a hard-coded location for storing the default enabled state.
- */
- if (!lsm->enabled) {
- if (enabled)
- lsm->enabled = &lsm_enabled_true;
- else
- lsm->enabled = &lsm_enabled_false;
- } else if (lsm->enabled == &lsm_enabled_true) {
- if (!enabled)
- lsm->enabled = &lsm_enabled_false;
- } else if (lsm->enabled == &lsm_enabled_false) {
- if (enabled)
- lsm->enabled = &lsm_enabled_true;
- } else {
- *lsm->enabled = enabled;
- }
-}
-
-/* Is an LSM already listed in the ordered LSMs list? */
-static bool __init exists_ordered_lsm(struct lsm_info *lsm)
-{
- struct lsm_info **check;
-
- for (check = ordered_lsms; *check; check++)
- if (*check == lsm)
- return true;
-
- return false;
-}
-
-/* Append an LSM to the list of ordered LSMs to initialize. */
-static int last_lsm __initdata;
-static void __init append_ordered_lsm(struct lsm_info *lsm, const char *from)
-{
- /* Ignore duplicate selections. */
- if (exists_ordered_lsm(lsm))
- return;
-
- if (WARN(last_lsm == MAX_LSM_COUNT, "%s: out of LSM static calls!?\n", from))
- return;
-
- /* Enable this LSM, if it is not already set. */
- if (!lsm->enabled)
- lsm->enabled = &lsm_enabled_true;
- ordered_lsms[last_lsm++] = lsm;
-
- init_debug("%s ordered: %s (%s)\n", from, lsm->name,
- is_enabled(lsm) ? "enabled" : "disabled");
-}
-
-/* Is an LSM allowed to be initialized? */
-static bool __init lsm_allowed(struct lsm_info *lsm)
-{
- /* Skip if the LSM is disabled. */
- if (!is_enabled(lsm))
- return false;
-
- /* Not allowed if another exclusive LSM already initialized. */
- if ((lsm->flags & LSM_FLAG_EXCLUSIVE) && exclusive) {
- init_debug("exclusive disabled: %s\n", lsm->name);
- return false;
- }
-
- return true;
-}
-
-static void __init lsm_set_blob_size(int *need, int *lbs)
-{
- int offset;
-
- if (*need <= 0)
- return;
-
- offset = ALIGN(*lbs, sizeof(void *));
- *lbs = offset + *need;
- *need = offset;
-}
-
-static void __init lsm_set_blob_sizes(struct lsm_blob_sizes *needed)
-{
- if (!needed)
- return;
-
- lsm_set_blob_size(&needed->lbs_cred, &blob_sizes.lbs_cred);
- lsm_set_blob_size(&needed->lbs_file, &blob_sizes.lbs_file);
- lsm_set_blob_size(&needed->lbs_ib, &blob_sizes.lbs_ib);
- /*
- * The inode blob gets an rcu_head in addition to
- * what the modules might need.
- */
- if (needed->lbs_inode && blob_sizes.lbs_inode == 0)
- blob_sizes.lbs_inode = sizeof(struct rcu_head);
- lsm_set_blob_size(&needed->lbs_inode, &blob_sizes.lbs_inode);
- lsm_set_blob_size(&needed->lbs_ipc, &blob_sizes.lbs_ipc);
- lsm_set_blob_size(&needed->lbs_key, &blob_sizes.lbs_key);
- lsm_set_blob_size(&needed->lbs_msg_msg, &blob_sizes.lbs_msg_msg);
- lsm_set_blob_size(&needed->lbs_perf_event, &blob_sizes.lbs_perf_event);
- lsm_set_blob_size(&needed->lbs_sock, &blob_sizes.lbs_sock);
- lsm_set_blob_size(&needed->lbs_superblock, &blob_sizes.lbs_superblock);
- lsm_set_blob_size(&needed->lbs_task, &blob_sizes.lbs_task);
- lsm_set_blob_size(&needed->lbs_tun_dev, &blob_sizes.lbs_tun_dev);
- lsm_set_blob_size(&needed->lbs_xattr_count,
- &blob_sizes.lbs_xattr_count);
- lsm_set_blob_size(&needed->lbs_bdev, &blob_sizes.lbs_bdev);
- lsm_set_blob_size(&needed->lbs_bpf_map, &blob_sizes.lbs_bpf_map);
- lsm_set_blob_size(&needed->lbs_bpf_prog, &blob_sizes.lbs_bpf_prog);
- lsm_set_blob_size(&needed->lbs_bpf_token, &blob_sizes.lbs_bpf_token);
-}
-
-/* Prepare LSM for initialization. */
-static void __init prepare_lsm(struct lsm_info *lsm)
-{
- int enabled = lsm_allowed(lsm);
-
- /* Record enablement (to handle any following exclusive LSMs). */
- set_enabled(lsm, enabled);
-
- /* If enabled, do pre-initialization work. */
- if (enabled) {
- if ((lsm->flags & LSM_FLAG_EXCLUSIVE) && !exclusive) {
- exclusive = lsm;
- init_debug("exclusive chosen: %s\n", lsm->name);
- }
-
- lsm_set_blob_sizes(lsm->blobs);
- }
-}
-
-/* Initialize a given LSM, if it is enabled. */
-static void __init initialize_lsm(struct lsm_info *lsm)
-{
- if (is_enabled(lsm)) {
- int ret;
-
- init_debug("initializing %s\n", lsm->name);
- ret = lsm->init();
- WARN(ret, "%s failed to initialize: %d\n", lsm->name, ret);
- }
-}
-
-/*
- * Current index to use while initializing the lsm id list.
- */
-u32 lsm_active_cnt __ro_after_init;
-const struct lsm_id *lsm_idlist[MAX_LSM_COUNT];
-
-/* Populate ordered LSMs list from comma-separated LSM name list. */
-static void __init ordered_lsm_parse(const char *order, const char *origin)
-{
- struct lsm_info *lsm;
- char *sep, *name, *next;
-
- /* LSM_ORDER_FIRST is always first. */
- for (lsm = __start_lsm_info; lsm < __end_lsm_info; lsm++) {
- if (lsm->order == LSM_ORDER_FIRST)
- append_ordered_lsm(lsm, " first");
- }
-
- /* Process "security=", if given. */
- if (chosen_major_lsm) {
- struct lsm_info *major;
-
- /*
- * To match the original "security=" behavior, this
- * explicitly does NOT fallback to another Legacy Major
- * if the selected one was separately disabled: disable
- * all non-matching Legacy Major LSMs.
- */
- for (major = __start_lsm_info; major < __end_lsm_info;
- major++) {
- if ((major->flags & LSM_FLAG_LEGACY_MAJOR) &&
- strcmp(major->name, chosen_major_lsm) != 0) {
- set_enabled(major, false);
- init_debug("security=%s disabled: %s (only one legacy major LSM)\n",
- chosen_major_lsm, major->name);
- }
- }
- }
-
- sep = kstrdup(order, GFP_KERNEL);
- next = sep;
- /* Walk the list, looking for matching LSMs. */
- while ((name = strsep(&next, ",")) != NULL) {
- bool found = false;
-
- for (lsm = __start_lsm_info; lsm < __end_lsm_info; lsm++) {
- if (strcmp(lsm->name, name) == 0) {
- if (lsm->order == LSM_ORDER_MUTABLE)
- append_ordered_lsm(lsm, origin);
- found = true;
- }
- }
-
- if (!found)
- init_debug("%s ignored: %s (not built into kernel)\n",
- origin, name);
- }
-
- /* Process "security=", if given. */
- if (chosen_major_lsm) {
- for (lsm = __start_lsm_info; lsm < __end_lsm_info; lsm++) {
- if (exists_ordered_lsm(lsm))
- continue;
- if (strcmp(lsm->name, chosen_major_lsm) == 0)
- append_ordered_lsm(lsm, "security=");
- }
- }
-
- /* LSM_ORDER_LAST is always last. */
- for (lsm = __start_lsm_info; lsm < __end_lsm_info; lsm++) {
- if (lsm->order == LSM_ORDER_LAST)
- append_ordered_lsm(lsm, " last");
- }
-
- /* Disable all LSMs not in the ordered list. */
- for (lsm = __start_lsm_info; lsm < __end_lsm_info; lsm++) {
- if (exists_ordered_lsm(lsm))
- continue;
- set_enabled(lsm, false);
- init_debug("%s skipped: %s (not in requested order)\n",
- origin, lsm->name);
- }
-
- kfree(sep);
-}
-
-static void __init lsm_static_call_init(struct security_hook_list *hl)
-{
- struct lsm_static_call *scall = hl->scalls;
- int i;
-
- for (i = 0; i < MAX_LSM_COUNT; i++) {
- /* Update the first static call that is not used yet */
- if (!scall->hl) {
- __static_call_update(scall->key, scall->trampoline,
- hl->hook.lsm_func_addr);
- scall->hl = hl;
- static_branch_enable(scall->active);
- return;
- }
- scall++;
- }
- panic("%s - Ran out of static slots.\n", __func__);
-}
-
-static void __init lsm_early_cred(struct cred *cred);
-static void __init lsm_early_task(struct task_struct *task);
-
-static int lsm_append(const char *new, char **result);
-
-static void __init report_lsm_order(void)
-{
- struct lsm_info **lsm, *early;
- int first = 0;
-
- pr_info("initializing lsm=");
-
- /* Report each enabled LSM name, comma separated. */
- for (early = __start_early_lsm_info;
- early < __end_early_lsm_info; early++)
- if (is_enabled(early))
- pr_cont("%s%s", first++ == 0 ? "" : ",", early->name);
- for (lsm = ordered_lsms; *lsm; lsm++)
- if (is_enabled(*lsm))
- pr_cont("%s%s", first++ == 0 ? "" : ",", (*lsm)->name);
-
- pr_cont("\n");
-}
-
-static void __init ordered_lsm_init(void)
-{
- struct lsm_info **lsm;
-
- if (chosen_lsm_order) {
- if (chosen_major_lsm) {
- pr_warn("security=%s is ignored because it is superseded by lsm=%s\n",
- chosen_major_lsm, chosen_lsm_order);
- chosen_major_lsm = NULL;
- }
- ordered_lsm_parse(chosen_lsm_order, "cmdline");
- } else
- ordered_lsm_parse(builtin_lsm_order, "builtin");
-
- for (lsm = ordered_lsms; *lsm; lsm++)
- prepare_lsm(*lsm);
-
- report_lsm_order();
-
- init_debug("cred blob size = %d\n", blob_sizes.lbs_cred);
- init_debug("file blob size = %d\n", blob_sizes.lbs_file);
- init_debug("ib blob size = %d\n", blob_sizes.lbs_ib);
- init_debug("inode blob size = %d\n", blob_sizes.lbs_inode);
- init_debug("ipc blob size = %d\n", blob_sizes.lbs_ipc);
-#ifdef CONFIG_KEYS
- init_debug("key blob size = %d\n", blob_sizes.lbs_key);
-#endif /* CONFIG_KEYS */
- init_debug("msg_msg blob size = %d\n", blob_sizes.lbs_msg_msg);
- init_debug("sock blob size = %d\n", blob_sizes.lbs_sock);
- init_debug("superblock blob size = %d\n", blob_sizes.lbs_superblock);
- init_debug("perf event blob size = %d\n", blob_sizes.lbs_perf_event);
- init_debug("task blob size = %d\n", blob_sizes.lbs_task);
- init_debug("tun device blob size = %d\n", blob_sizes.lbs_tun_dev);
- init_debug("xattr slots = %d\n", blob_sizes.lbs_xattr_count);
- init_debug("bdev blob size = %d\n", blob_sizes.lbs_bdev);
- init_debug("bpf map blob size = %d\n", blob_sizes.lbs_bpf_map);
- init_debug("bpf prog blob size = %d\n", blob_sizes.lbs_bpf_prog);
- init_debug("bpf token blob size = %d\n", blob_sizes.lbs_bpf_token);
-
- /*
- * Create any kmem_caches needed for blobs
- */
- if (blob_sizes.lbs_file)
- lsm_file_cache = kmem_cache_create("lsm_file_cache",
- blob_sizes.lbs_file, 0,
- SLAB_PANIC, NULL);
- if (blob_sizes.lbs_inode)
- lsm_inode_cache = kmem_cache_create("lsm_inode_cache",
- blob_sizes.lbs_inode, 0,
- SLAB_PANIC, NULL);
-
- lsm_early_cred((struct cred *) current->cred);
- lsm_early_task(current);
- for (lsm = ordered_lsms; *lsm; lsm++)
- initialize_lsm(*lsm);
-}
-
-int __init early_security_init(void)
-{
- struct lsm_info *lsm;
-
- for (lsm = __start_early_lsm_info; lsm < __end_early_lsm_info; lsm++) {
- if (!lsm->enabled)
- lsm->enabled = &lsm_enabled_true;
- prepare_lsm(lsm);
- initialize_lsm(lsm);
- }
-
- return 0;
-}
-
/**
- * security_init - initializes the security framework
+ * lsm_file_alloc - allocate a composite file blob
+ * @file: the file that needs a blob
*
- * This should be called early in the kernel initialization sequence.
- */
-int __init security_init(void)
-{
- struct lsm_info *lsm;
-
- init_debug("legacy security=%s\n", chosen_major_lsm ? : " *unspecified*");
- init_debug(" CONFIG_LSM=%s\n", builtin_lsm_order);
- init_debug("boot arg lsm=%s\n", chosen_lsm_order ? : " *unspecified*");
-
- /*
- * Append the names of the early LSM modules now that kmalloc() is
- * available
- */
- for (lsm = __start_early_lsm_info; lsm < __end_early_lsm_info; lsm++) {
- init_debug(" early started: %s (%s)\n", lsm->name,
- is_enabled(lsm) ? "enabled" : "disabled");
- if (lsm->enabled)
- lsm_append(lsm->name, &lsm_names);
- }
-
- /* Load LSMs in specified order. */
- ordered_lsm_init();
-
- return 0;
-}
-
-/* Save user chosen LSM */
-static int __init choose_major_lsm(char *str)
-{
- chosen_major_lsm = str;
- return 1;
-}
-__setup("security=", choose_major_lsm);
-
-/* Explicitly choose LSM initialization order. */
-static int __init choose_lsm_order(char *str)
-{
- chosen_lsm_order = str;
- return 1;
-}
-__setup("lsm=", choose_lsm_order);
-
-/* Enable LSM order debugging. */
-static int __init enable_debug(char *str)
-{
- debug = true;
- return 1;
-}
-__setup("lsm.debug", enable_debug);
-
-static bool match_last_lsm(const char *list, const char *lsm)
-{
- const char *last;
-
- if (WARN_ON(!list || !lsm))
- return false;
- last = strrchr(list, ',');
- if (last)
- /* Pass the comma, strcmp() will check for '\0' */
- last++;
- else
- last = list;
- return !strcmp(last, lsm);
-}
-
-static int lsm_append(const char *new, char **result)
-{
- char *cp;
-
- if (*result == NULL) {
- *result = kstrdup(new, GFP_KERNEL);
- if (*result == NULL)
- return -ENOMEM;
- } else {
- /* Check if it is the last registered name */
- if (match_last_lsm(*result, new))
- return 0;
- cp = kasprintf(GFP_KERNEL, "%s,%s", *result, new);
- if (cp == NULL)
- return -ENOMEM;
- kfree(*result);
- *result = cp;
- }
- return 0;
-}
-
-/**
- * security_add_hooks - Add a modules hooks to the hook lists.
- * @hooks: the hooks to add
- * @count: the number of hooks to add
- * @lsmid: the identification information for the security module
+ * Allocate the file blob for all the modules
*
- * Each LSM has to register its hooks with the infrastructure.
+ * Returns 0, or -ENOMEM if memory can't be allocated.
*/
-void __init security_add_hooks(struct security_hook_list *hooks, int count,
- const struct lsm_id *lsmid)
+static int lsm_file_alloc(struct file *file)
{
- int i;
-
- /*
- * A security module may call security_add_hooks() more
- * than once during initialization, and LSM initialization
- * is serialized. Landlock is one such case.
- * Look at the previous entry, if there is one, for duplication.
- */
- if (lsm_active_cnt == 0 || lsm_idlist[lsm_active_cnt - 1] != lsmid) {
- if (lsm_active_cnt >= MAX_LSM_COUNT)
- panic("%s Too many LSMs registered.\n", __func__);
- lsm_idlist[lsm_active_cnt++] = lsmid;
+ if (!lsm_file_cache) {
+ file->f_security = NULL;
+ return 0;
}
- for (i = 0; i < count; i++) {
- hooks[i].lsmid = lsmid;
- lsm_static_call_init(&hooks[i]);
- }
-
- /*
- * Don't try to append during early_security_init(), we'll come back
- * and fix this up afterwards.
- */
- if (slab_is_available()) {
- if (lsm_append(lsmid->name, &lsm_names) < 0)
- panic("%s - Cannot get early memory.\n", __func__);
- }
+ file->f_security = kmem_cache_zalloc(lsm_file_cache, GFP_KERNEL);
+ if (file->f_security == NULL)
+ return -ENOMEM;
+ return 0;
}
/**
@@ -679,46 +199,11 @@ static int lsm_blob_alloc(void **dest, size_t size, gfp_t gfp)
*
* Returns 0, or -ENOMEM if memory can't be allocated.
*/
-static int lsm_cred_alloc(struct cred *cred, gfp_t gfp)
+int lsm_cred_alloc(struct cred *cred, gfp_t gfp)
{
return lsm_blob_alloc(&cred->security, blob_sizes.lbs_cred, gfp);
}
-/**
- * lsm_early_cred - during initialization allocate a composite cred blob
- * @cred: the cred that needs a blob
- *
- * Allocate the cred blob for all the modules
- */
-static void __init lsm_early_cred(struct cred *cred)
-{
- int rc = lsm_cred_alloc(cred, GFP_KERNEL);
-
- if (rc)
- panic("%s: Early cred alloc failed.\n", __func__);
-}
-
-/**
- * lsm_file_alloc - allocate a composite file blob
- * @file: the file that needs a blob
- *
- * Allocate the file blob for all the modules
- *
- * Returns 0, or -ENOMEM if memory can't be allocated.
- */
-static int lsm_file_alloc(struct file *file)
-{
- if (!lsm_file_cache) {
- file->f_security = NULL;
- return 0;
- }
-
- file->f_security = kmem_cache_zalloc(lsm_file_cache, GFP_KERNEL);
- if (file->f_security == NULL)
- return -ENOMEM;
- return 0;
-}
-
/**
* lsm_inode_alloc - allocate a composite inode blob
* @inode: the inode that needs a blob
@@ -749,7 +234,7 @@ static int lsm_inode_alloc(struct inode *inode, gfp_t gfp)
*
* Returns 0, or -ENOMEM if memory can't be allocated.
*/
-static int lsm_task_alloc(struct task_struct *task)
+int lsm_task_alloc(struct task_struct *task)
{
return lsm_blob_alloc(&task->security, blob_sizes.lbs_task, GFP_KERNEL);
}
@@ -851,20 +336,6 @@ static int lsm_bpf_token_alloc(struct bpf_token *token)
}
#endif /* CONFIG_BPF_SYSCALL */
-/**
- * lsm_early_task - during initialization allocate a composite task blob
- * @task: the task that needs a blob
- *
- * Allocate the task blob for all the modules
- */
-static void __init lsm_early_task(struct task_struct *task)
-{
- int rc = lsm_task_alloc(task);
-
- if (rc)
- panic("%s: Early task alloc failed.\n", __func__);
-}
-
/**
* lsm_superblock_alloc - allocate a composite superblock blob
* @sb: the superblock that needs a blob
--
2.51.1.dirty
^ permalink raw reply related
* [PATCH v5 01/34] lsm: split the notifier code out into lsm_notifier.c
From: Paul Moore @ 2025-10-17 20:24 UTC (permalink / raw)
To: linux-security-module, linux-integrity, selinux
Cc: John Johansen, Mimi Zohar, Roberto Sassu, Fan Wu,
Mickaël Salaün, Günther Noack, Kees Cook,
Micah Morton, Casey Schaufler, Tetsuo Handa, Nicolas Bouchinet,
Xiu Jianfeng
In-Reply-To: <20251017202456.484010-36-paul@paul-moore.com>
In an effort to decompose security/security.c somewhat to make it less
twisted and unwieldy, pull out the LSM notifier code into a new file
as it is fairly well self-contained.
No code changes.
Reviewed-by: Kees Cook <kees@kernel.org>
Reviewed-by: John Johansen <john.johansen@canonical.com>
Reviewed-by: Casey Schaufler <casey@schaufler-ca.com>
Reviewed-by: Mimi Zohar <zohar@linux.ibm.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
---
security/Makefile | 2 +-
security/lsm_notifier.c | 31 +++++++++++++++++++++++++++++++
security/security.c | 23 -----------------------
3 files changed, 32 insertions(+), 24 deletions(-)
create mode 100644 security/lsm_notifier.c
diff --git a/security/Makefile b/security/Makefile
index 22ff4c8bd8ce..14d87847bce8 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -11,7 +11,7 @@ obj-$(CONFIG_SECURITY) += lsm_syscalls.o
obj-$(CONFIG_MMU) += min_addr.o
# Object file lists
-obj-$(CONFIG_SECURITY) += security.o
+obj-$(CONFIG_SECURITY) += security.o lsm_notifier.o
obj-$(CONFIG_SECURITYFS) += inode.o
obj-$(CONFIG_SECURITY_SELINUX) += selinux/
obj-$(CONFIG_SECURITY_SMACK) += smack/
diff --git a/security/lsm_notifier.c b/security/lsm_notifier.c
new file mode 100644
index 000000000000..c92fad5d57d4
--- /dev/null
+++ b/security/lsm_notifier.c
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * LSM notifier functions
+ *
+ */
+
+#include <linux/notifier.h>
+#include <linux/security.h>
+
+static BLOCKING_NOTIFIER_HEAD(blocking_lsm_notifier_chain);
+
+int call_blocking_lsm_notifier(enum lsm_event event, void *data)
+{
+ return blocking_notifier_call_chain(&blocking_lsm_notifier_chain,
+ event, data);
+}
+EXPORT_SYMBOL(call_blocking_lsm_notifier);
+
+int register_blocking_lsm_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&blocking_lsm_notifier_chain,
+ nb);
+}
+EXPORT_SYMBOL(register_blocking_lsm_notifier);
+
+int unregister_blocking_lsm_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_unregister(&blocking_lsm_notifier_chain,
+ nb);
+}
+EXPORT_SYMBOL(unregister_blocking_lsm_notifier);
diff --git a/security/security.c b/security/security.c
index 4d3c03a4524c..667479c2e82f 100644
--- a/security/security.c
+++ b/security/security.c
@@ -90,8 +90,6 @@ const char *const lockdown_reasons[LOCKDOWN_CONFIDENTIALITY_MAX + 1] = {
[LOCKDOWN_CONFIDENTIALITY_MAX] = "confidentiality",
};
-static BLOCKING_NOTIFIER_HEAD(blocking_lsm_notifier_chain);
-
static struct kmem_cache *lsm_file_cache;
static struct kmem_cache *lsm_inode_cache;
@@ -649,27 +647,6 @@ void __init security_add_hooks(struct security_hook_list *hooks, int count,
}
}
-int call_blocking_lsm_notifier(enum lsm_event event, void *data)
-{
- return blocking_notifier_call_chain(&blocking_lsm_notifier_chain,
- event, data);
-}
-EXPORT_SYMBOL(call_blocking_lsm_notifier);
-
-int register_blocking_lsm_notifier(struct notifier_block *nb)
-{
- return blocking_notifier_chain_register(&blocking_lsm_notifier_chain,
- nb);
-}
-EXPORT_SYMBOL(register_blocking_lsm_notifier);
-
-int unregister_blocking_lsm_notifier(struct notifier_block *nb)
-{
- return blocking_notifier_chain_unregister(&blocking_lsm_notifier_chain,
- nb);
-}
-EXPORT_SYMBOL(unregister_blocking_lsm_notifier);
-
/**
* lsm_blob_alloc - allocate a composite blob
* @dest: the destination for the blob
--
2.51.1.dirty
^ permalink raw reply related
* [PATCH v5 0/34] Rework the LSM initialization
From: Paul Moore @ 2025-10-17 20:24 UTC (permalink / raw)
To: linux-security-module, linux-integrity, selinux
Cc: John Johansen, Mimi Zohar, Roberto Sassu, Fan Wu,
Mickaël Salaün, Günther Noack, Kees Cook,
Micah Morton, Casey Schaufler, Tetsuo Handa, Nicolas Bouchinet,
Xiu Jianfeng
This is the fifth, and likely final, revision of the LSM rework patchset.
The number of changes in this revision are very minor and barring any
surprises I expect to merge this into the lsm/dev branch next week; I'll
send a notice when I do. While there isn't anything in this revision
that people haven't seen previously, if you do have any concerns or
feedback, please let me know. Once again, thank you to all of you that
have taken the time to review these patches.
I've aldo updated the working-lsm_init_rework branch of the main LSM
tree to contain the latest v5 revision of the patchset:
https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/lsm.git/log/?h=working-lsm_init_rework
The v4 patchset:
https://lore.kernel.org/linux-security-module/20250916220355.252592-36-paul@paul-moore.com/
The RFC/v3 patchset:
https://lore.kernel.org/linux-security-module/20250814225159.275901-36-paul@paul-moore.com/
The RFC/v2 patchset:
https://lore.kernel.org/linux-security-module/20250721232142.77224-36-paul@paul-moore.com/
The RFC/v1 patchset is below, the cover letter provides some background
and motivation for this series which still applies:
https://lore.kernel.org/linux-security-module/20250409185019.238841-31-paul@paul-moore.com/
CHANGELOG
v5:
- rebased to lsm/dev branch post v6.18-rc1
- fixed a !CONFIG_SECURITYFS bug (kernel test robot)
- fixed a missing "__rcu" annotation on a cast (kernel test robot)
v4:
- reworked the lsm_read() function (John, Roberto, Tetsuo)
- replaced the IMA/EVM patch with one from Roberto
RFC/v3:
- rebased to lsm/dev branch
- fixed IMA/EVM initcall comment (Roberto)
- fixed CONFIG_IMA and CONFIG_EVM problems (Nicolas, Roberto)
- fixed CONFIG_SECURITY_SMACK_NETFILTER problems (Roberto)
- fixed the IMA/EVM header file include macro protections
- fixed an off-by-one string length issue in lsm_read() (Casey)
RFC/v2:
- rename lsm_prep_single() to lsm_prepare()
- drop the lsm_prop counting patch
- drop the platform_certs changes from the IMA/EVM patch (Mimi)
- split/reorder anough patches in the patchset that I lost track
- added missing function comment blocks in the SELinux patches
- split patch 04/29 into smaller patches (Kees)
- fix an LSM list output problem in an intermediate patch (Kees)
- preserve the "lsm_active_cnt" variable name (Casey)
- cache the lsm_read() string (Kees)
- squashed, split, and reordered the enabled/ordering patches
- reworked the Smack patch (Casey)
- conditionalized the SELinux IB init code (Stephen)
- fixed missing Smack "__init" annotation (Fan)
- fixed a potential unused variable warning in IMA/EVM (John)
- fixed the placeholder commit descriptions (various)
RFC/v1:
- initial version
--
Paul Moore (33):
lsm: split the notifier code out into lsm_notifier.c
lsm: split the init code out into lsm_init.c
lsm: consolidate lsm_allowed() and prepare_lsm() into
lsm_prepare()
lsm: introduce looping macros for the initialization code
lsm: integrate report_lsm_order() code into caller
lsm: integrate lsm_early_cred() and lsm_early_task() into caller
lsm: rename ordered_lsm_init() to lsm_init_ordered()
lsm: replace the name field with a pointer to the lsm_id struct
lsm: rename the lsm order variables for consistency
lsm: rework lsm_active_cnt and lsm_idlist[]
lsm: get rid of the lsm_names list and do some cleanup
lsm: rework the LSM enable/disable setter/getter functions
lsm: rename exists_ordered_lsm() to lsm_order_exists()
lsm: rename/rework append_ordered_lsm() into lsm_order_append()
lsm: rename/rework ordered_lsm_parse() to lsm_order_parse()
lsm: cleanup the LSM blob size code
lsm: cleanup initialize_lsm() and rename to lsm_init_single()
lsm: fold lsm_init_ordered() into security_init()
lsm: add/tweak function header comment blocks in lsm_init.c
lsm: cleanup the debug and console output in lsm_init.c
lsm: output available LSMs when debugging
lsm: group lsm_order_parse() with the other lsm_order_*()
functions
lsm: introduce an initcall mechanism into the LSM framework
loadpin: move initcalls to the LSM framework
ipe: move initcalls to the LSM framework
smack: move initcalls to the LSM framework
tomoyo: move initcalls to the LSM framework
safesetid: move initcalls to the LSM framework
apparmor: move initcalls to the LSM framework
lockdown: move initcalls to the LSM framework
selinux: move initcalls to the LSM framework
lsm: consolidate all of the LSM framework initcalls
lsm: add a LSM_STARTED_ALL notification event
Roberto Sassu (1):
ima,evm: move initcalls to the LSM framework
include/linux/lsm_hooks.h | 73 +-
include/linux/security.h | 3
security/Makefile | 2
security/apparmor/apparmorfs.c | 4
security/apparmor/crypto.c | 3
security/apparmor/include/apparmorfs.h | 2
security/apparmor/include/crypto.h | 1
security/apparmor/lsm.c | 11
security/bpf/hooks.c | 2
security/commoncap.c | 2
security/inode.c | 46 +
security/integrity/evm/evm_main.c | 5
security/integrity/evm/evm_secfs.c | 11
security/integrity/iint.c | 14
security/integrity/ima/ima_fs.c | 11
security/integrity/ima/ima_main.c | 6
security/integrity/integrity.h | 2
security/ipe/fs.c | 4
security/ipe/ipe.c | 3
security/ipe/ipe.h | 2
security/landlock/setup.c | 2
security/loadpin/loadpin.c | 15
security/lockdown/lockdown.c | 5
security/lsm.h | 58 ++
security/lsm_init.c | 563 ++++++++++++++++++++++
security/lsm_notifier.c | 31 +
security/lsm_syscalls.c | 2
security/min_addr.c | 5
security/safesetid/lsm.c | 3
security/safesetid/lsm.h | 2
security/safesetid/securityfs.c | 3
security/security.c | 623 +------------------------
security/selinux/Makefile | 2
security/selinux/hooks.c | 11
security/selinux/ibpkey.c | 5
security/selinux/include/audit.h | 9
security/selinux/include/initcalls.h | 19
security/selinux/initcalls.c | 52 ++
security/selinux/netif.c | 5
security/selinux/netlink.c | 5
security/selinux/netnode.c | 5
security/selinux/netport.c | 5
security/selinux/selinuxfs.c | 5
security/selinux/ss/services.c | 26 -
security/smack/smack.h | 14
security/smack/smack_lsm.c | 11
security/smack/smack_netfilter.c | 4
security/smack/smackfs.c | 4
security/tomoyo/common.h | 2
security/tomoyo/securityfs_if.c | 4
security/tomoyo/tomoyo.c | 3
security/yama/yama_lsm.c | 2
52 files changed, 1000 insertions(+), 712 deletions(-)
^ permalink raw reply
* Re: [PATCH bpf-next v2 0/3] BPF signature hash chains
From: Paul Moore @ 2025-10-17 18:39 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: Linus Torvalds, Alexei Starovoitov, KP Singh, Blaise Boscaccy,
James Bottomley, bpf, LSM List, K. Y. Srinivasan, Daniel Borkmann,
Andrii Nakryiko, wufan, Quentin Monnet
In-Reply-To: <CAADnVQLRtfPrH6sffaPVyFP4Aib+e7uVVWLi7bb79d9TrHjHpQ@mail.gmail.com>
On Fri, Oct 17, 2025 at 2:03 PM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
> On Thu, Oct 16, 2025 at 6:36 PM Paul Moore <paul@paul-moore.com> wrote:
> > On Thu, Oct 16, 2025 at 6:01 PM Alexei Starovoitov
> > <alexei.starovoitov@gmail.com> wrote:
> > > On Thu, Oct 16, 2025 at 1:51 PM Paul Moore <paul@paul-moore.com> wrote:
> > > > On Sun, Oct 12, 2025 at 10:12 PM Paul Moore <paul@paul-moore.com> wrote:
> > > > > On Sat, Oct 11, 2025 at 1:09 PM James Bottomley
> > > > > <James.Bottomley@hansenpartnership.com> wrote:
> > > > > > On Sat, 2025-10-11 at 09:31 -0700, Alexei Starovoitov wrote:
> > > > > > > On Sat, Oct 11, 2025 at 7:52 AM James Bottomley
> > > > > > > <James.Bottomley@hansenpartnership.com> wrote:
> > > > > > > >
> > > > > > > > It doesn't need to, once we check both the loader and the map, the
> > > > > > > > integrity is verified and the loader can be trusted to run and
> > > > > > > > relocate the map into the bpf program
> > > > > > >
> > > > > > > You should read KP's cover letter again and then research trusted
> > > > > > > hash chains. Here is a quote from the first googled link:
> > > > > > >
> > > > > > > "A trusted hash chain is a cryptographic process used to verify the
> > > > > > > integrity and authenticity of data by creating a sequence of hash
> > > > > > > values, where each hash is linked to the next".
> > > > > > >
> > > > > > > In addition KP's algorithm was vetted by various security teams.
> > > > > > > There is nothing novel here. It's a classic algorithm used
> > > > > > > to verify integrity and that's what was implemented.
> > > > > >
> > > > > > Both KP and Blaise's patch sets are implementations of trusted hash
> > > > > > chains. The security argument isn't about whether the hash chain
> > > > > > algorithm works, it's about where, in relation to the LSM hook, the
> > > > > > hash chain verification completes.
> > >
> > > Not true. Blaise's patch is a trusted hash chain denial.
> >
> > It would be helpful if you could clarify what you mean by "trusted
> > hash chain denial" and how that differs from a "trusted hash chain".
>
> Paul,
> This is getting ridiculous. You're arguing about the code that you
> don't understand.
Alexei,
Asking for clarification on a phrase which is not commonly used is far
from ridiculous, it's part of a reasonable discussion. We've talked a
lot about "trusted hash chains", with KP's patchset providing a rather
thorough explanation, but I don't recall a "trusted hash chain denial"
definition and it isn't a term I recall hearing in an algorithm
context, at least not outside of "verification of the trusted hash
chain failed resulting in the operation being denied", which doesn't
match with the context used in your comment.
> Stop this broken phone and let Blaise defend his code.
He has, in this thread and others.
James has defended the code, in this thread and others.
I've defended the code, in this thread and others.
Support, or criticism, of an idea shouldn't be limited to the original
author. In fact I would say as contributors, and definitely
maintainers, we have a responsibility to review and provide feedback
on proposed changes; that's how this whole thing works.
--
paul-moore.com
^ permalink raw reply
* Re: [PATCH bpf-next v2 0/3] BPF signature hash chains
From: Alexei Starovoitov @ 2025-10-17 18:03 UTC (permalink / raw)
To: Paul Moore
Cc: Linus Torvalds, Alexei Starovoitov, KP Singh, Blaise Boscaccy,
James Bottomley, bpf, LSM List, K. Y. Srinivasan, Daniel Borkmann,
Andrii Nakryiko, wufan, Quentin Monnet
In-Reply-To: <CAHC9VhSRiZacAy=JTKgWnBDbycey37JRVC61373HERTEUFmxEA@mail.gmail.com>
On Thu, Oct 16, 2025 at 6:36 PM Paul Moore <paul@paul-moore.com> wrote:
>
> On Thu, Oct 16, 2025 at 6:01 PM Alexei Starovoitov
> <alexei.starovoitov@gmail.com> wrote:
> > On Thu, Oct 16, 2025 at 1:51 PM Paul Moore <paul@paul-moore.com> wrote:
> > > On Sun, Oct 12, 2025 at 10:12 PM Paul Moore <paul@paul-moore.com> wrote:
> > > > On Sat, Oct 11, 2025 at 1:09 PM James Bottomley
> > > > <James.Bottomley@hansenpartnership.com> wrote:
> > > > > On Sat, 2025-10-11 at 09:31 -0700, Alexei Starovoitov wrote:
> > > > > > On Sat, Oct 11, 2025 at 7:52 AM James Bottomley
> > > > > > <James.Bottomley@hansenpartnership.com> wrote:
> > > > > > >
> > > > > > > It doesn't need to, once we check both the loader and the map, the
> > > > > > > integrity is verified and the loader can be trusted to run and
> > > > > > > relocate the map into the bpf program
> > > > > >
> > > > > > You should read KP's cover letter again and then research trusted
> > > > > > hash chains. Here is a quote from the first googled link:
> > > > > >
> > > > > > "A trusted hash chain is a cryptographic process used to verify the
> > > > > > integrity and authenticity of data by creating a sequence of hash
> > > > > > values, where each hash is linked to the next".
> > > > > >
> > > > > > In addition KP's algorithm was vetted by various security teams.
> > > > > > There is nothing novel here. It's a classic algorithm used
> > > > > > to verify integrity and that's what was implemented.
> > > > >
> > > > > Both KP and Blaise's patch sets are implementations of trusted hash
> > > > > chains. The security argument isn't about whether the hash chain
> > > > > algorithm works, it's about where, in relation to the LSM hook, the
> > > > > hash chain verification completes.
> >
> > Not true. Blaise's patch is a trusted hash chain denial.
>
> It would be helpful if you could clarify what you mean by "trusted
> hash chain denial" and how that differs from a "trusted hash chain".
Paul,
This is getting ridiculous. You're arguing about the code that you
don't understand. Stop this broken phone and let Blaise defend his code.
^ permalink raw reply
* Re: [PATCH] ima: Fall back to default kernel module signature verification
From: Mimi Zohar @ 2025-10-17 17:49 UTC (permalink / raw)
To: Coiby Xu
Cc: linux-integrity, Dmitry Torokhov, Karel Srot, Roberto Sassu,
Dmitry Kasatkin, Eric Snowberg, Paul Moore, James Morris,
Serge E. Hallyn, open list:SECURITY SUBSYSTEM, open list
In-Reply-To: <5bzredottmp2tdm3uebzjfqjr6c7bwssqkrbdqvudruvzr764e@37j6ycjci2sk>
On Fri, 2025-10-17 at 11:19 +0800, Coiby Xu wrote:
> On Thu, Oct 16, 2025 at 10:31:35PM -0400, Mimi Zohar wrote:
> > On Thu, 2025-10-16 at 11:46 +0800, Coiby Xu wrote:
> > > On Tue, Sep 30, 2025 at 04:28:14PM -0400, Mimi Zohar wrote:
> > > > On Tue, 2025-09-30 at 09:57 -0400, Mimi Zohar wrote:
> > > > > On Sun, 2025-09-28 at 11:03 +0800, Coiby Xu wrote:
> > > > > > Currently, for any IMA policy that requires appraisal for kernel modules
> > > > > > e.g. ima_policy=secure_boot, PowerPC architecture specific policy,
> > > > > > booting will fail because IMA will reject a kernel module which will
> > > > > > be decompressed in the kernel space and then have its signature
> > > > > > verified.
> > > > > >
> > > > > > This happens because when in-kernel module decompression
> > > > > > (CONFIG_MODULE_DECOMPRESS) is enabled, kmod will use finit_module
> > > > > > syscall instead of init_module to load a module. And IMA mandates IMA
> > > > > > xattr verification for finit_module unless appraise_type=imasig|modsig
> > > > > > is specified in the rule. However currently initramfs doesn't support
> > > > > > xattr. And IMA rule "func=MODULE_CHECK appraise_type=imasig|modsig"
> > > > > > doesn't work either because IMA will treat to-be-decompressed kernel
> > > > > > module as not having module signature as it can't decompress kernel
> > > > > > module to check if signature exists.
> > > > > >
> > > > > > So fall back to default kernel module signature verification when we have
> > > > > > no way to verify IMA xattr.
> > > > > >
> > > > > > Reported-by: Karel Srot <ksrot@redhat.com>
> > > > > > Signed-off-by: Coiby Xu <coxu@redhat.com>
> > > > > > ---
> > > > > > Another approach will be to make IMA decompress the kernel module to
> > > > > > check the signature. This requires refactoring kernel module code to
> > > > > > make the in-kernel module decompressing feature modular and seemingly
> > > > > > more efforts are needed. A second disadvantage is it feels
> > > > > > counter-intuitive to verify the same kernel module signature twice. And
> > > > > > we still need to make ima_policy=secure_boot allow verifying appended
> > > > > > module signature.
> > > > > >
> > > > > > Anyways, I'm open to suggestions and can try the latter approach if
> > > > > > there are some benefits I'm not aware of or a better approach.
> > > > >
> > > > > Coiby, there are multiple issues being discussed here. Before deciding on an
> > > > > appropriate solution, let's frame the issues(s) properly.
> > >
> > > Hi Mimi,
> > >
> > > Thanks for listing and framing the issues! Sorry, it took me a while to
> > > go through your feedback as I also had a 8-day holiday.
> > >
> > > > >
> > > > > 1. The finit_module syscall eventually calls init_module_from_file() to read the
> > > > > module into memory and then decompress it. The problem is that the kernel
> > > > > module signature verification occurs during the kernel_read_file(), before the
> > > > > kernel module is decompressed. Thus, the appended kernel module signature
> > > > > cannot be verified.
> > >
> > > Since IMA only accesses a kernel module as a fd or struct file*, even if
> > > IMA hook is triggerd after kernel module is decompressed, IMA still
> > > doesn't know the default verification has passed or can't access the
> > > decompressed kernel buffer [2] to do the verification by itself.
> > >
> > > [2] https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/module/main.c?h=v6.17#n3689
> > >
> > > > >
> > > > > 2. CPIO doesn't have xattr support. There were multiple attempts at including
> > > > > xattrs in CPIO, but none were upstreamed [1]. If file signatures stored in
> > > > > security.ima were available in the initramfs, then finit_module() could verify
> > > > > them, as opposed to the appended kernel module signature.
> > >
> > > Thanks you for pointing me to the work [1]. I'll take a more careful
> > > look at [1]. I think CPIO supporting xattr can be a long-term solution
> > > and also close a important security gap.
> > >
> > > > >
> > > > > 3. The issues described above are generic, not limited to Power. When
> > > > > CONFIG_MODULE_SIG is configured, the arch specific IMA policy rules do not
> > > > > include an "appraise func=MODULE_CHECK".
> > >
> > > Yes, the issue is not limited to Power. And thanks for correcting me
> > > that Power arch specific IMA policy rules don't have this problem! Sorry
> > > I misread the code.
> > >
> > > > >
> > > > > 4. Unlike the arch specific IMA policy rules, the built-in secure boot IMA
> > > > > policy, specified on the boot command line as "ima_policy=secure_boot", always
> > > > > enforces the IMA signature stored in security.ima.
> > > > >
> > > > > Partial solutions without kernel changes:
> > > > > - Enable CONFIG_MODULE_SIG (Doesn't solve 4)
> > > > > - Disable kernel module compression.
> > > > >
> > > > > Complete solution:
> > > > > - Pick up and upstream Roberto Sassu's last version of initramfs support [1].
> > > > > - Somehow prevent kernel_read_file() from failing when the kernel_read_file_id
> > > > > enumeration is READING_MODULE and the kernel module is compressed. The change
> > > > > might be limited to ima_post_read_file().
> > > >
> > > > or perhaps not totally.
> > > >
> > > > init_module_from_file() doesn't pass the flags variable to kernel_read_file().
> > > > You might want to consider defining a new kernel_read_file_id enumeration named
> > > > READING_COMPRESSED_MODULE.
> > >
> > > Thanks for suggesting the solutions! I like the solution of CPIO
> > > supporting xattr but it seems it won't land in upstream soon. So I
> > > prefer the last approach. I've implemented one [3] by defining a new
> > > kernel_read_file_id enumeration, would you like me to post the patches
> > > to the mailing list directly?
> > >
> > > [3] https://github.com/coiby/linux/tree/in_kernel_decompression_ima
> >
> > A few thoughts, before you post the patch.
>
> Thank you for sharing your thoughts!
>
> >
> > 1. In the general case, the kernel module could be compressed, but without an
> > appended signature. The new code should only defer appended signature
> > verification, if there isn't an xattr or appended signature.
>
> I'll add these two condition checks, thanks!
>
> >
> > 2. Instead of defining an additional process_measurement() argument to identify
> > compressed kernel modules, to simplify the code it might be possible to define a
> > new "func" named COMPRESSED_MODULE_CHECK.
> >
> > + [READING_COMPRESSED_MODULE] = MODULE_CHECK, -> COMPRESSED_MODULE_CHECK
>
> I also thought about this approach. But IMA rule maps kernel module
> loading to MODULE_CHECK. If we define a new rule and ask users to use
> this new rule, ima_policy=secure_boot still won't work.
I don't have a problem with extending the "secure-boot" policy to support
uncompressed kernel modules appended signatures, based on whether
CONFIG_MODULE_SIG is enabled. The new rule would be in addition to the existing
MODULE_CHECK rule.
>
> >
> > 3. The patch title "ima: Use default kernel module signature verification for
> > compressed ones" is a bit off. It should be something along the lines of "ima:
> > defer compressed kernel module appended signature verification".
>
> >
> > 4. Simplify the patch description.
>
> I'll rephrase the title and try simplifying it. Thanks!
Thank you.
^ permalink raw reply
* Re: [PATCH v2 2/2] landlock: selftests for LANDLOCK_RESTRICT_SELF_TSYNC
From: Mickaël Salaün @ 2025-10-17 15:05 UTC (permalink / raw)
To: Günther Noack
Cc: Konstantin Meskhidze, Tingmao Wang, Paul Moore,
linux-security-module, Jann Horn
In-Reply-To: <20251001111807.18902-3-gnoack@google.com>
On Wed, Oct 01, 2025 at 01:18:07PM +0200, Günther Noack wrote:
> Exercise various scenarios where Landlock domains are enforced across
> all of a processes' threads.
>
> Cc: Mickaël Salaün <mic@digikod.net>
> Cc: Paul Moore <paul@paul-moore.com>
> Cc: linux-security-module@vger.kernel.org
> Signed-off-by: Günther Noack <gnoack@google.com>
> ---
> tools/testing/selftests/landlock/base_test.c | 6 +-
> tools/testing/selftests/landlock/tsync_test.c | 99 +++++++++++++++++++
> 2 files changed, 102 insertions(+), 3 deletions(-)
> create mode 100644 tools/testing/selftests/landlock/tsync_test.c
>
> diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
> index 7b69002239d7..b9be6dcb7e8c 100644
> --- a/tools/testing/selftests/landlock/base_test.c
> +++ b/tools/testing/selftests/landlock/base_test.c
> @@ -288,7 +288,7 @@ TEST(restrict_self_fd)
> EXPECT_EQ(EBADFD, errno);
> }
>
> -TEST(restrict_self_fd_flags)
> +TEST(restrict_self_fd_logging_flags)
> {
> int fd;
>
> @@ -304,9 +304,9 @@ TEST(restrict_self_fd_flags)
> EXPECT_EQ(EBADFD, errno);
> }
>
> -TEST(restrict_self_flags)
> +TEST(restrict_self_logging_flags)
> {
> - const __u32 last_flag = LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF;
> + const __u32 last_flag = LANDLOCK_RESTRICT_SELF_TSYNC;
>
> /* Tests invalid flag combinations. */
>
> diff --git a/tools/testing/selftests/landlock/tsync_test.c b/tools/testing/selftests/landlock/tsync_test.c
> new file mode 100644
> index 000000000000..356e20de352f
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/tsync_test.c
> @@ -0,0 +1,99 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Landlock tests - Enforcing the same restrictions across multiple threads
> + *
> + * Copyright © 2025 Günther Noack <gnoack3000@gmail.com>
> + */
> +
> +#define _GNU_SOURCE
> +#include <pthread.h>
> +#include <sys/prctl.h>
> +#include <linux/landlock.h>
> +
> +#include "common.h"
> +
> +/* create_ruleset - Create a simple ruleset FD common to all tests */
> +static int create_ruleset(struct __test_metadata *const _metadata)
> +{
> + struct landlock_ruleset_attr ruleset_attr = {
> + .handled_access_fs = (LANDLOCK_ACCESS_FS_WRITE_FILE |
> + LANDLOCK_ACCESS_FS_TRUNCATE),
> + };
> + const int ruleset_fd =
> + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
> +
> + ASSERT_LE(0, ruleset_fd);
> + return ruleset_fd;
> +}
> +
> +TEST(single_threaded_success)
> +{
> + const int ruleset_fd = create_ruleset(_metadata);
> +
> + disable_caps(_metadata);
> +
> + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
> + ASSERT_EQ(0, landlock_restrict_self(ruleset_fd,
> + LANDLOCK_RESTRICT_SELF_TSYNC));
> +
> + ASSERT_EQ(0, close(ruleset_fd));
> +}
> +
> +void *idle(void *data)
> +{
> + while (true)
> + sleep(1);
while (!usleep(1));
> +}
> +
> +TEST(multi_threaded_success)
> +{
> + pthread_t t1, t2;
> + const int ruleset_fd = create_ruleset(_metadata);
> +
> + disable_caps(_metadata);
> +
> + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
> +
> + ASSERT_EQ(0, pthread_create(&t1, NULL, idle, NULL));
> + ASSERT_EQ(0, pthread_create(&t2, NULL, idle, NULL));
> +
> + EXPECT_EQ(0, landlock_restrict_self(ruleset_fd,
> + LANDLOCK_RESTRICT_SELF_TSYNC));
> +
> + ASSERT_EQ(0, pthread_cancel(t1));
> + ASSERT_EQ(0, pthread_cancel(t2));
> + ASSERT_EQ(0, pthread_join(t1, NULL));
> + ASSERT_EQ(0, pthread_join(t2, NULL));
> + ASSERT_EQ(0, close(ruleset_fd));
> +}
It would be useful for these multi_* tests to actually test the
restrictions.
A dedicated (positive and negative) test for ptrace and signal scoping
(back and forth) would also demonstrate that they are in the same
domain.
> +
> +TEST(multi_threaded_success_despite_diverging_domains)
> +{
> + pthread_t t1, t2;
> + const int ruleset_fd = create_ruleset(_metadata);
> +
> + disable_caps(_metadata);
> +
> + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
> +
> + ASSERT_EQ(0, pthread_create(&t1, NULL, idle, NULL));
> + ASSERT_EQ(0, pthread_create(&t2, NULL, idle, NULL));
> +
> + /*
> + * The main thread enforces a ruleset,
> + * thereby bringing the threads' Landlock domains out of sync.
> + */
> + EXPECT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
> +
> + /* Still, TSYNC succeeds, bringing the threads in sync again. */
> + EXPECT_EQ(0, landlock_restrict_self(ruleset_fd,
> + LANDLOCK_RESTRICT_SELF_TSYNC));
> +
> + ASSERT_EQ(0, pthread_cancel(t1));
> + ASSERT_EQ(0, pthread_cancel(t2));
> + ASSERT_EQ(0, pthread_join(t1, NULL));
> + ASSERT_EQ(0, pthread_join(t2, NULL));
> + ASSERT_EQ(0, close(ruleset_fd));
> +}
> +
> +TEST_HARNESS_MAIN
> --
> 2.51.0.618.g983fd99d29-goog
>
>
^ permalink raw reply
* Re: [PATCH v2 1/2] landlock: Multithreading support for landlock_restrict_self()
From: Mickaël Salaün @ 2025-10-17 15:04 UTC (permalink / raw)
To: Günther Noack, Jann Horn
Cc: Konstantin Meskhidze, Tingmao Wang, Paul Moore,
linux-security-module
In-Reply-To: <20251001111807.18902-2-gnoack@google.com>
This patch series is complex but it looks very good, thanks for all the
explanations! I don't have major concern but a few comments.
I'm wondering if this could be simplified but, except the flat array, I
couldn't find a better approach.
Jann, could you please take a look?
On Wed, Oct 01, 2025 at 01:18:06PM +0200, Günther Noack wrote:
> Introduce the LANDLOCK_RESTRICT_SELF_TSYNC flag. With this flag, a
> given Landlock ruleset is applied to all threads of the calling
> process, instead of only the current one.
>
> Without this flag, multithreaded userspace programs currently resort
> to using the nptl(7)/libpsx hack for multithreaded policy enforcement,
> which is also used by libcap and for setuid(2). Using this scheme,
> the threads of a process enforce the same Landlock ruleset, but the
> resulting Landlock domains are still separate, which makes a
> difference for Landlock's "scoped" access rights, where the domain
> identity and nesting is used. As a result, when using
> LANLDOCK_SCOPE_SIGNAL, signaling between sibling threads stops
> working. This is a problem for programming languages and frameworks
> which are inherently multithreaded (e.g. Go).
Having different Landlock domains also means that we get different
domains ID, and the audit logs might confuse users.
>
> Cc: Mickaël Salaün <mic@digikod.net>
> Cc: Paul Moore <paul@paul-moore.com>
> Cc: linux-security-module@vger.kernel.org
> Suggested-by: Jann Horn <jannh@google.com>
> Signed-off-by: Günther Noack <gnoack@google.com>
> ---
> include/uapi/linux/landlock.h | 4 +
> security/landlock/cred.h | 12 +
> security/landlock/limits.h | 2 +-
> security/landlock/syscalls.c | 433 +++++++++++++++++++++++++++++++++-
You can move most of this code to a new tsync.c file.
> 4 files changed, 448 insertions(+), 3 deletions(-)
>
> diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
> index f030adc462ee..7c6c7f004a41 100644
> --- a/include/uapi/linux/landlock.h
> +++ b/include/uapi/linux/landlock.h
> @@ -117,11 +117,15 @@ struct landlock_ruleset_attr {
> * future nested domains, not the one being created. It can also be used
> * with a @ruleset_fd value of -1 to mute subdomain logs without creating a
> * domain.
> + *
> + * %LANDLOCK_RESTRICT_SELF_TSYNC
> + * Apply the given ruleset atomically to all threads of the current process.
Please bump the Landlock ABI version and update the doc.
> */
> /* clang-format off */
> #define LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF (1U << 0)
> #define LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON (1U << 1)
> #define LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF (1U << 2)
> +#define LANDLOCK_RESTRICT_SELF_TSYNC (1U << 3)
> /* clang-format on */
>
> /**
> diff --git a/security/landlock/cred.h b/security/landlock/cred.h
> index c82fe63ec598..eb28eeade760 100644
> --- a/security/landlock/cred.h
> +++ b/security/landlock/cred.h
> @@ -65,6 +65,18 @@ landlock_cred(const struct cred *cred)
> return cred->security + landlock_blob_sizes.lbs_cred;
> }
>
> +static inline void landlock_cred_copy(struct landlock_cred_security *dst,
> + const struct landlock_cred_security *src)
> +{
You can simplify by removing the domain checks which are already done by
landlock_put/get_ruleset().
> + if (dst->domain)
> + landlock_put_ruleset(dst->domain);
> +
> + *dst = *src;
> +
> + if (dst->domain)
> + landlock_get_ruleset(src->domain);
> +}
> +
> static inline struct landlock_ruleset *landlock_get_current_domain(void)
> {
> return landlock_cred(current_cred())->domain;
> diff --git a/security/landlock/limits.h b/security/landlock/limits.h
> index 65b5ff051674..eb584f47288d 100644
> --- a/security/landlock/limits.h
> +++ b/security/landlock/limits.h
> @@ -31,7 +31,7 @@
> #define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1)
> #define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE)
>
> -#define LANDLOCK_LAST_RESTRICT_SELF LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF
> +#define LANDLOCK_LAST_RESTRICT_SELF LANDLOCK_RESTRICT_SELF_TSYNC
> #define LANDLOCK_MASK_RESTRICT_SELF ((LANDLOCK_LAST_RESTRICT_SELF << 1) - 1)
>
> /* clang-format on */
> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
> index 0116e9f93ffe..5ba14d641c11 100644
> --- a/security/landlock/syscalls.c
> +++ b/security/landlock/syscalls.c
> @@ -14,6 +14,7 @@
> #include <linux/capability.h>
> #include <linux/cleanup.h>
> #include <linux/compiler_types.h>
> +#include <linux/completion.h>
> #include <linux/dcache.h>
> #include <linux/err.h>
> #include <linux/errno.h>
> @@ -25,6 +26,7 @@
> #include <linux/security.h>
> #include <linux/stddef.h>
> #include <linux/syscalls.h>
> +#include <linux/task_work.h>
> #include <linux/types.h>
> #include <linux/uaccess.h>
> #include <uapi/linux/landlock.h>
> @@ -445,6 +447,416 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
>
> /* Enforcement */
>
> +/*
> + * Shared state between multiple threads which are enforcing Landlock rulesets
> + * in lockstep with each other.
> + */
> +struct tsync_shared_context {
> + /* The old and tentative new creds of the calling thread. */
> + const struct cred *old_cred;
> + const struct cred *new_cred;
> +
> + /* An error encountered in preparation step, or 0. */
> + atomic_t preparation_error;
> +
> + /*
> + * Barrier after preparation step in the inner loop.
> + * The calling thread waits for completion.
> + *
> + * Re-initialized on every round of looking for newly spawned threads.
> + */
> + atomic_t num_preparing;
> + struct completion all_prepared;
> +
> + /* Sibling threads wait for completion. */
> + struct completion ready_to_commit;
> +
> + /*
> + * Barrier after commit step (used by syscall impl to wait for
> + * completion).
> + */
> + atomic_t num_unfinished;
> + struct completion all_finished;
> +};
> +
> +struct tsync_work {
> + struct callback_head work;
> + struct task_struct *task;
> + struct tsync_shared_context *shared_ctx;
> +};
> +
> +/*
> + * restrict_one_thread - update a thread's Landlock domain in lockstep with the
> + * other threads in the same process
> + *
> + * When this is run, the same function gets run in all other threads in the same
> + * process (except for the calling thread which called landlock_restrict_self).
> + * The concurrently running invocations of restrict_one_thread coordinate
> + * through the shared ctx object to do their work in lockstep to implement
> + * all-or-nothing semantics for enforcing the new Landlock domain.
> + *
> + * Afterwards, depending on the presence of an error, all threads either commit
> + * or abort the prepared credentials. The commit operation can not fail any more.
> + */
> +static void restrict_one_thread(struct tsync_shared_context *ctx)
> +{
> + int res;
For consistency with existing code, please use "err" instead of "res".
> + struct cred *cred = NULL;
> + const struct cred *current_cred = current_cred();
No need for this variable.
> +
> + if (current_cred == ctx->old_cred) {
> + /*
> + * As a shortcut, switch out old_cred with new_cred, if
> + * possible.
> + *
> + * Note: We are intentionally dropping the const qualifier here,
> + * because it is required by commit_creds() and abort_creds().
> + */
> + cred = (struct cred *)get_cred(ctx->new_cred);
Good. You can extend the comment to explain that this optimization
avoid creating new credentials in most cases, and then save memory.
> + } else {
> + /* Else, prepare new creds and populate them. */
> + cred = prepare_creds();
> +
> + if (!cred) {
> + atomic_set(&ctx->preparation_error, -ENOMEM);
> +
> + /*
> + * Even on error, we need to adhere to the protocol and
> + * coordinate with concurrently running invocations.
> + */
> + if (atomic_dec_return(&ctx->num_preparing) == 0)
> + complete_all(&ctx->all_prepared);
> +
> + goto out;
> + }
> +
> + landlock_cred_copy(landlock_cred(cred),
> + landlock_cred(ctx->new_cred));
> + }
> +
> + /*
> + * Barrier: Wait until all threads are done preparing.
> + * After this point, we can have no more failures.
> + */
> + if (atomic_dec_return(&ctx->num_preparing) == 0)
> + complete_all(&ctx->all_prepared);
> +
> + /*
> + * Wait for signal from calling thread that it's safe to read the
> + * preparation error now and we are ready to commit (or abort).
> + */
> + wait_for_completion(&ctx->ready_to_commit);
> +
> + /* Abort the commit if any of the other threads had an error. */
> + res = atomic_read(&ctx->preparation_error);
> + if (res) {
> + abort_creds(cred);
> + goto out;
> + }
> +
> + /* If needed, establish enforcement prerequisites. */
> + if (!ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN))
> + task_set_no_new_privs(current);
We should always set PR_SET_NO_NEW_PRIVS if it is set on the calling
thread as done by seccomp. We should just store the result of
task_no_new_privs() in tsync_shared_context and use it as condition here.
This should be explained in the documentation.
This also mean that if the calling thread has CAP_SYS_ADMIN but not
PR_SET_NO_NEW_PRIVS, then a sibling thread could not have CAP_SYS_ADMIN
nor PR_SET_NO_NEW_PRIVS. This would be a risky state but mainly because
of the CAP_SYS_ADMIN inconsistency, not really the missing
PR_SET_NO_NEW_PRIVS.
> +
> + commit_creds(cred);
> +
> +out:
> + /* Notify the calling thread once all threads are done */
> + if (atomic_dec_return(&ctx->num_unfinished) == 0)
> + complete_all(&ctx->all_finished);
> +}
> +
> +/*
> + * restrict_one_thread_callback - task_work callback for restricting a thread
> + *
> + * Calls restrict_one_thread with the struct landlock_shared_tsync_context.
> + */
> +static void restrict_one_thread_callback(struct callback_head *work)
> +{
> + struct tsync_work *ctx = container_of(work, struct tsync_work, work);
> +
> + restrict_one_thread(ctx->shared_ctx);
> +}
> +
> +/*
> + * struct tsync_works - a growable array of per-task contexts
> + *
> + * The zero-initialized struct represents the empty array.
> + */
> +struct tsync_works {
> + struct tsync_work **works;
> + size_t size;
> + size_t capacity;
> +};
> +
> +/*
> + * tsync_works_provide - provides a preallocated tsync_work for the given task
> + *
> + * This also stores a task pointer in the context and increments the reference
> + * count of the task.
> + *
> + * Returns:
> + * A pointer to the preallocated context struct, with task filled in.
> + *
> + * NULL, if we ran out of preallocated context structs.
> + */
> +static struct tsync_work *tsync_works_provide(struct tsync_works *s,
> + struct task_struct *task)
> +{
> + struct tsync_work *ctx;
> +
> + if (s->size >= s->capacity)
In which case can this happen? Should we wrap this in a WARN_ON_ONCE()?
> + return NULL;
> +
> + ctx = s->works[s->size];
> + s->size++;
> +
> + ctx->task = get_task_struct(task);
> + return ctx;
> +}
> +
> +/*
> + * tsync_works_grow_by - preallocates space for n more contexts in s
> + *
> + * Returns:
> + * -ENOMEM if the (re)allocation fails
> + * 0 if the allocation succeeds, partially succeeds, or no reallocation was needed
> + */
> +static int tsync_works_grow_by(struct tsync_works *s, size_t n, gfp_t flags)
> +{
> + int i;
size_t i;
> + size_t new_capacity = s->capacity + n;
> + struct tsync_work **works;
> +
> + if (new_capacity <= s->capacity)
This integer overflow check should return an error instead.
> + return 0;
> +
> + works = krealloc_array(s->works, new_capacity, sizeof(s->works[0]),
> + flags);
> + if (IS_ERR(works))
> + return PTR_ERR(works);
> +
> + s->works = works;
> +
> + for (i = s->capacity; i < new_capacity; i++) {
> + s->works[i] = kzalloc(sizeof(*s->works[i]), flags);
We should use a local variable to avoid storing an error code in
s->works[i] and potentially dereferencing it later (e.g. in
tsync_work_free).
Why can't we avoid this loop entirely and allocate a flat array with
only one call to krealloc_array()? Why struct tsync_works->works needs
to be a pointer to a pointer?
> + if (IS_ERR(s->works[i])) {
> + /*
> + * Leave the object in a consistent state,
> + * but return an error.
> + */
> + s->capacity = i;
> + return PTR_ERR(s->works[i]);
> + }
> + }
> + s->capacity = new_capacity;
> + return 0;
> +}
> +
> +/*
> + * tsync_works_contains - checks for presence of task in s
> + */
> +static bool tsync_works_contains_task(struct tsync_works *s,
> + struct task_struct *task)
> +{
> + size_t i;
> +
> + for (i = 0; i < s->size; i++)
> + if (s->works[i]->task == task)
> + return true;
> + return false;
> +}
> +
> +/*
> + * tsync_works_free - free memory held by s and drop all task references
> + */
> +static void tsync_works_free(struct tsync_works *s)
tsync_works_put() would be more appropriate since this function doesn't
free s.
> +{
> + int i;
> +
> + for (i = 0; i < s->size; i++)
> + put_task_struct(s->works[i]->task);
> + for (i = 0; i < s->capacity; i++)
> + kfree(s->works[i]);
> + kfree(s->works);
> + s->works = NULL;
> + s->size = 0;
> + s->capacity = 0;
> +}
> +
> +/*
> + * restrict_sibling_threads - enables a Landlock policy for all sibling threads
> + */
> +static int restrict_sibling_threads(const struct cred *old_cred,
> + const struct cred *new_cred)
> +{
> + int res;
> + struct task_struct *thread, *caller;
> + struct tsync_shared_context shared_ctx;
> + struct tsync_works works = {};
> + size_t newly_discovered_threads;
> + bool found_more_threads;
> + struct tsync_work *ctx;
> +
> + atomic_set(&shared_ctx.preparation_error, 0);
> + init_completion(&shared_ctx.all_prepared);
> + init_completion(&shared_ctx.ready_to_commit);
> + atomic_set(&shared_ctx.num_unfinished, 0);
> + init_completion(&shared_ctx.all_finished);
> + shared_ctx.old_cred = old_cred;
> + shared_ctx.new_cred = new_cred;
> +
> + caller = current;
> +
> + /*
> + * We schedule a pseudo-signal task_work for each of the calling task's
> + * sibling threads. In the task work, each thread:
> + *
> + * 1) runs prepare_creds() and writes back the error to
> + * shared_ctx.preparation_error, if needed.
> + *
> + * 2) signals that it's done with prepare_creds() to the calling task.
> + * (completion "all_prepared").
> + *
> + * 3) waits for the completion "ready_to_commit". This is sent by the
> + * calling task after ensuring that all sibling threads have done
> + * with the "preparation" stage.
> + *
> + * After this barrier is reached, it's safe to read
> + * shared_ctx.preparation_error.
> + *
> + * 4) reads shared_ctx.preparation_error and then either does
> + * commit_creds() or abort_creds().
> + *
> + * 5) signals that it's done altogether (barrier synchronization
> + * "all_finished")
> + */
> + do {
> + found_more_threads = false;
> +
> + /*
> + * The "all_prepared" barrier is used locally to the inner loop,
> + * this use of for_each_thread(). We can reset it on each loop
> + * iteration because all previous loop iterations are done with
> + * it already.
> + *
> + * num_preparing is initialized to 1 so that the counter can not
> + * go to 0 and mark the completion as done before all task works
> + * are registered. (We decrement it at the end of this loop.)
> + */
> + atomic_set(&shared_ctx.num_preparing, 1);
> + reinit_completion(&shared_ctx.all_prepared);
> +
> + /* In RCU read-lock, count the threads we need. */
> + newly_discovered_threads = 0;
> + rcu_read_lock();
> + for_each_thread(caller, thread) {
> + /* Skip current, since it is initiating the sync. */
> + if (thread == caller)
> + continue;
> +
> + /* Skip exited threads. */
> + if (thread->flags & PF_EXITING)
> + continue;
> +
> + /* Skip threads that we have already seen. */
> + if (tsync_works_contains_task(&works, thread))
> + continue;
> +
> + newly_discovered_threads++;
> + }
> + rcu_read_unlock();
> +
> + if (newly_discovered_threads == 0)
> + break; /* done */
> +
> + res = tsync_works_grow_by(&works, newly_discovered_threads,
> + GFP_KERNEL_ACCOUNT);
> + if (res) {
> + atomic_set(&shared_ctx.preparation_error, res);
> + break;
> + }
> +
> + rcu_read_lock();
> + for_each_thread(caller, thread) {
> + /* Skip current, since it is initiating the sync. */
> + if (thread == caller)
> + continue;
> +
> + /* Skip exited threads. */
> + if (thread->flags & PF_EXITING)
> + continue;
> +
> + /* Skip threads that we already looked at. */
> + if (tsync_works_contains_task(&works, thread))
> + continue;
> +
> + /*
> + * We found a sibling thread that is not doing its
> + * task_work yet, and which might spawn new threads
> + * before our task work runs, so we need at least one
> + * more round in the outer loop.
> + */
> + found_more_threads = true;
> +
> + ctx = tsync_works_provide(&works, thread);
> + if (!ctx) {
> + /*
> + * We ran out of preallocated contexts -- we
> + * need to try again with this thread at a later
> + * time! found_more_threads is already true
> + * at this point.
> + */
> + break;
> + }
> +
> + ctx->shared_ctx = &shared_ctx;
> +
> + atomic_inc(&shared_ctx.num_preparing);
> + atomic_inc(&shared_ctx.num_unfinished);
> +
> + init_task_work(&ctx->work,
> + restrict_one_thread_callback);
> + res = task_work_add(thread, &ctx->work, TWA_SIGNAL);
> + if (res) {
> + /*
> + * Remove the task from ctx so that we will
> + * revisit the task at a later stage, if it
> + * still exists.
> + */
> + put_task_struct_rcu_user(ctx->task);
> + ctx->task = NULL;
> +
> + atomic_set(&shared_ctx.preparation_error, res);
> + atomic_dec(&shared_ctx.num_preparing);
> + atomic_dec(&shared_ctx.num_unfinished);
> + }
> + }
> + rcu_read_unlock();
> +
> + /*
> + * Decrement num_preparing for current, to undo that we
> + * initialized it to 1 at the beginning of the inner loop.
> + */
> + if (atomic_dec_return(&shared_ctx.num_preparing) > 0)
> + wait_for_completion(&shared_ctx.all_prepared);
> + } while (found_more_threads &&
> + !atomic_read(&shared_ctx.preparation_error));
> +
> + /*
> + * We now have all sibling threads blocking and in "prepared" state in
> + * the task work. Ask all threads to commit.
> + */
> + complete_all(&shared_ctx.ready_to_commit);
> +
> + if (works.size)
> + wait_for_completion(&shared_ctx.all_finished);
> +
> + tsync_works_free(&works);
> +
> + return atomic_read(&shared_ctx.preparation_error);
> +}
> +
> /**
> * sys_landlock_restrict_self - Enforce a ruleset on the calling thread
> *
> @@ -454,12 +866,20 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
> * - %LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF
> * - %LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON
> * - %LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF
> + * - %LANDLOCK_RESTRICT_SELF_TSYNC
> *
> - * This system call enables to enforce a Landlock ruleset on the current
> - * thread. Enforcing a ruleset requires that the task has %CAP_SYS_ADMIN in its
> + * This system call enforces a Landlock ruleset on the current thread.
> + * Enforcing a ruleset requires that the task has %CAP_SYS_ADMIN in its
> * namespace or is running with no_new_privs. This avoids scenarios where
> * unprivileged tasks can affect the behavior of privileged children.
> *
> + * If %LANDLOCK_RESTRICT_SELF_TSYNC is specified in @flags, all other threads of
> + * the process will be brought into the exact same Landlock configuration as the
> + * calling thread. This includes both the enforced ruleset and logging
> + * configuration, and happens irrespective of previously established rulesets
> + * and logging configurations on these threads. If required, this operation
> + * also enables the no_new_privs flag for these threads.
> + *
> * Possible returned errors are:
> *
> * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
> @@ -484,6 +904,7 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
> struct landlock_cred_security *new_llcred;
> bool __maybe_unused log_same_exec, log_new_exec, log_subdomains,
> prev_log_subdomains;
> + int res;
>
> if (!is_initialized())
> return -EOPNOTSUPP;
> @@ -566,5 +987,13 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
> new_llcred->domain_exec |= BIT(new_dom->num_layers - 1);
> #endif /* CONFIG_AUDIT */
>
> + if (flags & LANDLOCK_RESTRICT_SELF_TSYNC) {
> + res = restrict_sibling_threads(current_cred(), new_cred);
> + if (res != 0) {
if (!err) {
> + abort_creds(new_cred);
> + return res;
> + }
> + }
> +
> return commit_creds(new_cred);
> }
> --
> 2.51.0.618.g983fd99d29-goog
>
>
^ permalink raw reply
* Re: [PATCH] ima: Fall back to default kernel module signature verification
From: Coiby Xu @ 2025-10-17 3:19 UTC (permalink / raw)
To: Mimi Zohar
Cc: linux-integrity, Dmitry Torokhov, Karel Srot, Roberto Sassu,
Dmitry Kasatkin, Eric Snowberg, Paul Moore, James Morris,
Serge E. Hallyn, open list:SECURITY SUBSYSTEM, open list
In-Reply-To: <9d279fd3d7b3cbb2778183ec777d6b9da8a64b82.camel@linux.ibm.com>
On Thu, Oct 16, 2025 at 10:31:35PM -0400, Mimi Zohar wrote:
>On Thu, 2025-10-16 at 11:46 +0800, Coiby Xu wrote:
>> On Tue, Sep 30, 2025 at 04:28:14PM -0400, Mimi Zohar wrote:
>> > On Tue, 2025-09-30 at 09:57 -0400, Mimi Zohar wrote:
>> > > On Sun, 2025-09-28 at 11:03 +0800, Coiby Xu wrote:
>> > > > Currently, for any IMA policy that requires appraisal for kernel modules
>> > > > e.g. ima_policy=secure_boot, PowerPC architecture specific policy,
>> > > > booting will fail because IMA will reject a kernel module which will
>> > > > be decompressed in the kernel space and then have its signature
>> > > > verified.
>> > > >
>> > > > This happens because when in-kernel module decompression
>> > > > (CONFIG_MODULE_DECOMPRESS) is enabled, kmod will use finit_module
>> > > > syscall instead of init_module to load a module. And IMA mandates IMA
>> > > > xattr verification for finit_module unless appraise_type=imasig|modsig
>> > > > is specified in the rule. However currently initramfs doesn't support
>> > > > xattr. And IMA rule "func=MODULE_CHECK appraise_type=imasig|modsig"
>> > > > doesn't work either because IMA will treat to-be-decompressed kernel
>> > > > module as not having module signature as it can't decompress kernel
>> > > > module to check if signature exists.
>> > > >
>> > > > So fall back to default kernel module signature verification when we have
>> > > > no way to verify IMA xattr.
>> > > >
>> > > > Reported-by: Karel Srot <ksrot@redhat.com>
>> > > > Signed-off-by: Coiby Xu <coxu@redhat.com>
>> > > > ---
>> > > > Another approach will be to make IMA decompress the kernel module to
>> > > > check the signature. This requires refactoring kernel module code to
>> > > > make the in-kernel module decompressing feature modular and seemingly
>> > > > more efforts are needed. A second disadvantage is it feels
>> > > > counter-intuitive to verify the same kernel module signature twice. And
>> > > > we still need to make ima_policy=secure_boot allow verifying appended
>> > > > module signature.
>> > > >
>> > > > Anyways, I'm open to suggestions and can try the latter approach if
>> > > > there are some benefits I'm not aware of or a better approach.
>> > >
>> > > Coiby, there are multiple issues being discussed here. Before deciding on an
>> > > appropriate solution, let's frame the issues(s) properly.
>>
>> Hi Mimi,
>>
>> Thanks for listing and framing the issues! Sorry, it took me a while to
>> go through your feedback as I also had a 8-day holiday.
>>
>> > >
>> > > 1. The finit_module syscall eventually calls init_module_from_file() to read the
>> > > module into memory and then decompress it. The problem is that the kernel
>> > > module signature verification occurs during the kernel_read_file(), before the
>> > > kernel module is decompressed. Thus, the appended kernel module signature
>> > > cannot be verified.
>>
>> Since IMA only accesses a kernel module as a fd or struct file*, even if
>> IMA hook is triggerd after kernel module is decompressed, IMA still
>> doesn't know the default verification has passed or can't access the
>> decompressed kernel buffer [2] to do the verification by itself.
>>
>> [2] https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/module/main.c?h=v6.17#n3689
>>
>> > >
>> > > 2. CPIO doesn't have xattr support. There were multiple attempts at including
>> > > xattrs in CPIO, but none were upstreamed [1]. If file signatures stored in
>> > > security.ima were available in the initramfs, then finit_module() could verify
>> > > them, as opposed to the appended kernel module signature.
>>
>> Thanks you for pointing me to the work [1]. I'll take a more careful
>> look at [1]. I think CPIO supporting xattr can be a long-term solution
>> and also close a important security gap.
>>
>> > >
>> > > 3. The issues described above are generic, not limited to Power. When
>> > > CONFIG_MODULE_SIG is configured, the arch specific IMA policy rules do not
>> > > include an "appraise func=MODULE_CHECK".
>>
>> Yes, the issue is not limited to Power. And thanks for correcting me
>> that Power arch specific IMA policy rules don't have this problem! Sorry
>> I misread the code.
>>
>> > >
>> > > 4. Unlike the arch specific IMA policy rules, the built-in secure boot IMA
>> > > policy, specified on the boot command line as "ima_policy=secure_boot", always
>> > > enforces the IMA signature stored in security.ima.
>> > >
>> > > Partial solutions without kernel changes:
>> > > - Enable CONFIG_MODULE_SIG (Doesn't solve 4)
>> > > - Disable kernel module compression.
>> > >
>> > > Complete solution:
>> > > - Pick up and upstream Roberto Sassu's last version of initramfs support [1].
>> > > - Somehow prevent kernel_read_file() from failing when the kernel_read_file_id
>> > > enumeration is READING_MODULE and the kernel module is compressed. The change
>> > > might be limited to ima_post_read_file().
>> >
>> > or perhaps not totally.
>> >
>> > init_module_from_file() doesn't pass the flags variable to kernel_read_file().
>> > You might want to consider defining a new kernel_read_file_id enumeration named
>> > READING_COMPRESSED_MODULE.
>>
>> Thanks for suggesting the solutions! I like the solution of CPIO
>> supporting xattr but it seems it won't land in upstream soon. So I
>> prefer the last approach. I've implemented one [3] by defining a new
>> kernel_read_file_id enumeration, would you like me to post the patches
>> to the mailing list directly?
>>
>> [3] https://github.com/coiby/linux/tree/in_kernel_decompression_ima
>
>A few thoughts, before you post the patch.
Thank you for sharing your thoughts!
>
>1. In the general case, the kernel module could be compressed, but without an
>appended signature. The new code should only defer appended signature
>verification, if there isn't an xattr or appended signature.
I'll add these two condition checks, thanks!
>
>2. Instead of defining an additional process_measurement() argument to identify
>compressed kernel modules, to simplify the code it might be possible to define a
>new "func" named COMPRESSED_MODULE_CHECK.
>
> + [READING_COMPRESSED_MODULE] = MODULE_CHECK, -> COMPRESSED_MODULE_CHECK
I also thought about this approach. But IMA rule maps kernel module
loading to MODULE_CHECK. If we define a new rule and ask users to use
this new rule, ima_policy=secure_boot still won't work.
>
>3. The patch title "ima: Use default kernel module signature verification for
>compressed ones" is a bit off. It should be something along the lines of "ima:
>defer compressed kernel module appended signature verification".
>
>4. Simplify the patch description.
I'll rephrase the title and try simplifying it. Thanks!
>
>> >
>Mimi
>
--
Best regards,
Coiby
^ permalink raw reply
* Re: [PATCH] ima: Fall back to default kernel module signature verification
From: Mimi Zohar @ 2025-10-17 2:31 UTC (permalink / raw)
To: Coiby Xu
Cc: linux-integrity, Dmitry Torokhov, Karel Srot, Roberto Sassu,
Dmitry Kasatkin, Eric Snowberg, Paul Moore, James Morris,
Serge E. Hallyn, open list:SECURITY SUBSYSTEM, open list
In-Reply-To: <xq7bgyg63xlbogcik2we26yr5uf62f6kj3qn7ooljmqaoccrix@kkmuhza5cfdr>
On Thu, 2025-10-16 at 11:46 +0800, Coiby Xu wrote:
> On Tue, Sep 30, 2025 at 04:28:14PM -0400, Mimi Zohar wrote:
> > On Tue, 2025-09-30 at 09:57 -0400, Mimi Zohar wrote:
> > > On Sun, 2025-09-28 at 11:03 +0800, Coiby Xu wrote:
> > > > Currently, for any IMA policy that requires appraisal for kernel modules
> > > > e.g. ima_policy=secure_boot, PowerPC architecture specific policy,
> > > > booting will fail because IMA will reject a kernel module which will
> > > > be decompressed in the kernel space and then have its signature
> > > > verified.
> > > >
> > > > This happens because when in-kernel module decompression
> > > > (CONFIG_MODULE_DECOMPRESS) is enabled, kmod will use finit_module
> > > > syscall instead of init_module to load a module. And IMA mandates IMA
> > > > xattr verification for finit_module unless appraise_type=imasig|modsig
> > > > is specified in the rule. However currently initramfs doesn't support
> > > > xattr. And IMA rule "func=MODULE_CHECK appraise_type=imasig|modsig"
> > > > doesn't work either because IMA will treat to-be-decompressed kernel
> > > > module as not having module signature as it can't decompress kernel
> > > > module to check if signature exists.
> > > >
> > > > So fall back to default kernel module signature verification when we have
> > > > no way to verify IMA xattr.
> > > >
> > > > Reported-by: Karel Srot <ksrot@redhat.com>
> > > > Signed-off-by: Coiby Xu <coxu@redhat.com>
> > > > ---
> > > > Another approach will be to make IMA decompress the kernel module to
> > > > check the signature. This requires refactoring kernel module code to
> > > > make the in-kernel module decompressing feature modular and seemingly
> > > > more efforts are needed. A second disadvantage is it feels
> > > > counter-intuitive to verify the same kernel module signature twice. And
> > > > we still need to make ima_policy=secure_boot allow verifying appended
> > > > module signature.
> > > >
> > > > Anyways, I'm open to suggestions and can try the latter approach if
> > > > there are some benefits I'm not aware of or a better approach.
> > >
> > > Coiby, there are multiple issues being discussed here. Before deciding on an
> > > appropriate solution, let's frame the issues(s) properly.
>
> Hi Mimi,
>
> Thanks for listing and framing the issues! Sorry, it took me a while to
> go through your feedback as I also had a 8-day holiday.
>
> > >
> > > 1. The finit_module syscall eventually calls init_module_from_file() to read the
> > > module into memory and then decompress it. The problem is that the kernel
> > > module signature verification occurs during the kernel_read_file(), before the
> > > kernel module is decompressed. Thus, the appended kernel module signature
> > > cannot be verified.
>
> Since IMA only accesses a kernel module as a fd or struct file*, even if
> IMA hook is triggerd after kernel module is decompressed, IMA still
> doesn't know the default verification has passed or can't access the
> decompressed kernel buffer [2] to do the verification by itself.
>
> [2] https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/module/main.c?h=v6.17#n3689
>
> > >
> > > 2. CPIO doesn't have xattr support. There were multiple attempts at including
> > > xattrs in CPIO, but none were upstreamed [1]. If file signatures stored in
> > > security.ima were available in the initramfs, then finit_module() could verify
> > > them, as opposed to the appended kernel module signature.
>
> Thanks you for pointing me to the work [1]. I'll take a more careful
> look at [1]. I think CPIO supporting xattr can be a long-term solution
> and also close a important security gap.
>
> > >
> > > 3. The issues described above are generic, not limited to Power. When
> > > CONFIG_MODULE_SIG is configured, the arch specific IMA policy rules do not
> > > include an "appraise func=MODULE_CHECK".
>
> Yes, the issue is not limited to Power. And thanks for correcting me
> that Power arch specific IMA policy rules don't have this problem! Sorry
> I misread the code.
>
> > >
> > > 4. Unlike the arch specific IMA policy rules, the built-in secure boot IMA
> > > policy, specified on the boot command line as "ima_policy=secure_boot", always
> > > enforces the IMA signature stored in security.ima.
> > >
> > > Partial solutions without kernel changes:
> > > - Enable CONFIG_MODULE_SIG (Doesn't solve 4)
> > > - Disable kernel module compression.
> > >
> > > Complete solution:
> > > - Pick up and upstream Roberto Sassu's last version of initramfs support [1].
> > > - Somehow prevent kernel_read_file() from failing when the kernel_read_file_id
> > > enumeration is READING_MODULE and the kernel module is compressed. The change
> > > might be limited to ima_post_read_file().
> >
> > or perhaps not totally.
> >
> > init_module_from_file() doesn't pass the flags variable to kernel_read_file().
> > You might want to consider defining a new kernel_read_file_id enumeration named
> > READING_COMPRESSED_MODULE.
>
> Thanks for suggesting the solutions! I like the solution of CPIO
> supporting xattr but it seems it won't land in upstream soon. So I
> prefer the last approach. I've implemented one [3] by defining a new
> kernel_read_file_id enumeration, would you like me to post the patches
> to the mailing list directly?
>
> [3] https://github.com/coiby/linux/tree/in_kernel_decompression_ima
A few thoughts, before you post the patch.
1. In the general case, the kernel module could be compressed, but without an
appended signature. The new code should only defer appended signature
verification, if there isn't an xattr or appended signature.
2. Instead of defining an additional process_measurement() argument to identify
compressed kernel modules, to simplify the code it might be possible to define a
new "func" named COMPRESSED_MODULE_CHECK.
+ [READING_COMPRESSED_MODULE] = MODULE_CHECK, -> COMPRESSED_MODULE_CHECK
3. The patch title "ima: Use default kernel module signature verification for
compressed ones" is a bit off. It should be something along the lines of "ima:
defer compressed kernel module appended signature verification".
4. Simplify the patch description.
> >
Mimi
^ permalink raw reply
* Re: [PATCH bpf-next v2 0/3] BPF signature hash chains
From: Paul Moore @ 2025-10-17 1:36 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: Linus Torvalds, Alexei Starovoitov, KP Singh, Blaise Boscaccy,
James Bottomley, bpf, LSM List, K. Y. Srinivasan, Daniel Borkmann,
Andrii Nakryiko, wufan, Quentin Monnet
In-Reply-To: <CAADnVQJw_B-T6=TauUdyMLOxcfMDZ1hdHUFVnk59NmeWDBnEtw@mail.gmail.com>
On Thu, Oct 16, 2025 at 6:01 PM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
> On Thu, Oct 16, 2025 at 1:51 PM Paul Moore <paul@paul-moore.com> wrote:
> > On Sun, Oct 12, 2025 at 10:12 PM Paul Moore <paul@paul-moore.com> wrote:
> > > On Sat, Oct 11, 2025 at 1:09 PM James Bottomley
> > > <James.Bottomley@hansenpartnership.com> wrote:
> > > > On Sat, 2025-10-11 at 09:31 -0700, Alexei Starovoitov wrote:
> > > > > On Sat, Oct 11, 2025 at 7:52 AM James Bottomley
> > > > > <James.Bottomley@hansenpartnership.com> wrote:
> > > > > >
> > > > > > It doesn't need to, once we check both the loader and the map, the
> > > > > > integrity is verified and the loader can be trusted to run and
> > > > > > relocate the map into the bpf program
> > > > >
> > > > > You should read KP's cover letter again and then research trusted
> > > > > hash chains. Here is a quote from the first googled link:
> > > > >
> > > > > "A trusted hash chain is a cryptographic process used to verify the
> > > > > integrity and authenticity of data by creating a sequence of hash
> > > > > values, where each hash is linked to the next".
> > > > >
> > > > > In addition KP's algorithm was vetted by various security teams.
> > > > > There is nothing novel here. It's a classic algorithm used
> > > > > to verify integrity and that's what was implemented.
> > > >
> > > > Both KP and Blaise's patch sets are implementations of trusted hash
> > > > chains. The security argument isn't about whether the hash chain
> > > > algorithm works, it's about where, in relation to the LSM hook, the
> > > > hash chain verification completes.
>
> Not true. Blaise's patch is a trusted hash chain denial.
It would be helpful if you could clarify what you mean by "trusted
hash chain denial" and how that differs from a "trusted hash chain".
> > > Alexei, considering the discussion from the past few days, and the
> > > responses to all of your objections, I'm not seeing a clear reason why
> > > you are opposed to sending Blaise's patchset up to Linus. What is
> > > preventing you from sending Blaise's patch up to Linus?
> >
> > With the merge window behind us, and the link tag discussion winding
> > down ;) , I thought it might be worthwhile to bubble this thread back
> > up to the top of everyone's inbox.
>
> Please stop this spam. The reasons for rejection were explained
> multiple times.
I believe we have provided satisfactory counter arguments in each of
those cases, and those counter arguments remain unanswered. Without a
response it's impossible to move forward towards a solution that
everyone can find acceptable, unless that is the core issue? Is your
position that any solution outside of what you sent to Linus during
this past merge window is unacceptable?
--
paul-moore.com
^ permalink raw reply
* Re: [PATCH bpf-next v2 0/3] BPF signature hash chains
From: Alexei Starovoitov @ 2025-10-16 22:00 UTC (permalink / raw)
To: Paul Moore
Cc: Linus Torvalds, Alexei Starovoitov, KP Singh, Blaise Boscaccy,
James Bottomley, bpf, LSM List, K. Y. Srinivasan, Daniel Borkmann,
Andrii Nakryiko, wufan, Quentin Monnet
In-Reply-To: <CAHC9VhTvxgufmxHZFBd023xgkOyp9Cmq-hA-Gv8sJF1xYQBFSA@mail.gmail.com>
On Thu, Oct 16, 2025 at 1:51 PM Paul Moore <paul@paul-moore.com> wrote:
>
> On Sun, Oct 12, 2025 at 10:12 PM Paul Moore <paul@paul-moore.com> wrote:
> > On Sat, Oct 11, 2025 at 1:09 PM James Bottomley
> > <James.Bottomley@hansenpartnership.com> wrote:
> > > On Sat, 2025-10-11 at 09:31 -0700, Alexei Starovoitov wrote:
> > > > On Sat, Oct 11, 2025 at 7:52 AM James Bottomley
> > > > <James.Bottomley@hansenpartnership.com> wrote:
> > > > >
> > > > > It doesn't need to, once we check both the loader and the map, the
> > > > > integrity is verified and the loader can be trusted to run and
> > > > > relocate the map into the bpf program
> > > >
> > > > You should read KP's cover letter again and then research trusted
> > > > hash chains. Here is a quote from the first googled link:
> > > >
> > > > "A trusted hash chain is a cryptographic process used to verify the
> > > > integrity and authenticity of data by creating a sequence of hash
> > > > values, where each hash is linked to the next".
> > > >
> > > > In addition KP's algorithm was vetted by various security teams.
> > > > There is nothing novel here. It's a classic algorithm used
> > > > to verify integrity and that's what was implemented.
> > >
> > > Both KP and Blaise's patch sets are implementations of trusted hash
> > > chains. The security argument isn't about whether the hash chain
> > > algorithm works, it's about where, in relation to the LSM hook, the
> > > hash chain verification completes.
Not true. Blaise's patch is a trusted hash chain denial.
> >
> > Alexei, considering the discussion from the past few days, and the
> > responses to all of your objections, I'm not seeing a clear reason why
> > you are opposed to sending Blaise's patchset up to Linus. What is
> > preventing you from sending Blaise's patch up to Linus?
>
> With the merge window behind us, and the link tag discussion winding
> down ;) , I thought it might be worthwhile to bubble this thread back
> up to the top of everyone's inbox.
Please stop this spam. The reasons for rejection were explained
multiple times.
^ permalink raw reply
* Re: [PATCH bpf-next v2 0/3] BPF signature hash chains
From: Paul Moore @ 2025-10-16 20:51 UTC (permalink / raw)
To: Alexei Starovoitov, Linus Torvalds
Cc: Alexei Starovoitov, KP Singh, Blaise Boscaccy, James Bottomley,
bpf, LSM List, K. Y. Srinivasan, Daniel Borkmann, Andrii Nakryiko,
wufan, Quentin Monnet
In-Reply-To: <CAHC9VhSU0UCHW9ApHsVQLX9ar6jTEfAW4b4bBi5-fbbsOaashg@mail.gmail.com>
On Sun, Oct 12, 2025 at 10:12 PM Paul Moore <paul@paul-moore.com> wrote:
> On Sat, Oct 11, 2025 at 1:09 PM James Bottomley
> <James.Bottomley@hansenpartnership.com> wrote:
> > On Sat, 2025-10-11 at 09:31 -0700, Alexei Starovoitov wrote:
> > > On Sat, Oct 11, 2025 at 7:52 AM James Bottomley
> > > <James.Bottomley@hansenpartnership.com> wrote:
> > > >
> > > > It doesn't need to, once we check both the loader and the map, the
> > > > integrity is verified and the loader can be trusted to run and
> > > > relocate the map into the bpf program
> > >
> > > You should read KP's cover letter again and then research trusted
> > > hash chains. Here is a quote from the first googled link:
> > >
> > > "A trusted hash chain is a cryptographic process used to verify the
> > > integrity and authenticity of data by creating a sequence of hash
> > > values, where each hash is linked to the next".
> > >
> > > In addition KP's algorithm was vetted by various security teams.
> > > There is nothing novel here. It's a classic algorithm used
> > > to verify integrity and that's what was implemented.
> >
> > Both KP and Blaise's patch sets are implementations of trusted hash
> > chains. The security argument isn't about whether the hash chain
> > algorithm works, it's about where, in relation to the LSM hook, the
> > hash chain verification completes.
>
> Alexei, considering the discussion from the past few days, and the
> responses to all of your objections, I'm not seeing a clear reason why
> you are opposed to sending Blaise's patchset up to Linus. What is
> preventing you from sending Blaise's patch up to Linus?
With the merge window behind us, and the link tag discussion winding
down ;) , I thought it might be worthwhile to bubble this thread back
up to the top of everyone's inbox.
--
paul-moore.com
^ permalink raw reply
* [PATCH] apparmor: Replace deprecated strcpy in d_namespace_path
From: Thorsten Blum @ 2025-10-16 17:49 UTC (permalink / raw)
To: John Johansen, Paul Moore, James Morris, Serge E. Hallyn
Cc: linux-hardening, Thorsten Blum, apparmor, linux-security-module,
linux-kernel
strcpy() is deprecated; replace it with a direct '/' assignment. The
buffer is already NUL-terminated, so there is no need to copy an
additional NUL terminator as strcpy() did.
Update the comment and add the local variable 'is_root' for clarity.
Link: https://github.com/KSPP/linux/issues/88
Signed-off-by: Thorsten Blum <thorsten.blum@linux.dev>
---
security/apparmor/path.c | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/security/apparmor/path.c b/security/apparmor/path.c
index d6c74c357ffd..65a0ca5cc1bd 100644
--- a/security/apparmor/path.c
+++ b/security/apparmor/path.c
@@ -164,12 +164,15 @@ static int d_namespace_path(const struct path *path, char *buf, char **name,
}
out:
- /*
- * Append "/" to the pathname. The root directory is a special
- * case; it already ends in slash.
+ /* Append "/" to directory paths, except for root "/" which
+ * already ends in a slash.
*/
- if (!error && isdir && ((*name)[1] != '\0' || (*name)[0] != '/'))
- strcpy(&buf[aa_g_path_max - 2], "/");
+ if (!error && isdir) {
+ bool is_root = (*name)[0] == '/' && (*name)[1] == '\0';
+
+ if (!is_root)
+ buf[aa_g_path_max - 2] = '/';
+ }
return error;
}
--
2.51.0
^ permalink raw reply related
* [PATCH] apparmor: Replace deprecated strcpy with memcpy in gen_symlink_name
From: Thorsten Blum @ 2025-10-16 15:41 UTC (permalink / raw)
To: John Johansen, Paul Moore, James Morris, Serge E. Hallyn
Cc: linux-hardening, Thorsten Blum, apparmor, linux-security-module,
linux-kernel
strcpy() is deprecated; use memcpy() instead. Unlike strcpy(), memcpy()
does not copy the NUL terminator from the source string, which would be
overwritten anyway on every iteration when using strcpy(). snprintf()
then ensures that 'char *s' is NUL-terminated.
Replace the hard-coded path length to remove the magic number 6, and add
a comment explaining the extra 11 bytes.
Link: https://github.com/KSPP/linux/issues/88
Signed-off-by: Thorsten Blum <thorsten.blum@linux.dev>
---
security/apparmor/apparmorfs.c | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
index 391a586d0557..4b2752200ce2 100644
--- a/security/apparmor/apparmorfs.c
+++ b/security/apparmor/apparmorfs.c
@@ -1602,16 +1602,20 @@ static char *gen_symlink_name(int depth, const char *dirname, const char *fname)
{
char *buffer, *s;
int error;
- int size = depth * 6 + strlen(dirname) + strlen(fname) + 11;
+ const char *path = "../../";
+ size_t path_len = strlen(path);
+ int size;
+ /* Extra 11 bytes: "raw_data" (9) + two slashes "//" (2) */
+ size = depth * path_len + strlen(dirname) + strlen(fname) + 11;
s = buffer = kmalloc(size, GFP_KERNEL);
if (!buffer)
return ERR_PTR(-ENOMEM);
for (; depth > 0; depth--) {
- strcpy(s, "../../");
- s += 6;
- size -= 6;
+ memcpy(s, path, path_len);
+ s += path_len;
+ size -= path_len;
}
error = snprintf(s, size, "raw_data/%s/%s", dirname, fname);
--
2.51.0
^ permalink raw reply related
* Re: [f2fs-dev] [PATCH kvm-next V11 6/7] KVM: guest_memfd: Enforce NUMA mempolicy using shared policy
From: Gregory Price @ 2025-10-16 14:17 UTC (permalink / raw)
To: Sean Christopherson
Cc: Shivank Garg, jgowans, mhocko, jack, kvm, david, linux-btrfs, aik,
papaluri, kalyazin, peterx, linux-mm, clm, ddutile,
linux-kselftest, shdhiman, gshan, ying.huang, shuah, roypat,
matthew.brost, linux-coco, zbestahu, lorenzo.stoakes,
linux-bcachefs, ira.weiny, dhavale, jmorris, willy, hch, chao.gao,
tabba, ziy, rientjes, yuzhao, xiang, nikunj, serge, amit,
thomas.lendacky, ashish.kalra, chao.p.peng, yan.y.zhao, byungchul,
michael.day, Neeraj.Upadhyay, michael.roth, bfoster, bharata,
josef, Liam.Howlett, ackerleytng, dsterba, viro, jefflexu,
jaegeuk, dan.j.williams, surenb, vbabka, paul, joshua.hahnjy,
apopple, brauner, quic_eberman, rakie.kim, cgzones, pvorel,
linux-erofs, kent.overstreet, linux-kernel, linux-f2fs-devel,
pankaj.gupta, linux-security-module, lihongbo22, linux-fsdevel,
pbonzini, akpm, vannapurve, suzuki.poulose, rppt, jgg
In-Reply-To: <aPAkxp67-R9aQ8oN@google.com>
On Wed, Oct 15, 2025 at 03:48:38PM -0700, Sean Christopherson wrote:
> On Wed, Oct 15, 2025, Gregory Price wrote:
> > why is __kvm_gmem_get_policy using
> > mpol_shared_policy_lookup()
> > instead of
> > get_vma_policy()
>
> With the disclaimer that I haven't followed the gory details of this series super
> closely, my understanding is...
>
> Because the VMA is a means to an end, and we want the policy to persist even if
> the VMA goes away.
>
Ah, you know, now that i've taken a close look, I can see that you've
essentially modeled this after ipc/shm.c | mm/shmem.c pattern.
What's had me scratching my chin is that shm/shmem already has a
mempolicy pattern which ends up using folio_alloc_mpol() where the
relationship is
tmpfs: sb_info->mpol = default set by user
create_file: inode inherits copy of sb_info->mpol
fault: mpol = shmem_get_pgoff_policy(info, index, order, &ilx);
folio = folio_alloc_mpol(gfp, order, mpol, ilx, numa_node_id())
So this inode mempolicy in guest_memfd is really acting more as a the
filesystem-default mempolicy, which you want to survive even if userland
never maps the memory/unmaps the memory.
So the relationship is more like
guest_memfd -> creates fd/inode <- copies task mempolicy (if set)
vm: allocates memory via filemap_get_folio_mpol()
userland mmap(fd):
creates new inode<->vma mapping
vma->mpol = kvm_gmem_get_policy()
calls to set/get_policy/mbind go through kvm_gmem
This makes sense, sorry for the noise. Have been tearing apart
mempolicy lately and I'm disliking the general odor coming off
it as a whole. I had been poking at adding mempolicy support to
filemap and you got there first. Overall I think there are still
other problems with mempolicy, but this all looks fine as-is.
~Gregory
^ permalink raw reply
* Re: [f2fs-dev] [PATCH kvm-next V11 6/7] KVM: guest_memfd: Enforce NUMA mempolicy using shared policy
From: Garg, Shivank @ 2025-10-16 12:58 UTC (permalink / raw)
To: Sean Christopherson, Gregory Price
Cc: jgowans, mhocko, jack, kvm, david, linux-btrfs, aik, papaluri,
kalyazin, peterx, linux-mm, clm, ddutile, linux-kselftest,
shdhiman, gshan, ying.huang, shuah, roypat, matthew.brost,
linux-coco, zbestahu, lorenzo.stoakes, linux-bcachefs, ira.weiny,
dhavale, jmorris, willy, hch, chao.gao, tabba, ziy, rientjes,
yuzhao, xiang, nikunj, serge, amit, thomas.lendacky, ashish.kalra,
chao.p.peng, yan.y.zhao, byungchul, michael.day, Neeraj.Upadhyay,
michael.roth, bfoster, bharata, josef, Liam.Howlett, ackerleytng,
dsterba, viro, jefflexu, jaegeuk, dan.j.williams, surenb, vbabka,
paul, joshua.hahnjy, apopple, brauner, quic_eberman, rakie.kim,
cgzones, pvorel, linux-erofs, kent.overstreet, linux-kernel,
pankaj.gupta, linux-security-module, lihongbo22, linux-fsdevel,
pbonzini, akpm, vannapurve, suzuki.poulose, rppt, jgg,
linux-f2fs-devel
In-Reply-To: <aPAkxp67-R9aQ8oN@google.com>
On 10/16/2025 4:18 AM, Sean Christopherson wrote:
> On Wed, Oct 15, 2025, Gregory Price wrote:
>> On Fri, Sep 26, 2025 at 12:36:27PM -0700, Sean Christopherson via Linux-f2fs-devel wrote:
>>>>
>>>> static struct mempolicy *kvm_gmem_get_policy(struct vm_area_struct *vma,
>>>> unsigned long addr, pgoff_t *pgoff)
>>>> {
>>>> *pgoff = vma->vm_pgoff + ((addr - vma->vm_start) >> PAGE_SHIFT);
>>>>
>>>> return __kvm_gmem_get_policy(GMEM_I(file_inode(vma->vm_file)), *pgoff);
>>>
>>> Argh!!!!! This breaks the selftest because do_get_mempolicy() very specifically
>>> falls back to the default_policy, NOT to the current task's policy. That is
>>> *exactly* the type of subtle detail that needs to be commented, because there's
>>> no way some random KVM developer is going to know that returning NULL here is
>>> important with respect to get_mempolicy() ABI.
>>>
>>
>> Do_get_mempolicy was designed to be accessed by the syscall, not as an
>> in-kernel ABI.
>
> Ya, by "get_mempolicy() ABI" I meant the uABI for the get_mempolicy syscall.
>
>> get_task_policy also returns the default policy if there's nothing
>> there, because that's what applies.
>>
>> I have dangerous questions:
>
> Not dangerous at all, I find them very helpful!
>
>> why is __kvm_gmem_get_policy using
>> mpol_shared_policy_lookup()
>> instead of
>> get_vma_policy()
>
> With the disclaimer that I haven't followed the gory details of this series super
> closely, my understanding is...
>
> Because the VMA is a means to an end, and we want the policy to persist even if
> the VMA goes away.
>
> With guest_memfd, KVM effectively inverts the standard MMU model. Instead of mm/
> being the primary MMU and KVM being a secondary MMU, guest_memfd is the primary
> MMU and any VMAs are secondary (mostly; it's probably more like 1a and 1b). This
> allows KVM to map guest_memfd memory into a guest without a VMA, or with more
> permissions than are granted to host userspace, e.g. guest_memfd memory could be
> writable by the guest, but read-only for userspace.
>
> But we still want to support things like mbind() so that userspace can ensure
> guest_memfd allocations align with the vNUMA topology presented to the guest,
> or are bound to the NUMA node where the VM will run. We considered adding equivalent
> file-based syscalls, e.g. fbind(), but IIRC the consensus was that doing so was
> unnecessary (and potentially messy?) since we were planning on eventually adding
> mmap() support to guest_memfd anyways.
>
>> get_vma_policy does this all for you
>
> I assume that doesn't work if the intent is for new VMAs to pick up the existing
> policy from guest_memfd? And more importantly, guest_memfd needs to hook
> ->set_policy so that changes through e.g. mbind() persist beyond the lifetime of
> the VMA.
>
Additionally, the shared_policy based design enables range-based policies via its RB-tree
implementation. IIUC, this will not work with VMA-specific policy design.
>> struct mempolicy *get_vma_policy(struct vm_area_struct *vma,
>> unsigned long addr, int order, pgoff_t *ilx)
>> {
>> struct mempolicy *pol;
>>
>> pol = __get_vma_policy(vma, addr, ilx);
>> if (!pol)
>> pol = get_task_policy(current);
>> if (pol->mode == MPOL_INTERLEAVE ||
>> pol->mode == MPOL_WEIGHTED_INTERLEAVE) {
>> *ilx += vma->vm_pgoff >> order;
>> *ilx += (addr - vma->vm_start) >> (PAGE_SHIFT + order);
>> }
>> return pol;
>> }
>>
>> Of course you still have the same issue: get_task_policy will return the
>> default, because that's what applies.
>>
>> do_get_mempolicy just seems like the completely incorrect interface to
>> be using here.
^ permalink raw reply
* Re: [PATCH v2] nbd: override creds to kernel when calling sock_{send,recv}msg()
From: Ming Lei @ 2025-10-16 11:02 UTC (permalink / raw)
To: Ondrej Mosnacek
Cc: Josef Bacik, Jens Axboe, linux-block, nbd, linux-security-module,
selinux
In-Reply-To: <20251010080900.1680512-1-omosnace@redhat.com>
On Fri, Oct 10, 2025 at 10:09:00AM +0200, Ondrej Mosnacek wrote:
> sock_{send,recv}msg() internally calls security_socket_{send,recv}msg(),
> which does security checks (e.g. SELinux) for socket access against the
> current task. However, _sock_xmit() in drivers/block/nbd.c may be called
> indirectly from a userspace syscall, where the NBD socket access would
> be incorrectly checked against the calling userspace task (which simply
> tries to read/write a file that happens to reside on an NBD device).
>
> To fix this, temporarily override creds to kernel ones before calling
> the sock_*() functions. This allows the security modules to recognize
> this as internal access by the kernel, which will normally be allowed.
>
> A way to trigger the issue is to do the following (on a system with
> SELinux set to enforcing):
>
> ### Create nbd device:
> truncate -s 256M /tmp/testfile
> nbd-server localhost:10809 /tmp/testfile
>
> ### Connect to the nbd server:
> nbd-client localhost
>
> ### Create mdraid array
> mdadm --create -l 1 -n 2 /dev/md/testarray /dev/nbd0 missing
>
> After these steps, assuming the SELinux policy doesn't allow the
> unexpected access pattern, errors will be visible on the kernel console:
>
> [ 142.204243] nbd0: detected capacity change from 0 to 524288
> [ 165.189967] md: async del_gendisk mode will be removed in future, please upgrade to mdadm-4.5+
> [ 165.252299] md/raid1:md127: active with 1 out of 2 mirrors
> [ 165.252725] md127: detected capacity change from 0 to 522240
> [ 165.255434] block nbd0: Send control failed (result -13)
> [ 165.255718] block nbd0: Request send failed, requeueing
> [ 165.256006] block nbd0: Dead connection, failed to find a fallback
> [ 165.256041] block nbd0: Receive control failed (result -32)
> [ 165.256423] block nbd0: shutting down sockets
> [ 165.257196] I/O error, dev nbd0, sector 2048 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 2
> [ 165.257736] Buffer I/O error on dev md127, logical block 0, async page read
> [ 165.258263] I/O error, dev nbd0, sector 2048 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 2
> [ 165.259376] Buffer I/O error on dev md127, logical block 0, async page read
> [ 165.259920] I/O error, dev nbd0, sector 2048 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 2
> [ 165.260628] Buffer I/O error on dev md127, logical block 0, async page read
> [ 165.261661] ldm_validate_partition_table(): Disk read failed.
> [ 165.262108] I/O error, dev nbd0, sector 2048 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 2
> [ 165.262769] Buffer I/O error on dev md127, logical block 0, async page read
> [ 165.263697] I/O error, dev nbd0, sector 2048 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 2
> [ 165.264412] Buffer I/O error on dev md127, logical block 0, async page read
> [ 165.265412] I/O error, dev nbd0, sector 2048 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 2
> [ 165.265872] Buffer I/O error on dev md127, logical block 0, async page read
> [ 165.266378] I/O error, dev nbd0, sector 2048 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 2
> [ 165.267168] Buffer I/O error on dev md127, logical block 0, async page read
> [ 165.267564] md127: unable to read partition table
> [ 165.269581] I/O error, dev nbd0, sector 0 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 2
> [ 165.269960] Buffer I/O error on dev nbd0, logical block 0, async page read
> [ 165.270316] I/O error, dev nbd0, sector 0 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 2
> [ 165.270913] Buffer I/O error on dev nbd0, logical block 0, async page read
> [ 165.271253] I/O error, dev nbd0, sector 0 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 2
> [ 165.271809] Buffer I/O error on dev nbd0, logical block 0, async page read
> [ 165.272074] ldm_validate_partition_table(): Disk read failed.
> [ 165.272360] nbd0: unable to read partition table
> [ 165.289004] ldm_validate_partition_table(): Disk read failed.
> [ 165.289614] nbd0: unable to read partition table
>
> The corresponding SELinux denial on Fedora/RHEL will look like this
> (assuming it's not silenced):
> type=AVC msg=audit(1758104872.510:116): avc: denied { write } for pid=1908 comm="mdadm" laddr=::1 lport=32772 faddr=::1 fport=10809 scontext=system_u:system_r:mdadm_t:s0-s0:c0.c1023 tcontext=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 tclass=tcp_socket permissive=0
>
> The respective backtrace looks like this:
> @security[mdadm, -13,
> handshake_exit+221615650
> handshake_exit+221615650
> handshake_exit+221616465
> security_socket_sendmsg+5
> sock_sendmsg+106
> handshake_exit+221616150
> sock_sendmsg+5
> __sock_xmit+162
> nbd_send_cmd+597
> nbd_handle_cmd+377
> nbd_queue_rq+63
> blk_mq_dispatch_rq_list+653
> __blk_mq_do_dispatch_sched+184
> __blk_mq_sched_dispatch_requests+333
> blk_mq_sched_dispatch_requests+38
> blk_mq_run_hw_queue+239
> blk_mq_dispatch_plug_list+382
> blk_mq_flush_plug_list.part.0+55
> __blk_flush_plug+241
> __submit_bio+353
> submit_bio_noacct_nocheck+364
> submit_bio_wait+84
> __blkdev_direct_IO_simple+232
> blkdev_read_iter+162
> vfs_read+591
> ksys_read+95
> do_syscall_64+92
> entry_SYSCALL_64_after_hwframe+120
> ]: 1
>
> The issue has started to appear since commit 060406c61c7c ("block: add
> plug while submitting IO").
>
> Cc: Ming Lei <ming.lei@redhat.com>
> Link: https://bugzilla.redhat.com/show_bug.cgi?id=2348878
> Fixes: 060406c61c7c ("block: add plug while submitting IO")
> Signed-off-by: Ondrej Mosnacek <omosnace@redhat.com>
> ---
>
> Changes in v2:
> * Move put_cred() after destroy_workqueue() in nbd_cleanup() to avoid a UAF
> * Add some more details into the commit message
> * Add a Fixes: tag
Hello Jens,
Any chance to pull this patch into v6.18 if you are fine?
Thanks,
Ming
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox