mbox series

[v8,0/8] Fork brute force attack mitigation

Message ID 20210605150405.6936-1-john.wood@gmx.com (mailing list archive)
Headers show
Series Fork brute force attack mitigation | expand

Message

John Wood June 5, 2021, 3:03 p.m. UTC
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

Comments

Kees Cook June 8, 2021, 11:19 p.m. UTC | #1
On Sat, Jun 05, 2021 at 05:03:57PM +0200, John Wood wrote:
> [...]
> 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.

I have some concerns about this being actually usable and not creating
DoS situations. For example, let's say an attacker had found a hard-to-hit
bug in "sudo", and starts brute forcing it. When the brute LSM notices,
it'll make "sudo" unusable for the entire system, yes?

And a reboot won't fix it, either, IIUC.

It seems like there is a need to track "user" running "prog", and have
that be timed out. Are there use-cases here where that wouldn't be
sufficient?

-Kees
Andi Kleen June 8, 2021, 11:38 p.m. UTC | #2
On 6/8/2021 4:19 PM, Kees Cook wrote:
> On Sat, Jun 05, 2021 at 05:03:57PM +0200, John Wood wrote:
>> [...]
>> 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.
> I have some concerns about this being actually usable and not creating
> DoS situations. For example, let's say an attacker had found a hard-to-hit
> bug in "sudo", and starts brute forcing it. When the brute LSM notices,
> it'll make "sudo" unusable for the entire system, yes?
>
> And a reboot won't fix it, either, IIUC.
>
The whole point of the mitigation is to trade potential attacks against DOS.

If you're worried about DOS the whole thing is not for you.

-Andi
Kees Cook June 9, 2021, 4:52 p.m. UTC | #3
On Tue, Jun 08, 2021 at 04:38:15PM -0700, Andi Kleen wrote:
> 
> On 6/8/2021 4:19 PM, Kees Cook wrote:
> > On Sat, Jun 05, 2021 at 05:03:57PM +0200, John Wood wrote:
> > > [...]
> > > 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.
> > I have some concerns about this being actually usable and not creating
> > DoS situations. For example, let's say an attacker had found a hard-to-hit
> > bug in "sudo", and starts brute forcing it. When the brute LSM notices,
> > it'll make "sudo" unusable for the entire system, yes?
> > 
> > And a reboot won't fix it, either, IIUC.
> > 
> The whole point of the mitigation is to trade potential attacks against DOS.
> 
> If you're worried about DOS the whole thing is not for you.

Right, but there's no need to make a system unusable for everyone else.
There's nothing here that relaxes the defense (i.e. stop spawning apache
for 10 minutes). Writing it to disk with nothing that undoes it seems a
bit too much. :)
John Wood June 11, 2021, 3:41 p.m. UTC | #4
On Wed, Jun 09, 2021 at 09:52:29AM -0700, Kees Cook wrote:
> On Tue, Jun 08, 2021 at 04:38:15PM -0700, Andi Kleen wrote:
> >
> > On 6/8/2021 4:19 PM, Kees Cook wrote:
> > > On Sat, Jun 05, 2021 at 05:03:57PM +0200, John Wood wrote:
> > > > [...]
> > > > 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.
> > >
> > > I have some concerns about this being actually usable and not creating
> > > DoS situations. For example, let's say an attacker had found a hard-to-hit
> > > bug in "sudo", and starts brute forcing it. When the brute LSM notices,
> > > it'll make "sudo" unusable for the entire system, yes?
> > >
> > > And a reboot won't fix it, either, IIUC.
> > >
> > The whole point of the mitigation is to trade potential attacks against DOS.
> >
> > If you're worried about DOS the whole thing is not for you.
>
> Right, but there's no need to make a system unusable for everyone else.
> There's nothing here that relaxes the defense (i.e. stop spawning apache
> for 10 minutes). Writing it to disk with nothing that undoes it seems a
> bit too much. :)

Here I have merge the first reply.

> It seems like there is a need to track "user" running "prog", and have
> that be timed out. Are there use-cases here where that wouldn't be
> sufficient?

Ok, what do you think of the following proposal:

Add an uid_t field to the structure saved in the xattr. So this struct
contains now

faults: Number of crashes.
nsecs: Last crash timestamp as the number of nanoseconds in the
       International Atomic Time (TAI) reference.
period: Crash period's moving average.
flags: Statistics flags.
uid: User id not allowed to run the executable.

The logic would be the following:

1. faults, nsecs and period are updated in every crash and is a common info
   for all the users.
2. If the max number of faults is reached, it is "not allowed" to run the
   executable by any user. This condition blocks the file until root clear
   the xattr. No timeout.
3. When an attack is detected the uid of the user that is running the app
   is saved in the xattr and the executable is marked as "not allowed" to
   run by this user. The "not allowed" state has a timeout (more below).
4. When someone tries to run the executable, if his uid is different from
   the uid saved in the xattr, then the operation is "allowed".
5. When someone tries to run the executable, if his uid is equal to the
   uid saved in the xattr, then the operation is "not allowed". This user
   is banned for a timeout.
6. When someone tries to run the executable and the timeout has expired,
   the operation is "allowed" and the saved uid is removed.
7. If the executable crashes again when it is run by a user different from
   the one saved in the xattr (and the timeout has no expired), the file
   is marked as "not allowed" to run by any user. All users are banned for
   a timeout.

The timeout: I think there are two options here.

1. A fixed timeout set by a sysctl attribute.
2. A dynamic timeout calculated from the info stored in the xattr. The
   timeout would be the needed period to guarantee that when the app is
   run again and it crashes, the attack detection will not be triggered.
   To be more clear I expose the formulas:

   Mathematically the application crash period's EMA can be expressed as
   follows:

   period_ema[i] = period[i] * weight + period_ema[i - 1] * (1 - weight)

   If we isolate period:

   period[i] = (period_ema[i] - period_ema[i - 1] * (1 - weight)) / weight

   Where period_ema[i] is the "crash_period_threshold", period_ema[i - 1]
   is the last period ema saved in the xattr and period[i] is the dynamic
   timeout.

As a final point. Possibly there are more cases but the logic would be the
one explained. I think that it is not necessary to save the uid for every
user that crashes the app nor the crashes info for every user. If more
than one user crashes the application, something "bad" is happening. So,
all users are banned for a timeout. This way the info saved in the xattr
has a fixed size and we prevent an attacker from abusing this size.

I hope this proposal can be enough. What do you think?

John Wood.