lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20201025134540.3770-5-john.wood@gmx.com>
Date:   Sun, 25 Oct 2020 14:45:36 +0100
From:   John Wood <john.wood@....com>
To:     Kees Cook <keescook@...omium.org>, Jann Horn <jannh@...gle.com>
Cc:     John Wood <john.wood@....com>, Jonathan Corbet <corbet@....net>,
        James Morris <jmorris@...ei.org>,
        "Serge E. Hallyn" <serge@...lyn.com>, linux-doc@...r.kernel.org,
        linux-kernel@...r.kernel.org,
        linux-security-module@...r.kernel.org,
        kernel-hardening@...ts.openwall.com
Subject: [PATCH v2 4/8] security/brute: Detect a fork brute force attack

To detect a fork brute force attack is necessary that the list that
holds the last crashes timestamps be updated in every fatal crash. To do
so, use the "task_fatal_signal" LSM hook added in a previous step.

Then, an only when this list is large enough, the application crash
period can be computed as the difference between the newest crash
timestamp and the oldest one divided by the size of the list. This way,
the scenario where an application crashes few times in a short period of
time due to reasons unrelated to a real attack is avoided.

Finally, an application crash period that falls under the defined
threshold shows that the application is crashing quickly and there is a
clear signal that an attack is happening.

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

diff --git a/security/brute/brute.c b/security/brute/brute.c
index 29835fe2f141..223a18c2084a 100644
--- a/security/brute/brute.c
+++ b/security/brute/brute.c
@@ -339,6 +339,135 @@ static void brute_task_free(struct task_struct *task)
 		spin_unlock(&(*stats)->lock);
 }

+/**
+ * brute_add_timestamp() - Add a new entry to the last crashes timestamps list.
+ * @stats: Statistics that hold the last crashes timestamps list.
+ * @new_entry: New timestamp to add to the list.
+ *
+ * The statistics that hold the last crashes timestamps list cannot be NULL. The
+ * new timestamp to add to the list cannot be NULL.
+ *
+ * Context: Must be called with stats->lock held.
+ */
+static void brute_add_timestamp(struct brute_stats *stats,
+				struct brute_timestamp *new_entry)
+{
+	list_add_tail(&new_entry->node, &stats->timestamps);
+	stats->timestamps_size += 1;
+}
+
+/**
+ * brute_old_timestamp_entry() - Get the oldest timestamp entry.
+ * @head: Last crashes timestamps list.
+ *
+ * Context: Must be called with stats->lock held.
+ * Return: The oldest entry added to the last crashes timestamps list.
+ */
+#define brute_old_timestamp_entry(head) \
+	list_first_entry(head, struct brute_timestamp, node)
+
+/**
+ * brute_update_timestamps_list() - Update the last crashes timestamps list.
+ * @stats: Statistics that hold the last crashes timestamps list.
+ * @new_entry: New timestamp to update the list.
+ *
+ * Add a new timestamp structure to the list if this one has not reached the
+ * maximum size yet. Replace the oldest timestamp entry otherwise.
+ *
+ * The statistics that hold the last crashes timestamps list cannot be NULL. The
+ * new timestamp to update the list cannot be NULL.
+ *
+ * Context: Must be called with stats->lock held.
+ * Return: The oldest timestamp that has been replaced. NULL otherwise.
+ */
+static struct brute_timestamp *
+brute_update_timestamps_list(struct brute_stats *stats,
+			     struct brute_timestamp *new_entry)
+{
+	unsigned int list_size;
+	struct brute_timestamp *old_entry;
+
+	list_size = (unsigned int)stats->timestamps_size;
+	if (list_size < brute_timestamps_list_size) {
+		brute_add_timestamp(stats, new_entry);
+		return NULL;
+	}
+
+	old_entry = brute_old_timestamp_entry(&stats->timestamps);
+	list_replace(&old_entry->node, &new_entry->node);
+	list_rotate_left(&stats->timestamps);
+
+	return old_entry;
+}
+
+/**
+ * brute_get_crash_period() - Get the application crash period.
+ * @new_entry: New timestamp added to the last crashes timestamps list.
+ * @old_entry: Old timestamp replaced in the last crashes timestamps list.
+ *
+ * The application crash period is computed as the difference between the newest
+ * crash timestamp and the oldest one divided by the size of the list. This way,
+ * the scenario where an application crashes few times in a short period of time
+ * due to reasons unrelated to a real attack is avoided.
+ *
+ * The new and old timestamp cannot be NULL.
+ *
+ * Context: Must be called with stats->lock held.
+ * Return: The application crash period in milliseconds.
+ */
+static u64 brute_get_crash_period(struct brute_timestamp *new_entry,
+				  struct brute_timestamp *old_entry)
+{
+	u64 jiffies;
+
+	jiffies = new_entry->jiffies - old_entry->jiffies;
+	jiffies /= (u64)brute_timestamps_list_size;
+
+	return jiffies64_to_msecs(jiffies);
+}
+
+/**
+ * brute_task_fatal_signal() - Target for the task_fatal_signal hook.
+ * @siginfo: Contains the signal information.
+ *
+ * To detect a fork brute force attack is necessary that the list that holds the
+ * last crashes timestamps be updated in every fatal crash. Then, an only when
+ * this list is large enough, the application crash period can be computed an
+ * compared with the defined threshold.
+ *
+ * It's mandatory to disable interrupts before acquiring the lock since the
+ * task_free hook can be called from an IRQ context during the execution of the
+ * task_fatal_signal hook.
+ */
+static void brute_task_fatal_signal(const kernel_siginfo_t *siginfo)
+{
+	struct brute_stats **stats;
+	struct brute_timestamp *new_entry, *old_entry;
+	unsigned long flags;
+	u64 crash_period;
+
+	stats = brute_stats_ptr(current);
+	if (WARN(!*stats, "No statistical data\n"))
+		return;
+
+	new_entry = brute_new_timestamp();
+	if (WARN(!new_entry, "Cannot allocate last crash timestamp\n"))
+		return;
+
+	spin_lock_irqsave(&(*stats)->lock, flags);
+	old_entry = brute_update_timestamps_list(*stats, new_entry);
+
+	if (old_entry) {
+		crash_period = brute_get_crash_period(new_entry, old_entry);
+		kfree(old_entry);
+
+		if (crash_period < (u64)brute_crash_period_threshold)
+			pr_warn("Fork brute force attack detected\n");
+	}
+
+	spin_unlock_irqrestore(&(*stats)->lock, flags);
+}
+
 /**
  * brute_hooks - Targets for the LSM's hooks.
  */
@@ -346,6 +475,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),
 };

 #ifdef CONFIG_SYSCTL
--
2.25.1

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