linux-kselftest.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* Re: [PATCH v8 3/8] security/brute: Detect a brute force attack
@ 2021-07-01 23:55 Alexander Lobakin
  2021-07-02 14:59 ` John Wood
  0 siblings, 1 reply; 7+ messages in thread
From: Alexander Lobakin @ 2021-07-01 23:55 UTC (permalink / raw)
  To: John Wood
  Cc: Alexander Lobakin, Kees Cook, Jann Horn, Jonathan Corbet,
	James Morris, Serge E. Hallyn, Shuah Khan, Thomas Gleixner,
	Ingo Molnar, Borislav Petkov, x86, H. Peter Anvin, Arnd Bergmann,
	Andi Kleen, valdis.kletnieks, Greg Kroah-Hartman, Randy Dunlap,
	Andrew Morton, linux-doc, linux-kernel, linux-security-module,
	linux-kselftest, linux-arch, linux-hardening, kernel-hardening

Hi,

From: John Wood <john.wood@gmx.com>
Date: Sat, 5 Jun 2021 17:04:00 +0200

> For a correct management of a fork brute force attack it is necessary to
> track all the information related to the application crashes. To do so,
> use the extended attributes (xattr) of the executable files and define a
> statistical data structure to hold all the necessary information shared
> by all the fork hierarchy processes. This info is the number of crashes,
> the last crash timestamp and the crash period's moving average.
>
> The same can be achieved using a pointer to the fork hierarchy
> statistical data held by the task_struct structure. But this has an
> important drawback: a brute force attack that happens through the execve
> system call losts the faults info since these statistics are freed when
> the fork hierarchy disappears. Using this method makes not possible to
> manage this attack type that can be successfully treated using extended
> attributes.
>
> Also, to avoid false positives during the attack detection it is
> necessary to narrow the possible cases. So, 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
>
> To mark that a privilege boundary has been crossed it is only necessary
> to create a new stats for the executable file via the extended attribute
> and only if it has no previous statistical data. This is done using four
> different LSM hooks, one per privilege boundary:
>
> setuid/setgid process --> bprm_creds_from_file hook (based on secureexec
>                           flag).
> network to local -------> socket_accept hook (taking into account only
>                           external connections).
> privilege changes ------> task_fix_setuid and task_fix_setgid hooks.
>
> To detect a brute force attack it is necessary that the executable file
> statistics 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.
>
> Based on the updated statistics two different attacks can be handled. 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.
>
> Moreover, 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.
>
> Signed-off-by: John Wood <john.wood@gmx.com>
>
> <snip>
>
> +static int brute_get_xattr_stats(struct dentry *dentry, struct inode *inode,
> +				 struct brute_stats *stats)
> +{
> +	int rc;
> +	struct brute_raw_stats raw_stats;
> +
> +	rc = __vfs_getxattr(dentry, inode, XATTR_NAME_BRUTE, &raw_stats,
> +			    sizeof(raw_stats));
> +	if (rc < 0)
> +		return rc;
> +
> +	stats->faults = le32_to_cpu(raw_stats.faults);
> +	stats->nsecs = le64_to_cpu(raw_stats.nsecs);
> +	stats->period = le64_to_cpu(raw_stats.period);
> +	stats->flags = raw_stats.flags;
> +	return 0;
> +}
>
> <snip>
>
> +static int brute_task_execve(struct linux_binprm *bprm, struct file *file)
> +{
> +	struct dentry *dentry = file_dentry(bprm->file);
> +	struct inode *inode = file_inode(bprm->file);
> +	struct brute_stats stats;
> +	int rc;
> +
> +	inode_lock(inode);
> +	rc = brute_get_xattr_stats(dentry, inode, &stats);
> +	if (WARN_ON_ONCE(rc && rc != -ENODATA))
> +		goto unlock;

I think I caught a problem here. Have you tested this with
initramfs?

According to init/do_mount.c's
init_rootfs()/rootfs_init_fs_context(), when `root=` cmdline
parameter is not empty, kernel creates rootfs of type ramfs
(tmpfs otherwise).
The thing about ramfs is that it doesn't support xattrs.

I'm running this v8 on a regular PC with initramfs and having
`root=` in cmdline, and Brute doesn't allow the kernel to run
any init processes (/init, /sbin/init, ...) with err == -95
(-EOPNOTSUPP) -- I'm getting a

WARNING: CPU: 0 PID: 173 at brute_task_execve+0x15d/0x200
<snip>
Failed to execute /init (error -95)

and so on (and a panic at the end).

If I omit `root=` from cmdline, then the kernel runs init process
just fine -- I guess because initramfs is then placed inside tmpfs
with xattr support.

As for me, this ramfs/tmpfs selection based on `root=` presence
is ridiculous and I don't see or know any reasons behind that.
But that's another story, and ramfs might be not the only one
system without xattr support.
I think Brute should have a fallback here, e.g. it could simply
ignore files from xattr-incapable filesystems instead of such
WARNING splats and stuff.

> +
> +	if (rc == -ENODATA && bprm->secureexec) {
> +		brute_reset_stats(&stats);
> +		rc = brute_set_xattr_stats(dentry, inode, &stats);
> +		if (WARN_ON_ONCE(rc))
> +			goto unlock;
> +	}
> +
> +	rc = 0;
> +unlock:
> +	inode_unlock(inode);
> +	return rc;
> +}
> +
>
> <snip>

Thanks,
Al


^ permalink raw reply	[flat|nested] 7+ messages in thread
* [PATCH v8 0/8] Fork brute force attack mitigation
@ 2021-06-05 15:03 John Wood
  2021-06-05 15:04 ` [PATCH v8 3/8] security/brute: Detect a brute force attack John Wood
  0 siblings, 1 reply; 7+ messages in thread
