[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <202009101649.2A0BF95@keescook>
Date: Thu, 10 Sep 2020 16:56:19 -0700
From: Kees Cook <keescook@...omium.org>
To: kernel-hardening@...ts.openwall.com
Cc: John Wood <john.wood@....com>,
Matthew Wilcox <willy@...radead.org>,
Jonathan Corbet <corbet@....net>,
Alexander Viro <viro@...iv.linux.org.uk>,
Ingo Molnar <mingo@...hat.com>,
Peter Zijlstra <peterz@...radead.org>,
Juri Lelli <juri.lelli@...hat.com>,
Vincent Guittot <vincent.guittot@...aro.org>,
Dietmar Eggemann <dietmar.eggemann@....com>,
Steven Rostedt <rostedt@...dmis.org>,
Ben Segall <bsegall@...gle.com>, Mel Gorman <mgorman@...e.de>,
Luis Chamberlain <mcgrof@...nel.org>,
Iurii Zaikin <yzaikin@...gle.com>,
James Morris <jmorris@...ei.org>,
"Serge E. Hallyn" <serge@...lyn.com>, linux-doc@...r.kernel.org,
linux-kernel@...r.kernel.org, linux-fsdevel@...r.kernel.org,
linux-security-module@...r.kernel.org
Subject: Re: [RFC PATCH 6/6] security/fbfam: Mitigate a fork brute force
attack
On Thu, Sep 10, 2020 at 01:21:07PM -0700, Kees Cook wrote:
> From: John Wood <john.wood@....com>
>
> In order to mitigate a fork brute force attack it is necessary to kill
> all the offending tasks. This tasks are all the ones that share the
> statistical data with the current task (the task that has crashed).
>
> Since the attack detection is done in the function fbfam_handle_attack()
> that is called every time a core dump is triggered, only is needed to
> kill the others tasks that share the same statistical data, not the
> current one as this is in the path to be killed.
>
> When the SIGKILL signal is sent to the offending tasks from the function
> fbfam_kill_tasks(), this one will be called again during the core dump
> due to the shared statistical data shows a quickly crashing rate. So, to
> avoid kill again the same tasks due to a recursive call of this
> function, it is necessary to disable the attack detection.
>
> To disable this attack detection, add a condition in the function
> fbfam_handle_attack() to not compute the crashing rate when the jiffies
> stored in the statistical data are set to zero.
>
> Signed-off-by: John Wood <john.wood@....com>
> ---
> security/fbfam/fbfam.c | 76 +++++++++++++++++++++++++++++++++++++++---
> 1 file changed, 71 insertions(+), 5 deletions(-)
>
> diff --git a/security/fbfam/fbfam.c b/security/fbfam/fbfam.c
> index 3aa669e4ea51..173a6122390f 100644
> --- a/security/fbfam/fbfam.c
> +++ b/security/fbfam/fbfam.c
> @@ -4,8 +4,11 @@
> #include <linux/errno.h>
> #include <linux/gfp.h>
> #include <linux/jiffies.h>
> +#include <linux/pid.h>
> #include <linux/printk.h>
> +#include <linux/rcupdate.h>
> #include <linux/refcount.h>
> +#include <linux/sched/signal.h>
> #include <linux/signal.h>
> #include <linux/slab.h>
>
> @@ -24,7 +27,8 @@ unsigned long sysctl_crashing_rate_threshold = 30000;
> * struct fbfam_stats - Fork brute force attack mitigation statistics.
> * @refc: Reference counter.
> * @faults: Number of crashes since jiffies.
> - * @jiffies: First fork or execve timestamp.
> + * @jiffies: First fork or execve timestamp. If zero, the attack detection is
> + * disabled.
> *
> * The purpose of this structure is to manage all the necessary information to
> * compute the crashing rate of an application. So, it holds a first fork or
> @@ -175,13 +179,69 @@ int fbfam_exit(void)
> }
>
> /**
> - * fbfam_handle_attack() - Fork brute force attack detection.
> + * fbfam_kill_tasks() - Kill the offending tasks
> + *
> + * When a fork brute force attack is detected it is necessary to kill all the
> + * offending tasks. Since this function is called from fbfam_handle_attack(),
> + * and so, every time a core dump is triggered, only is needed to kill the
> + * others tasks that share the same statistical data, not the current one as
> + * this is in the path to be killed.
> + *
> + * When the SIGKILL signal is sent to the offending tasks, this function will be
> + * called again during the core dump due to the shared statistical data shows a
> + * quickly crashing rate. So, to avoid kill again the same tasks due to a
> + * recursive call of this function, it is necessary to disable the attack
> + * detection setting the jiffies to zero.
> + *
> + * To improve the for_each_process loop it is possible to end it when all the
> + * tasks that shared the same statistics are found.
> + *
> + * Return: -EFAULT if the current task doesn't have statistical data. Zero
> + * otherwise.
> + */
> +static int fbfam_kill_tasks(void)
> +{
> + struct fbfam_stats *stats = current->fbfam_stats;
> + struct task_struct *p;
> + unsigned int to_kill, killed = 0;
> +
> + if (!stats)
> + return -EFAULT;
> +
> + to_kill = refcount_read(&stats->refc) - 1;
> + if (!to_kill)
> + return 0;
> +
> + /* Disable the attack detection */
> + stats->jiffies = 0;
> + rcu_read_lock();
> +
> + for_each_process(p) {
> + if (p == current || p->fbfam_stats != stats)
> + continue;
> +
> + do_send_sig_info(SIGKILL, SEND_SIG_PRIV, p, PIDTYPE_PID);
> + pr_warn("fbfam: Offending process with PID %d killed\n",
> + p->pid);
I'd make this ratelimited (along with Jann's suggestions). Also, instead
of the explicit "fbfam:" prefix, use the regular prefixing method:
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> + killed += 1;
> + if (killed >= to_kill)
> + break;
> + }
> +
> + rcu_read_unlock();
Can't newly created processes escape this RCU read lock? I think this
need alternate locking, or something in the task_alloc hook that will
block any new process from being created within the stats group.
> + return 0;
> +}
> +
> +/**
> + * fbfam_handle_attack() - Fork brute force attack detection and mitigation.
> * @signal: Signal number that causes the core dump.
> *
> * The crashing rate of an application is computed in milliseconds per fault in
> * each crash. So, if this rate goes under a certain threshold there is a clear
> * signal that the application is crashing quickly. At this moment, a fork brute
> - * force attack is happening.
> + * force attack is happening. Under this scenario it is necessary to kill all
> + * the offending tasks in order to mitigate the attack.
> *
> * Return: -EFAULT if the current task doesn't have statistical data. Zero
> * otherwise.
> @@ -195,6 +255,10 @@ int fbfam_handle_attack(int signal)
> if (!stats)
> return -EFAULT;
>
> + /* The attack detection is disabled */
> + if (!stats->jiffies)
> + return 0;
> +
> if (!(signal == SIGILL || signal == SIGBUS || signal == SIGKILL ||
> signal == SIGSEGV || signal == SIGSYS))
> return 0;
> @@ -205,9 +269,11 @@ int fbfam_handle_attack(int signal)
> delta_time = jiffies64_to_msecs(delta_jiffies);
> crashing_rate = delta_time / (u64)stats->faults;
>
> - if (crashing_rate < (u64)sysctl_crashing_rate_threshold)
> - pr_warn("fbfam: Fork brute force attack detected\n");
> + if (crashing_rate >= (u64)sysctl_crashing_rate_threshold)
> + return 0;
>
> + pr_warn("fbfam: Fork brute force attack detected\n");
> + fbfam_kill_tasks();
> return 0;
> }
>
> --
> 2.25.1
>
--
Kees Cook
Powered by blists - more mailing lists