kernelnewbies.kernelnewbies.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/4] Help to debug spinlocks
@ 2020-12-19 11:46 John Wood
  2020-12-19 11:46 ` [PATCH 1/4] security: Add LSM hook at the point where a task gets a fatal signal John Wood
                   ` (3 more replies)
  0 siblings, 4 replies; 7+ messages in thread
From: John Wood @ 2020-12-19 11:46 UTC (permalink / raw)
  To: kernelnewbies; +Cc: John Wood

Hi,

I'm working in a new LSM to detect and mitigate any fork brute force
attack against vulnerable userspace processes. I'm testing the detection
method but I have found some problems that I think are related to locking
since the kernel gets stuck but not crashes. This work is a WIP to obtain
the v3 version. The mitigation, documentation and fine tunning detection
are under construction.

My problem is that I don't be able to find the cause of this behaviour and
any help would be greatly appreciated.

To test this feature I use the following userspace program:

#include <stdio.h>

int main(void)
{
	int *p = 0;
	*p = 0;
	return 0;
}

This program triggers a "Segmentation fault" that is what I want. Then I
run the binary multiple times to obtain many faults. The method used are
the following commands wrote directly in the shell:

while :
do
./test
done

But at this moment the kernel gets stuck and any message is shown. On one
occasion I got the following message.

[  200.447700] watchdog: BUG: soft lockup - CPU#0 stuck for 22s! [test:277]
[  200.450553] Modules linked in:
[  200.451208] irq event stamp: 0
[  200.451868] hardirqs last  enabled at (0): [<0000000000000000>] 0x0
[  200.453186] hardirqs last disabled at (0): [<ffffffffb789cd6b>] copy_process+0x6bb/0x1c40
[  200.455230] softirqs last  enabled at (0): [<ffffffffb789cd6b>] copy_process+0x6bb/0x1c40
[  200.457316] softirqs last disabled at (0): [<0000000000000000>] 0x0
[  200.458853] CPU: 0 PID: 277 Comm: test Not tainted 5.10.0+ #98
[  200.460320] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1ubuntu1 04/01/2014
[  200.462735] RIP: 0010:queued_write_lock_slowpath+0x50/0x90
[  200.464402] Code: 0d ba ff 00 00 00 3e 0f b1 13 85 c0 74 33 3e 81 03 00 01 00 00 b9 ff 00 00 00 be 00 01 00 00 8b 03 3d 00 01 00 00 74 0c 5
[  200.469109] RSP: 0000:ffffaed4c0003e38 EFLAGS: 00000206
[  200.470191] RAX: 0000000000000300 RBX: ffffffffb92dc7e0 RCX: 00000000000000ff
[  200.471658] RDX: 0000000000000300 RSI: 0000000000000100 RDI: ffffffffb92dc7e0
[  200.473106] RBP: ffffaed4c0003e48 R08: 0000000000000001 R09: 0000000000000000
[  200.474625] R10: ffffffffb92dc7f8 R11: 0000000000000000 R12: ffffffffb92dc7e4
[  200.476410] R13: ffffffffb92dc7f8 R14: ffff8d14c04a2380 R15: ffff8d14c0c8c2d0
[  200.478179] FS:  00007f3384f5a500(0000) GS:ffff8d14c7800000(0000) knlGS:0000000000000000
[  200.480313] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[  200.481826] CR2: 0000000000000000 CR3: 00000000043a0000 CR4: 00000000000006f0
[  200.483698] Call Trace:
[  200.484481]  <IRQ>
[  200.485141]  do_raw_write_lock+0xae/0xb0
[  200.486265]  _raw_write_lock+0x6c/0x70
[  200.487366]  brute_task_free+0x86/0xf0
[  200.488477]  security_task_free+0x27/0x50
[  200.489657]  __put_task_struct+0x6d/0x150
[  200.490824]  delayed_put_task_struct+0x9b/0x110
[  200.492142]  rcu_core+0x412/0x6d0
[  200.493113]  ? rcu_core+0x3de/0x6d0
[  200.493864]  rcu_core_si+0xe/0x10
[  200.494568]  __do_softirq+0xcf/0x428
[  200.495325]  asm_call_irq_on_stack+0x12/0x20
[  200.496407]  </IRQ>
[  200.496969]  do_softirq_own_stack+0x61/0x70
[  200.498030]  irq_exit_rcu+0xc1/0xd0
[  200.498913]  sysvec_apic_timer_interrupt+0x52/0xb0
[  200.500179]  asm_sysvec_apic_timer_interrupt+0x12/0x20
[  200.501773] RIP: 0010:vprintk_emit+0x134/0x3a0
[  200.502906] Code: 89 f9 4c 89 f2 44 89 ef e8 b9 fc ff ff 48 c7 c7 e0 ca 15 b9 41 89 c4 e8 3a 1e b2 00 e8 e5 0e 00 00 4c 8b 4d c8 4c 89 cf 3
[  200.507074] RSP: 0000:ffffaed4c0c63c60 EFLAGS: 00000246
[  200.508400] RAX: ffffaed4c0c63ca0 RBX: ffffaed4c0c63ce8 RCX: 0000000000000a17
[  200.510198] RDX: 000000000000002e RSI: ffffffffb7934e26 RDI: 0000000000000246
[  200.511942] RBP: ffffaed4c0c63ca0 R08: 0000000000000000 R09: 0000000000000246
[  200.513799] R10: 0000000000000001 R11: 0000000000000000 R12: 000000000000002e
[  200.515593] R13: 0000000000000000 R14: 0000000000000000 R15: ffffffffb8dcb218
[  200.517388]  ? vprintk_emit+0x1b6/0x3a0
[  200.518403]  ? lock_acquire+0x1ae/0x3b0
[  200.519306]  vprintk_default+0x1d/0x20
[  200.520088]  vprintk_func+0x68/0x120
[  200.520845]  ? _raw_spin_unlock_irqrestore+0x47/0x50
[  200.521904]  printk+0x58/0x6f
[  200.522537]  brute_task_fatal_signal+0x1ed/0x210
[  200.523463]  security_task_fatal_signal+0x27/0x40
[  200.524408]  get_signal+0x176/0xc70
[  200.525122]  arch_do_signal+0x34/0x8f0
[  200.525902]  ? force_sig_fault+0x63/0x80
[  200.526710]  ? trace_hardirqs_off+0x13/0xd0
[  200.527549]  exit_to_user_mode_prepare+0x155/0x200
[  200.528517]  irqentry_exit_to_user_mode+0x9/0x30
[  200.529463]  irqentry_exit+0x5e/0x80
[  200.530232]  exc_page_fault+0xad/0x2a0
[  200.530989]  ? asm_exc_page_fault+0x8/0x30
[  200.531816]  asm_exc_page_fault+0x1e/0x30
[  200.532629] RIP: 0033:0x564ce0c6b13d
[  200.533394] Code: 5d c3 0f 1f 00 c3 0f 1f 80 00 00 00 00 f3 0f 1e fa e9 77 ff ff ff f3 0f 1e fa 55 48 89 e5 48 c7 45 f8 00 00 00 00 48 8b e
[  200.538044] RSP: 002b:00007ffc2423f3b0 EFLAGS: 00010246
[  200.539210] RAX: 0000000000000000 RBX: 0000000000000000 RCX: 00007f3384f53718
[  200.541326] RDX: 00007ffc2423f4a8 RSI: 00007ffc2423f498 RDI: 0000000000000001
[  200.543111] RBP: 00007ffc2423f3b0 R08: 00007f3384f54d80 R09: 00007f3384f54d80
[  200.544538] R10: 0000000000000000 R11: 00007f3384f15188 R12: 0000564ce0c6b040
[  200.546349] R13: 00007ffc2423f490 R14: 0000000000000000 R15: 0000000000000000

