kernel-hardening.lists.openwall.com archive mirror
 help / color / mirror / Atom feed
From: John Wood <john.wood@gmx.com>
To: Kees Cook <keescook@chromium.org>
Cc: John Wood <john.wood@gmx.com>, Jann Horn <jannh@google.com>,
	Randy Dunlap <rdunlap@infradead.org>,
	Jonathan Corbet <corbet@lwn.net>,
	James Morris <jmorris@namei.org>, Shuah Khan <shuah@kernel.org>,
	"Serge E. Hallyn" <serge@hallyn.com>,
	Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	Andi Kleen <ak@linux.intel.com>,
	kernel test robot <oliver.sang@intel.com>,
	linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-security-module@vger.kernel.org,
	linux-kselftest@vger.kernel.org,
	kernel-hardening@lists.openwall.com
Subject: Re: [PATCH v6 4/8] security/brute: Fine tuning the attack detection
Date: Sat, 20 Mar 2021 16:46:48 +0100	[thread overview]
Message-ID: <20210320154648.GC3023@ubuntu> (raw)
In-Reply-To: <202103171957.16C0560D@keescook>

On Wed, Mar 17, 2021 at 09:00:51PM -0700, Kees Cook wrote:
> On Sun, Mar 07, 2021 at 12:30:27PM +0100, John Wood wrote:
> >  #include <asm/current.h>
> > +#include <asm/rwonce.h>
> > +#include <asm/siginfo.h>
> > +#include <asm/signal.h>
> > +#include <linux/binfmts.h>
> >  #include <linux/bug.h>
> >  #include <linux/compiler.h>
> > +#include <linux/cred.h>
> > +#include <linux/dcache.h>
> >  #include <linux/errno.h>
> > +#include <linux/fs.h>
> >  #include <linux/gfp.h>
> > +#include <linux/if.h>
> >  #include <linux/init.h>
> >  #include <linux/jiffies.h>
> >  #include <linux/kernel.h>
> >  #include <linux/lsm_hooks.h>
> >  #include <linux/math64.h>
> > +#include <linux/netdevice.h>
> > +#include <linux/path.h>
> >  #include <linux/printk.h>
> >  #include <linux/refcount.h>
> >  #include <linux/rwlock.h>
> > @@ -19,9 +29,35 @@
> >  #include <linux/sched.h>
> >  #include <linux/sched/signal.h>
> >  #include <linux/sched/task.h>
> > +#include <linux/signal.h>
> > +#include <linux/skbuff.h>
> >  #include <linux/slab.h>
> >  #include <linux/spinlock.h>
> > +#include <linux/stat.h>
> >  #include <linux/types.h>
> > +#include <linux/uidgid.h>
>
> This is really a LOT of includes. Are you sure all of these are
> explicitly needed?

I try to add the needed header for every macro and function used. If
there is a better method to do it I will apply it. Thanks.

> >  /**
> >   * struct brute_stats - Fork brute force attack statistics.
> > @@ -30,6 +66,9 @@
> >   * @faults: Number of crashes.
> >   * @jiffies: Last crash timestamp.
> >   * @period: Crash period's moving average.
> > + * @saved_cred: Saved credentials.
> > + * @network: Network activity flag.
> > + * @bounds_crossed: Privilege bounds crossed flag.
> >   *
> >   * This structure holds the statistical data shared by all the fork hierarchy
> >   * processes.
> > @@ -40,6 +79,9 @@ struct brute_stats {
> >  	unsigned char faults;
> >  	u64 jiffies;
> >  	u64 period;
> > +	struct brute_cred saved_cred;
> > +	unsigned char network : 1;
> > +	unsigned char bounds_crossed : 1;
>
> If you really want to keep faults a "char", I would move these bools
> after "faults" to avoid adding more padding.

Understood. Thanks.

> > +/**
> > + * brute_is_setid() - Test if the executable file has the setid flags set.
> > + * @bprm: Points to the linux_binprm structure.
> > + *
> > + * Return: True if the executable file has the setid flags set. False otherwise.
> > + */
> > +static bool brute_is_setid(const struct linux_binprm *bprm)
> > +{
> > +	struct file *file = bprm->file;
> > +	struct inode *inode;
> > +	umode_t mode;
> > +
> > +	if (!file)
> > +		return false;
> > +
> > +	inode = file->f_path.dentry->d_inode;
> > +	mode = inode->i_mode;
> > +
> > +	return !!(mode & (S_ISUID | S_ISGID));
> > +}
>
> Oh, er, no, this should not reinvent the wheel. You just want to know if
> creds got elevated, so you want bprm->secureexec; this gets correctly
> checked in cap_bprm_creds_from_file().

Ok, I will work on it for the next version.

