diff mbox series

LSM: allow loadable kernel module based LSM modules

Message ID caafb609-8bef-4840-a080-81537356fc60@I-love.SAKURA.ne.jp (mailing list archive)
State Rejected
Delegated to: Paul Moore
Headers show
Series LSM: allow loadable kernel module based LSM modules | expand

Commit Message

Tetsuo Handa Sept. 4, 2024, 7:10 a.m. UTC
Until 2.6.23, it was officially possible to register/unregister LSM modules
that are implemented as loadable kernel modules. But from 2.6.24 to 6.11,
it is not officially possible to do so due to commit 20510f2f4e2d
("security: Convert LSM into a static interface"). When that commit was
discussed, effectively SELinux was the only in-tree LSM user, and therefore
out-of-tree LSM users were not able to express opinions.

But that commit missed realities that

  how difficult/unrealistic for Linux users who are using prebuilt kernel
  packages provided by Linux distributors to replace their kernels

  how difficult for Linux distributors to allow their users to use in-tree
  LSM modules which that distributor is not familiar with [1] because Linux
  distributors are supposed to support kernel packages they built and
  shipped

  Linux distributors do not want to enable out-of-tree code due to upstream
  first policy, while Linux kernel development community can not afford
  accepting whatever proposed code due to limited resources

. These realities keep away out-of-tree LSMs (or even in-tree LSMs) which
are not built into vmlinux, and caused unhappy times for Linux users who
want to try a new LSM module. An approach that can survive is that LSM
modules that cannot be built into vmlinux is to provide as a loadable
kernel module.

