From patchwork Sat Jun 5 15:03:58 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: John Wood X-Patchwork-Id: 454844 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=unavailable 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 52F93C48BC2 for ; Sat, 5 Jun 2021 15:27:54 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 474CE613EF for ; Sat, 5 Jun 2021 15:27:54 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230010AbhFEP3l (ORCPT ); Sat, 5 Jun 2021 11:29:41 -0400 Received: from mout.gmx.net ([212.227.15.18]:42997 "EHLO mout.gmx.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229930AbhFEP3k (ORCPT ); Sat, 5 Jun 2021 11:29:40 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=gmx.net; s=badeba3b8450; t=1622906786; bh=V+/W+fEoSiOjB3w8FDJE2wQ1DNe8mLPtzk6quPwutvo=; h=X-UI-Sender-Class:From:To:Cc:Subject:Date:In-Reply-To:References; b=iojLmS3oTN1DZsuRR/9kSaXZCGZfZFJdGoPfE4LYE35NzhqE+PH0i8QWuoSoO3ig4 0JngVPtJQX19ChfEqfqd/uYPbTfQMIgFOXT9yfcsDzgp5iQWkZ6M5rdwdgYtppmBQ+ AgsMyUCQsUzlrWcQ2uUUGoVQflciOPKlRo41cXxA= X-UI-Sender-Class: 01bb95c1-4bf8-414a-932a-4f6e2808ef9c Received: from localhost.localdomain ([83.52.228.41]) by mail.gmx.net (mrgmx005 [212.227.17.184]) with ESMTPSA (Nemesis) id 1MxUnz-1lQzxz0ynB-00xwA9; Sat, 05 Jun 2021 17:26:26 +0200 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 1/8] security: Add LSM hook at the point where a task gets a fatal signal Date: Sat, 5 Jun 2021 17:03:58 +0200 Message-Id: <20210605150405.6936-2-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:AXIG8C6g+ihrnMi0Z4HkpARpgiKyCwVIo4zDsrAYAVY7GlNoZU6 7O/98xMjJRXPp5Qat/wXAqITJjdc3Cg9KVDUkv9jMTAC4TmaJiwFSk+4T6TOSiObnjiNVug 5nZhRlb2yHN0s2og2Qe+i3xYhvcGTiItAt6TDApjVwCMOjKleIr60AAiwo95nL9p1fVQ/6I S8e6Adp97nN0pqVcqVFpQ== X-UI-Out-Filterresults: notjunk:1; V03:K0:+rmuCovomkA=:ToTPjaCqTPRANlpKwgY+BI Td9B1Od85sW7rN2BHlZx0S9yGMlNrRoNPYKw30EdwZpFQCw0MJ1R0CK7jNGYsnXyO/JWmtABx BP8IkODttWs14kSIcSCNKNy4q9JWsUjjZTaSYWrXwi64xsSYLhWaaddKFpk8MUuuvSv9uqJAX E/hUUtc0B9GArMYdIA78pHLTWuSIdCh8Q6yZFjSzqfSAR3zBStXRTFgc6GEb7llc9k4u8FD+R XtlGDWysWOLPvtgnghV5DjcNE6AUKsVR7/3NR/PDlkpUufHxi09Ppq7iL9YoFl0WE5upzqFIj q2ZzkVHB18mUQ06x7p6IFopYkvLGrVWFazETXmveEJbPqu/5Xu7Riy03cOUfz3v11HrIzMpMz +aIux0EuCwEfa60cVU4z8Nv7Zm4W7JsHaheyBhL6dxdOf/4gr4Hgajen8Eh3d8F6FbVSPOsxt s6Uybc/P9ER94vsN5/ev4rT+BM0jb2diW8cHTOUaV3rYTpxStwxWfbvo2PowP9gpvZVXNz+hx TSva2yUeepFKks37O8PHbO7IArxS+MAlRplA68Dn18k9iGuaEofOOzhOSpZcTTyMvillCS1iM xqv87MAi6Ut/ohkzwdAmStfNxUwBmvnY7nV9e8iChfcxFcplTo9Rx6RldUnyeSImra4jB7Vzx RpXyAAGqsDguITO8wNAo58W2qV5VbIh+W+g4Bv/8q+pxIopd3BEaf7KRG3D8XZq/xwcthsF6j wqyLd1NJlM3kM3uZl0ZVlHGMTBb4M37rkjQU5DqMJuu5YsZffDQO5e6ZNnBtcnt+pa8zge1yT auceOueO8F+cLAD8VsMsN/Y5wu6H3S0/kPvI24SydTLiJRY6XYuHqkDh2qVoyPCJnfkeGgDa7 l+JKO2UYiadlnCcOEv/DRYUnCcD3MQAWGmIgoIUXbLesQfwXrIwoUVlqfxVLY+/2X5KxnltMR lU21EnKuEaYy41oQ7lvgOfWYYsSQ4i/w/egMQ05Hi30jVL3FoXlaWlI9UMRBTcs13VMp4M3JP cpn10EqaCybbccD3nURqqCVccxWiBfvbdBPp/8NvdtR7Uibubz38DMLWBhCZoPwm+Cn4J4NwY 1+oTCRz76ExaLmdSmDYJMKJUpA2ai+aDo4z Precedence: bulk List-ID: X-Mailing-List: linux-kselftest@vger.kernel.org Add a security hook that allows a LSM to be notified when a task gets a fatal signal. This patch is a previous step on the way to compute the task crash period by the "brute" LSM (linux security module to detect and mitigate fork brute force attack against vulnerable userspace processes). Signed-off-by: John Wood Reviewed-by: Kees Cook --- include/linux/lsm_hook_defs.h | 1 + include/linux/lsm_hooks.h | 4 ++++ include/linux/security.h | 4 ++++ kernel/signal.c | 1 + security/security.c | 5 +++++ 5 files changed, 15 insertions(+) -- 2.25.1 diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h index 04c01794de83..e28468e84300 100644 --- a/include/linux/lsm_hook_defs.h +++ b/include/linux/lsm_hook_defs.h @@ -225,6 +225,7 @@ LSM_HOOK(int, -ENOSYS, task_prctl, int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5) LSM_HOOK(void, LSM_RET_VOID, task_to_inode, struct task_struct *p, struct inode *inode) +LSM_HOOK(void, LSM_RET_VOID, task_fatal_signal, const kernel_siginfo_t *siginfo) LSM_HOOK(int, 0, ipc_permission, struct kern_ipc_perm *ipcp, short flag) LSM_HOOK(void, LSM_RET_VOID, ipc_getsecid, struct kern_ipc_perm *ipcp, u32 *secid) diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 5c4c5c0602cb..fc8bef0f15d9 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -799,6 +799,10 @@ * security attributes, e.g. for /proc/pid inodes. * @p contains the task_struct for the task. * @inode contains the inode structure for the inode. + * @task_fatal_signal: + * This hook allows security modules to be notified when a task gets a + * fatal signal. + * @siginfo contains the signal information. * * Security hooks for Netlink messaging. * diff --git a/include/linux/security.h b/include/linux/security.h index 06f7c50ce77f..609c76c6c764 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -433,6 +433,7 @@ int security_task_kill(struct task_struct *p, struct kernel_siginfo *info, int security_task_prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5); void security_task_to_inode(struct task_struct *p, struct inode *inode); +void security_task_fatal_signal(const kernel_siginfo_t *siginfo); int security_ipc_permission(struct kern_ipc_perm *ipcp, short flag); void security_ipc_getsecid(struct kern_ipc_perm *ipcp, u32 *secid); int security_msg_msg_alloc(struct msg_msg *msg); @@ -1183,6 +1184,9 @@ static inline int security_task_prctl(int option, unsigned long arg2, static inline void security_task_to_inode(struct task_struct *p, struct inode *inode) { } +static inline void security_task_fatal_signal(const kernel_siginfo_t *siginfo) +{ } + static inline int security_ipc_permission(struct kern_ipc_perm *ipcp, short flag) { diff --git a/kernel/signal.c b/kernel/signal.c index f7c6ffcbd044..4380763b3d8d 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -2804,6 +2804,7 @@ bool get_signal(struct ksignal *ksig) /* * Anything else is fatal, maybe with a core dump. */ + security_task_fatal_signal(&ksig->info); current->flags |= PF_SIGNALED; if (sig_kernel_coredump(signr)) { diff --git a/security/security.c b/security/security.c index b38155b2de83..208e3e7d4284 100644 --- a/security/security.c +++ b/security/security.c @@ -1891,6 +1891,11 @@ void security_task_to_inode(struct task_struct *p, struct inode *inode) call_void_hook(task_to_inode, p, inode); } +void security_task_fatal_signal(const kernel_siginfo_t *siginfo) +{ + call_void_hook(task_fatal_signal, siginfo); +} + int security_ipc_permission(struct kern_ipc_perm *ipcp, short flag) { return call_int_hook(ipc_permission, 0, ipcp, flag); 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: 454843 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.6 required=3.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,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=unavailable 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 478DBC4743E for ; Sat, 5 Jun 2021 16:32:47 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 24A3F61287 for ; Sat, 5 Jun 2021 16:32:47 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229964AbhFEQee (ORCPT ); Sat, 5 Jun 2021 12:34:34 -0400 Received: from mout.gmx.net ([212.227.15.19]:53599 "EHLO mout.gmx.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229930AbhFEQee (ORCPT ); Sat, 5 Jun 2021 12:34:34 -0400 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 Received: from localhost.localdomain ([83.52.228.41]) by mail.gmx.net (mrgmx005 [212.227.17.184]) with ESMTPSA (Nemesis) id 1MeU4s-1lHKJz3J9Y-00aT8a; Sat, 05 Jun 2021 18:31:40 +0200 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 Precedence: bulk List-ID: X-Mailing-List: linux-kselftest@vger.kernel.org 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); From patchwork Sat Jun 5 15:04:03 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: John Wood X-Patchwork-Id: 454842 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=unavailable 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 1AE7BC4743F for ; Sat, 5 Jun 2021 17:16:32 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 06914613DF for ; Sat, 5 Jun 2021 17:16:32 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230048AbhFERSS (ORCPT ); Sat, 5 Jun 2021 13:18:18 -0400 Received: from mout.gmx.net ([212.227.17.20]:59473 "EHLO mout.gmx.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229933AbhFERSR (ORCPT ); Sat, 5 Jun 2021 13:18:17 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=gmx.net; s=badeba3b8450; t=1622913309; bh=n09vl0Qi955c0FFlwEbTMm5JhNR/MCo9V3zzQRkL+oI=; h=X-UI-Sender-Class:From:To:Cc:Subject:Date:In-Reply-To:References; b=TVdFfiFbg6tNuSDK1EnSO4mtTbjz/F9xIVSzvk/yY7yzGgvx9eMQz4wmWpjFfmlhK Jz9snPAupOyOiIthsnxojbscFpimdIQkMq5ByPxLBIAPcbbBYSpAcDGw7caObxAtNI XtbJtApZyAoc+AeO6HWOKZ8FPNW7NZkoS0b62l7k= X-UI-Sender-Class: 01bb95c1-4bf8-414a-932a-4f6e2808ef9c Received: from localhost.localdomain ([83.52.228.41]) by mail.gmx.net (mrgmx104 [212.227.17.174]) with ESMTPSA (Nemesis) id 1Mw9QC-1lZ10S3Npc-00s4aG; Sat, 05 Jun 2021 19:15:09 +0200 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 6/8] selftests/brute: Add tests for the Brute LSM Date: Sat, 5 Jun 2021 17:04:03 +0200 Message-Id: <20210605150405.6936-7-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:DtjoHAETHk4xGDZFpK9+7KXukSk5D6Su3506SD5xQyJNEHT/Msb C0+ipAxxT7EQtxPug3EGTNT471AZFtU/nhXAh+9Yukyj9xcB8SpKZltr4aP+gZPYcVLeLAN CJedZwSGUe2aDoI2xezzqGdkhrQFMo0ySy+FMobwT9nedex2ek0koSOQEV5XwNg1lstNQf9 gQGj17Dc2mchjJPJ48grw== X-UI-Out-Filterresults: notjunk:1; V03:K0:dUgzQiRyfpU=:LrUUAWdlWwsuT+lDThaKc4 vc6YBl8vTgX5Q3t10Q/NQGh3aRFvOj2hKbGu/AZ7m4kn3vHwQvWpQypUcod08Fl1wu5w+TLgK ISzWAp9a18UcRrGV1kPKe/KIQVKf7Kg5OCh6jYRHnVRuM2R20pCoytlV2C/hUQTpa0frPoxSf QKlupC+srZIGTD799PhxEfDtQ49KgJhjpKYzwMHDykTOdQrcXJtPXA2wHTH8I3GUanF6cgvJQ eKot2StW8eQ2b4spdLn72BBcWwMrjO+frA5unN9PkNX3ld9jlLTkZnHvIhFHsm88Cza8Ul/QG xL4eSnRO6q6j5RGiZzgmiyGnSovx4lP4IR0BWQJNGV9+0SLHPQ67IkYYVZ1QEA1wxYrx+PYPO cqpluXnN8nVDOr+tyhr3hp5s5/YjuVoHLD6VtxwBBIDbuZXIx+wuLMC5Cpy/mwIJfTNEMCYwk DJwCGzHL756OgwgZRGRCvRZ65ShUs6GoNJ9JdUZPcIxm/Iv3BNvo6jkScEJCUEGHxEDMkKxOn qlIb0vZ4smzx5gbsKA2789AztAmUsIsw6NAZHqECtARFxbbXIVHTVL18HieJDWYLtUvgMt39Q XZ5smKfL8WR9Gi/8cF1usL/sEOItdmm/n490sjoG2i+QsWxdBD7dccqIKb5B2aqs9B60Yli+Q 9SMG4x6EAVahGh3FGZwgfC3fVHt5xDralZMvnB1GU+7xyXRTnruFJJLGr7WrXD8PMNbSNsJj7 b3C/NSRA+KYGDIZCJcBpXrGCm5mHe3z5wW4ZVti3kQe8PHJHsNNY7PcbTrcC+OieotJBJc/2s Be/GkgrJChFrd+ZD68tLsl0B3pX9Z1UogeVTzSKUlHaED1wuBY8dq4UZPGwieDMERgO1QhL96 ggttYik8nqSbiYR/29Ih6qDIpyGlgbAQTExN/2n9TDk95l6SHnAGo98PkRDKWfoPrdYuteD2n WWZONe9XCsL+Bmsb9hunvr5B6z1MPAot0sYC2c0tSt68UFeQYbFxBj7Etcz7QwieZ6k7OsdJG NgT3M5cTamzxAUz7ELwYe0UDidYH0I+6lS4Rh+wqW8HhCtF7+vVuFSITzZACxzdkhFnf5nYlS fvtDDGhx+raVB0no97W8jNWnfQmPIXFG6iB Precedence: bulk List-ID: X-Mailing-List: linux-kselftest@vger.kernel.org Add tests to check the Brute LSM functionality and cover fork/exec brute force attacks crossing the following privilege boundaries: 1.- setuid process 2.- privilege changes 3.- network to local Also, as a first step check that fork/exec brute force attacks without crossing any privilege boundary already commented doesn't trigger the detection and mitigation stage. Moreover, test if the userspace notification, via "waitid" system call, is sent when an attack is mitigated (to inform that all the offending tasks involved in the attack have been killed by Brute LSM). Once a brute force attack is detected, the "test" executable is marked as "not allowed". To start again a new test, use the "rmxattr" app to revert this state. This way, all the tests can be run using the same binary. Signed-off-by: John Wood --- 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 ++++++++++++ 8 files changed, 866 insertions(+) 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 diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index bc3299a20338..5c413a010849 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -2,6 +2,7 @@ TARGETS = arm64 TARGETS += bpf TARGETS += breakpoints +TARGETS += brute TARGETS += capabilities TARGETS += cgroup TARGETS += clone3 diff --git a/tools/testing/selftests/brute/.gitignore b/tools/testing/selftests/brute/.gitignore new file mode 100644 index 000000000000..a02aa79249a1 --- /dev/null +++ b/tools/testing/selftests/brute/.gitignore @@ -0,0 +1,3 @@ +exec +rmxattr +test diff --git a/tools/testing/selftests/brute/Makefile b/tools/testing/selftests/brute/Makefile new file mode 100644 index 000000000000..3975338c1ecc --- /dev/null +++ b/tools/testing/selftests/brute/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 +CFLAGS += -Wall -O2 +TEST_PROGS := test.sh +TEST_GEN_FILES := exec rmxattr test +include ../lib.mk diff --git a/tools/testing/selftests/brute/config b/tools/testing/selftests/brute/config new file mode 100644 index 000000000000..3587b7bf6c23 --- /dev/null +++ b/tools/testing/selftests/brute/config @@ -0,0 +1 @@ +CONFIG_SECURITY_FORK_BRUTE=y diff --git a/tools/testing/selftests/brute/exec.c b/tools/testing/selftests/brute/exec.c new file mode 100644 index 000000000000..a7fc5705f97c --- /dev/null +++ b/tools/testing/selftests/brute/exec.c @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include + +static __attribute__((noreturn)) void error_failure(const char *message) +{ + perror(message); + exit(EXIT_FAILURE); +} + +#define PROG_NAME basename(argv[0]) +#define CLD_BRUTE 7 /* child was killed by brute LSM */ + +int main(int argc, char **argv) +{ + pid_t pid; + int rc; + siginfo_t siginfo; + + if (argc < 2) { + printf("Usage: %s \n", PROG_NAME); + exit(EXIT_FAILURE); + } + + pid = fork(); + if (pid < 0) + error_failure("fork"); + + /* Child process */ + if (!pid) { + execve(argv[1], &argv[1], NULL); + error_failure("execve"); + } + + /* Parent process */ + rc = waitid(P_PID, pid, &siginfo, WEXITED); + if (rc) + error_failure("waitid"); + + return siginfo.si_code != CLD_BRUTE; +} diff --git a/tools/testing/selftests/brute/rmxattr.c b/tools/testing/selftests/brute/rmxattr.c new file mode 100644 index 000000000000..9ed90409d337 --- /dev/null +++ b/tools/testing/selftests/brute/rmxattr.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include + +static __attribute__((noreturn)) void error_failure(const char *message) +{ + perror(message); + exit(EXIT_FAILURE); +} + +#define PROG_NAME basename(argv[0]) + +#define XATTR_SECURITY_PREFIX "security." +#define XATTR_BRUTE_SUFFIX "brute" +#define XATTR_NAME_BRUTE XATTR_SECURITY_PREFIX XATTR_BRUTE_SUFFIX + +int main(int argc, char **argv) +{ + int rc; + + if (argc < 2) { + printf("Usage: %s \n", PROG_NAME); + exit(EXIT_FAILURE); + } + + rc = removexattr(argv[1], XATTR_NAME_BRUTE); + if (rc) + error_failure("removexattr"); + + return EXIT_SUCCESS; +} diff --git a/tools/testing/selftests/brute/test.c b/tools/testing/selftests/brute/test.c new file mode 100644 index 000000000000..44c32f446dca --- /dev/null +++ b/tools/testing/selftests/brute/test.c @@ -0,0 +1,507 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *message = "message"; + +enum mode { + MODE_NONE, + MODE_CRASH, + MODE_SERVER_CRASH, + MODE_CLIENT, +}; + +enum crash_after { + CRASH_AFTER_NONE, + CRASH_AFTER_FORK, + CRASH_AFTER_EXEC, +}; + +enum signal_from { + SIGNAL_FROM_NONE, + SIGNAL_FROM_USER, + SIGNAL_FROM_KERNEL, +}; + +struct args { + uint32_t ip; + uint16_t port; + int counter; + long timeout; + enum mode mode; + enum crash_after crash_after; + enum signal_from signal_from; + unsigned char has_counter : 1; + unsigned char has_change_priv : 1; + unsigned char has_ip : 1; + unsigned char has_port : 1; + unsigned char has_timeout : 1; +}; + +#define OPT_STRING "hm:c:s:n:Ca:p:t:" + +static void usage(const char *prog) +{ + printf("Usage: %s \n", prog); + printf("OPTIONS:\n"); + printf(" -h: Show this help and exit. Optional.\n"); + printf(" -m (crash | server_crash | client): Mode. Required.\n"); + printf("Options for crash mode:\n"); + printf(" -c (fork | exec): Crash after. Optional.\n"); + printf(" -s (user | kernel): Signal from. Required.\n"); + printf(" -n counter: Number of crashes.\n"); + printf(" Required if the option -c is used.\n"); + printf(" Not used without the option -c.\n"); + printf(" Range from 1 to INT_MAX.\n"); + printf(" -C: Change privileges before crash. Optional.\n"); + printf("Options for server_crash mode:\n"); + printf(" -a ip: Ip v4 address to accept. Required.\n"); + printf(" -p port: Port number. Required.\n"); + printf(" Range from 1 to UINT16_MAX.\n"); + printf(" -t secs: Accept timeout. Required.\n"); + printf(" Range from 1 to LONG_MAX.\n"); + printf(" -c (fork | exec): Crash after. Required.\n"); + printf(" -s (user | kernel): Signal from. Required.\n"); + printf(" -n counter: Number of crashes. Required.\n"); + printf(" Range from 1 to INT_MAX.\n"); + printf("Options for client mode:\n"); + printf(" -a ip: Ip v4 address to connect. Required.\n"); + printf(" -p port: Port number. Required.\n"); + printf(" Range from 1 to UINT16_MAX.\n"); + printf(" -t secs: Connect timeout. Required.\n"); + printf(" Range from 1 to LONG_MAX.\n"); +} + +static __attribute__((noreturn)) void info_failure(const char *message, + const char *prog) +{ + printf("%s\n", message); + usage(prog); + exit(EXIT_FAILURE); +} + +static enum mode get_mode(const char *text, const char *prog) +{ + if (!strcmp(text, "crash")) + return MODE_CRASH; + + if (!strcmp(text, "server_crash")) + return MODE_SERVER_CRASH; + + if (!strcmp(text, "client")) + return MODE_CLIENT; + + info_failure("Invalid mode option [-m].", prog); +} + +static enum crash_after get_crash_after(const char *text, const char *prog) +{ + if (!strcmp(text, "fork")) + return CRASH_AFTER_FORK; + + if (!strcmp(text, "exec")) + return CRASH_AFTER_EXEC; + + info_failure("Invalid crash after option [-c].", prog); +} + +static enum signal_from get_signal_from(const char *text, const char *prog) +{ + if (!strcmp(text, "user")) + return SIGNAL_FROM_USER; + + if (!strcmp(text, "kernel")) + return SIGNAL_FROM_KERNEL; + + info_failure("Invalid signal from option [-s]", prog); +} + +static int get_counter(const char *text, const char *prog) +{ + int counter; + + counter = atoi(text); + if (counter > 0) + return counter; + + info_failure("Invalid counter option [-n].", prog); +} + +static __attribute__((noreturn)) void error_failure(const char *message) +{ + perror(message); + exit(EXIT_FAILURE); +} + +static uint32_t get_ip(const char *text, const char *prog) +{ + int ret; + uint32_t ip; + + ret = inet_pton(AF_INET, text, &ip); + if (!ret) + info_failure("Invalid ip option [-a].", prog); + else if (ret < 0) + error_failure("inet_pton"); + + return ip; +} + +static uint16_t get_port(const char *text, const char *prog) +{ + long port; + + port = atol(text); + if ((port > 0) && (port <= UINT16_MAX)) + return htons(port); + + info_failure("Invalid port option [-p].", prog); +} + +static long get_timeout(const char *text, const char *prog) +{ + long timeout; + + timeout = atol(text); + if (timeout > 0) + return timeout; + + info_failure("Invalid timeout option [-t].", prog); +} + +static void check_args(const struct args *args, const char *prog) +{ + if (args->mode == MODE_CRASH && args->crash_after != CRASH_AFTER_NONE && + args->signal_from != SIGNAL_FROM_NONE && args->has_counter && + !args->has_ip && !args->has_port && !args->has_timeout) + return; + + if (args->mode == MODE_CRASH && args->signal_from != SIGNAL_FROM_NONE && + args->crash_after == CRASH_AFTER_NONE && !args->has_counter && + !args->has_ip && !args->has_port && !args->has_timeout) + return; + + if (args->mode == MODE_SERVER_CRASH && args->has_ip && args->has_port && + args->has_timeout && args->crash_after != CRASH_AFTER_NONE && + args->signal_from != SIGNAL_FROM_NONE && args->has_counter && + !args->has_change_priv) + return; + + if (args->mode == MODE_CLIENT && args->has_ip && args->has_port && + args->has_timeout && args->crash_after == CRASH_AFTER_NONE && + args->signal_from == SIGNAL_FROM_NONE && !args->has_counter && + !args->has_change_priv) + return; + + info_failure("Invalid use of options.", prog); +} + +static uid_t get_non_root_uid(void) +{ + struct passwd *pwent; + uid_t uid; + + while (true) { + errno = 0; + pwent = getpwent(); + if (!pwent) { + if (errno) { + perror("getpwent"); + endpwent(); + exit(EXIT_FAILURE); + } + break; + } + + if (pwent->pw_uid) { + uid = pwent->pw_uid; + endpwent(); + return uid; + } + } + + endpwent(); + printf("A user different of root is needed.\n"); + exit(EXIT_FAILURE); +} + +static inline void do_sigsegv(void) +{ + int *p = NULL; + *p = 0; +} + +static void do_sigkill(void) +{ + int ret; + + ret = kill(getpid(), SIGKILL); + if (ret) + error_failure("kill"); +} + +static void crash(enum signal_from signal_from, bool change_priv) +{ + int ret; + + if (change_priv) { + ret = setuid(get_non_root_uid()); + if (ret) + error_failure("setuid"); + } + + if (signal_from == SIGNAL_FROM_KERNEL) + do_sigsegv(); + + do_sigkill(); +} + +static void execve_crash(char *const argv[]) +{ + execve(argv[0], argv, NULL); + error_failure("execve"); +} + +static void exec_crash_user(void) +{ + char *const argv[] = { + "./test", "-m", "crash", "-s", "user", NULL, + }; + + execve_crash(argv); +} + +static void exec_crash_user_change_priv(void) +{ + char *const argv[] = { + "./test", "-m", "crash", "-s", "user", "-C", NULL, + }; + + execve_crash(argv); +} + +static void exec_crash_kernel(void) +{ + char *const argv[] = { + "./test", "-m", "crash", "-s", "kernel", NULL, + }; + + execve_crash(argv); +} + +static void exec_crash_kernel_change_priv(void) +{ + char *const argv[] = { + "./test", "-m", "crash", "-s", "kernel", "-C", NULL, + }; + + execve_crash(argv); +} + +static void exec_crash(enum signal_from signal_from, bool change_priv) +{ + if (signal_from == SIGNAL_FROM_USER && !change_priv) + exec_crash_user(); + if (signal_from == SIGNAL_FROM_USER && change_priv) + exec_crash_user_change_priv(); + if (signal_from == SIGNAL_FROM_KERNEL && !change_priv) + exec_crash_kernel(); + if (signal_from == SIGNAL_FROM_KERNEL && change_priv) + exec_crash_kernel_change_priv(); +} + +static void do_crash(enum crash_after crash_after, enum signal_from signal_from, + int counter, bool change_priv) +{ + pid_t pid; + int status; + + if (crash_after == CRASH_AFTER_NONE) + crash(signal_from, change_priv); + + while (counter > 0) { + pid = fork(); + if (pid < 0) + error_failure("fork"); + + /* Child process */ + if (!pid) { + if (crash_after == CRASH_AFTER_FORK) + crash(signal_from, change_priv); + + exec_crash(signal_from, change_priv); + } + + /* Parent process */ + counter -= 1; + pid = waitpid(pid, &status, 0); + if (pid < 0) + error_failure("waitpid"); + } +} + +static __attribute__((noreturn)) void error_close_failure(const char *message, + int fd) +{ + perror(message); + close(fd); + exit(EXIT_FAILURE); +} + +static void do_server(uint32_t ip, uint16_t port, long accept_timeout) +{ + int sockfd; + int ret; + struct sockaddr_in address; + struct timeval timeout; + int newsockfd; + + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) + error_failure("socket"); + + address.sin_family = AF_INET; + address.sin_addr.s_addr = ip; + address.sin_port = port; + + ret = bind(sockfd, (const struct sockaddr *)&address, sizeof(address)); + if (ret) + error_close_failure("bind", sockfd); + + ret = listen(sockfd, 1); + if (ret) + error_close_failure("listen", sockfd); + + timeout.tv_sec = accept_timeout; + timeout.tv_usec = 0; + ret = setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, + (const struct timeval *)&timeout, sizeof(timeout)); + if (ret) + error_close_failure("setsockopt", sockfd); + + newsockfd = accept(sockfd, NULL, NULL); + if (newsockfd < 0) + error_close_failure("accept", sockfd); + + close(sockfd); + close(newsockfd); +} + +static void do_client(uint32_t ip, uint16_t port, long connect_timeout) +{ + int sockfd; + int ret; + struct timeval timeout; + struct sockaddr_in address; + + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) + error_failure("socket"); + + timeout.tv_sec = connect_timeout; + timeout.tv_usec = 0; + ret = setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, + (const struct timeval *)&timeout, sizeof(timeout)); + if (ret) + error_close_failure("setsockopt", sockfd); + + address.sin_family = AF_INET; + address.sin_addr.s_addr = ip; + address.sin_port = port; + + ret = connect(sockfd, (const struct sockaddr *)&address, + sizeof(address)); + if (ret) + error_close_failure("connect", sockfd); + + ret = write(sockfd, message, strlen(message)); + if (ret < 0) + error_close_failure("write", sockfd); + + close(sockfd); +} + +#define PROG_NAME basename(argv[0]) + +int main(int argc, char **argv) +{ + int opt; + struct args args = { + .mode = MODE_NONE, + .crash_after = CRASH_AFTER_NONE, + .signal_from = SIGNAL_FROM_NONE, + .has_counter = false, + .has_change_priv = false, + .has_ip = false, + .has_port = false, + .has_timeout = false, + }; + + while ((opt = getopt(argc, argv, OPT_STRING)) != -1) { + switch (opt) { + case 'h': + usage(PROG_NAME); + return EXIT_SUCCESS; + case 'm': + args.mode = get_mode(optarg, PROG_NAME); + break; + case 'c': + args.crash_after = get_crash_after(optarg, PROG_NAME); + break; + case 's': + args.signal_from = get_signal_from(optarg, PROG_NAME); + break; + case 'n': + args.counter = get_counter(optarg, PROG_NAME); + args.has_counter = true; + break; + case 'C': + args.has_change_priv = true; + break; + case 'a': + args.ip = get_ip(optarg, PROG_NAME); + args.has_ip = true; + break; + case 'p': + args.port = get_port(optarg, PROG_NAME); + args.has_port = true; + break; + case 't': + args.timeout = get_timeout(optarg, PROG_NAME); + args.has_timeout = true; + break; + default: + usage(PROG_NAME); + return EXIT_FAILURE; + } + } + + check_args(&args, PROG_NAME); + + if (args.mode == MODE_CRASH) { + do_crash(args.crash_after, args.signal_from, args.counter, + args.has_change_priv); + } else if (args.mode == MODE_SERVER_CRASH) { + do_server(args.ip, args.port, args.timeout); + do_crash(args.crash_after, args.signal_from, args.counter, + false); + } else if (args.mode == MODE_CLIENT) { + do_client(args.ip, args.port, args.timeout); + } + + return EXIT_SUCCESS; +} diff --git a/tools/testing/selftests/brute/test.sh b/tools/testing/selftests/brute/test.sh new file mode 100755 index 000000000000..393b651ab635 --- /dev/null +++ b/tools/testing/selftests/brute/test.sh @@ -0,0 +1,269 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 + +TCID="test.sh" + +KSFT_PASS=0 +KSFT_FAIL=1 +KSFT_SKIP=4 + +errno=$KSFT_PASS + +check_root() +{ + local uid=$(id -u) + if [ $uid -ne 0 ]; then + echo $TCID: must be run as root >&2 + exit $KSFT_SKIP + fi +} + +tmp_files_setup() +{ + DMESG=$(mktemp --tmpdir -t brute-dmesg-XXXXXX) +} + +tmp_files_cleanup() +{ + rm -f "$DMESG" +} + +save_dmesg() +{ + dmesg > "$DMESG" +} + +count_attack_matches() +{ + dmesg | comm --nocheck-order -13 "$DMESG" - | \ + grep "brute: fork brute force attack detected" | wc -l +} + +assert_equal() +{ + local val1=$1 + local val2=$2 + + if [ $val1 -eq $val2 ]; then + echo "$TCID: $message [PASS]" + else + echo "$TCID: $message [FAIL]" + errno=$KSFT_FAIL + fi +} + +test_fork_user() +{ + COUNTER=20 + + save_dmesg + ./test -m crash -c fork -s user -n $COUNTER + count=$(count_attack_matches) + + message="fork attack (user signals, no bounds crossed)" + assert_equal $count 0 +} + +test_fork_kernel() +{ + save_dmesg + ./test -m crash -c fork -s kernel -n $COUNTER + count=$(count_attack_matches) + + message="fork attack (kernel signals, no bounds crossed)" + assert_equal $count 0 +} + +test_exec_user() +{ + save_dmesg + ./test -m crash -c exec -s user -n $COUNTER + count=$(count_attack_matches) + + message="exec attack (user signals, no bounds crossed)" + assert_equal $count 0 +} + +test_exec_kernel() +{ + save_dmesg + ./test -m crash -c exec -s kernel -n $COUNTER + count=$(count_attack_matches) + + message="exec attack (kernel signals, no bounds crossed)" + assert_equal $count 0 +} + +assert_not_equal() +{ + local val1=$1 + local val2=$2 + + if [ $val1 -ne $val2 ]; then + echo $TCID: $message [PASS] + else + echo $TCID: $message [FAIL] + errno=$KSFT_FAIL + fi +} + +remove_xattr() +{ + ./rmxattr test >/dev/null 2>&1 +} + +test_fork_kernel_setuid() +{ + save_dmesg + chmod u+s test + ./test -m crash -c fork -s kernel -n $COUNTER + chmod u-s test + count=$(count_attack_matches) + + message="fork attack (kernel signals, setuid binary)" + assert_not_equal $count 0 + remove_xattr +} + +test_exec_kernel_setuid() +{ + save_dmesg + chmod u+s test + ./test -m crash -c exec -s kernel -n $COUNTER + chmod u-s test + count=$(count_attack_matches) + + message="exec attack (kernel signals, setuid binary)" + assert_not_equal $count 0 + remove_xattr +} + +test_fork_kernel_change_priv() +{ + save_dmesg + ./test -m crash -c fork -s kernel -n $COUNTER -C + count=$(count_attack_matches) + + message="fork attack (kernel signals, change privileges)" + assert_not_equal $count 0 + remove_xattr +} + +test_exec_kernel_change_priv() +{ + save_dmesg + ./test -m crash -c exec -s kernel -n $COUNTER -C + count=$(count_attack_matches) + + message="exec attack (kernel signals, change privileges)" + assert_not_equal $count 0 + remove_xattr +} + +network_ns_setup() +{ + local vnet_name=$1 + local veth_name=$2 + local ip_src=$3 + local ip_dst=$4 + + ip netns add $vnet_name + ip link set $veth_name netns $vnet_name + ip -n $vnet_name addr add $ip_src/24 dev $veth_name + ip -n $vnet_name link set $veth_name up + ip -n $vnet_name route add $ip_dst/24 dev $veth_name +} + +network_setup() +{ + VETH0_NAME=veth0 + VNET0_NAME=vnet0 + VNET0_IP=10.0.1.0 + VETH1_NAME=veth1 + VNET1_NAME=vnet1 + VNET1_IP=10.0.2.0 + + ip link add $VETH0_NAME type veth peer name $VETH1_NAME + network_ns_setup $VNET0_NAME $VETH0_NAME $VNET0_IP $VNET1_IP + network_ns_setup $VNET1_NAME $VETH1_NAME $VNET1_IP $VNET0_IP +} + +test_fork_kernel_network_to_local() +{ + INADDR_ANY=0.0.0.0 + PORT=65535 + TIMEOUT=5 + + save_dmesg + ip netns exec $VNET0_NAME ./test -m server_crash -a $INADDR_ANY \ + -p $PORT -t $TIMEOUT -c fork -s kernel -n $COUNTER & + sleep 1 + ip netns exec $VNET1_NAME ./test -m client -a $VNET0_IP -p $PORT \ + -t $TIMEOUT + sleep 1 + count=$(count_attack_matches) + + message="fork attack (kernel signals, network to local)" + assert_not_equal $count 0 + remove_xattr +} + +test_exec_kernel_network_to_local() +{ + save_dmesg + ip netns exec $VNET0_NAME ./test -m server_crash -a $INADDR_ANY \ + -p $PORT -t $TIMEOUT -c exec -s kernel -n $COUNTER & + sleep 1 + ip netns exec $VNET1_NAME ./test -m client -a $VNET0_IP -p $PORT \ + -t $TIMEOUT + sleep 1 + count=$(count_attack_matches) + + message="exec attack (kernel signals, network to local)" + assert_not_equal $count 0 + remove_xattr +} + +network_cleanup() +{ + ip netns del $VNET0_NAME >/dev/null 2>&1 + ip netns del $VNET1_NAME >/dev/null 2>&1 + ip link delete $VETH0_NAME >/dev/null 2>&1 + ip link delete $VETH1_NAME >/dev/null 2>&1 +} + +test_waitid() +{ + ./exec test -m crash -c fork -s kernel -n $COUNTER -C + local rc=$? + + message="notification to userspace via waitid system call" + assert_equal $rc 0 + remove_xattr +} + +cleanup() +{ + network_cleanup + tmp_files_cleanup + chmod u-s test + remove_xattr +} +trap cleanup EXIT + +check_root +tmp_files_setup +test_fork_user +test_fork_kernel +test_exec_user +test_exec_kernel +test_fork_kernel_setuid +test_exec_kernel_setuid +test_fork_kernel_change_priv +test_exec_kernel_change_priv +network_setup +test_fork_kernel_network_to_local +test_exec_kernel_network_to_local +network_cleanup +test_waitid +exit $errno From patchwork Sat Jun 5 15:04:05 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: John Wood X-Patchwork-Id: 454841 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=unavailable 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 4330BC4743D for ; Sat, 5 Jun 2021 17:59:29 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 2395B6141C for ; Sat, 5 Jun 2021 17:59:29 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230060AbhFESBP (ORCPT ); Sat, 5 Jun 2021 14:01:15 -0400 Received: from mout.gmx.net ([212.227.15.15]:41791 "EHLO mout.gmx.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229964AbhFESBO (ORCPT ); Sat, 5 Jun 2021 14:01:14 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=gmx.net; s=badeba3b8450; t=1622915918; bh=LJZMH4mKKG4Y4RH/IKRmcuoSuYAs3YZ6GSXZNks3Imw=; h=X-UI-Sender-Class:From:To:Cc:Subject:Date:In-Reply-To:References; b=EXvn/Qe1G20n0eCfzE5oqH9nCio58KhDVyrKq87mvUn5a8gNLLkdFSi6kOi+BoMnY 1hDvjbSm7pE5rTXadLV9eQ8lFHUU+l7oMzrA0fvIEEgGuFDDUwD7mQLglDQSkSDCKk 7YpGopCJ5tA0g8uLT9Zl3u6SXCYduGi1BElJOQoY= X-UI-Sender-Class: 01bb95c1-4bf8-414a-932a-4f6e2808ef9c Received: from localhost.localdomain ([83.52.228.41]) by mail.gmx.net (mrgmx004 [212.227.17.184]) with ESMTPSA (Nemesis) id 1M9FnZ-1llRpx0p9T-006QJB; Sat, 05 Jun 2021 19:58:38 +0200 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 8/8] MAINTAINERS: Add a new entry for the Brute LSM Date: Sat, 5 Jun 2021 17:04:05 +0200 Message-Id: <20210605150405.6936-9-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:zkSY+Nk10Hb9RRhiRldy4BtP2AIFThepaw+IBLiQxGRKAu76Tfz u/xwkD/5Xjj+4nPrpcNbps49Prlmno1jLjtPMU0HsCFJ8K+bm+sJ7XuKlC6BGL7piwviohY sTlX6Cko1RB0w10cj7CJ1tXWloewJz9zjikX65FCu8KyF9jqJGcXtdHzxZDjQX0PCOxFvnd 7U8jbQRCot2JkQcmxsVhA== X-UI-Out-Filterresults: notjunk:1; V03:K0:Zll4jERGK4o=:b0ECLFhKc1ov8XftBxIOl9 Bm00CA3vk6hTwdngrb1VLwvu2bvhDT96UWxpd5QHIfH1c2O5zW+gq6YD8aknBSKHms6Dub/s5 f7jMvDzfr5Mutwll/uQ5YbB8JC7nFGHTzf6hbcttzV66aa+9jlbs3/ABbaQDmtH+Ly+qYDQE6 FJpIA61UHheW6K0IyVh8t2m1+jHn7my8B3BQhdc+PrMe0gQHPjEU+ZJis00K5/gvkyrxTuGHo 3iKblKo1mjMcUN1oGVKrRjirMb/TSVxwz7XTUB/P51Velw8SgxuMTg53j1FnpuIvvv+ZG7xcM rmG1oAg0tdheQ+pPQogFWskdNK4Ag8VyzweuZ0fUVY+dWaKKtmpBQ874+29gkw6Y3+70hvc1t jKfB8ckTMcibE9oDb6mKVl78uE1GRAIS8nhyb/kne1ua2bEqJ7tysrbjFc8360U1qjgPFE4a8 kWRb0oTX6CQSAAeJ3YGCjz+WCiRyJYAMcfo4Gn83R2JOjPBXdJoPX6iGxF/2A24DJQBruU8hr Mt5TAwuaw/q+PHowLaTJbqCFVrXJ0aCas4utyh4tULM1nV2SfxU9vYtxtz598ePmrFz2Xgi0N dlWbtJrwwBzPMAiQBUv0awrFs6FmK0tviDCY30HkQcsnHvCZbJHH3BVCrJqzP/oRtfGvpjpF7 R6w3tXo5XyxV95pQb8np03wgVP40n7oGErvs5Elsw1r2nS4zDRzQKQ/0CQgjA4tAH2DIAd8fn NzoaFJZg0jIvsi7Mx8a575YtGSGwFGOSaMS2YstcN/TkldgpaI0M9AGAxklmmiTQ9w0ZRsBAD zAAsVtzFlfXsBbPEYB16GqQVR1GfJw+QirLv+4i1jFPM6vcesajUz1HKeqxtFjkNlHVVEZzTY thwIgWTL1P9kdR7uy66I1xegj0nPCui+X1+xHTtLWMLfBk6Ci9RK2kI28meJyPq0ITYlS6OGv N3iR4UwFaRr2RLHQPZFXPZW46bgHRTI7sKQOtVwRVVSEGZCyTVyf/dR8zdcZig0VOlLAIWyvz OGAAlfnYKEtf+eT3FDxtEzVc1viYBZUKzCl716RqEIlhUc1D9FYU7g/5bQrWTIyT/wv1i6IbI PubMFX8o76gckRAj7fkDwA2yyiLtOqwYun5 Precedence: bulk List-ID: X-Mailing-List: linux-kselftest@vger.kernel.org In order to maintain the code for the Brute LSM add a new entry to the maintainers list. Signed-off-by: John Wood --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) -- 2.25.1 diff --git a/MAINTAINERS b/MAINTAINERS index 503fd21901f1..665cd6aaadac 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3847,6 +3847,14 @@ L: netdev@vger.kernel.org S: Supported F: drivers/net/ethernet/brocade/bna/ +BRUTE SECURITY MODULE +M: John Wood +S: Maintained +F: Documentation/admin-guide/LSM/Brute.rst +F: include/brute/ +F: security/brute/ +F: tools/testing/selftests/brute/ + BSG (block layer generic sg v4 driver) M: FUJITA Tomonori L: linux-scsi@vger.kernel.org