> > +
> > +/**
> > + * brute_reset_stats() - Reset the statistical data.
> > + * @stats: Statistics to be reset.
> > + * @is_setid: The executable file has the setid flags set.
> > + *
> > + * Reset the faults and period and set the last crash timestamp to now. This
> > + * way, it is possible to compute the application crash period at the next
> > + * fault. Also, save the credentials of the current task and update the
> > + * bounds_crossed flag based on a previous network activity and the is_setid
> > + * parameter.
> > + *
> > + * The statistics to be reset cannot be NULL.
> > + *
> > + * Context: Must be called with interrupts disabled and brute_stats_ptr_lock
> > + *          and brute_stats::lock held.
> > + */
> > +static void brute_reset_stats(struct brute_stats *stats, bool is_setid)
> > +{
> > +	const struct cred *cred = current_cred();
> > +
> > +	stats->faults = 0;
> > +	stats->jiffies = get_jiffies_64();
> > +	stats->period = 0;
> > +	stats->saved_cred.uid = cred->uid;
> > +	stats->saved_cred.gid = cred->gid;
> > +	stats->saved_cred.suid = cred->suid;
> > +	stats->saved_cred.sgid = cred->sgid;
> > +	stats->saved_cred.euid = cred->euid;
> > +	stats->saved_cred.egid = cred->egid;
> > +	stats->saved_cred.fsuid = cred->fsuid;
> > +	stats->saved_cred.fsgid = cred->fsgid;
> > +	stats->bounds_crossed = stats->network || is_setid;
> > +}
>
> I would include brute_reset_stats() in the first patch (and add to it as
> needed). To that end, it can start with a memset(stats, 0, sizeof(*stats));

So, need all the struct fields to be introduced in the initial patch?
Even if they are not needed in the initial patch? I'm confused.

> > +/**
> > + * brute_priv_have_changed() - Test if the privileges have changed.
> > + * @stats: Statistics that hold the saved credentials.
> > + *
> > + * The privileges have changed if the credentials of the current task are
> > + * different from the credentials saved in the statistics structure.
> > + *
> > + * The statistics that hold the saved credentials cannot be NULL.
> > + *
> > + * Context: Must be called with interrupts disabled and brute_stats_ptr_lock
> > + *          and brute_stats::lock held.
> > + * Return: True if the privileges have changed. False otherwise.
> > + */
> > +static bool brute_priv_have_changed(struct brute_stats *stats)
> > +{
> > +	const struct cred *cred = current_cred();
> > +	bool priv_have_changed;
> > +
> > +	priv_have_changed = !uid_eq(stats->saved_cred.uid, cred->uid) ||
> > +		!gid_eq(stats->saved_cred.gid, cred->gid) ||
> > +		!uid_eq(stats->saved_cred.suid, cred->suid) ||
> > +		!gid_eq(stats->saved_cred.sgid, cred->sgid) ||
> > +		!uid_eq(stats->saved_cred.euid, cred->euid) ||
> > +		!gid_eq(stats->saved_cred.egid, cred->egid) ||
> > +		!uid_eq(stats->saved_cred.fsuid, cred->fsuid) ||
> > +		!gid_eq(stats->saved_cred.fsgid, cred->fsgid);
> > +
> > +	return priv_have_changed;
> > +}
>
> This should just be checked from bprm->secureexec, which is valid by the
> time you get to the bprm_committing_creds hook. You can just save the
> value to your stats struct instead of re-interrogating current_cred,
> etc.

Ok. Thanks.

> > +
> > +/**
> > + * brute_threat_model_supported() - Test if the threat model is supported.
> > + * @siginfo: Contains the signal information.
> > + * @stats: Statistical data shared by all the fork hierarchy processes.
> > + *
> > + * To avoid false positives during the attack detection it is necessary to
> > + * narrow the possible cases. Only the following scenarios are taken into
> > + * account:
> > + *
> > + * 1.- Launching (fork()/exec()) a setuid/setgid process repeatedly until a
> > + *     desirable memory layout is got (e.g. Stack Clash).
> > + * 2.- Connecting to an exec()ing network daemon (e.g. xinetd) repeatedly until
> > + *     a desirable memory layout is got (e.g. what CTFs do for simple network
> > + *     service).
> > + * 3.- Launching processes without exec() (e.g. Android Zygote) and exposing
> > + *     state to attack a sibling.
> > + * 4.- Connecting to a fork()ing network daemon (e.g. apache) repeatedly until
> > + *     the previously shared memory layout of all the other children is exposed
> > + *     (e.g. kind of related to HeartBleed).
> > + *
> > + * In each case, a privilege boundary has been crossed:
> > + *
> > + * Case 1: setuid/setgid process
> > + * Case 2: network to local
> > + * Case 3: privilege changes
> > + * Case 4: network to local
> > + *
> > + * Also, only the signals delivered by the kernel are taken into account with
> > + * the exception of the SIGABRT signal since the latter is used by glibc for
> > + * stack canary, malloc, etc failures, which may indicate that a mitigation has
> > + * been triggered.
> > + *
> > + * The signal information and 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 interrupts disabled and brute_stats_ptr_lock
> > + *          held.
> > + * Return: True if the threat model is supported. False otherwise.
> > + */
> > +static bool brute_threat_model_supported(const kernel_siginfo_t *siginfo,
> > +					 struct brute_stats *stats)
> > +{
> > +	bool bounds_crossed;
> > +
> > +	if (siginfo->si_signo == SIGKILL && siginfo->si_code != SIGABRT)
> > +		return false;
> > +
> > +	spin_lock(&stats->lock);
> > +	bounds_crossed = stats->bounds_crossed;
> > +	bounds_crossed = bounds_crossed || brute_priv_have_changed(stats);
> > +	stats->bounds_crossed = bounds_crossed;
> > +	spin_unlock(&stats->lock);
> > +
> > +	return bounds_crossed;
> > +}
>
> I think this logic can be done with READ_ONCE()s and moved directly into
> brute_task_fatal_signal().