Therefore, I had been providing a workaround called AKARI that allows Linux
users to use TOMOYO as a loadable kernel module [2] from 2.6.0 to 6.11. But
commit 417c5643cd67 ("lsm: replace indirect LSM hook calls with static
calls") made it difficult to use AKARI due to replacing the linked list
(which can allow registering more than number of built-in LSM modules) with
static call slots (which cannot allow registering more than number of
built-in LSM modules).

I considered trying Kprobes and BPF-LSM for reimplementing TOMOYO, but the
conclusion is that Kprobes and BPF-LSM are too restricted to reimplement
TOMOYO.

Paul Moore has commented

  I do not intentionally plan to make life difficult for the out-of-tree
  LSMs, but if that happens as a result of design decisions intended to
  benefit in-tree LSMs that is acceptable as far as I am concerned.

at [3]. But the static calls change suddenly jumped in, and that made life
difficult for the in-tree but not built-in LSMs as a result of design
decisions intended to benefit in-tree and built-in LSMs.

Now that the static calls change is going to be merged into Linux 6.12,
I propose this patch for recovering life for "in-tree but not built-in"
LSMs, by officially allowing loadable kernel module based LSM modules.

I'm not planning to propose a change for allowing unregistration of LSM
modules after boot, for I agree one of concerns

  the ability to unload a security module is not required by in-tree
  users and potentially complicates the overall security architecture

in "security: Convert LSM into a static interface" change. But I assert
that the ability to load a security module (i.e. loadable kernel module
based LSMs) is inevitable due to unsolvable realities.

The LSM hooks for loadable kernel module based LSMs are enabled only if
the kernel is booted with 'dynamic_lsm' kernel command line option added
in order to honer the administrator's decision and avoid overhead when
the administrator does not plan to use loadable kernel module based LSMs.

Of course, Linux distributors might revert this patch in their kernels if
their kernels are intended for very specific/dedicated purposes. But what
is important is that we again officially support LSM modules that are
implemented as loadable kernel modules in accordance with upstream first
policy.

Link: https://bugzilla.redhat.com/show_bug.cgi?id=542986 [1]
Link: https://tomoyo.sourceforge.net/akari/comparison.html [2]
Link: https://lkml.kernel.org/r/CAHC9VhSG2UzE9N0-tAJc8B3Mj1PEuJ2b6wso_DUs_Y83yqwhjA@mail.gmail.com [3]
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
---
I'll start updating security/tomoyo after this change is accepted. For
those who want to try this change now, an example module is shown below.
-----
#include <linux/lsm_hooks.h>

static void test_bprm_committed_creds(const struct linux_binprm *bprm)
{
        pr_info_once("%s() is called\n", __func__);
}

static int test_file_open(struct file *f)
{
        pr_info_once("%s() is called\n", __func__);
        return 0;
}

static const struct lsm_id test_lsmid = {
        .name = "test",
};

static struct security_dynamic_hook_list test_hooks[] = {
        LSM_HOOK_INIT(file_open, test_file_open),
        LSM_HOOK_INIT(bprm_committed_creds, test_bprm_committed_creds),
};

static int __init test_init(void)
{
        return security_add_dynamic_hooks(test_hooks, ARRAY_SIZE(test_hooks),
                                          &test_lsmid);
}

module_init(test_init);
MODULE_LICENSE("GPL");
-----

 include/linux/lsm_hooks.h |  24 +++++
 security/security.c       | 200 +++++++++++++++++++++++++++++++++++---
 2 files changed, 210 insertions(+), 14 deletions(-)

Comments

Paul Moore Sept. 4, 2024, 2:23 p.m. UTC | #1
On Wed, Sep 4, 2024 at 3:10 AM Tetsuo Handa
<penguin-kernel@i-love.sakura.ne.jp> wrote:
>
> Until 2.6.23, it was officially possible to register/unregister LSM modules
> that are implemented as loadable kernel modules.

...

> Paul Moore has commented
>
>   I do not intentionally plan to make life difficult for the out-of-tree
>   LSMs, but if that happens as a result of design decisions intended to
>   benefit in-tree LSMs that is acceptable as far as I am concerned.

Patches that add complexity to the LSM framework without any benefit
to the upstream, in-tree LSMs, or the upstream kernel in general, are
not good candidates for inclusion in the upstream kernel.
Tetsuo Handa Sept. 6, 2024, 7:43 a.m. UTC | #2
On 2024/09/04 23:23, Paul Moore wrote:
> On Wed, Sep 4, 2024 at 3:10 AM Tetsuo Handa
> <penguin-kernel@i-love.sakura.ne.jp> wrote:
>>
>> Until 2.6.23, it was officially possible to register/unregister LSM modules
>> that are implemented as loadable kernel modules.
> 
> ...
> 
>> Paul Moore has commented
>>
>>   I do not intentionally plan to make life difficult for the out-of-tree
>>   LSMs, but if that happens as a result of design decisions intended to
>>   benefit in-tree LSMs that is acceptable as far as I am concerned.
> 
> Patches that add complexity to the LSM framework without any benefit
> to the upstream, in-tree LSMs, or the upstream kernel in general, are
> not good candidates for inclusion in the upstream kernel.
> 

The idea and implementation for using LSM from loadable kernel modules is what
I demonstrated you in a lightening talk session in LinuxCon North America 2010.
It is 14 years since we learned my concern, and you had been ignoring my concern
until now.

The first solution is "do not use static calls". But you won't agree it. Also,
I'm not against use of static calls as long as LKM-based LSM is supported.

The second solution is "export static calls" (and leave how it is used by
LKM-based LSMs). But some of LSM people do not like solutions that can allow
LKMs to disable built-in LSMs.

The third solution is "continue using linked list for LKM-based LSMs" which was
suggested by KP Singh [1]. I'm OK with this solution, though it is unlucky that
LKM-based LSMs can't be benefited from "static calls".

If you ignore my concern, I have to NACK the static call changes you are
going to send in the upcoming merge window.



Link: https://lkml.kernel.org/r/CACYkzJ7ght66802wQFKzokfJKMKDOobYgeaCpu5Gx=iX0EuJVg@mail.gmail.com [1]
Tetsuo Handa Sept. 6, 2024, 10:55 a.m. UTC | #3
On 2024/09/06 16:43, Tetsuo Handa wrote:
> On 2024/09/04 23:23, Paul Moore wrote:
>> Patches that add complexity to the LSM framework without any benefit
>> to the upstream, in-tree LSMs, or the upstream kernel in general, are
>> not good candidates for inclusion in the upstream kernel.

This patch adds a clear value for Linux users that people get more chances to
use LSM modules which match their needs.

Quoting from [1]:

  Regarding CONFIG_MODULES=y,
  "Vendor-A enables module-A" == "Vendor-A provides support for module-A" and
  "Vendor-B enables module-B" == "Vendor-B provides support for module-B".

  Regarding CONFIG_SECURITY=y (namely in the RH world),
  "Distributor-A enables LSM-A" == "Distributor-A provides support for LSM-A".
  However, "Distributor-A does not enable LSM-B" == "Some vendor is impossible to
  provide support for LSM-B".

  "Distributor-A does not enable module-B" == "Distributor-A is not responsible for
  providing support for module-B" and "Vendor-B enables LSM-B" == "Vendor-B provides
  support for LSM-B" are what I expect.

  Current LSM interface does not allow LSM-B to exist in Distributor-A's systems.
  The "enable" == "support" model should be allowed for LSM interface as well.
  What a strange asymmetry rule!

Your "any benefit to in-tree LSMs" is completely ignoring Linux users.
LSM is for all Linux users, LSM is not only for LSM developers.



Link: https://lkml.kernel.org/r/c2a3279d-451d-23df-0911-e545d21492e6@I-love.SAKURA.ne.jp [1]
Paul Moore Sept. 6, 2024, 2:24 p.m. UTC | #4
On Fri, Sep 6, 2024 at 3:43 AM Tetsuo Handa
<penguin-kernel@i-love.sakura.ne.jp> wrote:
> On 2024/09/04 23:23, Paul Moore wrote:
> > On Wed, Sep 4, 2024 at 3:10 AM Tetsuo Handa
> > <penguin-kernel@i-love.sakura.ne.jp> wrote:

...

> If you ignore my concern, I have to NACK the static call changes you are
> going to send in the upcoming merge window.

I'm not ignoring your concern, I've responded to your emails and
patches on the topic over, and over, and over, and over again by
trying to explain to you that your goal of supporting out-of-tree LSMs
regardless of the impact to the upstream LSM effort is not something
that is acceptable to the upstream LSM effort, or the Linux kernel in
general.

I've already recorded your NACK on several patches on two of the four
static call commits, if you like I can add it to the other two please
let me know and I'll be sure to do that.  I've recorded your NACKs on
other patches in the past and mentioned those NACKs to Linus when
sending the pull request, and I will do so again during this upcoming
merge window.
Tetsuo Handa Sept. 7, 2024, 10:14 a.m. UTC | #5
On 2024/09/06 23:24, Paul Moore wrote:
> On Fri, Sep 6, 2024 at 3:43 AM Tetsuo Handa
> <penguin-kernel@i-love.sakura.ne.jp> wrote:
>> If you ignore my concern, I have to NACK the static call changes you are
>> going to send in the upcoming merge window.
> 
> I'm not ignoring your concern, I've responded to your emails and
> patches on the topic over, and over, and over, and over again by
> trying to explain to you that your goal of supporting out-of-tree LSMs
> regardless of the impact to the upstream LSM effort is not something
> that is acceptable to the upstream LSM effort, or the Linux kernel in
> general.

I want LKM-based LSM support in order to allow one of in-tree LSMs (namely
TOMOYO) to be delivered to my intended users, for nobody can solve the
realities that commit 20510f2f4e2d ("security: Convert LSM into a static
interface") missed:

  how difficult/unrealistic for Linux users who are using prebuilt kernel
  packages provided by Linux distributors to replace their kernels

  how difficult for Linux distributors to allow their users to use in-tree
  LSM modules which that distributor is not familiar with [1] because Linux
  distributors are supposed to support kernel packages they built and
  shipped

  Linux distributors do not want to enable out-of-tree code due to upstream
  first policy, while Linux kernel development community can not afford
  accepting whatever proposed code due to limited resources

One of LSM developers commented me ( Mon, 22 Jul 2024 17:12:05 +0200
in off-list discusstion) about AKARI

  Ofcourse you found a way to hack it. You want me to curl bash pipe
  your kernel module code that disables certain protections and runs
  arbitrary hacks on my machine? No thank you!

  Ofcourse you change the memory mapping of data. You are misleading
  your users into trusting you and instead of contributing code and
  working with distros for your use case, you are shipping hacks and
  making noise without any constructive code contribution or alignment
  with distros for your use-case (below RHEL won't eneable it even
  if we had a proper API). 

and this patch is for following that comment. All concerns about updating
__ro_after_init data is gone if we move to a dual static call and linked
list based approach. I'm very very very sad that you did not respond to

  I think what you can do is send patches for an API for LKM based LSMs
  and have it merged before my series, I will work with the code I have
  and make LKM based LSMs work. If this work gets merged, and your
  use-case is accepted (I think I can speak for at least Kees [if not
  others] too here) we will help you if you get stuck with MAX_LSM_COUNT
  or a dual static call and linked list based approach.

in [2], and started saying "No" to this approach after you accepted
the static call changes. You are ignoring my concern!



Link: https://bugzilla.redhat.com/show_bug.cgi?id=542986 [1]
Link: https://lkml.kernel.org/r/CACYkzJ7ght66802wQFKzokfJKMKDOobYgeaCpu5Gx=iX0EuJVg@mail.gmail.com [2]
Tetsuo Handa Sept. 7, 2024, 1:41 p.m. UTC | #6
On 2024/09/06 23:24, Paul Moore wrote:
> I've already recorded your NACK on several patches on two of the four
> static call commits, if you like I can add it to the other two please
> let me know and I'll be sure to do that.  I've recorded your NACKs on
> other patches in the past and mentioned those NACKs to Linus when
> sending the pull request, and I will do so again during this upcoming
> merge window.

Adding Nacked-by: lines is not an indulgence for ignoring my concerns.
Commit f3b8788cde61 ("LSM: Identify modules by more than name") is an example
you added Nacked-by: line without adding hints for why I nacked it (e.g.
links to my posts).

  LSM: Identify modules by more than name

  Create a struct lsm_id to contain identifying information about Linux
  Security Modules (LSMs). At inception this contains the name of the
  module and an identifier associated with the security module.  Change
  the security_add_hooks() interface to use this structure.  Change the
  individual modules to maintain their own struct lsm_id and pass it to
  security_add_hooks().

  The values are for LSM identifiers are defined in a new UAPI
  header file linux/lsm.h. Each existing LSM has been updated to
  include it's LSMID in the lsm_id.

  The LSM ID values are sequential, with the oldest module
  LSM_ID_CAPABILITY being the lowest value and the existing modules
  numbered in the order they were included in the main line kernel.
  This is an arbitrary convention for assigning the values, but
  none better presents itself. The value 0 is defined as being invalid.
  The values 1-99 are reserved for any special case uses which may
  arise in the future. This may include attributes of the LSM
  infrastructure itself, possibly related to namespacing or network
  attribute management. A special range is identified for such attributes
  to help reduce confusion for developers unfamiliar with LSMs.

  LSM attribute values are defined for the attributes presented by
  modules that are available today. As with the LSM IDs, The value 0
  is defined as being invalid. The values 1-99 are reserved for any
  special case uses which may arise in the future.

How can people (or Linus) find why I nacked it from patch description of that commit?
The reason is partially explained in commit 063a7ce32ddc ("Merge tag 'lsm-pr-20240105'
of git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/lsm"), but is not accurate.

  - Add three new syscalls: lsm_list_modules(), lsm_get_self_attr(), and
    lsm_set_self_attr().

    The first syscall simply lists the LSMs enabled, while the second and
    third get and set the current process' LSM attributes. Yes, these
    syscalls may provide similar functionality to what can be found under
    /proc or /sys, but they were designed to support multiple,
    simultaneaous (stacked) LSMs from the start as opposed to the current
    /proc based solutions which were created at a time when only one LSM
    was allowed to be active at a given time.

    We have spent considerable time discussing ways to extend the
    existing /proc interfaces to support multiple, simultaneaous LSMs and
    even our best ideas have been far too ugly to support as a kernel
    API; after +20 years in the kernel, I felt the LSM layer had
    established itself enough to justify a handful of syscalls.

    Support amongst the individual LSM developers has been nearly
    unanimous, with a single objection coming from Tetsuo (TOMOYO) as he
    is worried that the LSM_ID_XXX token concept will make it more
    difficult for out-of-tree LSMs to survive. Several members of the LSM
    community have demonstrated the ability for out-of-tree LSMs to
    continue to exist by picking high/unused LSM_ID values as well as
    pointing out that many kernel APIs rely on integer identifiers, e.g.
    syscalls (!), but unfortunately Tetsuo's objections remain.

    My personal opinion is that while I have no interest in penalizing
    out-of-tree LSMs, I'm not going to penalize in-tree development to
    support out-of-tree development, and I view this as a necessary step
    forward to support the push for expanded LSM stacking and reduce our
    reliance on /proc and /sys which has occassionally been problematic
    for some container users. Finally, we have included the linux-api
    folks on (all?) recent revisions of the patchset and addressed all of
    their concerns.

I am not against about having LSM ID itself. I am against about the fact
that out-of-tree LSM modules cannot have stable LSM ID. Commit f3b8788cde61
wants to assign LSM ID sequentially whereas those who demonstrated me
suggests assigning LSM ID non-sequentially and pushes the burden of
managing collision to out-of-tree LSM modules. As a result, out-of-tree
LSM modules cannot start using userspace tools which rely on LSM ID.
Rewriting userspace tools when that out-of-tree LSM module succeeded
becoming in-tree is a penalty, for it breaks existing userspace tools
and also remains the risk of old LSM ID being reused by unrelated LSM
module.

The fact that out-of-tree LSM modules cannot have stable LSM ID penalizes
out-of-tree LSMs due to the risk of collision, and making it difficult for
Linux users to find LSMs they want because Linux users cannot know what
LSMs are available in the world. That is not a good usage of identifiers.

I suggested you that the LSM community should allow assigning stable LSM ID
to any LSM as long as that LSM is available to anybody, and serve as index
for helping people to find LSMs that match their needs.

Paul, where did you explain above when you sent pull request to Linus?
Linus, did you understand why I nacked it from that pull request from Paul?
Paul Moore Sept. 9, 2024, 8:18 p.m. UTC | #7
On Sat, Sep 7, 2024 at 6:14 AM Tetsuo Handa
<penguin-kernel@i-love.sakura.ne.jp> wrote:
> On 2024/09/06 23:24, Paul Moore wrote:
> > On Fri, Sep 6, 2024 at 3:43 AM Tetsuo Handa
> > <penguin-kernel@i-love.sakura.ne.jp> wrote:
> >> If you ignore my concern, I have to NACK the static call changes you are
> >> going to send in the upcoming merge window.
> >
> > I'm not ignoring your concern, I've responded to your emails and
> > patches on the topic over, and over, and over, and over again by
> > trying to explain to you that your goal of supporting out-of-tree LSMs
> > regardless of the impact to the upstream LSM effort is not something
> > that is acceptable to the upstream LSM effort, or the Linux kernel in
> > general.
>
> I want LKM-based LSM support in order to allow one of in-tree LSMs (namely
> TOMOYO) to be delivered to my intended users ...

As discussed many times already, the solution to in-tree LSMs not
being enabled is to simply enable them in a kernel for your users.  If
your users are limited to a specific kernel configuration due to
distro support issues/contracts, that is a problem you need to address
with the relevant distribution.  If the distribution is unwilling to
alter their kernel configuration to suit your needs, or your users,
that still does not make this an upstream problem, it is a problem
between you, your users, and the distribution.

> Adding Nacked-by: lines is not an indulgence for ignoring my concerns.

Adding NACKs, just like adding ACKs or any other patch metadata, is my
responsibility, nothing more, nothing less.

> Commit f3b8788cde61 ("LSM: Identify modules by more than name") is an example
> you added Nacked-by: line without adding hints for why I nacked it (e.g.
> links to my posts).

Please see the associated pull request email I sent to Linus where I
wrote several sentences about your objections:

https://lore.kernel.org/linux-security-module/3f5a7bc467d221543444a268dd1a1fe0@paul-moore.com

"Support amongst the individual LSM developers has been nearly
unanimous, with a single objection coming from Tetsuo (TOMOYO) as he
is worried that the LSM_ID_XXX token concept will make it more
difficult for out-of-tree LSMs to survive.  Several members of the LSM
 community have demonstrated the ability for out-of-tree LSMs to
continue to exist by picking high/unused LSM_ID values as well as
pointing out that many kernel APIs rely on integer identifiers, e.g.
syscalls (!), but unfortunately Tetsuo's objections remain.  My
personal opinion is that while I have no interest in penalizing
out-of-tree LSMs, I'm not going to penalize in-tree development to
support out-of-tree development, and I view this as a necessary step
forward to support the push for expanded LSM stacking and reduce our
reliance on /proc and /sys which has occassionally been problematic
for some container users."

Unless you present any new ideas in this thread, which I consider
highly unlikely at this point, this will be my last email to you in
this thread.  As mentioned previously, if you would like to see your
NACK recorded in the static call patch, let me know.
diff mbox series

Patch

diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index 090d1d3e19fed..afe4eeeb7bd52 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -71,6 +71,12 @@  struct lsm_static_calls_table {
 	#undef LSM_HOOK
 } __packed __randomize_layout;
 
+struct security_dynamic_hook_heads {
+	#define LSM_HOOK(RET, DEFAULT, NAME, ...) struct hlist_head NAME;
+	#include "lsm_hook_defs.h"
+	#undef LSM_HOOK
+} __randomize_layout;
+
 /**
  * struct lsm_id - Identify a Linux Security Module.
  * @lsm: name of the LSM, must be approved by the LSM maintainers
@@ -98,6 +104,13 @@  struct security_hook_list {
 	const struct lsm_id *lsmid;
 } __randomize_layout;
 
+struct security_dynamic_hook_list {
+	struct hlist_node		list;
+	struct hlist_head		*head;
+	union security_list_options	hook;
+	const struct lsm_id		*lsmid;
+} __randomize_layout;
+
 /*
  * Security blob size or offset data.
  */
@@ -130,14 +143,24 @@  struct lsm_blob_sizes {
  * care of the common case and reduces the amount of
  * text involved.
  */
+#ifndef MODULE
 #define LSM_HOOK_INIT(NAME, HOOK)			\
 	{						\
 		.scalls = static_calls_table.NAME,	\
 		.hook = { .NAME = HOOK }		\
 	}
+#else
+#define LSM_HOOK_INIT(NAME, HOOK)			\
+	{						\
+		.head = &security_hook_heads.NAME,	\
+		.hook = { .NAME = HOOK }		\
+	}
+#endif
 
 extern void security_add_hooks(struct security_hook_list *hooks, int count,
 			       const struct lsm_id *lsmid);
+extern int security_add_dynamic_hooks(struct security_dynamic_hook_list *hooks, int count,
+				      const struct lsm_id *lsmid);
 
 #define LSM_FLAG_LEGACY_MAJOR	BIT(0)
 #define LSM_FLAG_EXCLUSIVE	BIT(1)
@@ -170,6 +193,7 @@  struct lsm_info {
 /* 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 security_dynamic_hook_heads security_hook_heads;
 extern struct lsm_info __start_lsm_info[], __end_lsm_info[];
 extern struct lsm_info __start_early_lsm_info[], __end_early_lsm_info[];
 
diff --git a/security/security.c b/security/security.c
index 7272bbea05cb8..1b6c64c631ac0 100644
--- a/security/security.c
+++ b/security/security.c
@@ -130,6 +130,10 @@  static __initdata struct lsm_info *exclusive;
 #undef LSM_HOOK
 #undef DEFINE_LSM_STATIC_CALL
 
+struct security_dynamic_hook_heads security_hook_heads;
+EXPORT_SYMBOL_GPL(security_hook_heads);
+static DEFINE_STATIC_KEY_FALSE_RO(security_dynamic_hook_key);
+
 /*
  * Initialise a table of static calls for each LSM hook.
  * DEFINE_STATIC_CALL_NULL invocation above generates a key (STATIC_CALL_KEY)
@@ -644,6 +648,32 @@  void __init security_add_hooks(struct security_hook_list *hooks, int count,
 	}
 }
 
+static int __init enable_dynamic_hooks(char *str)
+{
+	static_branch_enable(&security_dynamic_hook_key);
+	pr_info("Dynamic LSM hook enabled.\n");
+	return 1;
+}
+__setup("dynamic_lsm", enable_dynamic_hooks);
+
+int security_add_dynamic_hooks(struct security_dynamic_hook_list *hooks, int count,
+			       const struct lsm_id *lsmid)
+{
+	int i;
+
+	if (!static_key_enabled(&security_dynamic_hook_key)) {
+		pr_info("Boot with 'dynamic_lsm' kernel command line option to enable dynamic LSM hook.\n");
+		return -EINVAL;
+	}
+	pr_info("Dynamic LSM hook: adding '%s' module.\n", lsmid->name);
+	for (i = 0; i < count; i++) {
+		hooks[i].lsmid = lsmid;
+		hlist_add_tail_rcu(&hooks[i].list, hooks[i].head);
+	}
+	return 0;
+}
+EXPORT_SYMBOL_GPL(security_add_dynamic_hooks);
+
 int call_blocking_lsm_notifier(enum lsm_event event, void *data)
 {
 	return blocking_notifier_call_chain(&blocking_lsm_notifier_chain,
@@ -952,9 +982,15 @@  do {									     \
 	}								     \
 } while (0);
 
-#define call_void_hook(HOOK, ...)                                 \
-	do {                                                      \
+#define call_void_hook(HOOK, ...)					\
+	do {								\
 		LSM_LOOP_UNROLL(__CALL_STATIC_VOID, HOOK, __VA_ARGS__); \
+		if (static_key_enabled(&security_dynamic_hook_key)) { \
+			struct security_dynamic_hook_list *P;		\
+									\
+			hlist_for_each_entry(P, &security_hook_heads.HOOK, list) \
+				P->hook.HOOK(__VA_ARGS__);		\
+		}							\
 	} while (0)
 
 
@@ -973,6 +1009,15 @@  do {									     \
 	int RC = LSM_RET_DEFAULT(HOOK);					\
 									\
 	LSM_LOOP_UNROLL(__CALL_STATIC_INT, RC, HOOK, OUT, __VA_ARGS__);	\
+	if (static_key_enabled(&security_dynamic_hook_key)) {	\
+		struct security_dynamic_hook_list *P;			\
+									\
+		hlist_for_each_entry(P, &security_hook_heads.HOOK, list) { \
+			RC = P->hook.HOOK(__VA_ARGS__);			\
+			if (RC != LSM_RET_DEFAULT(HOOK))		\
+				goto OUT;				\
+		}							\
+	}								\
 OUT:									\
 	RC;								\
 })
@@ -1230,9 +1275,21 @@  int security_vm_enough_memory_mm(struct mm_struct *mm, long pages)
 		rc = scall->hl->hook.vm_enough_memory(mm, pages);
 		if (rc < 0) {
 			cap_sys_admin = 0;
-			break;
+			goto done;
 		}
 	}
+	if (static_key_enabled(&security_dynamic_hook_key)) {
+		struct security_dynamic_hook_list *hp;
+
+		hlist_for_each_entry(hp, &security_hook_heads.vm_enough_memory, list) {
+			rc = hp->hook.vm_enough_memory(mm, pages);
+			if (rc <= 0) {
+				cap_sys_admin = 0;
+				break;
+			}
+		}
+	}
+ done:
 	return __vm_enough_memory(mm, pages, cap_sys_admin);
 }
 
@@ -1385,6 +1442,18 @@  int security_fs_context_parse_param(struct fs_context *fc,
 		else if (trc != -ENOPARAM)
 			return trc;
 	}
+	if (static_key_enabled(&security_dynamic_hook_key)) {
+		struct security_dynamic_hook_list *hp;
+
+		hlist_for_each_entry(hp, &security_hook_heads.fs_context_parse_param,
+				     list) {
+			trc = hp->hook.fs_context_parse_param(fc, param);
+			if (trc == 0)
+				rc = 0;
+			else if (trc != -ENOPARAM)
+				return trc;
+		}
+	}
 	return rc;
 }
 
@@ -1616,8 +1685,20 @@  int security_sb_set_mnt_opts(struct super_block *sb,
 		rc = scall->hl->hook.sb_set_mnt_opts(sb, mnt_opts, kern_flags,
 					      set_kern_flags);
 		if (rc != LSM_RET_DEFAULT(sb_set_mnt_opts))
-			break;
+			goto done;
+	}
+	if (static_key_enabled(&security_dynamic_hook_key)) {
+		struct security_dynamic_hook_list *hp;
+
+		hlist_for_each_entry(hp, &security_hook_heads.sb_set_mnt_opts,
+				     list) {
+			rc = hp->hook.sb_set_mnt_opts(sb, mnt_opts, kern_flags,
+						      set_kern_flags);
+			if (rc != LSM_RET_DEFAULT(sb_set_mnt_opts))
+				break;
+		}
 	}
+ done:
 	return rc;
 }
 EXPORT_SYMBOL(security_sb_set_mnt_opts);
@@ -1826,17 +1907,28 @@  int security_inode_init_security(struct inode *inode, struct inode *dir,
 			return -ENOMEM;
 	}
 
+	/*
+	 * As documented in lsm_hooks.h, -EOPNOTSUPP in this context
+	 * means that the LSM is not willing to provide an xattr, not
+	 * that it wants to signal an error. Thus, continue to invoke
+	 * the remaining LSMs.
+	 */
 	lsm_for_each_hook(scall, inode_init_security) {
 		ret = scall->hl->hook.inode_init_security(inode, dir, qstr, new_xattrs,
 						  &xattr_count);
 		if (ret && ret != -EOPNOTSUPP)
 			goto out;
-		/*
-		 * As documented in lsm_hooks.h, -EOPNOTSUPP in this context
-		 * means that the LSM is not willing to provide an xattr, not
-		 * that it wants to signal an error. Thus, continue to invoke
-		 * the remaining LSMs.
-		 */
+	}
+	if (static_key_enabled(&security_dynamic_hook_key)) {
+		struct security_dynamic_hook_list *hp;
+
+		hlist_for_each_entry(hp, &security_hook_heads.inode_init_security,
+				     list) {
+			ret = hp->hook.inode_init_security(inode, dir, qstr, new_xattrs,
+							   &xattr_count);
+			if (ret && ret != -EOPNOTSUPP)
+				goto out;
+		}
 	}
 
 	/* If initxattrs() is NULL, xattr_count is zero, skip the call. */
@@ -3681,9 +3773,22 @@  int security_task_prctl(int option, unsigned long arg2, unsigned long arg3,
 		if (thisrc != LSM_RET_DEFAULT(task_prctl)) {
 			rc = thisrc;
 			if (thisrc != 0)
-				break;
+				goto done;
 		}
 	}
+	if (static_key_enabled(&security_dynamic_hook_key)) {
+		struct security_dynamic_hook_list *hp;
+
+		hlist_for_each_entry(hp, &security_hook_heads.task_prctl, list) {
+			thisrc = hp->hook.task_prctl(option, arg2, arg3, arg4, arg5);
+			if (thisrc != LSM_RET_DEFAULT(task_prctl)) {
+				rc = thisrc;
+				if (thisrc != 0)
+					break;
+			}
+		}
+	}
+ done:
 	return rc;
 }
 
@@ -4144,8 +4249,38 @@  int security_getselfattr(unsigned int attr, struct lsm_ctx __user *uctx,
 		total += entrysize;
 		count += rc;
 		if (single)
-			break;
+			goto done;
 	}
+	if (static_key_enabled(&security_dynamic_hook_key)) {
+		struct security_dynamic_hook_list *hp;
+
+		hlist_for_each_entry(hp, &security_hook_heads.getselfattr, list) {
+			if (single && lctx.id != hp->lsmid->id)
+				continue;
+			entrysize = left;
+			if (base)
+				uctx = (struct lsm_ctx __user *)(base + total);
+			rc = hp->hook.getselfattr(attr, uctx, &entrysize, flags);
+			if (rc == -EOPNOTSUPP) {
+				rc = 0;
+				continue;
+			}
+			if (rc == -E2BIG) {
+				rc = 0;
+				left = 0;
+				toobig = true;
+			} else if (rc < 0)
+				return rc;
+			else
+				left -= entrysize;
+
+			total += entrysize;
+			count += rc;
+			if (single)
+				break;
+		}
+	}
+ done:
 	if (put_user(total, size))
 		return -EFAULT;
 	if (toobig)
@@ -4202,8 +4337,17 @@  int security_setselfattr(unsigned int attr, struct lsm_ctx __user *uctx,
 	lsm_for_each_hook(scall, setselfattr)
 		if ((scall->hl->lsmid->id) == lctx->id) {
 			rc = scall->hl->hook.setselfattr(attr, lctx, size, flags);
-			break;
+			goto free_out;
 		}
+	if (static_key_enabled(&security_dynamic_hook_key)) {
+		struct security_dynamic_hook_list *hp;
+
+		hlist_for_each_entry(hp, &security_hook_heads.setselfattr, list)
+			if ((hp->lsmid->id) == lctx->id) {
+				rc = hp->hook.setselfattr(attr, lctx, size, flags);
+				break;
+			}
+	}
 
 free_out:
 	kfree(lctx);
@@ -4231,6 +4375,15 @@  int security_getprocattr(struct task_struct *p, int lsmid, const char *name,
 			continue;
 		return scall->hl->hook.getprocattr(p, name, value);
 	}
+	if (static_key_enabled(&security_dynamic_hook_key)) {
+		struct security_dynamic_hook_list *hp;
+
+		hlist_for_each_entry(hp, &security_hook_heads.getprocattr, list) {
+			if (lsmid != 0 && lsmid != hp->lsmid->id)
+				continue;
+			return hp->hook.getprocattr(p, name, value);
+		}
+	}
 	return LSM_RET_DEFAULT(getprocattr);
 }
 
@@ -4255,6 +4408,15 @@  int security_setprocattr(int lsmid, const char *name, void *value, size_t size)
 			continue;
 		return scall->hl->hook.setprocattr(name, value, size);
 	}
+	if (static_key_enabled(&security_dynamic_hook_key)) {
+		struct security_dynamic_hook_list *hp;
+
+		hlist_for_each_entry(hp, &security_hook_heads.setprocattr, list) {
+			if (lsmid != 0 && lsmid != hp->lsmid->id)
+				continue;
+			return hp->hook.setprocattr(name, value, size);
+		}
+	}
 	return LSM_RET_DEFAULT(setprocattr);
 }
 
@@ -5399,8 +5561,18 @@  int security_xfrm_state_pol_flow_match(struct xfrm_state *x,
 	 */
 	lsm_for_each_hook(scall, xfrm_state_pol_flow_match) {
 		rc = scall->hl->hook.xfrm_state_pol_flow_match(x, xp, flic);
-		break;
+		goto out;
+	}
+	if (static_key_enabled(&security_dynamic_hook_key)) {
+		struct security_dynamic_hook_list *hp;
+
+		hlist_for_each_entry(hp, &security_hook_heads.xfrm_state_pol_flow_match,
+				     list) {
+			rc = hp->hook.xfrm_state_pol_flow_match(x, xp, flic);
+			break;
+		}
 	}
+ out:
 	return rc;
 }