I don't have any experience debugging errors caused by locking and I don't
know how to proceed.

I turn on the following options in my .config file but during the test it
doesn't appear any "DEADLOCK" warning messages.

#
# Lock Debugging (spinlocks, mutexes, etc...)
#
CONFIG_LOCK_DEBUGGING_SUPPORT=y
CONFIG_PROVE_LOCKING=y
CONFIG_PROVE_RAW_LOCK_NESTING=y
CONFIG_LOCK_STAT=y
CONFIG_DEBUG_RT_MUTEXES=y
CONFIG_DEBUG_SPINLOCK=y
CONFIG_DEBUG_MUTEXES=y
CONFIG_DEBUG_WW_MUTEX_SLOWPATH=y
CONFIG_DEBUG_RWSEMS=y
CONFIG_DEBUG_LOCK_ALLOC=y
CONFIG_LOCKDEP=y
CONFIG_DEBUG_LOCKDEP=y
CONFIG_DEBUG_ATOMIC_SLEEP=y
# CONFIG_DEBUG_LOCKING_API_SELFTESTS is not set
# CONFIG_LOCK_TORTURE_TEST is not set
# CONFIG_WW_MUTEX_SELFTEST is not set
# CONFIG_SCF_TORTURE_TEST is not set
# CONFIG_CSD_LOCK_WAIT_DEBUG is not set
# end of Lock Debugging (spinlocks, mutexes, etc...)

I also send my work to show the code used. Any help that points me to the
right direction would be greatly appreciated.

Thanks a lot.

John Wood (4):
  security: Add LSM hook at the point where a task gets a fatal signal
  security/brute: Define a LSM and manage statistical data
  securtiy/brute: Detect a brute force attack
  Documentation: Add documentation for the Brute LSM

 Documentation/admin-guide/LSM/Brute.rst | 186 +++++++
 Documentation/admin-guide/LSM/index.rst |   1 +
 include/linux/lsm_hook_defs.h           |   1 +
 include/linux/lsm_hooks.h               |   4 +
 include/linux/security.h                |   4 +
 kernel/signal.c                         |   1 +
 security/Kconfig                        |  11 +-
 security/Makefile                       |   4 +
 security/brute/Kconfig                  |  13 +
 security/brute/Makefile                 |   2 +
 security/brute/brute.c                  | 705 ++++++++++++++++++++++++
 security/security.c                     |   5 +
 12 files changed, 932 insertions(+), 5 deletions(-)
 create mode 100644 Documentation/admin-guide/LSM/Brute.rst
 create mode 100644 security/brute/Kconfig
 create mode 100644 security/brute/Makefile
 create mode 100644 security/brute/brute.c

--
2.25.1


_______________________________________________
Kernelnewbies mailing list
Kernelnewbies@kernelnewbies.org
https://lists.kernelnewbies.org/mailman/listinfo/kernelnewbies

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH 1/4] security: Add LSM hook at the point where a task gets a fatal signal
  2020-12-19 11:46 [PATCH 0/4] Help to debug spinlocks John Wood
@ 2020-12-19 11:46 ` John Wood
  2020-12-19 11:46 ` [PATCH 2/4] security/brute: Define a LSM and manage statistical data John Wood
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 7+ messages in thread
From: John Wood @ 2020-12-19 11:46 UTC (permalink / raw)
  To: kernelnewbies; +Cc: John Wood

Add a security hook that allows a LSM to be notified when a task gets a
fatal signal. This patch is a previous step on the way to compute the
task crash period by the "brute" LSM (linux security module to detect
and mitigate fork brute force attack against vulnerable userspace
processes).

Signed-off-by: John Wood <john.wood@gmx.com>
---
 include/linux/lsm_hook_defs.h | 1 +
 include/linux/lsm_hooks.h     | 4 ++++
 include/linux/security.h      | 4 ++++
 kernel/signal.c               | 1 +
 security/security.c           | 5 +++++
 5 files changed, 15 insertions(+)

diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 32a940117e7a..21aa120f3965 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -215,6 +215,7 @@ LSM_HOOK(int, -ENOSYS, task_prctl, int option, unsigned long arg2,
 	 unsigned long arg3, unsigned long arg4, unsigned long arg5)
 LSM_HOOK(void, LSM_RET_VOID, task_to_inode, struct task_struct *p,
 	 struct inode *inode)
+LSM_HOOK(void, LSM_RET_VOID, task_fatal_signal, const kernel_siginfo_t *siginfo)
 LSM_HOOK(int, 0, ipc_permission, struct kern_ipc_perm *ipcp, short flag)
 LSM_HOOK(void, LSM_RET_VOID, ipc_getsecid, struct kern_ipc_perm *ipcp,
 	 u32 *secid)
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index c503f7ab8afb..6b6063fcb4da 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -774,6 +774,10 @@
  *	security attributes, e.g. for /proc/pid inodes.
  *	@p contains the task_struct for the task.
  *	@inode contains the inode structure for the inode.
+ * @task_fatal_signal:
+ *	This hook allows security modules to be notified when a task gets a
+ *	fatal signal.
+ *	@siginfo contains the signal information.
  *
  * Security hooks for Netlink messaging.
  *