From: John Wood @ 2021-06-05 15:03 UTC (permalink / raw)
  To: Kees Cook, Jann Horn, Jonathan Corbet, James Morris,
	Serge E. Hallyn, Shuah Khan, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, x86, H. Peter Anvin, Arnd Bergmann
  Cc: John Wood, Andi Kleen, valdis.kletnieks, Greg Kroah-Hartman,
	Randy Dunlap, Andrew Morton, linux-doc, linux-kernel,
	linux-security-module, linux-kselftest, linux-arch,
	linux-hardening, kernel-hardening

Attacks against vulnerable userspace applications with the purpose to break
ASLR or bypass canaries traditionally use some level of brute force with
the help of the fork system call. This is possible since when creating a
new process using fork its memory contents are the same as those of the
parent process (the process that called the fork system call). So, the
attacker can test the memory infinite times to find the correct memory
values or the correct memory addresses without worrying about crashing the
application.

Based on the above scenario it would be nice to have this detected and
mitigated, and this is the goal of this patch serie. Specifically the
following attacks are expected to be detected:

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

So, what will really be detected are fork/exec brute force attacks that
cross any of the commented bounds.

The implementation details and comparison against other existing
implementations can be found in the "Documentation" patch.

It is important to mention that the v8 and v7 versions have changed the
method used to track the information related to the application crashes.
Prior this versions, a pointer per process (in the task_struct structure)
held a reference to the shared statistical data. Or in other words, these
stats were shared by all the fork hierarchy processes. But this has an
important drawback: a brute force attack that happens through the execve
system call losts the faults info since these statistics are freed when the
fork hierarchy disappears. So, the solution adopted in the v6 version was
to use an upper fork hierarchy to track the info for this attack type. But,
as Valdis Kletnieks pointed out during this discussion [1], this method can
be easily bypassed using a double exec (well, this was the method used in
the kselftest to avoid the detection ;) ). So, in this version, to track
all the statistical data (info related with application crashes), the
extended attributes feature for the executable files are used. The xattr is
also used to mark the executables as "not allowed" when an attack is
detected. Then, the execve system call rely on this flag to avoid following
executions of this file.

[1] https://lore.kernel.org/kernelnewbies/20210330173459.GA3163@ubuntu/

Moreover, I think this solves another problem pointed out by Andi Kleen
during the v5 review [2] related to the possibility that a supervisor
respawns processes killed by the Brute LSM. He suggested adding some way so
a supervisor can know that a process has been killed by Brute and then
decide to respawn or not. So, now, the supervisor can read the brute xattr
of one executable and know if it is blocked by Brute and why (using the
statistical data).

[2] https://lore.kernel.org/kernel-hardening/878s78dnrm.fsf@linux.intel.com/

Although the xattr of the executable is accessible from userspace, in
complex daemons this file may not be visible directly by the supervisor as
it may be run through some wrapper. So, an extension to the waitid() system
call has been added in this version. This was suggested by Andi Kleen [3]
during the v7 review. (The case with supervisors using cgroups is not yet
tested).

[3] https://lore.kernel.org/kernel-hardening/19903478-52e0-3829-0515-3e17669108f7@linux.intel.com/

Knowing all this information I will explain now the different patches:

The 1/8 patch defines a new LSM hook to get the fatal signal of a task.
This will be useful during the attack detection phase.

