diff --git a/security/Kconfig b/security/Kconfig
index 0ced7fd33e4d..2df1727f2c2c 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -241,6 +241,7 @@ source "security/lockdown/Kconfig"
source "security/landlock/Kconfig"
source "security/integrity/Kconfig"
+source "security/brute/Kconfig"
choice
prompt "First legacy 'major LSM' to be initialized"
@@ -278,11 +279,11 @@ endchoice
config LSM
string "Ordered list of enabled LSMs"
- default "landlock,lockdown,yama,loadpin,safesetid,integrity,smack,selinux,tomoyo,apparmor,bpf" if DEFAULT_SECURITY_SMACK
- default "landlock,lockdown,yama,loadpin,safesetid,integrity,apparmor,selinux,smack,tomoyo,bpf" if DEFAULT_SECURITY_APPARMOR
- default "landlock,lockdown,yama,loadpin,safesetid,integrity,tomoyo,bpf" if DEFAULT_SECURITY_TOMOYO
- default "landlock,lockdown,yama,loadpin,safesetid,integrity,bpf" if DEFAULT_SECURITY_DAC
- default "landlock,lockdown,yama,loadpin,safesetid,integrity,selinux,smack,tomoyo,apparmor,bpf"
+ default "landlock,lockdown,brute,yama,loadpin,safesetid,integrity,smack,selinux,tomoyo,apparmor,bpf" if DEFAULT_SECURITY_SMACK
+ default "landlock,lockdown,brute,yama,loadpin,safesetid,integrity,apparmor,selinux,smack,tomoyo,bpf" if DEFAULT_SECURITY_APPARMOR
+ default "landlock,lockdown,brute,yama,loadpin,safesetid,integrity,tomoyo,bpf" if DEFAULT_SECURITY_TOMOYO
+ default "landlock,lockdown,brute,yama,loadpin,safesetid,integrity,bpf" if DEFAULT_SECURITY_DAC
+ default "landlock,lockdown,brute,yama,loadpin,safesetid,integrity,selinux,smack,tomoyo,apparmor,bpf"
help
A comma-separated list of LSMs, in initialization order.
Any LSMs left off this list will be ignored. This can be
diff --git a/security/Makefile b/security/Makefile
index 47e432900e24..94d325256413 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -14,6 +14,7 @@ subdir-$(CONFIG_SECURITY_SAFESETID) += safesetid
subdir-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown
subdir-$(CONFIG_BPF_LSM) += bpf
subdir-$(CONFIG_SECURITY_LANDLOCK) += landlock
+subdir-$(CONFIG_SECURITY_FORK_BRUTE) += brute
# always enable default capabilities
obj-y += commoncap.o
@@ -34,6 +35,7 @@ obj-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown/
obj-$(CONFIG_CGROUPS) += device_cgroup.o
obj-$(CONFIG_BPF_LSM) += bpf/
obj-$(CONFIG_SECURITY_LANDLOCK) += landlock/
+obj-$(CONFIG_SECURITY_FORK_BRUTE) += brute/
# Object integrity file lists
subdir-$(CONFIG_INTEGRITY) += integrity
diff --git a/security/brute/Kconfig b/security/brute/Kconfig
new file mode 100644
index 000000000000..5da314d221aa
--- /dev/null
+++ b/security/brute/Kconfig
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0
+config SECURITY_FORK_BRUTE
+ bool "Fork brute force attack detection and mitigation"
+ depends on SECURITY
+ help
+ This is an LSM that stops any fork brute force attack against
+ vulnerable userspace processes. The detection method is based on
+ the application crash period and as a mitigation procedure all the
+ offending tasks are killed. Also, the executable file involved in the
+ attack will be marked as "not allowed" and new execve system calls
+ using this file will fail. Like capabilities, this security module
+ stacks with other LSMs.
+
+ If you are unsure how to answer this question, answer N.
diff --git a/security/brute/Makefile b/security/brute/Makefile
new file mode 100644
index 000000000000..d3f233a132a9
--- /dev/null
+++ b/security/brute/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_SECURITY_FORK_BRUTE) += brute.o
diff --git a/security/brute/brute.c b/security/brute/brute.c
new file mode 100644
index 000000000000..0edb89a58ab0
--- /dev/null
+++ b/security/brute/brute.c
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/lsm_hooks.h>
+#include <linux/sysctl.h>
+
+/**
+ * DOC: brute_ema_weight_numerator
+ *
+ * Weight's numerator of EMA.
+ */
+static unsigned int brute_ema_weight_numerator __read_mostly = 7;
+
+/**
+ * DOC: brute_ema_weight_denominator
+ *
+ * Weight's denominator of EMA.
+ */
+static unsigned int brute_ema_weight_denominator __read_mostly = 10;
+
+/**
+ * DOC: brute_max_faults
+ *
+ * Maximum number of faults.
+ *
+ * If a brute force attack is running slowly for a long time, the application
+ * crash period's EMA is not suitable for the detection. This type of attack
+ * must be detected using a maximum number of faults.
+ */
+static unsigned int brute_max_faults __read_mostly = 200;
+
+/**
+ * DOC: brute_min_faults
+ *
+ * Minimum number of faults.
+ *
+ * The application crash period's EMA cannot be used until a minimum number of
+ * data has been applied to it. This constraint allows getting a trend when this
+ * moving average is used.
+ */
+static unsigned int brute_min_faults __read_mostly = 5;
+
+/**
+ * DOC: brute_crash_period_threshold
+ *
+ * Application crash period threshold.
+ *
+ * A fast brute force attack is detected when the application crash period falls
+ * below this threshold. The units are expressed in seconds.
+ */
+static unsigned int brute_crash_period_threshold __read_mostly = 30;
+
+#ifdef CONFIG_SYSCTL
+static unsigned int uint_max = UINT_MAX;
+#define SYSCTL_UINT_MAX (&uint_max)
+
+/*
+ * brute_sysctl_path - Sysctl attributes path.
+ */
+static struct ctl_path brute_sysctl_path[] = {
+ { .procname = "kernel", },
+ { .procname = "brute", },
+ { }
+};
+
+/*
+ * brute_sysctl_table - Sysctl attributes.
+ */
+static struct ctl_table brute_sysctl_table[] = {
+ {
+ .procname = "ema_weight_numerator",
+ .data = &brute_ema_weight_numerator,
+ .maxlen = sizeof(brute_ema_weight_numerator),
+ .mode = 0644,
+ .proc_handler = proc_douintvec_minmax,
+ .extra1 = SYSCTL_ZERO,
+ .extra2 = &brute_ema_weight_denominator,
+ },
+ {
+ .procname = "ema_weight_denominator",
+ .data = &brute_ema_weight_denominator,
+ .maxlen = sizeof(brute_ema_weight_denominator),
+ .mode = 0644,
+ .proc_handler = proc_douintvec_minmax,
+ .extra1 = &brute_ema_weight_numerator,
+ .extra2 = SYSCTL_UINT_MAX,
+ },
+ {
+ .procname = "max_faults",
+ .data = &brute_max_faults,
+ .maxlen = sizeof(brute_max_faults),
+ .mode = 0644,
+ .proc_handler = proc_douintvec_minmax,
+ .extra1 = &brute_min_faults,
+ .extra2 = SYSCTL_UINT_MAX,
+ },
+ {
+ .procname = "min_faults",
+ .data = &brute_min_faults,
+ .maxlen = sizeof(brute_min_faults),
+ .mode = 0644,
+ .proc_handler = proc_douintvec_minmax,
+ .extra1 = SYSCTL_ONE,
+ .extra2 = &brute_max_faults,
+ },
+ {
+ .procname = "crash_period_threshold",
+ .data = &brute_crash_period_threshold,
+ .maxlen = sizeof(brute_crash_period_threshold),
+ .mode = 0644,
+ .proc_handler = proc_douintvec_minmax,
+ .extra1 = SYSCTL_ONE,
+ .extra2 = SYSCTL_UINT_MAX,
+ },
+ { }
+};
+
+/**
+ * brute_init_sysctl() - Initialize the sysctl interface.
+ */
+static void __init brute_init_sysctl(void)
+{
+ if (!register_sysctl_paths(brute_sysctl_path, brute_sysctl_table))
+ panic("sysctl registration failed\n");
+}
+
+#else
+static inline void brute_init_sysctl(void) { }
+#endif /* CONFIG_SYSCTL */
+
+/**
+ * brute_init() - Initialize the brute LSM.
+ *
+ * Return: Always returns zero.
+ */
+static int __init brute_init(void)
+{
+ pr_info("becoming mindful\n");
+ brute_init_sysctl();
+ return 0;
+}
+
+DEFINE_LSM(brute) = {
+ .name = KBUILD_MODNAME,
+ .init = brute_init,
+};
--