Received: by 2002:a05:6a10:9848:0:0:0:0 with SMTP id x8csp230296pxf; Wed, 17 Mar 2021 21:16:50 -0700 (PDT) X-Google-Smtp-Source: ABdhPJxNfshkG3oR1PD+HDr2gSJGWOQFSWtS7SArJqLpkMhNqVFmlh+nYO3ZdqYwIzukV7EUpKL4 X-Received: by 2002:aa7:d686:: with SMTP id d6mr1218865edr.146.1616041010499; Wed, 17 Mar 2021 21:16:50 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1616041010; cv=none; d=google.com; s=arc-20160816; b=O4v9ggsGQqcAhndYLJ7lc89oxyaLe5I0nUND3IPT4RcxFAG3SxOt9Mqq4jEMUnJtCQ zwrcdPOwg5NqI8/YQCsMhMdb3vJmt4kugVbrIVSZBUw13gf/oJ1lhVb+oPlaZR3PQ/t0 KXndxLOoljUJH3FsPmAJ84cxeuRn+abw62PmRDuaVNUsWCB2F77ue3celkbCuvnwlpZG pPokNcUVr/sAh4DEeFxMCOuRnF96PwpdTdo6pA9YgbUTHNrGQbl+1J2H+AxaS7TDsAWH /bjcBC6lFUhvTa7dKiXxFUHQHENvIAwvecLnDLVApbpsRyCZHFY0CO6MeF5jG5uK/nlT 7Upg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:in-reply-to:content-disposition:mime-version :references:message-id:subject:cc:to:from:date:dkim-signature; bh=v0FMiRP2ep+y/7iHbFg4j6pdz1i8NddTStWhM1r5FlE=; b=ihup/ri2/6500fNes4EMV33HgTrxCLzM+Atj9tJMzNBBwwYlXZd1Swhb2Kl1Pim5po kB0r02ABklVcYWkcHnlmxX2maNutuLMdStupd0kwbBWER4fUEpsBZF12eDYrtS+QLns7 yfvDGJqOA0TWOACUWEAWF19frSLpdh6wRhY0nI48YXBk1WmzSao+FkwSlZa8bzbJWvSm ayPwYplmXAPT+8w78q3jCoDqZeLTRwRxpOp8d/7d2GMxS38X0S0QoySu/PZ+OZVfgkYw UOgmtN0FRwaCED5OVg9av+RiWAv5OIBnSq1ciW3GyXb8NQ0w7uIqxSIs0q77IaXTeF44 Ib5A== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@chromium.org header.s=google header.b=LUwT0JdL; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=chromium.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [23.128.96.18]) by mx.google.com with ESMTP id ss21si719163ejb.338.2021.03.17.21.16.27; Wed, 17 Mar 2021 21:16:50 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) client-ip=23.128.96.18; Authentication-Results: mx.google.com; dkim=pass header.i=@chromium.org header.s=google header.b=LUwT0JdL; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=chromium.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229703AbhCREKX (ORCPT + 99 others); Thu, 18 Mar 2021 00:10:23 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:34096 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229699AbhCREKH (ORCPT ); Thu, 18 Mar 2021 00:10:07 -0400 Received: from mail-pf1-x42c.google.com (mail-pf1-x42c.google.com [IPv6:2607:f8b0:4864:20::42c]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 8F8FEC061760 for ; Wed, 17 Mar 2021 21:10:07 -0700 (PDT) Received: by mail-pf1-x42c.google.com with SMTP id y5so2580268pfn.1 for ; Wed, 17 Mar 2021 21:10:07 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; h=date:from:to:cc:subject:message-id:references:mime-version :content-disposition:in-reply-to; bh=v0FMiRP2ep+y/7iHbFg4j6pdz1i8NddTStWhM1r5FlE=; b=LUwT0JdLyskrtEhbtVVjzxbaFMS3wgwkqmhazE2U5tk+bSfpxRox6SBSAHLUIM1XeA ZtLim4S6h0SwXx1n4dIrZL1qZOHHCZLMCJPXP4NeNDtdIhrp6aX2fLipdMsn4I4KPOCg 73Yz47V/KK5Q8zw/E/CUc1YYPLO0l7CmdlrrA= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to; bh=v0FMiRP2ep+y/7iHbFg4j6pdz1i8NddTStWhM1r5FlE=; b=c44CYvl09YUmK5gYaLqm57QbmajGB3U9VshFX3FZUUKrHYIXBceu1YaDsApsRVopMb mDeVUqKS7RbgIPbsfrNxTObDnJfrVdS5c9RU7tk7r3APU/SZlfSezv3zoOwXJEs2QEiw czRYLgn6/D/KY9sJc0ZPfmmiMNFoa5TQxxLaYrqIX9yNt4gBYhWtbrFdooqSWffAnYvP p7ae62vW9M8BPwoMCn1RM2gZsuL88yAeGFwQLmeJbItan8HgiY5KmzbdKgkPt5MstXbv JoRO3JYrr3be7dd3hb/aZE4tfWC1xHQ4Y7modbw9X8Y0NJrCRG/hXyvpCmv3UHdpjL1+ rBmQ== X-Gm-Message-State: AOAM533V6NqbwnXElvgx/M68xzrOaEU+ZLb//CLHrdqvQ3tsKwDs70Kv bwdROhvy7145NOWSIFXKjYQIwA== X-Received: by 2002:a63:484b:: with SMTP id x11mr5343791pgk.2.1616040606977; Wed, 17 Mar 2021 21:10:06 -0700 (PDT) Received: from www.outflux.net (smtp.outflux.net. [198.145.64.163]) by smtp.gmail.com with ESMTPSA id p3sm495481pgl.88.2021.03.17.21.10.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 17 Mar 2021 21:10:06 -0700 (PDT) Date: Wed, 17 Mar 2021 21:10:05 -0700 From: Kees Cook To: John Wood Cc: Jann Horn , Randy Dunlap , Jonathan Corbet , James Morris , Shuah Khan , "Serge E. Hallyn" , Greg Kroah-Hartman , Andi Kleen , kernel test robot , 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 7/8] Documentation: Add documentation for the Brute LSM Message-ID: <202103172108.404F9B6ED2@keescook> References: <20210307113031.11671-1-john.wood@gmx.com> <20210307113031.11671-8-john.wood@gmx.com> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <20210307113031.11671-8-john.wood@gmx.com> Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org On Sun, Mar 07, 2021 at 12:30:30PM +0100, John Wood wrote: > Add some info detailing what is the Brute LSM, its motivation, weak > points of existing implementations, proposed solutions, enabling, > disabling and self-tests. > > Signed-off-by: John Wood > --- > Documentation/admin-guide/LSM/Brute.rst | 278 ++++++++++++++++++++++++ > Documentation/admin-guide/LSM/index.rst | 1 + > security/brute/Kconfig | 3 +- > 3 files changed, 281 insertions(+), 1 deletion(-) > create mode 100644 Documentation/admin-guide/LSM/Brute.rst > > diff --git a/Documentation/admin-guide/LSM/Brute.rst b/Documentation/admin-guide/LSM/Brute.rst > new file mode 100644 > index 000000000000..ca80aef9aa67 > --- /dev/null > +++ b/Documentation/admin-guide/LSM/Brute.rst > @@ -0,0 +1,278 @@ > +.. SPDX-License-Identifier: GPL-2.0 > +=========================================================== > +Brute: Fork brute force attack detection and mitigation LSM > +=========================================================== > + > +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 implementation. 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 really needs to be detected are fork/exec brute force attacks that > +cross any of the commented bounds. > + > + > +Other implementations > +===================== > + > +The public version of grsecurity, as a summary, is based on the idea of delaying > +the fork system call if a child died due to some fatal signal (SIGSEGV, SIGBUS, > +SIGKILL or SIGILL). This has some issues: > + > +Bad practices > +------------- > + > +Adding delays to the kernel is, in general, a bad idea. > + > +Scenarios not detected (false negatives) > +---------------------------------------- > + > +This protection acts only when the fork system call is called after a child has > +crashed. So, it would still be possible for an attacker to fork a big amount of > +children (in the order of thousands), then probe all of them, and finally wait > +the protection time before repeating the steps. > + > +Moreover, this method is based on the idea that the protection doesn't act if > +the parent crashes. So, it would still be possible for an attacker to fork a > +process and probe itself. Then, fork the child process and probe itself again. > +This way, these steps can be repeated infinite times without any mitigation. > + > +Scenarios detected (false positives) > +------------------------------------ > + > +Scenarios where an application rarely fails for reasons unrelated to a real > +attack. > + > + > +This implementation > +=================== > + > +The main idea behind this implementation is to improve the existing ones > +focusing on the weak points annotated before. Basically, the adopted solution is > +to detect a fast crash rate instead of only one simple crash and to detect both > +the crash of parent and child processes. Also, fine tune the detection focusing > +on privilege boundary crossing. And finally, as a mitigation method, kill all > +the offending tasks involved in the attack instead of using delays. > + > +To achieve this goal, and going into more details, this implementation is based > +on the use of some statistical data shared across all the processes that can > +have the same memory contents. Or in other words, a statistical data shared > +between all the fork hierarchy processes after an execve system call. > + > +The purpose of these statistics is, basically, collect all the necessary info > +to compute the application crash period in order to detect an attack. This crash > +period is the time between the execve system call and the first fault or the > +time between two consecutive faults, but this has a drawback. If an application > +crashes twice in a short period of time for some reason unrelated to a real > +attack, a false positive will be triggered. To avoid this scenario the > +exponential moving average (EMA) is used. This way, the application crash period > +will be a value that is not prone to change due to spurious data and follows the > +real crash period. > + > +To detect a brute force attack it is necessary that the statistics shared by all > +the fork hierarchy processes be updated in every fatal crash and the most > +important data to update is the application crash period. > + > +These statistics are hold by the brute_stats struct. > + > +struct brute_cred { > + kuid_t uid; > + kgid_t gid; > + kuid_t suid; > + kgid_t sgid; > + kuid_t euid; > + kgid_t egid; > + kuid_t fsuid; > + kgid_t fsgid; > +}; > + > +struct brute_stats { > + spinlock_t lock; > + refcount_t refc; > + unsigned char faults; > + u64 jiffies; > + u64 period; > + struct brute_cred saved_cred; > + unsigned char network : 1; > + unsigned char bounds_crossed : 1; > +}; Instead of open-coding this, just use the kerndoc references you've already built in the .c files: .. kernel-doc:: security/brute/brute.c > + > +This is a fixed sized struct, so the memory usage will be based on the current > +number of processes exec()ing. The previous sentence is true since in every fork > +system call the parent's statistics are shared with the child process and in > +every execve system call a new brute_stats struct is allocated. So, only one > +brute_stats struct is used for every fork hierarchy (hierarchy of processes from > +the execve system call). > + > +There are two types of brute force attacks that need to be detected. The first > +one is an attack that happens through the fork system call and the second one is > +an attack that happens through the execve system call. The first type uses the > +statistics shared by all the fork hierarchy processes, but the second type > +cannot use this statistical data due to these statistics dissapear when the > +involved tasks finished. In this last scenario the attack info should be tracked > +by the statistics of a higher fork hierarchy (the hierarchy that contains the > +process that forks before the execve system call). > + > +Moreover, these two attack types have two variants. A slow brute force attack > +that is detected if a 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. > + > +Once an attack has been detected, this is mitigated killing all the offending > +tasks involved. Or in other words, once an attack has been detected, this is > +mitigated killing all the processes that share the same statistics (the stats > +that show an slow or fast brute force attack). > + > +Fine tuning the attack detection > +-------------------------------- > + > +To avoid false positives during the attack detection it is necessary to narrow > +the possible cases. To do so, and based on the threat scenarios that we want to > +detect, this implementation also focuses on the crossing of privilege bounds. > + > +To be precise, only the following privilege bounds are taken into account: > + > +1.- setuid/setgid process > +2.- network to local > +3.- privilege changes > + > +Moreover, only the fatal signals delivered by the kernel are taken into account > +avoiding the fatal signals sent by userspace applications (with the exception of > +the SIGABRT user signal since this is used by glibc for stack canary, malloc, > +etc. failures, which may indicate that a mitigation has been triggered). > + > +Exponential moving average (EMA) > +-------------------------------- > + > +This kind of average defines a weight (between 0 and 1) for the new value to add > +and applies the remainder of the weight to the current average value. This way, > +some spurious data will not excessively modify the average and only if the new > +values are persistent, the moving average will tend towards them. > + > +Mathematically the application crash period's EMA can be expressed as follows: > + > +period_ema = period * weight + period_ema * (1 - weight) > + > +Related to the attack detection, the EMA must guarantee that not many crashes > +are needed. To demonstrate this, the scenario where an application has been > +running without any crashes for a month will be used. > + > +The period's EMA can be written now as: > + > +period_ema[i] = period[i] * weight + period_ema[i - 1] * (1 - weight) > + > +If the new crash periods have insignificant values related to the first crash > +period (a month in this case), the formula can be rewritten as: > + > +period_ema[i] = period_ema[i - 1] * (1 - weight) > + > +And by extension: > + > +period_ema[i - 1] = period_ema[i - 2] * (1 - weight) > +period_ema[i - 2] = period_ema[i - 3] * (1 - weight) > +period_ema[i - 3] = period_ema[i - 4] * (1 - weight) > + > +So, if the substitution is made: > + > +period_ema[i] = period_ema[i - 1] * (1 - weight) > +period_ema[i] = period_ema[i - 2] * pow((1 - weight) , 2) > +period_ema[i] = period_ema[i - 3] * pow((1 - weight) , 3) > +period_ema[i] = period_ema[i - 4] * pow((1 - weight) , 4) > + > +And in a more generic form: > + > +period_ema[i] = period_ema[i - n] * pow((1 - weight) , n) > + > +Where n represents the number of iterations to obtain an EMA value. Or in other > +words, the number of crashes to detect an attack. > + > +So, if we isolate the number of crashes: > + > +period_ema[i] / period_ema[i - n] = pow((1 - weight), n) > +log(period_ema[i] / period_ema[i - n]) = log(pow((1 - weight), n)) > +log(period_ema[i] / period_ema[i - n]) = n * log(1 - weight) > +n = log(period_ema[i] / period_ema[i - n]) / log(1 - weight) > + > +Then, in the commented scenario (an application has been running without any > +crashes for a month), the approximate number of crashes to detect an attack > +(using the implementation values for the weight and the crash period threshold) > +is: > + > +weight = 7 / 10 > +crash_period_threshold = 30 seconds > + > +n = log(crash_period_threshold / seconds_per_month) / log(1 - weight) > +n = log(30 / (30 * 24 * 3600)) / log(1 - 0.7) > +n = 9.44 > + > +So, with 10 crashes for this scenario an attack will be detected. If these steps > +are repeated for different scenarios and the results are collected: > + > +1 month without any crashes ----> 9.44 crashes to detect an attack > +1 year without any crashes -----> 11.50 crashes to detect an attack > +10 years without any crashes ---> 13.42 crashes to detect an attack > + > +However, this computation has a drawback. The first data added to the EMA not > +obtains a real average showing a trend. So the solution is simple, the EMA needs > +a minimum number of data to be able to be interpreted. This way, the case where > +a few first faults are fast enough followed by no crashes is avoided. > + > +Per system enabling/disabling > +----------------------------- > + > +This feature can be enabled at build time using the CONFIG_SECURITY_FORK_BRUTE > +option or using the visual config application under the following menu: > + > +Security options ---> Fork brute force attack detection and mitigation > + > +Also, at boot time, this feature can be disable too, by changing the "lsm=" boot > +parameter. > + > +Kernel selftests > +---------------- > + > +To validate all the expectations about this implementation, there is a set of > +selftests. This tests cover fork/exec brute force attacks crossing the following > +privilege boundaries: > + > +1.- setuid process > +2.- privilege changes > +3.- network to local > + > +Also, there are some tests to check that fork/exec brute force attacks without > +crossing any privilege boundariy already commented doesn't trigger the detection > +and mitigation stage. > + > +To build the tests: > +make -C tools/testing/selftests/ TARGETS=brute > + > +To run the tests: > +make -C tools/testing/selftests TARGETS=brute run_tests > + > +To package the tests: > +make -C tools/testing/selftests TARGETS=brute gen_tar > diff --git a/Documentation/admin-guide/LSM/index.rst b/Documentation/admin-guide/LSM/index.rst > index a6ba95fbaa9f..1f68982bb330 100644 > --- a/Documentation/admin-guide/LSM/index.rst > +++ b/Documentation/admin-guide/LSM/index.rst > @@ -41,6 +41,7 @@ subdirectories. > :maxdepth: 1 > > apparmor > + Brute > LoadPin > SELinux > Smack > diff --git a/security/brute/Kconfig b/security/brute/Kconfig > index 1bd2df1e2dec..334d7e88d27f 100644 > --- a/security/brute/Kconfig > +++ b/security/brute/Kconfig > @@ -7,6 +7,7 @@ config SECURITY_FORK_BRUTE > vulnerable userspace processes. The detection method is based on > the application crash period and as a mitigation procedure all the > offending tasks are killed. Like capabilities, this security module > - stacks with other LSMs. > + stacks with other LSMs. Further information can be found in > + Documentation/admin-guide/LSM/Brute.rst. > > If you are unsure how to answer this question, answer N. > -- > 2.25.1 > -- Kees Cook