Received: by 2002:a05:6a10:6744:0:0:0:0 with SMTP id w4csp604317pxu; Sun, 25 Oct 2020 10:54:11 -0700 (PDT) X-Google-Smtp-Source: ABdhPJydhHlbJhYqxdgUaPhFDCHXKaGoRjlobp1SovAfEgYbGqpqYbRIIfKL7rjAlZkr5yN4nU1j X-Received: by 2002:a50:9b1d:: with SMTP id o29mr12778453edi.56.1603648450777; Sun, 25 Oct 2020 10:54:10 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1603648450; cv=none; d=google.com; s=arc-20160816; b=HspkXg94aUFVX7zjexJuwUUGTyuwvggciyFa+XoThGJgjgCEfwKP8ZUGyoVz1U9yEW 2qOiqU4cbfV2B+hKiZehXqR6KI2FpYPv39JWuVpIKa4eeKsk+pfak6JV4rDgGhySrOly tgo3GhmG3jiiBkugKYFfxVt7WjuSVOYasD8mbSeQW6sINsJDcjqSyJYtW2/D+gnmFw8y beQU/iaCTsc2P9WEHCusMTFvxFIp+nlXCBwKfIjXFGfmChBIaXuusfIo5CcFiMDpgiv1 CP7XTk/VcdWgFJaymMIsugIgRTgeZ6KoZlADu2b3LhLkD8omLbxP65LJ1sSBd8moFBL9 yExQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=i8JwUROtYZXthUEYMb0ysQBMlyZZlPdcn52RahVOxSo=; b=sgXTmr/9Rjc73Q3x+XsDDbFhDpwcbFHiZOFbP6V7eftil9tsJUml7LQgijlFFQnBSP 8iRVZA5r/vey7QIJymuGfN7yg4QUhew0YdyrkeOetIKfWPusQUaXBlNxPYs8K50nJ9Xh FRSWAkk7s8DPeY7PiuoiVJ1P8Cf+PsIIKCNlmxj2fFOuMG7jeNp7hDZaT1SHnOixHdqD M3zksUOsxj7Sv6mS6jG4mmPB3rQiM2LUf3O5sx53Sw+WDegUyeoAwqbmTJ3JwbB8Ht3j l/Dmy6htzLRsFcT0UD8QWiL4fQgh4hDnGzq/YUNvVEqhkDxpaJtMXgaYvAy/oPx1dJAY ca2A== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@gmx.net header.s=badeba3b8450 header.b=NPKRYkx+; 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 Return-Path: Received: from vger.kernel.org (vger.kernel.org. [23.128.96.18]) by mx.google.com with ESMTP id bo1si5338623ejb.697.2020.10.25.10.53.48; Sun, 25 Oct 2020 10:54:10 -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=@gmx.net header.s=badeba3b8450 header.b=NPKRYkx+; 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 Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1417520AbgJYRHI (ORCPT + 99 others); Sun, 25 Oct 2020 13:07:08 -0400 Received: from mout.gmx.net ([212.227.17.21]:51833 "EHLO mout.gmx.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1417514AbgJYRHI (ORCPT ); Sun, 25 Oct 2020 13:07:08 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=gmx.net; s=badeba3b8450; t=1603645607; bh=qJWyMrjXpeE+rjYpUtbfZPi2nfj0bI6PEUYZ+A9TfME=; h=X-UI-Sender-Class:From:To:Cc:Subject:Date:In-Reply-To:References; b=NPKRYkx+eOfNAIhW+xIuStO2QsD5wrLZ6ymsj/ZQ2esY+11mnjL7KCoHWp9p8RjW0 o35wV9q8kHxPGoWUz3XJYyVL+yUUst/CvuCdSopsp9Ht2kEvdOvGyzABNDK5NEmSKV hQtNR3KZ3P1RK64oeyh1sqRkNyFthyjXwf2Z/eg0= X-UI-Sender-Class: 01bb95c1-4bf8-414a-932a-4f6e2808ef9c Received: from localhost.localdomain ([83.52.231.59]) by mail.gmx.com (mrgmx104 [212.227.17.174]) with ESMTPSA (Nemesis) id 1MLi8m-1koOt509Cq-00HjZH; Sun, 25 Oct 2020 18:06:47 +0100 From: John Wood To: Kees Cook , Jann Horn Cc: John Wood , Jonathan Corbet , James Morris , "Serge E. Hallyn" , linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, kernel-hardening@lists.openwall.com Subject: [PATCH v2 5/8] security/brute: Mitigate a fork brute force attack Date: Sun, 25 Oct 2020 14:45:37 +0100 Message-Id: <20201025134540.3770-6-john.wood@gmx.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20201025134540.3770-1-john.wood@gmx.com> References: <20201025134540.3770-1-john.wood@gmx.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Provags-ID: V03:K1:77xi6IvKdGSSBMMKfua9wA0Oa9DZLborxMu1NdaOiO2Lj+qhobI e38gbQKHc3gzjETsO/vAVViY33uuYot1GogAhq+40KanLotMoD8GGMw4JBxbMp2O8Y2mKYO d7BOq5wvtqJOWvevUTmA6D/NNTQhrPrYRqhRrITCDA4zhhJ62rUvMLc0nnU4gWnaDe+mg9A QlYZBFz4U2KzsP6JvLKGg== X-Spam-Flag: NO X-UI-Out-Filterresults: notjunk:1;V03:K0:T2PcRj1NkPg=:nHqPkat2rraJOPUjD5nn47 ip6v6TVOH0UHQFsLGj/QxRW/1FEgBtIWu46PUzycDhjzsEf/hAqZEVhXdj0NRqYDHk+A+/K6Y byJP6l93IOzGX0X3iJXZyc39BUHrU+P8t29c715bdTLMUPj1aoMOOQ6BLaBuyD1Ncnr99UVbR hqKcHmMpUhBjnrRbYUpBqgufMPgqSphdHaJNEuo0zO0KJut1U3S8obTnnBcu2z6gXPfghBfh8 THxCN/rdVxDccxxJIg/Pwmw450W3MlQQzDjAUHBlpH1YNjRmearOtpxVHbnBbHTdiPD6/94Ps xi0JYeFbmh4NQNIiGJOw0FtUSaU7V3qVR2qMWjh6TzWtX7xl5r9d92tcrtRj0MpNhThfWAwyX Jaafed1Ir6/E2GPpDIRPSRKydit+Cr5IqyaVm53FdvnTRiTmHDqgEweYvR6+E1NKmFlhht55T n3LqXUaZLSOSHHfzQvSwzgsp39tk0kBtBGGXsWjB9tFLtE4oI2xfbU+EbtwunU61ZqoHdraB9 j8opcY7Eb5nDskXBCE+8VBKHMEBJ2pibx0DhZ+K73YL6loYMClMIwjju54ZdVZw+b6blf1kp/ QlFyX+GkX50+hgnuRPBiJR3WuMn/ZEyArSsw4HBW2VqSrBBZvoQTha0xzgYfoSpUaKfr/CSCV RhqsyGXMNr2thrimFjFn2FXS5xQKT9IOwhWKmlfGBI69kDqDOVLO4NZlKx0PUS1EmF/mzXEmw /s1R3d8u6qT67cu2Nz7aeg3uX7bUOEGGAaEMmrv93yC3B9Sd4T1EZ2+7ddMC5bd35JNcnH20f rxO6/U4Qy3wVSfmp8AyqXPnTGXKmyidn8JDLaKh9/jQjEsJVGigFgeihmuYAOZWLudErigRsv iCNREpE+jL4hXCdQn+sA== Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org In order to mitigate a fork brute force attack it is necessary to kill all the offending tasks. This tasks are all the ones that share the statistical data with the current task (the task that has crashed). Since the attack detection is done in the task_fatal_signal LSM hook only is needed to kill the other tasks that share the same statistical data, not the ones that have the same group_leader that the current task since the latter are in the path to be killed. When the SIGKILL signal is sent to the offending tasks, the brute_kill_offending_tasks function will be called in a recursive way from the task_fatal_signal LSM hook due to a small crash period. So, to avoid kill again the same tasks due to a recursive call of this function, it is necessary to disable the attack detection for this fork hierarchy. To disable this attack detection, empty the last crashes timestamps list and avoid to compute the application crash period if the size of this list is zero. Signed-off-by: John Wood =2D-- security/brute/brute.c | 144 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 135 insertions(+), 9 deletions(-) diff --git a/security/brute/brute.c b/security/brute/brute.c index 223a18c2084a..a1bdf25ffcf9 100644 =2D-- a/security/brute/brute.c +++ b/security/brute/brute.c @@ -3,6 +3,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include #include #include #include @@ -14,9 +15,14 @@ #include #include #include +#include #include #include +#include #include +#include +#include +#include #include #include #include @@ -295,23 +301,39 @@ static void brute_task_execve(struct linux_binprm *b= prm) } /** - * brute_stats_free() - Deallocate a statistics structure. - * @stats: Statistics to be freed. + * brute_timestamps_free() - Empty a last crashes timestamp list. + * @timestamps: Last crashes timestamps list to be emptied. * - * Deallocate all the last crashes timestamps list entries and then the - * statistics structure. The statistics to be freed cannot be NULL. + * Empty the last crashes timestamps list and deallocate all the entries.= This + * list cannot be NULL. * - * Context: Must be called with stats->lock held and this function releas= es it. + * Context: Must be called with stats->lock held. */ -static void brute_stats_free(struct brute_stats *stats) +static void brute_timestamps_free(struct list_head *timestamps) { struct brute_timestamp *timestamp, *next; - list_for_each_entry_safe(timestamp, next, &stats->timestamps, node) { + if (list_empty(timestamps)) + return; + + list_for_each_entry_safe(timestamp, next, timestamps, node) { list_del(×tamp->node); kfree(timestamp); } +} +/** + * brute_stats_free() - Deallocate a statistics structure. + * @stats: Statistics to be freed. + * + * Deallocate all the last crashes timestamps list entries and then the + * statistics structure. The statistics to be freed cannot be NULL. + * + * Context: Must be called with stats->lock held and this function releas= es it. + */ +static void brute_stats_free(struct brute_stats *stats) +{ + brute_timestamps_free(&stats->timestamps); spin_unlock(&stats->lock); kfree(stats); } @@ -426,6 +448,104 @@ static u64 brute_get_crash_period(struct brute_times= tamp *new_entry, return jiffies64_to_msecs(jiffies); } +/** + * brute_disabled() - Test the fork brute force attack detection disablin= g. + * @stats: Statistical data shared by all the fork hierarchy processes. + * + * The fork brute force attack detection enabling / disabling is based on= the + * last crashes timestamps list current size. A size of zero indicates th= at this + * feature is disabled. A size greater than zero indicates that this atta= ck + * detection is enabled. + * + * The statistical data shared by all the fork hierarchy processes cannot= be + * NULL. + * + * It's mandatory to disable interrupts before acquiring the lock since t= he + * task_free hook can be called from an IRQ context during the execution = of the + * task_fatal_signal hook. + * + * Return: True if the fork brute force attack detection is disabled. Fal= se + * otherwise. + */ +static bool brute_disabled(struct brute_stats *stats) +{ + unsigned long flags; + bool disabled; + + spin_lock_irqsave(&stats->lock, flags); + disabled =3D !stats->timestamps_size; + spin_unlock_irqrestore(&stats->lock, flags); + + return disabled; +} + +/** + * brute_disable() - Disable the fork brute force attack detection. + * @stats: Statistical data shared by all the fork hierarchy processes. + * + * To disable the fork brute force attack detection it's only necessary t= o empty + * the last crashes timestamps list. So, a list size of zero indicates th= at this + * feature is disabled and a list size greater than zero indicates that t= his + * attack detection is enabled. + * + * The statistical data shared by all the fork hierarchy processes cannot= be + * NULL. + * + * Context: Must be called with stats->lock held. + */ +static void brute_disable(struct brute_stats *stats) +{ + brute_timestamps_free(&stats->timestamps); + stats->timestamps_size =3D 0; +} + +/** + * brute_kill_offending_tasks() - Kill the offending tasks. + * @stats: Statistical data shared by all the fork hierarchy processes. + * + * When a fork brute force attack is detected it is necessary to kill all= the + * offending tasks involved in the attack. In other words, it is necessar= y to + * kill all the tasks that share the same statistical data but not the on= es that + * have the same group_leader that the current task since the latter are = in the + * path to be killed. + * + * When the SIGKILL signal is sent to the offending tasks, this function = will be + * called again from the task_fatal_signal hook due to a small crash peri= od. So, + * to avoid kill again the same tasks due to a recursive call of this fun= ction, + * it is necessary to disable the attack detection for this fork hierarch= y. + * + * The statistical data shared by all the fork hierarchy processes cannot= be + * NULL. + * + * Context: Must be called with stats->lock held. + */ +static void brute_kill_offending_tasks(struct brute_stats *stats) +{ + struct task_struct *p; + struct brute_stats **p_stats; + + if (refcount_read(&stats->refc) =3D=3D 1) + return; + + brute_disable(stats); + read_lock(&tasklist_lock); + + for_each_process(p) { + if (p->group_leader =3D=3D current->group_leader) + continue; + + p_stats =3D brute_stats_ptr(p); + if (READ_ONCE(*p_stats) !=3D stats) + continue; + + do_send_sig_info(SIGKILL, SEND_SIG_PRIV, p, PIDTYPE_PID); + pr_warn_ratelimited("Offending process %d (%s) killed\n", + p->pid, p->comm); + } + + read_unlock(&tasklist_lock); +} + /** * brute_task_fatal_signal() - Target for the task_fatal_signal hook. * @siginfo: Contains the signal information. @@ -433,7 +553,8 @@ static u64 brute_get_crash_period(struct brute_timesta= mp *new_entry, * To detect a fork brute force attack is necessary that the list that ho= lds the * last crashes timestamps be updated in every fatal crash. Then, an only= when * this list is large enough, the application crash period can be compute= d an - * compared with the defined threshold. + * compared with the defined threshold. If at this moment an attack is de= tected, + * all the offending tasks must be killed. * * It's mandatory to disable interrupts before acquiring the lock since t= he * task_free hook can be called from an IRQ context during the execution = of the @@ -450,6 +571,9 @@ static void brute_task_fatal_signal(const kernel_sigin= fo_t *siginfo) if (WARN(!*stats, "No statistical data\n")) return; + if (brute_disabled(*stats)) + return; + new_entry =3D brute_new_timestamp(); if (WARN(!new_entry, "Cannot allocate last crash timestamp\n")) return; @@ -461,8 +585,10 @@ static void brute_task_fatal_signal(const kernel_sigi= nfo_t *siginfo) crash_period =3D brute_get_crash_period(new_entry, old_entry); kfree(old_entry); - if (crash_period < (u64)brute_crash_period_threshold) + if (crash_period < (u64)brute_crash_period_threshold) { pr_warn("Fork brute force attack detected\n"); + brute_kill_offending_tasks(*stats); + } } spin_unlock_irqrestore(&(*stats)->lock, flags); =2D- 2.25.1