The 2/8 patch defines a new LSM and the necessary sysctl attributes to fine
tuning the attack detection.

The 3/8 patch detects a fork/exec brute force attack and narrows the
possible cases taken into account the privilege boundary crossing.

The 4/8 patch mitigates a brute force attack.

The 5/8 patch adds the extension to the waitid system call to notify to
userspace that a task has been killed by Brute LSM when an attack is
mitigated.

The 6/8 patch adds self-tests to validate the Brute LSM expectations.

The 7/8 patch adds the documentation to explain this implementation.

The 8/8 patch updates the maintainers file.

This patch serie is a task of the KSPP [4] and can also be accessed from my
github tree [5] in the "brute_v8" branch.

[4] https://github.com/KSPP/linux/issues/39
[5] https://github.com/johwood/linux/

When I ran the "checkpatch" script I got the following errors, but I think
they are false positives as I follow the same coding style for the others
extended attributes suffixes.

----------------------------------------------------------------------------
../patches/brute_v8/v8-0003-security-brute-Detect-a-brute-force-attack.patch
----------------------------------------------------------------------------
ERROR: Macros with complex values should be enclosed in parentheses
89: FILE: include/uapi/linux/xattr.h:80:
+#define XATTR_NAME_BRUTE XATTR_SECURITY_PREFIX XATTR_BRUTE_SUFFIX

-----------------------------------------------------------------------------
../patches/brute_v8/v8-0006-selftests-brute-Add-tests-for-the-Brute-LSM.patch
-----------------------------------------------------------------------------
ERROR: Macros with complex values should be enclosed in parentheses
159: FILE: tools/testing/selftests/brute/rmxattr.c:18:
+#define XATTR_NAME_BRUTE XATTR_SECURITY_PREFIX XATTR_BRUTE_SUFFIX

When I ran the "kernel-doc" script with the following parameters:

./scripts/kernel-doc --none -v security/brute/brute.c

I got the following warning:

security/brute/brute.c:118: warning: contents before sections

But I don't understand why it is complaining. Could it be a false positive?

The previous versions can be found in:

RFC
https://lore.kernel.org/kernel-hardening/20200910202107.3799376-1-keescook@chromium.org/

Version 2
https://lore.kernel.org/kernel-hardening/20201025134540.3770-1-john.wood@gmx.com/

Version 3
https://lore.kernel.org/lkml/20210221154919.68050-1-john.wood@gmx.com/

Version 4
https://lore.kernel.org/lkml/20210227150956.6022-1-john.wood@gmx.com/

Version 5
https://lore.kernel.org/kernel-hardening/20210227153013.6747-1-john.wood@gmx.com/

Version 6
https://lore.kernel.org/kernel-hardening/20210307113031.11671-1-john.wood@gmx.com/

Version 7
https://lore.kernel.org/kernel-hardening/20210521172414.69456-1-john.wood@gmx.com/

Changelog RFC -> v2
-------------------
- Rename this feature with a more suitable name (Jann Horn, Kees Cook).
- Convert the code to an LSM (Kees Cook).
- Add locking  to avoid data races (Jann Horn).
- Add a new LSM hook to get the fatal signal of a task (Jann Horn, Kees
  Cook).
- Add the last crashes timestamps list to avoid false positives in the
  attack detection (Jann Horn).
- Use "period" instead of "rate" (Jann Horn).
- Other minor changes suggested (Jann Horn, Kees Cook).

Changelog v2 -> v3
------------------
- Compute the application crash period on an on-going basis (Kees Cook).
- Detect a brute force attack through the execve system call (Kees Cook).
- Detect an slow brute force attack (Randy Dunlap).
- Fine tuning the detection taken into account privilege boundary crossing
  (Kees Cook).
- Taken into account only fatal signals delivered by the kernel (Kees
  Cook).
- Remove the sysctl attributes to fine tuning the detection (Kees Cook).
- Remove the prctls to allow per process enabling/disabling (Kees Cook).
- Improve the documentation (Kees Cook).
- Fix some typos in the documentation (Randy Dunlap).
- Add self-test to validate the expectations (Kees Cook).

Changelog v3 -> v4
------------------
- Fix all the warnings shown by the tool "scripts/kernel-doc" (Randy
  Dunlap).

Changelog v4 -> v5
------------------
- Fix some typos (Randy Dunlap).

Changelog v5 -> v6
------------------
- Fix a reported deadlock (kernel test robot).
- Add high level details to the documentation (Andi Kleen).

Changelog v6 -> v7
------------------