diff --git a/include/linux/security.h b/include/linux/security.h
index 39642626a707..4f3fc487a71e 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -418,6 +418,7 @@ int security_task_kill(struct task_struct *p, struct kernel_siginfo *info,
 int security_task_prctl(int option, unsigned long arg2, unsigned long arg3,
 			unsigned long arg4, unsigned long arg5);
 void security_task_to_inode(struct task_struct *p, struct inode *inode);
+void security_task_fatal_signal(const kernel_siginfo_t *siginfo);
 int security_ipc_permission(struct kern_ipc_perm *ipcp, short flag);
 void security_ipc_getsecid(struct kern_ipc_perm *ipcp, u32 *secid);
 int security_msg_msg_alloc(struct msg_msg *msg);
@@ -1140,6 +1141,9 @@ static inline int security_task_prctl(int option, unsigned long arg2,
 static inline void security_task_to_inode(struct task_struct *p, struct inode *inode)
 { }

+static inline void security_task_fatal_signal(const kernel_siginfo_t *siginfo)
+{ }
+
 static inline int security_ipc_permission(struct kern_ipc_perm *ipcp,
 					  short flag)
 {
diff --git a/kernel/signal.c b/kernel/signal.c
index ef8f2a28d37c..e7373e5a0566 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -2735,6 +2735,7 @@ bool get_signal(struct ksignal *ksig)
 		/*
 		 * Anything else is fatal, maybe with a core dump.
 		 */
+		security_task_fatal_signal(&ksig->info);
 		current->flags |= PF_SIGNALED;

 		if (sig_kernel_coredump(signr)) {
diff --git a/security/security.c b/security/security.c
index a28045dc9e7f..4ee45c6ff4a4 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1826,6 +1826,11 @@ void security_task_to_inode(struct task_struct *p, struct inode *inode)
 	call_void_hook(task_to_inode, p, inode);
 }

+void security_task_fatal_signal(const kernel_siginfo_t *siginfo)
+{
+	call_void_hook(task_fatal_signal, siginfo);
+}
+
 int security_ipc_permission(struct kern_ipc_perm *ipcp, short flag)
 {
 	return call_int_hook(ipc_permission, 0, ipcp, flag);
--
2.25.1


_______________________________________________
Kernelnewbies mailing list
Kernelnewbies@kernelnewbies.org
https://lists.kernelnewbies.org/mailman/listinfo/kernelnewbies

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH 2/4] security/brute: Define a LSM and manage statistical data
  2020-12-19 11:46 [PATCH 0/4] Help to debug spinlocks John Wood
  2020-12-19 11:46 ` [PATCH 1/4] security: Add LSM hook at the point where a task gets a fatal signal John Wood
@ 2020-12-19 11:46 ` John Wood
  2020-12-19 11:46 ` [PATCH 3/4] securtiy/brute: Detect a brute force attack John Wood
  2020-12-20 11:47 ` [PATCH 0/4] Help to debug spinlocks John Wood
  3 siblings, 0 replies; 7+ messages in thread
From: John Wood @ 2020-12-19 11:46 UTC (permalink / raw)
  To: kernelnewbies; +Cc: John Wood

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

For a correct management of a fork brute force attack it is necessary
that all the tasks hold statistical data. The same statistical data
needs to be shared between all the tasks that hold the same memory
contents or in other words, between all the tasks that have been forked
without any execve call. So, define a statistical data structure to hold
all the necessary information shared by all the fork hierarchy
processes. This info is basically the number of crashes, the last crash
timestamp and the crash period's moving average.

When a forked task calls the execve system call, the memory contents are
set with new values. So, in this scenario the parent's statistical data
no need to be shared. Instead, a new statistical data structure must be
allocated to start a new hierarchy.

The statistical data that is shared between all the fork hierarchy
processes needs to be freed when this hierarchy disappears.

So, based in all the previous information define a LSM with three hooks
to manage all the commented cases. These hooks are "task_alloc" to do
the fork management, "bprm_committing_creds" to do the execve management
and "task_free" to release the resources.

Also, add to the task_struct's security blob the pointer to the
statistical data. This way, all the tasks will have access to this
information.

Signed-off-by: John Wood <john.wood@gmx.com>
---
 security/Kconfig        |  11 +-
 security/Makefile       |   4 +
 security/brute/Kconfig  |  12 ++
 security/brute/Makefile |   2 +
 security/brute/brute.c  | 252 ++++++++++++++++++++++++++++++++++++++++
 5 files changed, 276 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 7561f6f99f1d..204bb311b1f1 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -240,6 +240,7 @@ source "security/safesetid/Kconfig"
 source "security/lockdown/Kconfig"

 source "security/integrity/Kconfig"
+source "security/brute/Kconfig"

 choice
 	prompt "First legacy 'major LSM' to be initialized"
@@ -277,11 +278,11 @@ endchoice

 config LSM
 	string "Ordered list of enabled LSMs"
-	default "lockdown,yama,loadpin,safesetid,integrity,smack,selinux,tomoyo,apparmor,bpf" if DEFAULT_SECURITY_SMACK
-	default "lockdown,yama,loadpin,safesetid,integrity,apparmor,selinux,smack,tomoyo,bpf" if DEFAULT_SECURITY_APPARMOR
-	default "lockdown,yama,loadpin,safesetid,integrity,tomoyo,bpf" if DEFAULT_SECURITY_TOMOYO
-	default "lockdown,yama,loadpin,safesetid,integrity,bpf" if DEFAULT_SECURITY_DAC
-	default "lockdown,yama,loadpin,safesetid,integrity,selinux,smack,tomoyo,apparmor,bpf"
+	default "brute,lockdown,yama,loadpin,safesetid,integrity,smack,selinux,tomoyo,apparmor,bpf" if DEFAULT_SECURITY_SMACK
+	default "brute,lockdown,yama,loadpin,safesetid,integrity,apparmor,selinux,smack,tomoyo,bpf" if DEFAULT_SECURITY_APPARMOR
+	default "brute,lockdown,yama,loadpin,safesetid,integrity,tomoyo,bpf" if DEFAULT_SECURITY_TOMOYO
+	default "brute,lockdown,yama,loadpin,safesetid,integrity,bpf" if DEFAULT_SECURITY_DAC
+	default "brute,lockdown,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 3baf435de541..1236864876da 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -36,3 +36,7 @@ obj-$(CONFIG_BPF_LSM)			+= bpf/
 # Object integrity file lists
 subdir-$(CONFIG_INTEGRITY)		+= integrity
 obj-$(CONFIG_INTEGRITY)			+= integrity/
+
+# Object brute file lists
+subdir-$(CONFIG_SECURITY_FORK_BRUTE)	+= brute
+obj-$(CONFIG_SECURITY_FORK_BRUTE)	+= brute/
diff --git a/security/brute/Kconfig b/security/brute/Kconfig
new file mode 100644
index 000000000000..1bd2df1e2dec
--- /dev/null
+++ b/security/brute/Kconfig
@@ -0,0 +1,12 @@
+# 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. 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..7a15adc8af5b
--- /dev/null
+++ b/security/brute/brute.c
@@ -0,0 +1,252 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <asm/current.h>
+#include <linux/bug.h>
+#include <linux/compiler.h>
+#include <linux/errno.h>
+#include <linux/gfp.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/lsm_hooks.h>
+#include <linux/printk.h>
+#include <linux/refcount.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+/**
+ * struct brute_stats - Fork brute force attack statistics.
+ * @lock: Lock to protect the brute_stats structure.
+ * @refc: Reference counter.
+ * @faults: Number of crashes.
+ * @jiffies: Last crash timestamp.
+ * @period: Crash period's moving average.
+ *
+ * This structure holds the statistical data shared by all the fork hierarchy
+ * processes.
+ */
+struct brute_stats {
+	spinlock_t lock;
+	refcount_t refc;
+	unsigned char faults;
+	u64 jiffies;
+	u64 period;
+};
+
+/**
+ * brute_blob_sizes - LSM blob sizes.
+ *
+ * To share statistical data among all the fork hierarchy processes, define a
+ * pointer to the brute_stats structure as a part of the task_struct's security
+ * blob.
+ */
+static struct lsm_blob_sizes brute_blob_sizes __lsm_ro_after_init = {
+	.lbs_task = sizeof(struct brute_stats *),
+};
+
+/**
+ * brute_stats_ptr() - Get the pointer to the brute_stats structure.
+ * @task: Task that holds the statistical data.
+ *
+ * Return: A pointer to a pointer to the brute_stats structure.
+ */
+static inline struct brute_stats **brute_stats_ptr(struct task_struct *task)
+{
+	return task->security + brute_blob_sizes.lbs_task;
+}
+
+/**
+ * brute_new_stats() - Allocate a new statistics structure.
+ *
+ * If the allocation is successful the reference counter is set to one to
+ * indicate that there will be one task that points to this structure. Also, the
+ * last crash timestamp is set to now. This way, its possible to compute the
+ * application crash period at the first fault.
+ *
+ * Return: NULL if the allocation fails. A pointer to the new allocated
+ *         statistics structure if it success.
+ */
+static struct brute_stats *brute_new_stats(void)
+{
+	struct brute_stats *stats;
+
+	stats = kmalloc(sizeof(struct brute_stats), GFP_KERNEL);
+	if (!stats)
+		return NULL;
+
+	spin_lock_init(&stats->lock);
+	refcount_set(&stats->refc, 1);
+	stats->faults = 0;
+	stats->jiffies = get_jiffies_64();
+	stats->period = 0;
+
+	return stats;
+}
+
+/**
+ * brute_share_stats() - Share the statistical data between processes.
+ * @src: Source of statistics to be shared.
+ * @dst: Destination of statistics to be shared.
+ *
+ * Copy the src's pointer to the statistical data structure to the dst's pointer
+ * to the same structure. Since there is a new process that shares the same
+ * data, increase the reference counter. The src's pointer cannot be NULL.
+ *
+ * It's mandatory to disable interrupts before acquiring the brute_stats::lock
+ * since the task_free hook can be called from an IRQ context during the
+ * execution of the task_alloc hook.
+ */
+static void brute_share_stats(struct brute_stats *src,
+			      struct brute_stats **dst)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&src->lock, flags);
+	refcount_inc(&src->refc);
+	*dst = src;
+	spin_unlock_irqrestore(&src->lock, flags);
+}
+
+/**
+ * brute_task_alloc() - Target for the task_alloc hook.
+ * @task: Task being allocated.
+ * @clone_flags: Contains the flags indicating what should be shared.
+ *
+ * For a correct management of a fork brute force attack it is necessary that
+ * all the tasks hold statistical data. The same statistical data needs to be
+ * shared between all the tasks that hold the same memory contents or in other
+ * words, between all the tasks that have been forked without any execve call.
+ *
+ * To ensure this, if the current task doesn't have statistical data when forks,
+ * it is mandatory to allocate a new statistics structure and share it between
+ * this task and the new one being allocated. Otherwise, share the statistics
+ * that the current task already has.
+ *
+ * Return: -ENOMEM if the allocation of the new statistics structure fails. Zero
+ *         otherwise.
+ */
+static int brute_task_alloc(struct task_struct *task, unsigned long clone_flags)
+{
+	struct brute_stats **stats, **p_stats;
+
+	stats = brute_stats_ptr(task);
+	p_stats = brute_stats_ptr(current);
+
+	if (likely(*p_stats)) {
+		brute_share_stats(*p_stats, stats);
+		return 0;
+	}
+
+	*stats = brute_new_stats();
+	if (!*stats)
+		return -ENOMEM;
+
+	brute_share_stats(*stats, p_stats);
+	return 0;
+}
+
+/**
+ * brute_task_execve() - Target for the bprm_committing_creds hook.
+ * @bprm: Points to the linux_binprm structure.
+ *
+ * When a forked task calls the execve system call, the memory contents are set
+ * with new values. So, in this scenario the parent's statistical data no need
+ * to be shared. Instead, a new statistical data structure must be allocated to
+ * start a new hierarchy. This condition is detected when the statistics
+ * reference counter holds a value greater than or equal to two (a fork always
+ * sets the statistics reference counter to a minimum of two since the parent
+ * and the child task are sharing the same data).
+ *
+ * However, if the execve function is called immediately after another execve
+ * call, althought the memory contents are reset, there is no need to allocate
+ * a new statistical data structure. This is possible because at this moment
+ * only one task (the task that calls the execve function) points to the data.
+ * In this case, the previous allocation is used but the statistics are reset.
+ *
+ * It's mandatory to disable interrupts before acquiring the brute_stats::lock
+ * since the task_free hook can be called from an IRQ context during the
+ * execution of the bprm_committing_creds hook.
+ */
+static void brute_task_execve(struct linux_binprm *bprm)
+{
+	struct brute_stats **stats;
+	unsigned long flags;
+
+	stats = brute_stats_ptr(current);
+	if (WARN(!*stats, "No statistical data\n"))
+		return;
+
+	spin_lock_irqsave(&(*stats)->lock, flags);
+
+	if (!refcount_dec_not_one(&(*stats)->refc)) {
+		/* execve call after an execve call */
+		(*stats)->faults = 0;
+		(*stats)->jiffies = get_jiffies_64();
+		(*stats)->period = 0;
+		spin_unlock_irqrestore(&(*stats)->lock, flags);
+		return;
+	}
+
+	/* execve call after a fork call */
+	spin_unlock_irqrestore(&(*stats)->lock, flags);
+	*stats = brute_new_stats();
+	WARN(!*stats, "Cannot allocate statistical data\n");
+}
+
+/**
+ * brute_task_free() - Target for the task_free hook.
+ * @task: Task about to be freed.
+ *
+ * The statistical data that is shared between all the fork hierarchy processes
+ * needs to be freed when this hierarchy disappears.
+ */
+static void brute_task_free(struct task_struct *task)
+{
+	struct brute_stats **stats;
+	bool refc_is_zero;
+
+	stats = brute_stats_ptr(task);
+	if (WARN(!*stats, "No statistical data\n"))
+		return;
+
+	spin_lock(&(*stats)->lock);
+	refc_is_zero = refcount_dec_and_test(&(*stats)->refc);
+	spin_unlock(&(*stats)->lock);
+
+	if (refc_is_zero) {
+		kfree(*stats);
+		*stats = NULL;
+	}
+}
+
+/**
+ * brute_hooks - Targets for the LSM's hooks.
+ */
+static struct security_hook_list brute_hooks[] __lsm_ro_after_init = {
+	LSM_HOOK_INIT(task_alloc, brute_task_alloc),
+	LSM_HOOK_INIT(bprm_committing_creds, brute_task_execve),
+	LSM_HOOK_INIT(task_free, brute_task_free),
+};
+
+/**
+ * brute_init() - Initialize the brute LSM.
+ *
+ * Return: Always returns zero.
+ */
+static int __init brute_init(void)
+{
+	pr_info("Brute initialized\n");
+	security_add_hooks(brute_hooks, ARRAY_SIZE(brute_hooks),
+			   KBUILD_MODNAME);
+	return 0;
+}
+
+DEFINE_LSM(brute) = {
+	.name = KBUILD_MODNAME,
+	.init = brute_init,
+	.blobs = &brute_blob_sizes,
+};
--
2.25.1


_______________________________________________
Kernelnewbies mailing list
Kernelnewbies@kernelnewbies.org
https://lists.kernelnewbies.org/mailman/listinfo/kernelnewbies

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH 3/4] securtiy/brute: Detect a brute force attack
  2020-12-19 11:46 [PATCH 0/4] Help to debug spinlocks John Wood
  2020-12-19 11:46 ` [PATCH 1/4] security: Add LSM hook at the point where a task gets a fatal signal John Wood
  2020-12-19 11:46 ` [PATCH 2/4] security/brute: Define a LSM and manage statistical data John Wood
