From patchwork Fri Sep 6 15:24:51 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= X-Patchwork-Id: 11135541 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 3151F924 for ; Fri, 6 Sep 2019 15:26:44 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 13C142173B for ; Fri, 6 Sep 2019 15:26:44 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2404421AbfIFP0j (ORCPT ); Fri, 6 Sep 2019 11:26:39 -0400 Received: from smtp-sh2.infomaniak.ch ([128.65.195.6]:34011 "EHLO smtp-sh2.infomaniak.ch" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2404118AbfIFP0i (ORCPT ); Fri, 6 Sep 2019 11:26:38 -0400 Received: from smtp5.infomaniak.ch (smtp5.infomaniak.ch [83.166.132.18]) by smtp-sh2.infomaniak.ch (8.14.4/8.14.4/Debian-8+deb8u2) with ESMTP id x86FP3Og085821 (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Fri, 6 Sep 2019 17:25:03 +0200 Received: from localhost (ns3096276.ip-94-23-54.eu [94.23.54.103]) (authenticated bits=0) by smtp5.infomaniak.ch (8.14.5/8.14.5) with ESMTP id x86FP1FY047370; Fri, 6 Sep 2019 17:25:01 +0200 From: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= To: linux-kernel@vger.kernel.org Cc: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , Aleksa Sarai , Alexei Starovoitov , Al Viro , Andy Lutomirski , Christian Heimes , Daniel Borkmann , Eric Chiang , Florian Weimer , James Morris , Jan Kara , Jann Horn , Jonathan Corbet , Kees Cook , Matthew Garrett , Matthew Wilcox , Michael Kerrisk , =?utf-8?q?Micka=C3=ABl_Sala=C3=BC?= =?utf-8?q?n?= , Mimi Zohar , =?utf-8?q?Philippe_Tr=C3=A9buchet?= , Scott Shell , Sean Christopherson , Shuah Khan , Song Liu , Steve Dower , Steve Grubb , Thibaut Sautereau , Vincent Strubel , Yves-Alexis Perez , kernel-hardening@lists.openwall.com, linux-api@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org Subject: [PATCH v2 1/5] fs: Add support for an O_MAYEXEC flag on sys_open() Date: Fri, 6 Sep 2019 17:24:51 +0200 Message-Id: <20190906152455.22757-2-mic@digikod.net> X-Mailer: git-send-email 2.23.0.rc1 In-Reply-To: <20190906152455.22757-1-mic@digikod.net> References: <20190906152455.22757-1-mic@digikod.net> MIME-Version: 1.0 X-Antivirus: Dr.Web (R) for Unix mail servers drweb plugin ver.6.0.2.8 X-Antivirus-Code: 0x100000 Sender: linux-fsdevel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-fsdevel@vger.kernel.org When the O_MAYEXEC flag is passed, sys_open() may be subject to additional restrictions depending on a security policy implemented by an LSM through the inode_permission hook. The underlying idea is to be able to restrict scripts interpretation according to a policy defined by the system administrator. For this to be possible, script interpreters must use the O_MAYEXEC flag appropriately. To be fully effective, these interpreters also need to handle the other ways to execute code (for which the kernel can't help): command line parameters (e.g., option -e for Perl), module loading (e.g., option -m for Python), stdin, file sourcing, environment variables, configuration files... According to the threat model, it may be acceptable to allow some script interpreters (e.g. Bash) to interpret commands from stdin, may it be a TTY or a pipe, because it may not be enough to (directly) perform syscalls. A simple security policy implementation is available in a following patch for Yama. This is an updated subset of the patch initially written by Vincent Strubel for CLIP OS: https://github.com/clipos-archive/src_platform_clip-patches/blob/f5cb330d6b684752e403b4e41b39f7004d88e561/1901_open_mayexec.patch This patch has been used for more than 10 years with customized script interpreters. Some examples can be found here: https://github.com/clipos-archive/clipos4_portage-overlay/search?q=O_MAYEXEC Changes since v1: * set __FMODE_EXEC when using O_MAYEXEC to make this information available through the new fanotify/FAN_OPEN_EXEC event (suggested by Jan Kara and Matthew Bobrowski) Signed-off-by: Mickaël Salaün Signed-off-by: Thibaut Sautereau Signed-off-by: Vincent Strubel Reviewed-by: Philippe Trébuchet Cc: Al Viro Cc: Kees Cook Cc: Mickaël Salaün --- fs/fcntl.c | 2 +- fs/open.c | 6 ++++++ include/linux/fcntl.h | 2 +- include/linux/fs.h | 2 ++ include/uapi/asm-generic/fcntl.h | 3 +++ 5 files changed, 13 insertions(+), 2 deletions(-) diff --git a/fs/fcntl.c b/fs/fcntl.c index 3d40771e8e7c..4cf05a2fd162 100644 --- a/fs/fcntl.c +++ b/fs/fcntl.c @@ -1031,7 +1031,7 @@ static int __init fcntl_init(void) * Exceptions: O_NONBLOCK is a two bit define on parisc; O_NDELAY * is defined as O_NONBLOCK on some platforms and not on others. */ - BUILD_BUG_ON(21 - 1 /* for O_RDONLY being 0 */ != + BUILD_BUG_ON(22 - 1 /* for O_RDONLY being 0 */ != HWEIGHT32( (VALID_OPEN_FLAGS & ~(O_NONBLOCK | O_NDELAY)) | __FMODE_EXEC | __FMODE_NONOTIFY)); diff --git a/fs/open.c b/fs/open.c index a59abe3c669a..1b9b6fedf7cd 100644 --- a/fs/open.c +++ b/fs/open.c @@ -989,6 +989,12 @@ static inline int build_open_flags(int flags, umode_t mode, struct open_flags *o acc_mode = 0; } + /* Check execution permissions on open. */ + if (flags & O_MAYEXEC) { + acc_mode |= MAY_OPENEXEC; + flags |= __FMODE_EXEC; + } + op->open_flag = flags; /* O_TRUNC implies we need access checks for write permissions */ diff --git a/include/linux/fcntl.h b/include/linux/fcntl.h index d019df946cb2..af88fb6c8313 100644 --- a/include/linux/fcntl.h +++ b/include/linux/fcntl.h @@ -9,7 +9,7 @@ (O_RDONLY | O_WRONLY | O_RDWR | O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC | \ O_APPEND | O_NDELAY | O_NONBLOCK | O_NDELAY | __O_SYNC | O_DSYNC | \ FASYNC | O_DIRECT | O_LARGEFILE | O_DIRECTORY | O_NOFOLLOW | \ - O_NOATIME | O_CLOEXEC | O_PATH | __O_TMPFILE) + O_NOATIME | O_CLOEXEC | O_PATH | __O_TMPFILE | O_MAYEXEC) #ifndef force_o_largefile #define force_o_largefile() (!IS_ENABLED(CONFIG_ARCH_32BIT_OFF_T)) diff --git a/include/linux/fs.h b/include/linux/fs.h index 997a530ff4e9..848f5711bdf0 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -99,6 +99,8 @@ typedef int (dio_iodone_t)(struct kiocb *iocb, loff_t offset, #define MAY_CHDIR 0x00000040 /* called from RCU mode, don't block */ #define MAY_NOT_BLOCK 0x00000080 +/* the inode is opened with O_MAYEXEC */ +#define MAY_OPENEXEC 0x00000100 /* * flags in file.f_mode. Note that FMODE_READ and FMODE_WRITE must correspond diff --git a/include/uapi/asm-generic/fcntl.h b/include/uapi/asm-generic/fcntl.h index 9dc0bf0c5a6e..cbb9425d6e7c 100644 --- a/include/uapi/asm-generic/fcntl.h +++ b/include/uapi/asm-generic/fcntl.h @@ -97,6 +97,9 @@ #define O_NDELAY O_NONBLOCK #endif +/* command execution from file is intended, check exec permissions */ +#define O_MAYEXEC 040000000 + #define F_DUPFD 0 /* dup */ #define F_GETFD 1 /* get close_on_exec */ #define F_SETFD 2 /* set/clear close_on_exec */ From patchwork Fri Sep 6 15:24:52 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= X-Patchwork-Id: 11135553 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 8406014ED for ; Fri, 6 Sep 2019 15:26:57 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 6F8122186A for ; Fri, 6 Sep 2019 15:26:57 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2405709AbfIFP0n (ORCPT ); Fri, 6 Sep 2019 11:26:43 -0400 Received: from smtp-sh2.infomaniak.ch ([128.65.195.6]:53743 "EHLO smtp-sh2.infomaniak.ch" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2404161AbfIFP0i (ORCPT ); Fri, 6 Sep 2019 11:26:38 -0400 Received: from smtp5.infomaniak.ch (smtp5.infomaniak.ch [83.166.132.18]) by smtp-sh2.infomaniak.ch (8.14.4/8.14.4/Debian-8+deb8u2) with ESMTP id x86FP5ah085900 (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Fri, 6 Sep 2019 17:25:05 +0200 Received: from localhost (ns3096276.ip-94-23-54.eu [94.23.54.103]) (authenticated bits=0) by smtp5.infomaniak.ch (8.14.5/8.14.5) with ESMTP id x86FP4Cs047634; Fri, 6 Sep 2019 17:25:04 +0200 From: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= To: linux-kernel@vger.kernel.org Cc: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , Aleksa Sarai , Alexei Starovoitov , Al Viro , Andy Lutomirski , Christian Heimes , Daniel Borkmann , Eric Chiang , Florian Weimer , James Morris , Jan Kara , Jann Horn , Jonathan Corbet , Kees Cook , Matthew Garrett , Matthew Wilcox , Michael Kerrisk , =?utf-8?q?Micka=C3=ABl_Sala=C3=BC?= =?utf-8?q?n?= , Mimi Zohar , =?utf-8?q?Philippe_Tr=C3=A9buchet?= , Scott Shell , Sean Christopherson , Shuah Khan , Song Liu , Steve Dower , Steve Grubb , Thibaut Sautereau , Vincent Strubel , Yves-Alexis Perez , kernel-hardening@lists.openwall.com, linux-api@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org Subject: [PATCH v2 2/5] fs: Add a MAY_EXECMOUNT flag to infer the noexec mount propertie Date: Fri, 6 Sep 2019 17:24:52 +0200 Message-Id: <20190906152455.22757-3-mic@digikod.net> X-Mailer: git-send-email 2.23.0.rc1 In-Reply-To: <20190906152455.22757-1-mic@digikod.net> References: <20190906152455.22757-1-mic@digikod.net> MIME-Version: 1.0 X-Antivirus: Dr.Web (R) for Unix mail servers drweb plugin ver.6.0.2.8 X-Antivirus-Code: 0x100000 Sender: linux-fsdevel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-fsdevel@vger.kernel.org An LSM doesn't get path information related to an access request to open an inode. This new (internal) MAY_EXECMOUNT flag enables an LSM to check if the underlying mount point of an inode is marked as executable. This is useful to implement a security policy taking advantage of the noexec mount option. This flag is set according to path_noexec(), which checks if a mount point is mounted with MNT_NOEXEC or if the underlying superblock is SB_I_NOEXEC. Signed-off-by: Mickaël Salaün Reviewed-by: Philippe Trébuchet Reviewed-by: Thibaut Sautereau Cc: Al Viro Cc: Kees Cook Cc: Mickaël Salaün --- fs/namei.c | 2 ++ include/linux/fs.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/fs/namei.c b/fs/namei.c index 209c51a5226c..0a6b9483d0cb 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2968,6 +2968,8 @@ static int may_open(const struct path *path, int acc_mode, int flag) break; } + /* Pass the mount point executability. */ + acc_mode |= path_noexec(path) ? 0 : MAY_EXECMOUNT; error = inode_permission(inode, MAY_OPEN | acc_mode); if (error) return error; diff --git a/include/linux/fs.h b/include/linux/fs.h index 848f5711bdf0..e57609dac8dd 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -101,6 +101,8 @@ typedef int (dio_iodone_t)(struct kiocb *iocb, loff_t offset, #define MAY_NOT_BLOCK 0x00000080 /* the inode is opened with O_MAYEXEC */ #define MAY_OPENEXEC 0x00000100 +/* the mount point is marked as executable */ +#define MAY_EXECMOUNT 0x00000200 /* * flags in file.f_mode. Note that FMODE_READ and FMODE_WRITE must correspond From patchwork Fri Sep 6 15:24:53 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= X-Patchwork-Id: 11135549 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id A840014ED for ; Fri, 6 Sep 2019 15:26:55 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 8A86C214E0 for ; Fri, 6 Sep 2019 15:26:55 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2405725AbfIFP0o (ORCPT ); Fri, 6 Sep 2019 11:26:44 -0400 Received: from smtp-sh2.infomaniak.ch ([128.65.195.6]:33283 "EHLO smtp-sh2.infomaniak.ch" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2404269AbfIFP0i (ORCPT ); Fri, 6 Sep 2019 11:26:38 -0400 Received: from smtp5.infomaniak.ch (smtp5.infomaniak.ch [83.166.132.18]) by smtp-sh2.infomaniak.ch (8.14.4/8.14.4/Debian-8+deb8u2) with ESMTP id x86FP7rm085954 (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Fri, 6 Sep 2019 17:25:07 +0200 Received: from localhost (ns3096276.ip-94-23-54.eu [94.23.54.103]) (authenticated bits=0) by smtp5.infomaniak.ch (8.14.5/8.14.5) with ESMTP id x86FP6Sd047657; Fri, 6 Sep 2019 17:25:06 +0200 From: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= To: linux-kernel@vger.kernel.org Cc: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , Aleksa Sarai , Alexei Starovoitov , Al Viro , Andy Lutomirski , Christian Heimes , Daniel Borkmann , Eric Chiang , Florian Weimer , James Morris , Jan Kara , Jann Horn , Jonathan Corbet , Kees Cook , Matthew Garrett , Matthew Wilcox , Michael Kerrisk , =?utf-8?q?Micka=C3=ABl_Sala=C3=BC?= =?utf-8?q?n?= , Mimi Zohar , =?utf-8?q?Philippe_Tr=C3=A9buchet?= , Scott Shell , Sean Christopherson , Shuah Khan , Song Liu , Steve Dower , Steve Grubb , Thibaut Sautereau , Vincent Strubel , Yves-Alexis Perez , kernel-hardening@lists.openwall.com, linux-api@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org Subject: [PATCH v2 3/5] fs: Enable to enforce noexec mounts or file exec through O_MAYEXEC Date: Fri, 6 Sep 2019 17:24:53 +0200 Message-Id: <20190906152455.22757-4-mic@digikod.net> X-Mailer: git-send-email 2.23.0.rc1 In-Reply-To: <20190906152455.22757-1-mic@digikod.net> References: <20190906152455.22757-1-mic@digikod.net> MIME-Version: 1.0 X-Antivirus: Dr.Web (R) for Unix mail servers drweb plugin ver.6.0.2.8 X-Antivirus-Code: 0x100000 Sender: linux-fsdevel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-fsdevel@vger.kernel.org Enable to either propagate the mount options from the underlying VFS mount to prevent execution, or to propagate the file execute permission. This may allow a script interpreter to check execution permissions before reading commands from a file. The main goal is to be able to protect the kernel by restricting arbitrary syscalls that an attacker could perform with a crafted binary or certain script languages. It also improves multilevel isolation by reducing the ability of an attacker to use side channels with specific code. These restrictions can natively be enforced for ELF binaries (with the noexec mount option) but require this kernel extension to properly handle scripts (e.g., Python, Perl). Add a new sysctl fs.open_mayexec_enforce to control this behavior. A following patch adds documentation. Changes since v1: * move code from Yama to the FS subsystem (suggested by Kees Cook) * make omayexec_inode_permission() static (suggested by Jann Horn) * use mode 0600 for the sysctl * only match regular files (not directories nor other types), which follows the same semantic as commit 73601ea5b7b1 ("fs/open.c: allow opening only regular files during execve()") Signed-off-by: Mickaël Salaün Reviewed-by: Philippe Trébuchet Reviewed-by: Thibaut Sautereau Cc: Al Viro Cc: Kees Cook Cc: Mickaël Salaün --- fs/namei.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/fs.h | 3 ++ kernel/sysctl.c | 7 +++++ 3 files changed, 78 insertions(+) diff --git a/fs/namei.c b/fs/namei.c index 0a6b9483d0cb..abd29a76ecef 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -39,6 +39,7 @@ #include #include #include +#include #include "internal.h" #include "mount.h" @@ -411,6 +412,34 @@ static int sb_permission(struct super_block *sb, struct inode *inode, int mask) return 0; } +#define OMAYEXEC_ENFORCE_NONE 0 +#define OMAYEXEC_ENFORCE_MOUNT (1 << 0) +#define OMAYEXEC_ENFORCE_FILE (1 << 1) +#define _OMAYEXEC_LAST OMAYEXEC_ENFORCE_FILE +#define _OMAYEXEC_MASK ((_OMAYEXEC_LAST << 1) - 1) + +/** + * omayexec_inode_permission - check O_MAYEXEC before accessing an inode + * @inode: inode structure to check + * @mask: permission mask + * + * Return 0 if access is permitted, -EACCES otherwise. + */ +static int omayexec_inode_permission(struct inode *inode, int mask) +{ + if (!(mask & MAY_OPENEXEC)) + return 0; + + if ((sysctl_omayexec_enforce & OMAYEXEC_ENFORCE_MOUNT) && + !(mask & MAY_EXECMOUNT)) + return -EACCES; + + if (sysctl_omayexec_enforce & OMAYEXEC_ENFORCE_FILE) + return generic_permission(inode, MAY_EXEC); + + return 0; +} + /** * inode_permission - Check for access rights to a given inode * @inode: Inode to check permission on @@ -454,10 +483,48 @@ int inode_permission(struct inode *inode, int mask) if (retval) return retval; + retval = omayexec_inode_permission(inode, mask); + if (retval) + return retval; + return security_inode_permission(inode, mask); } EXPORT_SYMBOL(inode_permission); +/* + * Handle open_mayexec_enforce sysctl + */ +#ifdef CONFIG_SYSCTL +int proc_omayexec(struct ctl_table *table, int write, void __user *buffer, + size_t *lenp, loff_t *ppos) +{ + int error; + + if (write) { + struct ctl_table table_copy; + int tmp_mayexec_enforce; + + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + tmp_mayexec_enforce = *((int *)table->data); + table_copy = *table; + /* do not erase sysctl_omayexec_enforce */ + table_copy.data = &tmp_mayexec_enforce; + error = proc_dointvec(&table_copy, write, buffer, lenp, ppos); + if (error) + return error; + if ((tmp_mayexec_enforce | _OMAYEXEC_MASK) != _OMAYEXEC_MASK) + return -EINVAL; + *((int *)table->data) = tmp_mayexec_enforce; + } else { + error = proc_dointvec(table, write, buffer, lenp, ppos); + if (error) + return error; + } + return 0; +} +#endif + /** * path_get - get a reference to a path * @path: path to get the reference to @@ -887,6 +954,7 @@ int sysctl_protected_symlinks __read_mostly = 0; int sysctl_protected_hardlinks __read_mostly = 0; int sysctl_protected_fifos __read_mostly; int sysctl_protected_regular __read_mostly; +int sysctl_omayexec_enforce __read_mostly = OMAYEXEC_ENFORCE_NONE; /** * may_follow_link - Check symlink following for unsafe situations diff --git a/include/linux/fs.h b/include/linux/fs.h index e57609dac8dd..735f5950cfed 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -81,6 +81,7 @@ extern int sysctl_protected_symlinks; extern int sysctl_protected_hardlinks; extern int sysctl_protected_fifos; extern int sysctl_protected_regular; +extern int sysctl_omayexec_enforce; typedef __kernel_rwf_t rwf_t; @@ -3452,6 +3453,8 @@ int proc_nr_dentry(struct ctl_table *table, int write, void __user *buffer, size_t *lenp, loff_t *ppos); int proc_nr_inodes(struct ctl_table *table, int write, void __user *buffer, size_t *lenp, loff_t *ppos); +int proc_omayexec(struct ctl_table *table, int write, void __user *buffer, + size_t *lenp, loff_t *ppos); int __init get_filesystem_list(char *buf); #define __FMODE_EXEC ((__force int) FMODE_EXEC) diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 078950d9605b..eaaeb229a828 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -1911,6 +1911,13 @@ static struct ctl_table fs_table[] = { .extra1 = SYSCTL_ZERO, .extra2 = &two, }, + { + .procname = "open_mayexec_enforce", + .data = &sysctl_omayexec_enforce, + .maxlen = sizeof(int), + .mode = 0600, + .proc_handler = proc_omayexec, + }, #if defined(CONFIG_BINFMT_MISC) || defined(CONFIG_BINFMT_MISC_MODULE) { .procname = "binfmt_misc", From patchwork Fri Sep 6 15:24:54 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= X-Patchwork-Id: 11135561 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 7C89F14ED for ; Fri, 6 Sep 2019 15:27:00 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 4ABEA214E0 for ; Fri, 6 Sep 2019 15:27:00 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2405760AbfIFP06 (ORCPT ); Fri, 6 Sep 2019 11:26:58 -0400 Received: from smtp-sh2.infomaniak.ch ([128.65.195.6]:56639 "EHLO smtp-sh2.infomaniak.ch" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2404175AbfIFP0i (ORCPT ); Fri, 6 Sep 2019 11:26:38 -0400 Received: from smtp5.infomaniak.ch (smtp5.infomaniak.ch [83.166.132.18]) by smtp-sh2.infomaniak.ch (8.14.4/8.14.4/Debian-8+deb8u2) with ESMTP id x86FP8CG085979 (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Fri, 6 Sep 2019 17:25:08 +0200 Received: from localhost (ns3096276.ip-94-23-54.eu [94.23.54.103]) (authenticated bits=0) by smtp5.infomaniak.ch (8.14.5/8.14.5) with ESMTP id x86FP8Ai047682; Fri, 6 Sep 2019 17:25:08 +0200 From: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= To: linux-kernel@vger.kernel.org Cc: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , Aleksa Sarai , Alexei Starovoitov , Al Viro , Andy Lutomirski , Christian Heimes , Daniel Borkmann , Eric Chiang , Florian Weimer , James Morris , Jan Kara , Jann Horn , Jonathan Corbet , Kees Cook , Matthew Garrett , Matthew Wilcox , Michael Kerrisk , =?utf-8?q?Micka=C3=ABl_Sala=C3=BC?= =?utf-8?q?n?= , Mimi Zohar , =?utf-8?q?Philippe_Tr=C3=A9buchet?= , Scott Shell , Sean Christopherson , Shuah Khan , Song Liu , Steve Dower , Steve Grubb , Thibaut Sautereau , Vincent Strubel , Yves-Alexis Perez , kernel-hardening@lists.openwall.com, linux-api@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org Subject: [PATCH v2 4/5] selftest/exec: Add tests for O_MAYEXEC enforcing Date: Fri, 6 Sep 2019 17:24:54 +0200 Message-Id: <20190906152455.22757-5-mic@digikod.net> X-Mailer: git-send-email 2.23.0.rc1 In-Reply-To: <20190906152455.22757-1-mic@digikod.net> References: <20190906152455.22757-1-mic@digikod.net> MIME-Version: 1.0 X-Antivirus: Dr.Web (R) for Unix mail servers drweb plugin ver.6.0.2.8 X-Antivirus-Code: 0x100000 Sender: linux-fsdevel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-fsdevel@vger.kernel.org Test propagation of noexec mount points or file executability through files open with or without O_MAYEXEC. Changes since v1: * move tests from yama to exec * fix _GNU_SOURCE in kselftest_harness.h * add a new test sysctl_access_write to check if CAP_MAC_ADMIN is taken into account * test directory execution which is always forbidden since commit 73601ea5b7b1 ("fs/open.c: allow opening only regular files during execve()"), and also check that even the root user can not bypass file execution checks * make sure delete_workspace() always as enough right to succeed * cosmetic cleanup Signed-off-by: Mickaël Salaün Cc: Kees Cook Cc: Mickaël Salaün Cc: Shuah Khan --- tools/testing/selftests/exec/.gitignore | 1 + tools/testing/selftests/exec/Makefile | 4 +- tools/testing/selftests/exec/omayexec.c | 317 ++++++++++++++++++++ tools/testing/selftests/kselftest_harness.h | 3 + 4 files changed, 324 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/exec/omayexec.c diff --git a/tools/testing/selftests/exec/.gitignore b/tools/testing/selftests/exec/.gitignore index b02279da6fa1..78487c987c07 100644 --- a/tools/testing/selftests/exec/.gitignore +++ b/tools/testing/selftests/exec/.gitignore @@ -1,3 +1,4 @@ +/omayexec subdir* script* execveat diff --git a/tools/testing/selftests/exec/Makefile b/tools/testing/selftests/exec/Makefile index 33339e31e365..a62b9ca306e7 100644 --- a/tools/testing/selftests/exec/Makefile +++ b/tools/testing/selftests/exec/Makefile @@ -3,7 +3,7 @@ CFLAGS = -Wall CFLAGS += -Wno-nonnull CFLAGS += -D_GNU_SOURCE -TEST_GEN_PROGS := execveat +TEST_GEN_PROGS := execveat omayexec TEST_GEN_FILES := execveat.symlink execveat.denatured script subdir # Makefile is a run-time dependency, since it's accessed by the execveat test TEST_FILES := Makefile @@ -26,3 +26,5 @@ $(OUTPUT)/execveat.denatured: $(OUTPUT)/execveat cp $< $@ chmod -x $@ +$(OUTPUT)/omayexec: omayexec.c ../kselftest_harness.h + $(CC) $(CFLAGS) -Wl,-no-as-needed $(LDFLAGS) -lcap $< -o $@ diff --git a/tools/testing/selftests/exec/omayexec.c b/tools/testing/selftests/exec/omayexec.c new file mode 100644 index 000000000000..e4307b5a5417 --- /dev/null +++ b/tools/testing/selftests/exec/omayexec.c @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * O_MAYEXEC tests + * + * Copyright © 2018-2019 ANSSI + * + * Author: Mickaël Salaün + */ + +#include +#include /* O_CLOEXEC */ +#include +#include +#include /* strlen */ +#include +#include +#include /* mkdir */ +#include /* unlink, rmdir */ + +#include "../kselftest_harness.h" + +#ifndef O_MAYEXEC +#define O_MAYEXEC 040000000 +#endif + +#define SYSCTL_MAYEXEC "/proc/sys/fs/open_mayexec_enforce" + +#define BIN_DIR "./test-mount" +#define BIN_PATH BIN_DIR "/file" +#define DIR_PATH BIN_DIR "/directory" + +#define ALLOWED 1 +#define DENIED 0 + +static void ignore_dac(struct __test_metadata *_metadata, int override) +{ + cap_t caps; + const cap_value_t cap_val[2] = { + CAP_DAC_OVERRIDE, + CAP_DAC_READ_SEARCH, + }; + + caps = cap_get_proc(); + ASSERT_TRUE(!!caps); + ASSERT_FALSE(cap_set_flag(caps, CAP_EFFECTIVE, 2, cap_val, + override ? CAP_SET : CAP_CLEAR)); + ASSERT_FALSE(cap_set_proc(caps)); + EXPECT_FALSE(cap_free(caps)); +} + +static void ignore_mac(struct __test_metadata *_metadata, int override) +{ + cap_t caps; + const cap_value_t cap_val[1] = { + CAP_MAC_ADMIN, + }; + + caps = cap_get_proc(); + ASSERT_TRUE(!!caps); + ASSERT_FALSE(cap_set_flag(caps, CAP_EFFECTIVE, 1, cap_val, + override ? CAP_SET : CAP_CLEAR)); + ASSERT_FALSE(cap_set_proc(caps)); + EXPECT_FALSE(cap_free(caps)); +} + +static void test_omx(struct __test_metadata *_metadata, + const char *const path, const int exec_allowed) +{ + int fd; + + /* without O_MAYEXEC */ + fd = open(path, O_RDONLY | O_CLOEXEC); + ASSERT_NE(-1, fd); + EXPECT_FALSE(close(fd)); + + /* with O_MAYEXEC */ + fd = open(path, O_RDONLY | O_CLOEXEC | O_MAYEXEC); + if (exec_allowed) { + /* open should succeed */ + ASSERT_NE(-1, fd); + EXPECT_FALSE(close(fd)); + } else { + /* open should return EACCES */ + ASSERT_EQ(-1, fd); + ASSERT_EQ(EACCES, errno); + } +} + +static void test_omx_dir_file(struct __test_metadata *_metadata, + const char *const dir_path, const char *const file_path, + const int exec_allowed) +{ + /* + * directory execution is always denied since commit 73601ea5b7b1 + * ("fs/open.c: allow opening only regular files during execve()") + */ + test_omx(_metadata, dir_path, DENIED); + test_omx(_metadata, file_path, exec_allowed); +} + +static void test_dir_file(struct __test_metadata *_metadata, + const char *const dir_path, const char *const file_path, + const int exec_allowed) +{ + /* test as root */ + ignore_dac(_metadata, 1); + test_omx_dir_file(_metadata, dir_path, file_path, exec_allowed); + + /* test without bypass */ + ignore_dac(_metadata, 0); + test_omx_dir_file(_metadata, dir_path, file_path, exec_allowed); +} + +static void sysctl_write(struct __test_metadata *_metadata, + const char *path, const char *value) +{ + int fd; + size_t len_value; + ssize_t len_wrote; + + fd = open(path, O_WRONLY | O_CLOEXEC); + ASSERT_NE(-1, fd); + len_value = strlen(value); + len_wrote = write(fd, value, len_value); + ASSERT_EQ(len_wrote, len_value); + EXPECT_FALSE(close(fd)); +} + +static void create_workspace(struct __test_metadata *_metadata, + int mount_exec, int file_exec) +{ + int fd; + + /* + * Cleanup previous workspace if any error previously happened (don't + * check errors). + */ + umount(BIN_DIR); + rmdir(BIN_DIR); + + /* create a clean mount point */ + ASSERT_FALSE(mkdir(BIN_DIR, 00700)); + ASSERT_FALSE(mount("test", BIN_DIR, "tmpfs", + MS_MGC_VAL | (mount_exec ? 0 : MS_NOEXEC), + "mode=0700,size=4k")); + + /* create a test file */ + fd = open(BIN_PATH, O_CREAT | O_RDONLY | O_CLOEXEC, + file_exec ? 00500 : 00400); + ASSERT_NE(-1, fd); + EXPECT_NE(-1, close(fd)); + + /* create a test directory */ + ASSERT_FALSE(mkdir(DIR_PATH, file_exec ? 00500 : 00400)); +} + +static void delete_workspace(struct __test_metadata *_metadata) +{ + ignore_mac(_metadata, 1); + sysctl_write(_metadata, SYSCTL_MAYEXEC, "0"); + + /* no need to unlink BIN_PATH nor DIR_PATH */ + ASSERT_FALSE(umount(BIN_DIR)); + ASSERT_FALSE(rmdir(BIN_DIR)); +} + +FIXTURE_DATA(mount_exec_file_exec) { }; + +FIXTURE_SETUP(mount_exec_file_exec) +{ + create_workspace(_metadata, 1, 1); +} + +FIXTURE_TEARDOWN(mount_exec_file_exec) +{ + delete_workspace(_metadata); +} + +TEST_F(mount_exec_file_exec, mount) +{ + /* enforce mount exec check */ + sysctl_write(_metadata, SYSCTL_MAYEXEC, "1"); + test_dir_file(_metadata, DIR_PATH, BIN_PATH, ALLOWED); +} + +TEST_F(mount_exec_file_exec, file) +{ + /* enforce file exec check */ + sysctl_write(_metadata, SYSCTL_MAYEXEC, "2"); + test_dir_file(_metadata, DIR_PATH, BIN_PATH, ALLOWED); +} + +TEST_F(mount_exec_file_exec, mount_file) +{ + /* enforce mount and file exec check */ + sysctl_write(_metadata, SYSCTL_MAYEXEC, "3"); + test_dir_file(_metadata, DIR_PATH, BIN_PATH, ALLOWED); +} + +FIXTURE_DATA(mount_exec_file_noexec) { }; + +FIXTURE_SETUP(mount_exec_file_noexec) +{ + create_workspace(_metadata, 1, 0); +} + +FIXTURE_TEARDOWN(mount_exec_file_noexec) +{ + delete_workspace(_metadata); +} + +TEST_F(mount_exec_file_noexec, mount) +{ + /* enforce mount exec check */ + sysctl_write(_metadata, SYSCTL_MAYEXEC, "1"); + test_dir_file(_metadata, DIR_PATH, BIN_PATH, ALLOWED); +} + +TEST_F(mount_exec_file_noexec, file) +{ + /* enforce file exec check */ + sysctl_write(_metadata, SYSCTL_MAYEXEC, "2"); + test_dir_file(_metadata, DIR_PATH, BIN_PATH, DENIED); +} + +TEST_F(mount_exec_file_noexec, mount_file) +{ + /* enforce mount and file exec check */ + sysctl_write(_metadata, SYSCTL_MAYEXEC, "3"); + test_dir_file(_metadata, DIR_PATH, BIN_PATH, DENIED); +} + +FIXTURE_DATA(mount_noexec_file_exec) { }; + +FIXTURE_SETUP(mount_noexec_file_exec) +{ + create_workspace(_metadata, 0, 1); +} + +FIXTURE_TEARDOWN(mount_noexec_file_exec) +{ + delete_workspace(_metadata); +} + +TEST_F(mount_noexec_file_exec, mount) +{ + /* enforce mount exec check */ + sysctl_write(_metadata, SYSCTL_MAYEXEC, "1"); + test_dir_file(_metadata, DIR_PATH, BIN_PATH, DENIED); +} + +TEST_F(mount_noexec_file_exec, file) +{ + /* enforce file exec check */ + sysctl_write(_metadata, SYSCTL_MAYEXEC, "2"); + test_dir_file(_metadata, DIR_PATH, BIN_PATH, ALLOWED); +} + +TEST_F(mount_noexec_file_exec, mount_file) +{ + /* enforce mount and file exec check */ + sysctl_write(_metadata, SYSCTL_MAYEXEC, "3"); + test_dir_file(_metadata, DIR_PATH, BIN_PATH, DENIED); +} + +FIXTURE_DATA(mount_noexec_file_noexec) { }; + +FIXTURE_SETUP(mount_noexec_file_noexec) +{ + create_workspace(_metadata, 0, 0); +} + +FIXTURE_TEARDOWN(mount_noexec_file_noexec) +{ + delete_workspace(_metadata); +} + +TEST_F(mount_noexec_file_noexec, mount) +{ + /* enforce mount exec check */ + sysctl_write(_metadata, SYSCTL_MAYEXEC, "1"); + test_dir_file(_metadata, DIR_PATH, BIN_PATH, DENIED); +} + +TEST_F(mount_noexec_file_noexec, file) +{ + /* enforce file exec check */ + sysctl_write(_metadata, SYSCTL_MAYEXEC, "2"); + test_dir_file(_metadata, DIR_PATH, BIN_PATH, DENIED); +} + +TEST_F(mount_noexec_file_noexec, mount_file) +{ + /* enforce mount and file exec check */ + sysctl_write(_metadata, SYSCTL_MAYEXEC, "3"); + test_dir_file(_metadata, DIR_PATH, BIN_PATH, DENIED); +} + +TEST(sysctl_access_write) +{ + int fd; + ssize_t len_wrote; + + ignore_mac(_metadata, 1); + sysctl_write(_metadata, SYSCTL_MAYEXEC, "0"); + + ignore_mac(_metadata, 0); + fd = open(SYSCTL_MAYEXEC, O_WRONLY | O_CLOEXEC); + ASSERT_NE(-1, fd); + len_wrote = write(fd, "0", 1); + ASSERT_EQ(len_wrote, -1); + EXPECT_FALSE(close(fd)); + + ignore_mac(_metadata, 1); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/kselftest_harness.h b/tools/testing/selftests/kselftest_harness.h index 5336b26506ab..6ae816fa2f62 100644 --- a/tools/testing/selftests/kselftest_harness.h +++ b/tools/testing/selftests/kselftest_harness.h @@ -50,7 +50,10 @@ #ifndef __KSELFTEST_HARNESS_H #define __KSELFTEST_HARNESS_H +#ifndef _GNU_SOURCE #define _GNU_SOURCE +#endif + #include #include #include From patchwork Fri Sep 6 15:24:55 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= X-Patchwork-Id: 11135565 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id D117F924 for ; Fri, 6 Sep 2019 15:27:05 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id BC3EC214E0 for ; Fri, 6 Sep 2019 15:27:05 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2404327AbfIFP0i (ORCPT ); Fri, 6 Sep 2019 11:26:38 -0400 Received: from smtp-sh2.infomaniak.ch ([128.65.195.6]:57071 "EHLO smtp-sh2.infomaniak.ch" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2404126AbfIFP0i (ORCPT ); Fri, 6 Sep 2019 11:26:38 -0400 Received: from smtp5.infomaniak.ch (smtp5.infomaniak.ch [83.166.132.18]) by smtp-sh2.infomaniak.ch (8.14.4/8.14.4/Debian-8+deb8u2) with ESMTP id x86FP9Ih086018 (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Fri, 6 Sep 2019 17:25:09 +0200 Received: from localhost (ns3096276.ip-94-23-54.eu [94.23.54.103]) (authenticated bits=0) by smtp5.infomaniak.ch (8.14.5/8.14.5) with ESMTP id x86FP9Sx047701; Fri, 6 Sep 2019 17:25:09 +0200 From: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= To: linux-kernel@vger.kernel.org Cc: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , Aleksa Sarai , Alexei Starovoitov , Al Viro , Andy Lutomirski , Christian Heimes , Daniel Borkmann , Eric Chiang , Florian Weimer , James Morris , Jan Kara , Jann Horn , Jonathan Corbet , Kees Cook , Matthew Garrett , Matthew Wilcox , Michael Kerrisk , =?utf-8?q?Micka=C3=ABl_Sala=C3=BC?= =?utf-8?q?n?= , Mimi Zohar , =?utf-8?q?Philippe_Tr=C3=A9buchet?= , Scott Shell , Sean Christopherson , Shuah Khan , Song Liu , Steve Dower , Steve Grubb , Thibaut Sautereau , Vincent Strubel , Yves-Alexis Perez , kernel-hardening@lists.openwall.com, linux-api@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org Subject: [PATCH v2 5/5] doc: Add documentation for the fs.open_mayexec_enforce sysctl Date: Fri, 6 Sep 2019 17:24:55 +0200 Message-Id: <20190906152455.22757-6-mic@digikod.net> X-Mailer: git-send-email 2.23.0.rc1 In-Reply-To: <20190906152455.22757-1-mic@digikod.net> References: <20190906152455.22757-1-mic@digikod.net> MIME-Version: 1.0 X-Antivirus: Dr.Web (R) for Unix mail servers drweb plugin ver.6.0.2.8 X-Antivirus-Code: 0x100000 Sender: linux-fsdevel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-fsdevel@vger.kernel.org Changes since v1: * move from LSM/Yama to sysctl/fs Signed-off-by: Mickaël Salaün Reviewed-by: Philippe Trébuchet Reviewed-by: Thibaut Sautereau Cc: Jonathan Corbet Cc: Kees Cook Cc: Mickaël Salaün --- Documentation/admin-guide/sysctl/fs.rst | 43 +++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/Documentation/admin-guide/sysctl/fs.rst b/Documentation/admin-guide/sysctl/fs.rst index 2a45119e3331..f2f5bbe428d6 100644 --- a/Documentation/admin-guide/sysctl/fs.rst +++ b/Documentation/admin-guide/sysctl/fs.rst @@ -37,6 +37,7 @@ Currently, these files are in /proc/sys/fs: - inode-nr - inode-state - nr_open +- open_mayexec_enforce - overflowuid - overflowgid - pipe-user-pages-hard @@ -165,6 +166,48 @@ system needs to prune the inode list instead of allocating more. +open_mayexec_enforce +-------------------- + +The ``O_MAYEXEC`` flag can be passed to :manpage:`open(2)` to only open regular +files that are expected to be executable. If the file is not identified as +executable, then the syscall returns -EACCES. This may allow a script +interpreter to check executable permission before reading commands from a file. +One interesting use case is to enforce a "write xor execute" policy through +interpreters. + +Thanks to this flag, it is possible to enforce the ``noexec`` mount option +(i.e. the underlying mount point of the file is mounted with MNT_NOEXEC or its +underlying superblock is SB_I_NOEXEC) not only on ELF binaries but also on +scripts. This may be possible thanks to script interpreters using the +``O_MAYEXEC`` flag. The executable permission is then checked before reading +commands from a file, and thus can enforce the ``noexec`` at the interpreter +level by propagating this security policy to the scripts. To be fully +effective, these interpreters also need to handle the other ways to execute +code (for which the kernel can't help): command line parameters (e.g., option +``-e`` for Perl), module loading (e.g., option ``-m`` for Python), stdin, file +sourcing, environment variables, configuration files... According to the +threat model, it may be acceptable to allow some script interpreters (e.g. +Bash) to interpret commands from stdin, may it be a TTY or a pipe, because it +may not be enough to (directly) perform syscalls. + +There is two complementary security policies: enforce the ``noexec`` mount +option, or enforce executable file permission. These policies are handled by +the ``fs.open_mayexec_enforce`` sysctl (writable only with ``CAP_MAC_ADMIN``) +as a bitmask: + +1 - mount restriction: + check that the mount options for the underlying VFS mount do not prevent + execution. + +2 - file permission restriction: + check that the to-be-opened file is marked as executable for the current + process (e.g., POSIX permissions). + +Code samples can be found in tools/testing/selftests/exec/omayexec.c and +https://github.com/clipos-archive/clipos4_portage-overlay/search?q=O_MAYEXEC . + + overflowgid & overflowuid -------------------------