[PATCH v7 2/7] security/brute: Define a LSM and add sysctl attributes

From: John Wood
Date: Fri May 21 2021 - 13:45:19 EST


Add a new Kconfig file to define a menu entry under "Security options"
to enable the "Fork brute force attack detection and mitigation"
feature.

The detection of a brute force attack can be based on the number of
faults per application and its crash rate.

There are two types of brute force attacks that can be detected. The
first one is a slow brute force attack that is detected if the maximum
number of faults per fork hierarchy is reached. The second type is a
fast brute force attack that is detected if the application crash period
falls below a certain threshold.

The application crash period must be a value that is not prone to change
due to spurious data and follows the real crash period. So, to compute
it, the exponential moving average (EMA) will be used.

This kind of average defines a weight (between 0 and 1) for the new
value to add and applies the remainder of the weight to the current
average value. This way, some spurious data will not excessively modify
the average and only if the new values are persistent, the moving
average will tend towards them.

Mathematically the application crash period's EMA can be expressed as
follows:

period_ema = period * weight + period_ema * (1 - weight)

Moreover, it is important to note that a minimum number of faults is
needed to guarantee a trend in the crash period when the EMA is used.

So, based on all the previous information define a LSM with five sysctl
attributes that will be used to fine tune the attack detection.

ema_weight_numerator
ema_weight_denominator
max_faults
min_faults
crash_period_threshold

This patch is a previous step on the way to fine tune the attack
detection.

Signed-off-by: John Wood <john.wood@xxxxxxx>
---
security/Kconfig | 11 +--
security/Makefile | 2 +
security/brute/Kconfig | 14 ++++
security/brute/Makefile | 2 +
security/brute/brute.c | 147 ++++++++++++++++++++++++++++++++++++++++
5 files changed, 171 insertions(+), 5 deletions(-)
create mode 100644 security/brute/Kconfig
create mode 100644 security/brute/Makefile
create mode 100644 security/brute/brute.c

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,
+};
--
2.25.1