@ 2020-12-19 11:46 ` John Wood
  2020-12-20 11:47 ` [PATCH 0/4] Help to debug spinlocks John Wood
  3 siblings, 0 replies; 7+ messages in thread
From: John Wood @ 2020-12-19 11:46 UTC (permalink / raw)
  To: kernelnewbies; +Cc: John Wood

To detect a brute force attack it is necessary that the statistics
shared by all the fork hierarchy processes be updated in every fatal
crash and the most important data to update is the application crash
period. To do so, use the new "task_fatal_signal" LSM hook added in a
previous step.

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) is used.

There are two types of brute force attacks that need to be detected. The
first one is an attack that happens through the fork system call and the
second one is an attack that happens through the execve system call. The
first type uses the statistics shared by all the fork hierarchy
processes, but the second type cannot use this statistical data due to
these statistics dissapear when the involved tasks finished. In this
last scenario the attack info should be tracked by the statistics of a
higher fork hierarchy (the hierarchy that contains the process that
forks before the execve system call).

Moreover, these two attack types have two variants. A slow brute force
attack that is detected if the maximum number of faults per fork
hierarchy is reached and a fast brute force attack that is detected if
the application crash period falls below a certain threshold.

Also, this patch adds locking to protect the statistics pointer hold by
every process.