- Add the "Reviewed-by:" tag to the first patch.
- Rearrange the brute LSM between lockdown and yama (Kees Cook).
- Split subdir and obj in security/Makefile (Kees Cook).
- Reduce the number of header files included (Kees Cook).
- Print the pid when an attack is detected (Kees Cook).
- Use the socket_accept LSM hook instead of socket_sock_rcv_skb hook to
  avoid running a hook on every incoming network packet (Kees Cook).
- Update the documentation and fix it to render it properly (Jonathan
  Corbet).
- Manage correctly an exec brute force attack avoiding the bypass (Valdis
  Kletnieks).
- Other minor changes and cleanups.

Changelog v7 -> v8
------------------

- Rebase against v5.13-rc4.
- Fix a build error if CONFIG_IPV6 and/or CONFIG_SECURITY_NETWORK is not
  set (kernel test robot).
- Notify to userspace that a task has been killed by Brute LSM (Andi
  Kleen).
- Add a new test to verify that the userspace notification is working.
- Update the documentation accordingly with this new feature.
- Other minor changes and cleanups.

Any constructive comments are welcome.
Thanks in advance.

John Wood (8):
  security: Add LSM hook at the point where a task gets a fatal signal
  security/brute: Define a LSM and add sysctl attributes
  security/brute: Detect a brute force attack
  security/brute: Mitigate a brute force attack
  security/brute: Notify to userspace "task killed"
  selftests/brute: Add tests for the Brute LSM
  Documentation: Add documentation for the Brute LSM
  MAINTAINERS: Add a new entry for the Brute LSM

 Documentation/admin-guide/LSM/Brute.rst  | 359 ++++++++++
 Documentation/admin-guide/LSM/index.rst  |   1 +
 MAINTAINERS                              |   8 +
 arch/x86/kernel/signal_compat.c          |   2 +-
 include/brute/brute.h                    |  16 +
 include/linux/lsm_hook_defs.h            |   1 +
 include/linux/lsm_hooks.h                |   4 +
 include/linux/security.h                 |   4 +
 include/uapi/asm-generic/siginfo.h       |   3 +-
 include/uapi/linux/xattr.h               |   3 +
 kernel/exit.c                            |   6 +-
 kernel/signal.c                          |   5 +-
 security/Kconfig                         |  11 +-
 security/Makefile                        |   2 +
 security/brute/Kconfig                   |  15 +
 security/brute/Makefile                  |   2 +
 security/brute/brute.c                   | 795 +++++++++++++++++++++++
 security/security.c                      |   5 +
 tools/testing/selftests/Makefile         |   1 +
 tools/testing/selftests/brute/.gitignore |   3 +
 tools/testing/selftests/brute/Makefile   |   5 +
 tools/testing/selftests/brute/config     |   1 +
 tools/testing/selftests/brute/exec.c     |  46 ++
 tools/testing/selftests/brute/rmxattr.c  |  34 +
 tools/testing/selftests/brute/test.c     | 507 +++++++++++++++
 tools/testing/selftests/brute/test.sh    | 269 ++++++++
 26 files changed, 2099 insertions(+), 9 deletions(-)
 create mode 100644 Documentation/admin-guide/LSM/Brute.rst
 create mode 100644 include/brute/brute.h
 create mode 100644 security/brute/Kconfig
 create mode 100644 security/brute/Makefile
 create mode 100644 security/brute/brute.c
 create mode 100644 tools/testing/selftests/brute/.gitignore
 create mode 100644 tools/testing/selftests/brute/Makefile
 create mode 100644 tools/testing/selftests/brute/config
 create mode 100644 tools/testing/selftests/brute/exec.c
 create mode 100644 tools/testing/selftests/brute/rmxattr.c
 create mode 100644 tools/testing/selftests/brute/test.c
 create mode 100755 tools/testing/selftests/brute/test.sh

--
2.25.1


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

end of thread, other threads:[~2021-07-05 12:52 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-07-01 23:55 [PATCH v8 3/8] security/brute: Detect a brute force attack Alexander Lobakin
2021-07-02 14:59 ` John Wood
2021-07-02 17:08   ` Alexander Lobakin
2021-07-03 10:59     ` John Wood
2021-07-04 14:01       ` John Wood
2021-07-05 12:52         ` Alexander Lobakin
  -- strict thread matches above, loose matches on Subject: below --
2021-06-05 15:03 [PATCH v8 0/8] Fork brute force attack mitigation John Wood
2021-06-05 15:04 ` [PATCH v8 3/8] security/brute: Detect a brute force attack 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).