From patchwork Sat Jun 5 15:04:01 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: John Wood X-Patchwork-Id: 12301657 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 7BC2DC47082 for ; Sat, 5 Jun 2021 16:32:29 +0000 (UTC) Received: from mother.openwall.net (mother.openwall.net [195.42.179.200]) by mail.kernel.org (Postfix) with SMTP id 4D69261287 for ; Sat, 5 Jun 2021 16:32:28 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 4D69261287 Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=gmx.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=kernel-hardening-return-21280-kernel-hardening=archiver.kernel.org@lists.openwall.com Received: (qmail 32368 invoked by uid 550); 5 Jun 2021 16:32:21 -0000 Mailing-List: contact kernel-hardening-help@lists.openwall.com; run by ezmlm Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-ID: Received: (qmail 32348 invoked from network); 5 Jun 2021 16:32:20 -0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=gmx.net; s=badeba3b8450; t=1622910700; bh=d0ThzL6H3ZgX1iy++Zve4fg1GWGCL9gUNpUEh/9SJtk=; h=X-UI-Sender-Class:From:To:Cc:Subject:Date:In-Reply-To:References; b=BuDP4OKL2j+CO6onsweK/K1KkYYu7I/kH+jhZ5PntqQFdTIH5E8AtMJIcqgrDvBVP VvU7zSrKp+ubV7jeVznxxj8uhJ95HiQNaUsJmFq1kZPdS58nh04QD77n1icWS+x4Co ywVo6FBtmOUJHa0SRZK8zoeb6HPdd4rnxOvUy4E8= X-UI-Sender-Class: 01bb95c1-4bf8-414a-932a-4f6e2808ef9c From: John Wood To: Kees Cook , Jann Horn , Jonathan Corbet , James Morris , "Serge E. Hallyn" , Shuah Khan , Thomas Gleixner , Ingo Molnar , Borislav Petkov , x86@kernel.org, "H. Peter Anvin" , Arnd Bergmann Cc: John Wood , Andi Kleen , valdis.kletnieks@vt.edu, Greg Kroah-Hartman , Randy Dunlap , Andrew Morton , linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-arch@vger.kernel.org, linux-hardening@vger.kernel.org, kernel-hardening@lists.openwall.com Subject: [PATCH v8 4/8] security/brute: Mitigate a brute force attack Date: Sat, 5 Jun 2021 17:04:01 +0200 Message-Id: <20210605150405.6936-5-john.wood@gmx.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210605150405.6936-1-john.wood@gmx.com> References: <20210605150405.6936-1-john.wood@gmx.com> MIME-Version: 1.0 X-Provags-ID: V03:K1:eaoHbte0dcns/AzvPwU3vjWvzTt5OyHKFhMvw3CoF2yDAezUQXG HNL+Dz0RMocznxqLc/uqoDMWEiwIy9oVQAmVywfZcYdM0FqRELNwtjOp3/3Ez4hQkjUTbBe 3kE1mRljNdwia+or7ZFdi/nK+l1r97CttJ0H2x4emoeySF4meERwpAfOMWx4y+7eTpkASXL asnTW9LUdyzRx3OBp+gpQ== X-UI-Out-Filterresults: notjunk:1;V03:K0:Me4GWsjU9Io=:avuErcXWGckyfqeykMNLff v4oTeRj6F4cphQX08iNfKJbo7vA+WlE3ckHPPwaVQ6wN8qw+YEjPDb++wvEOD/tHdAo1MzILj 2DexURIr+Sb2AOWd/fF2RJTFpZzdOXenT4cKFcpQwA2ihfzVOqd5g33IxkUO9S6Z84liq797Z wIn+YwhKBnEUFgKUjn+WeBsDIBKB2zj3pcM3oJRI/OdsQVIqeYFmABL/ZLo6vH0yhJyLct1f/ /ZkKOAZzQfawoB8d6Ur6KdJUJMvb7gcuvsBooEnDiqvrMoL9GYA1oZoXuXbUUJc2zVjQgxSzJ UzIe7Dh07sq9k3zh7ZTxw2gNIm7DNUZsKctYzHIwHtTPTFQBHW58tif6qwdeGwxKXcZvqH0J7 48GVCvMUJrA8KAaGXLTvSgkRT4tGkJddivRbIxSrw/q9GNsuHYmzONOko9Lz//1Mq5JLdYp8t YZ+qSODc7EIT2gnjhYMUCnN9OaEY9BzzZtQYUr5fK2QdeIovdOwpmJBdYopW9p5iL8gxj8PJx UsCctXYdxfbDnS0qyqXs1lWA1tpePBa5sHFKEiC/eoc5J1fa70wo187nk3pZsWF9vi2ebtAbm ttkry6c+SPJiTy4AKv9V6YnHW1O/1kWXzpIgm3007gmUVaxN7tqz0M3fzcWBptPKKlbuoPuuO X5T+73RnwlcVvuV7DxpeVAxTtMKYfcvNNMZsVLCcpW+DTL0hgkMh8bxQxM6Cxj+MpHvkruRgh Ur9pUukiUmy4UKsxoTkyGU5Tm7huEkMm0sN/ariuHZOwzfKsySy2E/2+XpW2tKqpU3wtSqDeP RRJNC+GohDqsTbpHVrHOjGH5dfELUurgOoX89iPvA9FqW+H0/2moxpITU2zDam6oH1oriN6qG bXbuPXLEqqhPt/dV+5/yzNzZfpHeH2NtoQzbAlrUVA7QRAhlQjPPltNDkntGt1oMRxzffpay5 4GjXCHAjJhqv+7IpIPrG3j+NYvjYt/GWIAq3zz5r1M67nbvynw8pHL3LKnVVMY9fWIjdrHqd+ OqYMyUXnZnKywfcnKCAjX+3OeRGApG9iGv2Ju0y6hrZq9ZRzeA9aBpVCRON9jUrLAYC20rcQd CfSeyyC/3WD+4GONhvNRIpyVK5RBzbR5TZk When a brute force attack is detected all the offending tasks involved in the attack must be killed. In other words, it is necessary to kill all the tasks that are executing the same file that is running during the brute force attack. Also, to prevent the executable involved in the attack from being respawned by a supervisor, and thus prevent a brute force attack from being started again, test the "not_allowed" flag and avoid the file execution based on this. Signed-off-by: John Wood --- security/brute/brute.c | 113 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 102 insertions(+), 11 deletions(-) -- 2.25.1 diff --git a/security/brute/brute.c b/security/brute/brute.c index 03bebfd1ed1f..4e0fd23990c8 100644 --- a/security/brute/brute.c +++ b/security/brute/brute.c @@ -233,6 +233,88 @@ static inline void brute_print_attack_running(void) current->comm); } +/** + * brute_print_file_not_allowed() - Warn about a file not allowed. + * @dentry: The dentry of the file not allowed. + */ +static void brute_print_file_not_allowed(struct dentry *dentry) +{ + char *buf, *path; + + buf = __getname(); + if (WARN_ON_ONCE(!buf)) + return; + + path = dentry_path_raw(dentry, buf, PATH_MAX); + if (WARN_ON_ONCE(IS_ERR(path))) + goto free; + + pr_warn_ratelimited("%s not allowed\n", path); +free: + __putname(buf); +} + +/** + * brute_is_same_file() - Test if two files are the same. + * @file1: First file to compare. Cannot be NULL. + * @file2: Second file to compare. Cannot be NULL. + * + * Two files are the same if they have the same inode number and the same block + * device. + * + * Return: True if the two files are the same. False otherwise. + */ +static inline bool brute_is_same_file(const struct file *file1, + const struct file *file2) +{ + struct inode *inode1 = file_inode(file1); + struct inode *inode2 = file_inode(file2); + + return inode1->i_ino == inode2->i_ino && + inode1->i_sb->s_dev == inode2->i_sb->s_dev; +} + +/** + * brute_kill_offending_tasks() - Kill the offending tasks. + * @file: The file executed during a brute force attack. Cannot be NULL. + * + * When a brute force attack is detected all the offending tasks involved in the + * attack must be killed. In other words, it is necessary to kill all the tasks + * that are executing the same file that is running during the brute force + * attack. Moreover, the processes that have the same group_leader that the + * current task must be avoided since they are in the path to be killed. + * + * The for_each_process loop is protected by the tasklist_lock acquired in read + * mode instead of rcu_read_lock to avoid that the newly created processes + * escape this RCU read lock. + */ +static void brute_kill_offending_tasks(const struct file *file) +{ + struct task_struct *task; + struct file *exe_file; + bool is_same_file; + + read_lock(&tasklist_lock); + for_each_process(task) { + if (task->group_leader == current->group_leader) + continue; + + exe_file = get_task_exe_file(task); + if (!exe_file) + continue; + + is_same_file = brute_is_same_file(exe_file, file); + fput(exe_file); + if (!is_same_file) + continue; + + do_send_sig_info(SIGKILL, SEND_SIG_PRIV, task, PIDTYPE_PID); + pr_warn_ratelimited("offending process %d [%s] killed\n", + task->pid, task->comm); + } + read_unlock(&tasklist_lock); +} + /** * brute_get_xattr_stats() - Get the stats from an extended attribute. * @dentry: The dentry of the file to get the extended attribute. @@ -295,6 +377,10 @@ static int brute_set_xattr_stats(struct dentry *dentry, struct inode *inode, * created. This way, the scenario where an application has not crossed any * privilege boundary is avoided since the existence of the extended attribute * denotes the crossing of bounds. + * + * Also, do not update the statistics if the execution of the file is not + * allowed and kill all the offending tasks when a brute force attack is + * detected. */ static void brute_update_xattr_stats(const struct file *file) { @@ -306,7 +392,7 @@ static void brute_update_xattr_stats(const struct file *file) inode_lock(inode); rc = brute_get_xattr_stats(dentry, inode, &stats); WARN_ON_ONCE(rc && rc != -ENODATA); - if (rc) { + if (rc || (!rc && stats.not_allowed)) { inode_unlock(inode); return; } @@ -320,6 +406,9 @@ static void brute_update_xattr_stats(const struct file *file) rc = brute_set_xattr_stats(dentry, inode, &stats); WARN_ON_ONCE(rc); inode_unlock(inode); + + if (stats.not_allowed) + brute_kill_offending_tasks(file); } /** @@ -433,21 +522,17 @@ static void brute_task_fatal_signal(const kernel_siginfo_t *siginfo) * @bprm: Contains the linux_binprm structure. * @file: Binary that will be executed without an interpreter. * - * This hook is useful to mark that a privilege boundary (setuid/setgid process) - * has been crossed. This is done based on the "secureexec" flag. + * If there are statistics, test the "not_allowed" flag and avoid the file + * execution based on this. Also, this hook is useful to mark that a privilege + * boundary (setuid/setgid process) has been crossed. This is done based on the + * "secureexec" flag. * * To be defensive return an error code if it is not possible to get or set the * stats using an extended attribute since this blocks the execution of the * file. This scenario is treated as an attack. * - * It is important to note that here the brute_new_xattr_stats function could be - * used with a previous test of the secureexec flag. However it is better to use - * the basic xattr functions since in a future commit a test if the execution is - * allowed (via the brute_stats::not_allowed flag) will be necessary. This way, - * the stats of the file will be get only once. - * - * Return: An error code if it is not possible to get or set the statistical - * data. Zero otherwise. + * Return: -EPERM if the execution of the file is not allowed. An error code if + * it is not possible to get or set the statistical data. Zero otherwise. */ static int brute_task_execve(struct linux_binprm *bprm, struct file *file) { @@ -461,6 +546,12 @@ static int brute_task_execve(struct linux_binprm *bprm, struct file *file) if (WARN_ON_ONCE(rc && rc != -ENODATA)) goto unlock; + if (!rc && stats.not_allowed) { + brute_print_file_not_allowed(dentry); + rc = -EPERM; + goto unlock; + } + if (rc == -ENODATA && bprm->secureexec) { brute_reset_stats(&stats); rc = brute_set_xattr_stats(dentry, inode, &stats);