Signed-off-by: John Wood <john.wood@gmx.com>
---
 security/brute/brute.c | 465 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 459 insertions(+), 6 deletions(-)

diff --git a/security/brute/brute.c b/security/brute/brute.c
index 7a15adc8af5b..90f1271229ed 100644
--- a/security/brute/brute.c
+++ b/security/brute/brute.c
@@ -11,9 +11,14 @@
 #include <linux/jiffies.h>
 #include <linux/kernel.h>
 #include <linux/lsm_hooks.h>
+#include <linux/math64.h>
 #include <linux/printk.h>
 #include <linux/refcount.h>
+#include <linux/rwlock.h>
+#include <linux/rwlock_types.h>
 #include <linux/sched.h>
+#include <linux/sched/signal.h>
+#include <linux/sched/task.h>
 #include <linux/slab.h>
 #include <linux/spinlock.h>
 #include <linux/types.h>
@@ -37,6 +42,11 @@ struct brute_stats {
 	u64 period;
 };

+/**
+ * brute_stats_ptr_lock - Lock to protect the brute_stats structure pointer.
+ */
+static DEFINE_RWLOCK(brute_stats_ptr_lock);
+
 /**
  * brute_blob_sizes - LSM blob sizes.
  *
@@ -74,7 +84,7 @@ static struct brute_stats *brute_new_stats(void)
 {
 	struct brute_stats *stats;

-	stats = kmalloc(sizeof(struct brute_stats), GFP_KERNEL);
+	stats = kmalloc(sizeof(struct brute_stats), GFP_ATOMIC);
 	if (!stats)
 		return NULL;

@@ -99,6 +109,8 @@ static struct brute_stats *brute_new_stats(void)
  * It's mandatory to disable interrupts before acquiring the brute_stats::lock
  * since the task_free hook can be called from an IRQ context during the
  * execution of the task_alloc hook.
+ *
+ * Context: Must be called with brute_stats_ptr_lock held.
  */
 static void brute_share_stats(struct brute_stats *src,
 			      struct brute_stats **dst)