Thanks. I will work on locking.

> >
> > +/**
> > + * brute_network() - Target for the socket_sock_rcv_skb hook.
> > + * @sk: Contains the sock (not socket) associated with the incoming sk_buff.
> > + * @skb: Contains the incoming network data.
> > + *
> > + * A previous step to detect that a network to local boundary has been crossed
> > + * is to detect if there is network activity. To do this, it is only necessary
> > + * to check if there are data packets received from a network device other than
> > + * loopback.
> > + *
> > + * It's mandatory to disable interrupts before acquiring brute_stats_ptr_lock
> > + * and brute_stats::lock since the task_free hook can be called from an IRQ
> > + * context during the execution of the socket_sock_rcv_skb hook.
> > + *
> > + * Return: -EFAULT if the current task doesn't have statistical data. Zero
> > + *         otherwise.
> > + */
> > +static int brute_network(struct sock *sk, struct sk_buff *skb)
> > +{
> > +	struct brute_stats **stats;
> > +	unsigned long flags;
> > +
> > +	if (!skb->dev || (skb->dev->flags & IFF_LOOPBACK))
> > +		return 0;
> > +
> > +	stats = brute_stats_ptr(current);
>
> Uhh, is "current" valid here? I actually don't know this hook very well.

I think so, but I will try to study it. Thanks for noted this.

> > +	read_lock_irqsave(&brute_stats_ptr_lock, flags);
> > +
> > +	if (!*stats) {
> > +		read_unlock_irqrestore(&brute_stats_ptr_lock, flags);
> > +		return -EFAULT;
> > +	}
> > +
> > +	spin_lock(&(*stats)->lock);
> > +	(*stats)->network = true;
> > +	spin_unlock(&(*stats)->lock);
> > +	read_unlock_irqrestore(&brute_stats_ptr_lock, flags);
> > +	return 0;
> > +}

Thanks,
John Wood

  reply	other threads:[~2021-03-20 15:47 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-03-07 11:30 [PATCH v6 0/8] Fork brute force attack mitigation John Wood
2021-03-07 11:30 ` [PATCH v6 1/8] security: Add LSM hook at the point where a task gets a fatal signal John Wood
2021-03-18  1:22   ` Kees Cook
2021-03-07 11:30 ` [PATCH v6 2/8] security/brute: Define a LSM and manage statistical data John Wood
2021-03-18  2:00   ` Kees Cook
2021-03-20 15:01     ` John Wood
2021-03-21 17:37       ` Kees Cook
2021-03-07 11:30 ` [PATCH v6 3/8] securtiy/brute: Detect a brute force attack John Wood
2021-03-18  2:57   ` Kees Cook
2021-03-20 15:34     ` John Wood
2021-03-21 18:28       ` Kees Cook
2021-03-21 15:01     ` John Wood
2021-03-21 18:45       ` Kees Cook
2021-03-22 18:32         ` John Wood
2021-03-07 11:30 ` [PATCH v6 4/8] security/brute: Fine tuning the attack detection John Wood
2021-03-18  4:00   ` Kees Cook
2021-03-20 15:46     ` John Wood [this message]
2021-03-21 18:01       ` Kees Cook
2021-03-07 11:30 ` [PATCH v6 5/8] security/brute: Mitigate a brute force attack John Wood
2021-03-18  4:04   ` Kees Cook
2021-03-20 15:48     ` John Wood
2021-03-21 18:06       ` Kees Cook
2021-03-07 11:30 ` [PATCH v6 6/8] selftests/brute: Add tests for the Brute LSM John Wood
2021-03-18  4:08   ` Kees Cook
2021-03-20 15:49     ` John Wood
2021-03-07 11:30 ` [PATCH v6 7/8] Documentation: Add documentation " John Wood
2021-03-18  4:10   ` Kees Cook
2021-03-20 15:50     ` John Wood
2021-03-21 18:50   ` Jonathan Corbet
2021-03-26 15:41     ` John Wood
2021-03-07 11:30 ` [PATCH v6 8/8] MAINTAINERS: Add a new entry " John Wood

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210320154648.GC3023@ubuntu \
    --to=john.wood@gmx.com \
    --cc=ak@linux.intel.com \
    --cc=corbet@lwn.net \
    --cc=gregkh@linuxfoundation.org \
    --cc=jannh@google.com \
    --cc=jmorris@namei.org \
    --cc=keescook@chromium.org \
    --cc=kernel-hardening@lists.openwall.com \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=linux-security-module@vger.kernel.org \
    --cc=oliver.sang@intel.com \
    --cc=rdunlap@infradead.org \
    --cc=serge@hallyn.com \
    --cc=shuah@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).