@@ -126,26 +138,36 @@ static void brute_share_stats(struct brute_stats *src,
  * this task and the new one being allocated. Otherwise, share the statistics
  * that the current task already has.
  *
+ * It's mandatory to disable interrupts before acquiring in a write state the
+ * brute_stats_ptr_lock since the task_free hook can be called from an IRQ
+ * context during the execution of the task_alloc hook.
+ *
  * Return: -ENOMEM if the allocation of the new statistics structure fails. Zero
  *         otherwise.
  */
 static int brute_task_alloc(struct task_struct *task, unsigned long clone_flags)
 {
 	struct brute_stats **stats, **p_stats;
+	unsigned long flags;

 	stats = brute_stats_ptr(task);
 	p_stats = brute_stats_ptr(current);
+	write_lock_irqsave(&brute_stats_ptr_lock, flags);

 	if (likely(*p_stats)) {
 		brute_share_stats(*p_stats, stats);
+		write_unlock_irqrestore(&brute_stats_ptr_lock, flags);
 		return 0;
 	}

 	*stats = brute_new_stats();
-	if (!*stats)
+	if (!*stats) {
+		write_unlock_irqrestore(&brute_stats_ptr_lock, flags);
 		return -ENOMEM;
+	}

 	brute_share_stats(*stats, p_stats);
+	write_unlock_irqrestore(&brute_stats_ptr_lock, flags);
 	return 0;
 }

@@ -168,8 +190,9 @@ static int brute_task_alloc(struct task_struct *task, unsigned long clone_flags)
  * In this case, the previous allocation is used but the statistics are reset.
  *
  * It's mandatory to disable interrupts before acquiring the brute_stats::lock
- * since the task_free hook can be called from an IRQ context during the
- * execution of the bprm_committing_creds hook.
+ * and before acquiring in a write state the brute_stats_ptr_lock since the
+ * task_free hook can be called from an IRQ context during the execution of the
+ * bprm_committing_creds hook.
  */
 static void brute_task_execve(struct linux_binprm *bprm)
 {
@@ -177,8 +200,12 @@ static void brute_task_execve(struct linux_binprm *bprm)
 	unsigned long flags;

 	stats = brute_stats_ptr(current);
-	if (WARN(!*stats, "No statistical data\n"))
+	read_lock(&brute_stats_ptr_lock);
+
+	if (WARN(!*stats, "No statistical data\n")) {
+		read_unlock(&brute_stats_ptr_lock);
 		return;
+	}

 	spin_lock_irqsave(&(*stats)->lock, flags);

@@ -188,13 +215,18 @@ static void brute_task_execve(struct linux_binprm *bprm)
 		(*stats)->jiffies = get_jiffies_64();
 		(*stats)->period = 0;
 		spin_unlock_irqrestore(&(*stats)->lock, flags);
+		read_unlock(&brute_stats_ptr_lock);
 		return;
 	}

 	/* execve call after a fork call */
 	spin_unlock_irqrestore(&(*stats)->lock, flags);
+	read_unlock(&brute_stats_ptr_lock);
+
+	write_lock_irqsave(&brute_stats_ptr_lock, flags);
 	*stats = brute_new_stats();
 	WARN(!*stats, "Cannot allocate statistical data\n");
+	write_unlock_irqrestore(&brute_stats_ptr_lock, flags);
 }

 /**
@@ -210,17 +242,437 @@ static void brute_task_free(struct task_struct *task)
 	bool refc_is_zero;

 	stats = brute_stats_ptr(task);
-	if (WARN(!*stats, "No statistical data\n"))
+	read_lock(&brute_stats_ptr_lock);
+
+	if (WARN(!*stats, "No statistical data\n")) {
+		read_unlock(&brute_stats_ptr_lock);
 		return;
+	}

 	spin_lock(&(*stats)->lock);
 	refc_is_zero = refcount_dec_and_test(&(*stats)->refc);
 	spin_unlock(&(*stats)->lock);
+	read_unlock(&brute_stats_ptr_lock);

 	if (refc_is_zero) {
+		write_lock(&brute_stats_ptr_lock);
 		kfree(*stats);
 		*stats = NULL;
+		write_unlock(&brute_stats_ptr_lock);
+	}
+}
+
+/**
+ * BRUTE_EMA_WEIGHT_NUMERATOR - Weight's numerator of EMA.
+ */
+static const u64 BRUTE_EMA_WEIGHT_NUMERATOR = 7;
+
+/**
+ * BRUTE_EMA_WEIGHT_DENOMINATOR - Weight's denominator of EMA.
+ */
+static const u64 BRUTE_EMA_WEIGHT_DENOMINATOR = 10;
+
+/**
+ * brute_mul_by_ema_weight() - Multiply by EMA weight.
+ * @value: Value to multiply by EMA weight.
+ *
+ * Return: The result of the multiplication operation.
+ */
+static inline u64 brute_mul_by_ema_weight(u64 value)
+{
+	return mul_u64_u64_div_u64(value, BRUTE_EMA_WEIGHT_NUMERATOR,
+				   BRUTE_EMA_WEIGHT_DENOMINATOR);
+}
+
+/**
+ * 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 const unsigned char BRUTE_MAX_FAULTS = 200;
+
+/**
+ * brute_update_crash_period() - Update the application crash period.
+ * @stats: Statistics that hold the application crash period to update.
+ * @now: The current timestamp in jiffies.
+ *
+ * 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) is 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)
+ *
+ * If the operations are applied:
+ *
+ * period_ema = period * weight + period_ema - period_ema * weight
+ *
+ * If the operands are ordered:
+ *
+ * period_ema = period_ema - period_ema * weight + period * weight
+ *
+ * Finally, this formula can be written as follows:
+ *
+ * period_ema -= period_ema * weight;
+ * period_ema += period * weight;
+ *
+ * The statistics that hold the application crash period to update cannot be
+ * NULL.
+ *
+ * It's mandatory to disable interrupts before acquiring the brute_stats::lock
+ * since the task_free hook can be called from an IRQ context during the
+ * execution of the task_fatal_signal hook.
+ *
+ * Context: Must be called with brute_stats_ptr_lock held.
+ * Return: The last crash timestamp before updating it.
+ */
+static u64 brute_update_crash_period(struct brute_stats *stats, u64 now)
+{
+	unsigned long flags;
+	u64 current_period;
+	u64 last_crash_timestamp;
+
+	spin_lock_irqsave(&stats->lock, flags);
+	current_period = now - stats->jiffies;
+	last_crash_timestamp = stats->jiffies;
+	stats->jiffies = now;
+
+	stats->period -= brute_mul_by_ema_weight(stats->period);
+	stats->period += brute_mul_by_ema_weight(current_period);
+
+	if (stats->faults < BRUTE_MAX_FAULTS)
+		stats->faults += 1;
+
+	spin_unlock_irqrestore(&stats->lock, flags);
+	return last_crash_timestamp;
+}
+
+/**
+ * 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. Moreover, it avoids the scenario where an application
+ * fails quickly from execve system call due to reasons unrelated to a real
+ * attack.
+ */
+static const unsigned char BRUTE_MIN_FAULTS = 5;
+
+/**
+ * BRUTE_CRASH_PERIOD_THRESHOLD - Application crash period threshold.
+ *
+ * The units are expressed in milliseconds.
+ *
+ * A fast brute force attack is detected when the application crash period falls
+ * below this threshold.
+ */
+static const u64 BRUTE_CRASH_PERIOD_THRESHOLD = 30000;
+
+/**
+ * brute_attack_running() - Test if a brute force attack is happening.
+ * @stats: Statistical data shared by all the fork hierarchy processes.
+ *
+ * The decision if a brute force attack is running is based on the statistical
+ * data shared by all the fork hierarchy processes. This statistics cannot be
+ * NULL.
+ *
+ * There are two types of brute force attacks that can be detected using the
+ * statistical data. 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.
+ *
+ * Moreover, it is important to note that no attacks will be detected until a
+ * minimum number of faults have occurred. This allows to have a trend in the
+ * crash period when the EMA is used and also avoids the scenario where an
+ * application fails quickly from execve system call due to reasons unrelated to
+ * a real attack.
+ *
+ * It's mandatory to disable interrupts before acquiring the brute_stats::lock
+ * since the task_free hook can be called from an IRQ context during the
+ * execution of the task_fatal_signal hook.
+ *
+ * Context: Must be called with brute_stats_ptr_lock held.
+ * Return: True if a brute force attack is happening. False otherwise.
+ */
+static bool brute_attack_running(struct brute_stats *stats)
+{
+	unsigned long flags;
+	u64 crash_period;
+
+	spin_lock_irqsave(&stats->lock, flags);
+	if (stats->faults < BRUTE_MIN_FAULTS) {
+		spin_unlock_irqrestore(&stats->lock, flags);
+		return false;
+	}
+
+	if (stats->faults >= BRUTE_MAX_FAULTS) {
+		spin_unlock_irqrestore(&stats->lock, flags);
+		return true;
+	}
+
+	crash_period = jiffies64_to_msecs(stats->period);
+	spin_unlock_irqrestore(&stats->lock, flags);
+
+	return crash_period < BRUTE_CRASH_PERIOD_THRESHOLD;
+}
+
+/**
+ * print_fork_attack_running() - Warn about a fork brute force attack.
+ */
+static inline void print_fork_attack_running(void)
+{
+	pr_warn("Fork brute force attack detected [%s]\n", current->comm);
+}
+
+/**
+ * brute_manage_fork_attack() - Manage a fork brute force attack.
+ * @stats: Statistical data shared by all the fork hierarchy processes.
+ * @now: The current timestamp in jiffies.
+ *
+ * For a correct management of a fork brute force attack it is only necessary to
+ * update the statistics and test if an attack is happening based on these data.
+ *
+ * The statistical data shared by all the fork hierarchy processes cannot be
+ * NULL.
+ *
+ * Context: Must be called with brute_stats_ptr_lock held.
+ * Return: The last crash timestamp before updating it.
+ */
+static u64 brute_manage_fork_attack(struct brute_stats *stats, u64 now)
+{
+	u64 last_fork_crash;
+
+	last_fork_crash = brute_update_crash_period(stats, now);
+	if (brute_attack_running(stats))
+		print_fork_attack_running();
+
+	return last_fork_crash;
+}
+
+/**
+ * brute_get_exec_stats() - Get the exec statistics.
+ * @stats: When this function is called, this parameter must point to the
+ *         current process' statistical data. When this function returns, this
+ *         parameter points to the parent process' statistics of the fork
+ *         hierarchy that hold the current process' statistics.
+ *
+ * To manage a brute force attack that happens through the execve system call it
+ * is not possible to use the statistical data hold by this process due to these
+ * statistics dissapear when this task is finished. In this scenario this data
+ * should be tracked by the statistics of a higher fork hierarchy (the hierarchy
+ * that contains the process that forks before the execve system call).
+ *
+ * To find these statistics the current fork hierarchy must be traversed up
+ * until new statistics are found.
+ *
+ * Context: Must be called with brute_stats_ptr_lock held.
+ */
+static void brute_get_exec_stats(struct brute_stats **stats)
+{
+	const struct task_struct *task = current;
+	struct brute_stats **p_stats;
+
+	read_lock(&tasklist_lock);
+	do {
+		if (!task->real_parent) {
+			read_unlock(&tasklist_lock);
+			*stats = NULL;
+			return;
+		}
+
+		p_stats = brute_stats_ptr(task->real_parent);
+		task = task->real_parent;
+	} while (*stats == *p_stats);
+	read_unlock(&tasklist_lock);
+
+	*stats = *p_stats;
+}
+
+/**
+ * brute_update_exec_crash_period() - Update the exec crash period.
+ * @stats: When this function is called, this parameter must point to the
+ *         current process' statistical data. When this function returns, this
+ *         parameter points to the updated statistics (statistics that track the
+ *         info to manage a brute force attack that happens through the execve
+ *         system call).
+ * @now: The current timestamp in jiffies.
+ * @last_fork_crash: The last fork crash timestamp before updating it.
+ *
+ * If this is the first update of the statistics used to manage a brute force
+ * attack that happens through the execve system call, its last crash timestamp
+ * (the timestamp that shows when the execve was called) cannot be used to
+ * compute the crash period's EMA. Instead, the last fork crash timestamp should
+ * be used (the last crash timestamp of the child fork hierarchy before updating
+ * the crash period). This allows that in a brute force attack that happens
+ * through the fork system call, the exec and fork statistics are the same. In
+ * this situation, the mitigation method will act only in the processes that are
+ * sharing the fork statistics. This way, the process that forked before the
+ * execve system call will not be involved in the mitigation method. In this
+ * scenario, the parent is not responsible of the child's behaviour.
+ *
+ * It's mandatory to disable interrupts before acquiring the brute_stats::lock
+ * since the task_free hook can be called from an IRQ context during the
+ * execution of the task_fatal_signal hook.
+ *
+ * Context: Must be called with brute_stats_ptr_lock held.
+ * Return: -EFAULT if there are no exec statistics. Zero otherwise.
+ */
+static int brute_update_exec_crash_period(struct brute_stats **stats,
+					  u64 now, u64 last_fork_crash)
+{
+	unsigned long flags;
+
+	brute_get_exec_stats(stats);
+	if (!*stats)
+		return -EFAULT;
+
+	spin_lock_irqsave(&(*stats)->lock, flags);
+	if (!(*stats)->faults)
+		(*stats)->jiffies = last_fork_crash;
+	spin_unlock_irqrestore(&(*stats)->lock, flags);
+
+	brute_update_crash_period(*stats, now);
+	return 0;
+}
+
+/**
+ * brute_get_crash_period() - Get the application crash period.
+ * @stats: Statistical data shared by all the fork hierarchy processes.
+ *
+ * The statistical data shared by all the fork hierarchy processes cannot be
+ * NULL.
+ *
+ * It's mandatory to disable interrupts before acquiring the brute_stats::lock
+ * since the task_free hook can be called from an IRQ context during the
+ * execution of the task_fatal_signal hook.
+ *
+ * Context: Must be called with brute_stats_ptr_lock held.
+ * Return: The application crash period.
+ */
+static u64 brute_get_crash_period(struct brute_stats *stats)
+{
+	unsigned long flags;
+	u64 crash_period;
+
+	spin_lock_irqsave(&stats->lock, flags);
+	crash_period = stats->period;
+	spin_unlock_irqrestore(&stats->lock, flags);
+
+	return crash_period;
+}
+
+/**
+ * print_exec_attack_running() - Warn about an exec brute force attack.
+ * @stats: Statistical data shared by all the fork hierarchy processes.
+ *
+ * The statistical data shared by all the fork hierarchy processes cannot be
+ * NULL.
+ *
+ * Before showing the process name it is mandatory to find a process that holds
+ * a pointer to the exec statistics.
+ *
+ * Context: Must be called with brute_stats_ptr_lock held.
+ */
+static void print_exec_attack_running(const struct brute_stats *stats)
+{
+	struct task_struct *p;
+	struct brute_stats **p_stats;
+	bool found = false;
+
+	read_lock(&tasklist_lock);
+
+	for_each_process(p) {
+		p_stats = brute_stats_ptr(p);
+		if (*p_stats == stats) {
+			found = true;
+			break;
+		}
+	}
+
+	if (WARN(!found, "No exec process\n")) {
+		read_unlock(&tasklist_lock);
+		return;
 	}
+
+	pr_warn("Exec brute force attack detected [%s]\n", p->comm);
+	read_unlock(&tasklist_lock);
+}
+
+/**
+ * brute_manage_exec_attack() - Manage an exec brute force attack.
+ * @stats: Statistical data shared by all the fork hierarchy processes.
+ * @now: The current timestamp in jiffies.
+ * @last_fork_crash: The last fork crash timestamp before updating it.
+ *
+ * For a correct management of an exec brute force attack it is only necessary
+ * to update the exec statistics and test if an attack is happening based on
+ * these data.
+ *
+ * It is important to note that if the fork and exec crash periods are the same,
+ * the attack test is avoided. This allows that in a brute force attack that
+ * happens through the fork system call, the mitigation method does not act on
+ * the parent process of the fork hierarchy.
+ *
+ * The statistical data shared by all the fork hierarchy processes cannot be
+ * NULL.
+ *
+ * Context: Must be called with brute_stats_ptr_lock held.
+ */
+static void brute_manage_exec_attack(struct brute_stats *stats, u64 now,
+				     u64 last_fork_crash)
+{
+	int ret;
+	struct brute_stats *exec_stats = stats;
+	u64 fork_period;
+	u64 exec_period;
+
+	ret = brute_update_exec_crash_period(&exec_stats, now, last_fork_crash);
+	if (WARN(ret, "No exec statistical data\n"))
+		return;
+
+	fork_period = brute_get_crash_period(stats);
+	exec_period = brute_get_crash_period(exec_stats);
+	if (fork_period == exec_period)
+		return;
+
+	if (brute_attack_running(exec_stats))
+		print_exec_attack_running(exec_stats);
+}
+
+/**
+ * brute_task_fatal_signal() - Target for the task_fatal_signal hook.
+ * @siginfo: Contains the signal information.
+ *
+ * To detect a brute force attack is necessary to update the fork and exec
+ * statistics in every fatal crash and act based on these data.
+ */
+static void brute_task_fatal_signal(const kernel_siginfo_t *siginfo)
+{
+	struct brute_stats **stats;
+	u64 last_fork_crash;
+	u64 now = get_jiffies_64();
+
+	stats = brute_stats_ptr(current);
+	read_lock(&brute_stats_ptr_lock);
+
+	if (WARN(!*stats, "No statistical data\n")) {
+		read_unlock(&brute_stats_ptr_lock);
+		return;
+	}
+
+	last_fork_crash = brute_manage_fork_attack(*stats, now);
+	brute_manage_exec_attack(*stats, now, last_fork_crash);
+	read_unlock(&brute_stats_ptr_lock);
 }

 /**
@@ -230,6 +682,7 @@ static struct security_hook_list brute_hooks[] __lsm_ro_after_init = {
 	LSM_HOOK_INIT(task_alloc, brute_task_alloc),
 	LSM_HOOK_INIT(bprm_committing_creds, brute_task_execve),
 	LSM_HOOK_INIT(task_free, brute_task_free),
+	LSM_HOOK_INIT(task_fatal_signal, brute_task_fatal_signal),
 };

 /**
--
2.25.1


_______________________________________________
Kernelnewbies mailing list
Kernelnewbies@kernelnewbies.org
https://lists.kernelnewbies.org/mailman/listinfo/kernelnewbies

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH 0/4] Help to debug spinlocks
  2020-12-19 11:46 [PATCH 0/4] Help to debug spinlocks John Wood
                   ` (2 preceding siblings ...)
  2020-12-19 11:46 ` [PATCH 3/4] securtiy/brute: Detect a brute force attack John Wood
@ 2020-12-20 11:47 ` John Wood
  2020-12-20 20:06   ` Valdis Klētnieks
  3 siblings, 1 reply; 7+ messages in thread
From: John Wood @ 2020-12-20 11:47 UTC (permalink / raw)
  To: kernelnewbies; +Cc: John Wood

On Sat, Dec 19, 2020 at 12:46:37PM +0100, John Wood wrote:
> Hi,
>
> I'm working in a new LSM to detect and mitigate any fork brute force
> attack against vulnerable userspace processes. I'm testing the detection
> method but I have found some problems that I think are related to locking
> since the kernel gets stuck but not crashes. This work is a WIP to obtain
> the v3 version. The mitigation, documentation and fine tunning detection
> are under construction.
>
> My problem is that I don't be able to find the cause of this behaviour and
> any help would be greatly appreciated.

I think that I have found the cause of the problem.

I acquired the brute_stats_ptr_lock in the task_fatal_signal hook without
disable interrupts. Then, the task_free hook was call from an IRQ context
and tried to acquire the same lock in a write state. This cause a deadlock.

Thanks anyway,
John Wood


_______________________________________________
Kernelnewbies mailing list
Kernelnewbies@kernelnewbies.org
https://lists.kernelnewbies.org/mailman/listinfo/kernelnewbies

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 0/4] Help to debug spinlocks
  2020-12-20 11:47 ` [PATCH 0/4] Help to debug spinlocks John Wood
@ 2020-12-20 20:06   ` Valdis Klētnieks
  2020-12-21 18:02     ` John Wood
  0 siblings, 1 reply; 7+ messages in thread
From: Valdis Klētnieks @ 2020-12-20 20:06 UTC (permalink / raw)
  To: John Wood; +Cc: kernelnewbies


[-- Attachment #1.1: Type: text/plain, Size: 297 bytes --]

On Sun, 20 Dec 2020 12:47:08 +0100, John Wood said:

> disable interrupts. Then, the task_free hook was call from an IRQ context
> and tried to acquire the same lock in a write state.

OK, I'll bite.

Why was task_free called from an IRQ context in the first place? That sounds
awfully fishy.....

[-- Attachment #1.2: Type: application/pgp-signature, Size: 832 bytes --]

[-- Attachment #2: Type: text/plain, Size: 170 bytes --]

_______________________________________________
Kernelnewbies mailing list
Kernelnewbies@kernelnewbies.org
https://lists.kernelnewbies.org/mailman/listinfo/kernelnewbies

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 0/4] Help to debug spinlocks
  2020-12-20 20:06   ` Valdis Klētnieks
@ 2020-12-21 18:02     ` John Wood
  0 siblings, 0 replies; 7+ messages in thread
From: John Wood @ 2020-12-21 18:02 UTC (permalink / raw)
  To: Valdis Klētnieks; +Cc: John Wood, kernelnewbies

On Sun, Dec 20, 2020 at 03:06:52PM -0500, Valdis Klētnieks wrote:
> On Sun, 20 Dec 2020 12:47:08 +0100, John Wood said:
>
> > disable interrupts. Then, the task_free hook was call from an IRQ context
> > and tried to acquire the same lock in a write state.
>
> OK, I'll bite.
>
> Why was task_free called from an IRQ context in the first place? That sounds
> awfully fishy.....

If I understand correctly is what the call trace says:

[  200.483698] Call Trace:
[  200.484481]  <IRQ>
[  200.485141]  do_raw_write_lock+0xae/0xb0
[  200.486265]  _raw_write_lock+0x6c/0x70
[  200.487366]  brute_task_free+0x86/0xf0
[  200.488477]  security_task_free+0x27/0x50
[  200.489657]  __put_task_struct+0x6d/0x150
[  200.490824]  delayed_put_task_struct+0x9b/0x110
[  200.492142]  rcu_core+0x412/0x6d0
[  200.493113]  ? rcu_core+0x3de/0x6d0
[  200.493864]  rcu_core_si+0xe/0x10
[  200.494568]  __do_softirq+0xcf/0x428
[  200.495325]  asm_call_irq_on_stack+0x12/0x20
[  200.496407]  </IRQ>
[  200.496969]  do_softirq_own_stack+0x61/0x70
[  200.498030]  irq_exit_rcu+0xc1/0xd0
[  200.498913]  sysvec_apic_timer_interrupt+0x52/0xb0
[  200.500179]  asm_sysvec_apic_timer_interrupt+0x12/0x20

Thanks,
John Wood


_______________________________________________
Kernelnewbies mailing list
Kernelnewbies@kernelnewbies.org
https://lists.kernelnewbies.org/mailman/listinfo/kernelnewbies

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2020-12-21 18:02 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-12-19 11:46 [PATCH 0/4] Help to debug spinlocks John Wood
2020-12-19 11:46 ` [PATCH 1/4] security: Add LSM hook at the point where a task gets a fatal signal John Wood
2020-12-19 11:46 ` [PATCH 2/4] security/brute: Define a LSM and manage statistical data John Wood
2020-12-19 11:46 ` [PATCH 3/4] securtiy/brute: Detect a brute force attack John Wood
2020-12-20 11:47 ` [PATCH 0/4] Help to debug spinlocks John Wood
2020-12-20 20:06   ` Valdis Klētnieks
2020-12-21 18:02     ` John Wood

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).