From patchwork Tue May 5 15:31:51 2020 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: 11529437 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 255021392 for ; Tue, 5 May 2020 15:32:24 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 10F2720836 for ; Tue, 5 May 2020 15:32:24 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729508AbgEEPcX (ORCPT ); Tue, 5 May 2020 11:32:23 -0400 Received: from smtp-bc0f.mail.infomaniak.ch ([45.157.188.15]:44741 "EHLO smtp-bc0f.mail.infomaniak.ch" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1729281AbgEEPcX (ORCPT ); Tue, 5 May 2020 11:32:23 -0400 Received: from smtp-2-0001.mail.infomaniak.ch (unknown [10.5.36.108]) by smtp-2-3000.mail.infomaniak.ch (Postfix) with ESMTPS id 49GkHR5fPwzlhQFh; Tue, 5 May 2020 17:32:19 +0200 (CEST) Received: from localhost (unknown [94.23.54.103]) by smtp-2-0001.mail.infomaniak.ch (Postfix) with ESMTPA id 49GkHR2kgHzllqZW; Tue, 5 May 2020 17:32:19 +0200 (CEST) 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 , Deven Bowers , Eric Chiang , Florian Weimer , James Morris , Jan Kara , Jann Horn , Jonathan Corbet , Kees Cook , Lakshmi Ramasubramanian , 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 , Steve Dower , Steve Grubb , Thibaut Sautereau , Vincent Strubel , kernel-hardening@lists.openwall.com, linux-api@vger.kernel.org, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org Subject: [PATCH v5 1/6] fs: Add support for an O_MAYEXEC flag on openat2(2) Date: Tue, 5 May 2020 17:31:51 +0200 Message-Id: <20200505153156.925111-2-mic@digikod.net> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200505153156.925111-1-mic@digikod.net> References: <20200505153156.925111-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: owner-linux-security-module@vger.kernel.org Precedence: bulk List-ID: When the O_MAYEXEC flag is passed, openat2(2) may be subject to additional restrictions depending on a security policy managed by the kernel through a sysctl or implemented by an LSM thanks to the inode_permission hook. This new flag is ignored by open(2) and openat(2). 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: command line parameters (e.g., option -e for Perl), module loading (e.g., option -m for Python), stdin, file sourcing, environment variables, configuration files, etc. 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. Further documentation can be found in a following patch. A simple security policy implementation, configured through a dedicated sysctl, is available in a following patch. This is an updated subset of the patch initially written by Vincent Strubel for CLIP OS 4: https://github.com/clipos-archive/src_platform_clip-patches/blob/f5cb330d6b684752e403b4e41b39f7004d88e561/1901_open_mayexec.patch This patch has been used for more than 11 years with customized script interpreters. Some examples (with the original name O_MAYEXEC) can be found here: https://github.com/clipos-archive/clipos4_portage-overlay/search?q=O_MAYEXEC Signed-off-by: Mickaël Salaün Signed-off-by: Thibaut Sautereau Signed-off-by: Vincent Strubel Reviewed-by: Deven Bowers Cc: Aleksa Sarai Cc: Al Viro Cc: Kees Cook Signed-off-by: Vincent Strubel Signed-off-by: Thibaut Sautereau Signed-off-by: Mickaël Salaün Reviewed-by: Kees Cook --- Changes since v3: * Switch back to O_MAYEXEC, but only handle it with openat2(2) which checks unknown flags (suggested by Aleksa Sarai). Cf. https://lore.kernel.org/lkml/20200430015429.wuob7m5ofdewubui@yavin.dot.cyphar.com/ Changes since v2: * Replace O_MAYEXEC with RESOLVE_MAYEXEC from openat2(2). This change enables to not break existing application using bogus O_* flags that may be ignored by current kernels by using a new dedicated flag, only usable through openat2(2) (suggested by Jeff Layton). Using this flag will results in an error if the running kernel does not support it. User space needs to manage this case, as with other RESOLVE_* flags. The best effort approach to security (for most common distros) will simply consists of ignoring such an error and retry without RESOLVE_MAYEXEC. However, a fully controlled system may which to error out if such an inconsistency is detected. 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). --- fs/fcntl.c | 2 +- fs/open.c | 8 ++++++++ include/linux/fcntl.h | 2 +- include/linux/fs.h | 2 ++ include/uapi/asm-generic/fcntl.h | 7 +++++++ 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/fs/fcntl.c b/fs/fcntl.c index 2e4c0fa2074b..0357ad667563 100644 --- a/fs/fcntl.c +++ b/fs/fcntl.c @@ -1033,7 +1033,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 719b320ede52..f3f08a36d1d2 100644 --- a/fs/open.c +++ b/fs/open.c @@ -962,6 +962,8 @@ inline struct open_how build_open_how(int flags, umode_t mode) .mode = mode & S_IALLUGO, }; + /* O_MAYEXEC is ignored by syscalls relying on build_open_how(). */ + how.flags &= ~O_MAYEXEC; /* O_PATH beats everything else. */ if (how.flags & O_PATH) how.flags &= O_PATH_FLAGS; @@ -1029,6 +1031,12 @@ inline int build_open_flags(const struct open_how *how, struct open_flags *op) if (flags & __O_SYNC) flags |= O_DSYNC; + /* Checks 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 7bcdcf4f6ab2..e188a360fa5f 100644 --- a/include/linux/fcntl.h +++ b/include/linux/fcntl.h @@ -10,7 +10,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) /* List of all valid flags for the how->upgrade_mask argument: */ #define VALID_UPGRADE_FLAGS \ diff --git a/include/linux/fs.h b/include/linux/fs.h index 4f6f59b4f22a..313c934de9ee 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_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..bca90620119f 100644 --- a/include/uapi/asm-generic/fcntl.h +++ b/include/uapi/asm-generic/fcntl.h @@ -97,6 +97,13 @@ #define O_NDELAY O_NONBLOCK #endif +/* + * Code execution from file is intended, checks such permission. A simple + * policy can be enforced system-wide as explained in + * Documentation/admin-guide/sysctl/fs.rst . + */ +#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 Tue May 5 15:31:52 2020 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: 11529441 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 370D1139A for ; Tue, 5 May 2020 15:32:25 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 285E32084D for ; Tue, 5 May 2020 15:32:25 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1730028AbgEEPcY (ORCPT ); Tue, 5 May 2020 11:32:24 -0400 Received: from smtp-8fad.mail.infomaniak.ch ([83.166.143.173]:55715 "EHLO smtp-8fad.mail.infomaniak.ch" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1729760AbgEEPcX (ORCPT ); Tue, 5 May 2020 11:32:23 -0400 Received: from smtp-3-0001.mail.infomaniak.ch (unknown [10.4.36.108]) by smtp-2-3000.mail.infomaniak.ch (Postfix) with ESMTPS id 49GkHT2wK6zlhjXL; Tue, 5 May 2020 17:32:21 +0200 (CEST) Received: from localhost (unknown [94.23.54.103]) by smtp-3-0001.mail.infomaniak.ch (Postfix) with ESMTPA id 49GkHS4fpxzlsV54; Tue, 5 May 2020 17:32:20 +0200 (CEST) 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 , Deven Bowers , Eric Chiang , Florian Weimer , James Morris , Jan Kara , Jann Horn , Jonathan Corbet , Kees Cook , Lakshmi Ramasubramanian , 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 , Steve Dower , Steve Grubb , Thibaut Sautereau , Vincent Strubel , kernel-hardening@lists.openwall.com, linux-api@vger.kernel.org, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org Subject: [PATCH v5 2/6] fs: Add a MAY_EXECMOUNT flag to infer the noexec mount property Date: Tue, 5 May 2020 17:31:52 +0200 Message-Id: <20200505153156.925111-3-mic@digikod.net> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200505153156.925111-1-mic@digikod.net> References: <20200505153156.925111-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: owner-linux-security-module@vger.kernel.org Precedence: bulk List-ID: This new MAY_EXECMOUNT flag enables 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: Aleksa Sarai Cc: Al Viro Cc: Kees Cook Reviewed-by: Kees Cook --- 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 a320371899cf..33b6d372e74a 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2849,6 +2849,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 313c934de9ee..79435fca6c3e 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -103,6 +103,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 Tue May 5 15:31:53 2020 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: 11529457 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 344FB81 for ; Tue, 5 May 2020 15:32:48 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 209A22084D for ; Tue, 5 May 2020 15:32:48 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729281AbgEEPcr (ORCPT ); Tue, 5 May 2020 11:32:47 -0400 Received: from smtp-190a.mail.infomaniak.ch ([185.125.25.10]:58541 "EHLO smtp-190a.mail.infomaniak.ch" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1730012AbgEEPc0 (ORCPT ); Tue, 5 May 2020 11:32:26 -0400 Received: from smtp-3-0001.mail.infomaniak.ch (unknown [10.4.36.108]) by smtp-3-3000.mail.infomaniak.ch (Postfix) with ESMTPS id 49GkHV5NLFzlhTQ0; Tue, 5 May 2020 17:32:22 +0200 (CEST) Received: from localhost (unknown [94.23.54.103]) by smtp-3-0001.mail.infomaniak.ch (Postfix) with ESMTPA id 49GkHV1kgPzlsV51; Tue, 5 May 2020 17:32:22 +0200 (CEST) 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 , Deven Bowers , Eric Chiang , Florian Weimer , James Morris , Jan Kara , Jann Horn , Jonathan Corbet , Kees Cook , Lakshmi Ramasubramanian , 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 , Steve Dower , Steve Grubb , Thibaut Sautereau , Vincent Strubel , kernel-hardening@lists.openwall.com, linux-api@vger.kernel.org, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org Subject: [PATCH v5 3/6] fs: Enable to enforce noexec mounts or file exec through O_MAYEXEC Date: Tue, 5 May 2020 17:31:53 +0200 Message-Id: <20200505153156.925111-4-mic@digikod.net> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200505153156.925111-1-mic@digikod.net> References: <20200505153156.925111-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: owner-linux-security-module@vger.kernel.org Precedence: bulk List-ID: Enable to forbid access to files open with O_MAYEXEC. Thanks to the noexec option from the underlying VFS mount, or to the file execute permission, userspace can enforce these execution policies. This may allow script interpreters to check execution permission before reading commands from a file, or dynamic linkers to allow shared object loading. Add a new sysctl fs.open_mayexec_enforce to enable system administrators to enforce two complementary security policies according to the installed system: enforce the noexec mount option, and enforce executable file permission. Indeed, because of compatibility with installed systems, only system administrators are able to check that this new enforcement is in line with the system mount points and file permissions. A following patch adds documentation. For tailored Linux distributions, it is possible to enforce such restriction at build time thanks to the CONFIG_OMAYEXEC_STATIC option. The policy can then be configured with CONFIG_OMAYEXEC_ENFORCE_MOUNT and CONFIG_OMAYEXEC_ENFORCE_FILE. Being able to restrict execution also enables 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). To get a consistent execution policy, additional memory restrictions should also be enforced (e.g. thanks to SELinux). Signed-off-by: Mickaël Salaün Reviewed-by: Thibaut Sautereau Cc: Aleksa Sarai Cc: Al Viro Cc: Kees Cook --- Changes since v4: * Add kernel configuration options to enforce O_MAYEXEC at build time, and disable the sysctl in such case (requested by James Morris). * Reword commit message. Changes since v3: * Update comment with O_MAYEXEC. Changes since v2: * Cosmetic changes. 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()"). --- fs/namei.c | 87 +++++++++++++++++++++++++++++++++++++++++++++- include/linux/fs.h | 5 +++ kernel/sysctl.c | 9 +++++ security/Kconfig | 26 ++++++++++++++ 4 files changed, 126 insertions(+), 1 deletion(-) diff --git a/fs/namei.c b/fs/namei.c index 33b6d372e74a..70f179f6bc6c 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -39,6 +39,7 @@ #include #include #include +#include #include "internal.h" #include "mount.h" @@ -411,10 +412,90 @@ 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) + +#ifdef CONFIG_OMAYEXEC_STATIC +const int sysctl_omayexec_enforce = +#ifdef CONFIG_OMAYEXEC_ENFORCE_MOUNT + OMAYEXEC_ENFORCE_MOUNT | +#endif +#ifdef CONFIG_OMAYEXEC_ENFORCE_FILE + OMAYEXEC_ENFORCE_FILE | +#endif + OMAYEXEC_ENFORCE_NONE; +#else /* CONFIG_OMAYEXEC_STATIC */ +int sysctl_omayexec_enforce __read_mostly = OMAYEXEC_ENFORCE_NONE; +#endif /* CONFIG_OMAYEXEC_STATIC */ + +/* + * Handle open_mayexec_enforce sysctl + */ +#if defined(CONFIG_SYSCTL) && !defined(CONFIG_OMAYEXEC_STATIC) +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 + +/** + * omayexec_inode_permission - Check O_MAYEXEC before accessing an inode + * + * @inode: Inode to check permission on + * @mask: Right to check for (%MAY_OPENEXEC, %MAY_EXECMOUNT, %MAY_EXEC) + * + * Returns 0 if access is permitted, -EACCES otherwise. + */ +static inline 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 - * @mask: Right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC) + * @mask: Right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC, %MAY_OPENEXEC, + * %MAY_EXECMOUNT) * * Check for read/write/execute permissions on an inode. We use fs[ug]id for * this, letting us set arbitrary permissions for filesystem access without @@ -454,6 +535,10 @@ 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); diff --git a/include/linux/fs.h b/include/linux/fs.h index 79435fca6c3e..39c80a64d054 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -83,6 +83,9 @@ extern int sysctl_protected_symlinks; extern int sysctl_protected_hardlinks; extern int sysctl_protected_fifos; extern int sysctl_protected_regular; +#ifndef CONFIG_OMAYEXEC_STATIC +extern int sysctl_omayexec_enforce; +#endif typedef __kernel_rwf_t rwf_t; @@ -3545,6 +3548,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 8a176d8727a3..29bbf79f444c 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -1892,6 +1892,15 @@ static struct ctl_table fs_table[] = { .extra1 = SYSCTL_ZERO, .extra2 = &two, }, +#ifndef CONFIG_OMAYEXEC_STATIC + { + .procname = "open_mayexec_enforce", + .data = &sysctl_omayexec_enforce, + .maxlen = sizeof(int), + .mode = 0600, + .proc_handler = proc_omayexec, + }, +#endif #if defined(CONFIG_BINFMT_MISC) || defined(CONFIG_BINFMT_MISC_MODULE) { .procname = "binfmt_misc", diff --git a/security/Kconfig b/security/Kconfig index cd3cc7da3a55..d8fac9240d14 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -230,6 +230,32 @@ config STATIC_USERMODEHELPER_PATH If you wish for all usermode helper programs to be disabled, specify an empty string here (i.e. ""). +menuconfig OMAYEXEC_STATIC + tristate "Configure O_MAYEXEC behavior at build time" + ---help--- + Enable to enforce O_MAYEXEC at build time, and disable the dedicated + fs.open_mayexec_enforce sysctl. + + See Documentation/admin-guide/sysctl/fs.rst for more details. + +if OMAYEXEC_STATIC + +config OMAYEXEC_ENFORCE_MOUNT + bool "Mount restriction" + default y + ---help--- + Forbid opening files with the O_MAYEXEC option if their underlying VFS is + mounted with the noexec option or if their superblock forbids execution + of its content (e.g., /proc). + +config OMAYEXEC_ENFORCE_FILE + bool "File permission restriction" + ---help--- + Forbid opening files with the O_MAYEXEC option if they are not marked as + executable for the current process (e.g., POSIX permissions). + +endif # OMAYEXEC_STATIC + source "security/selinux/Kconfig" source "security/smack/Kconfig" source "security/tomoyo/Kconfig" From patchwork Tue May 5 15:31:54 2020 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: 11529463 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 3D55481 for ; Tue, 5 May 2020 15:32:53 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 2040B2084D for ; Tue, 5 May 2020 15:32:53 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729507AbgEEPcv (ORCPT ); Tue, 5 May 2020 11:32:51 -0400 Received: from smtp-42a9.mail.infomaniak.ch ([84.16.66.169]:55061 "EHLO smtp-42a9.mail.infomaniak.ch" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1730057AbgEEPc0 (ORCPT ); Tue, 5 May 2020 11:32:26 -0400 Received: from smtp-3-0001.mail.infomaniak.ch (unknown [10.4.36.108]) by smtp-2-3000.mail.infomaniak.ch (Postfix) with ESMTPS id 49GkHW71RXzlhVHW; Tue, 5 May 2020 17:32:23 +0200 (CEST) Received: from localhost (unknown [94.23.54.103]) by smtp-3-0001.mail.infomaniak.ch (Postfix) with ESMTPA id 49GkHW4Bb2zlsV51; Tue, 5 May 2020 17:32:23 +0200 (CEST) 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 , Deven Bowers , Eric Chiang , Florian Weimer , James Morris , Jan Kara , Jann Horn , Jonathan Corbet , Kees Cook , Lakshmi Ramasubramanian , 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 , Steve Dower , Steve Grubb , Thibaut Sautereau , Vincent Strubel , kernel-hardening@lists.openwall.com, linux-api@vger.kernel.org, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org Subject: [PATCH v5 4/6] selftest/openat2: Add tests for O_MAYEXEC enforcing Date: Tue, 5 May 2020 17:31:54 +0200 Message-Id: <20200505153156.925111-5-mic@digikod.net> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200505153156.925111-1-mic@digikod.net> References: <20200505153156.925111-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: owner-linux-security-module@vger.kernel.org Precedence: bulk List-ID: Test propagation of noexec mount points or file executability through files open with or without O_MAYEXEC, thanks to the fs.open_mayexec_enforce sysctl. Signed-off-by: Mickaël Salaün Reviewed-by: Thibaut Sautereau Cc: Aleksa Sarai Cc: Al Viro Cc: Kees Cook Cc: Shuah Khan --- Changes since v3: * Replace RESOLVE_MAYEXEC with O_MAYEXEC. * Add tests to check that O_MAYEXEC is ignored by open(2) and openat(2). Changes since v2: * Move tests from exec/ to openat2/ . * Replace O_MAYEXEC with RESOLVE_MAYEXEC from openat2(2). * Cleanup tests. 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. --- tools/testing/selftests/kselftest_harness.h | 3 + tools/testing/selftests/openat2/Makefile | 3 +- tools/testing/selftests/openat2/config | 1 + tools/testing/selftests/openat2/helpers.h | 1 + .../testing/selftests/openat2/omayexec_test.c | 330 ++++++++++++++++++ 5 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/openat2/config create mode 100644 tools/testing/selftests/openat2/omayexec_test.c diff --git a/tools/testing/selftests/kselftest_harness.h b/tools/testing/selftests/kselftest_harness.h index 2bb8c81fc0b4..f6e056ba4a13 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 diff --git a/tools/testing/selftests/openat2/Makefile b/tools/testing/selftests/openat2/Makefile index 4b93b1417b86..cb98bdb4d5b1 100644 --- a/tools/testing/selftests/openat2/Makefile +++ b/tools/testing/selftests/openat2/Makefile @@ -1,7 +1,8 @@ # SPDX-License-Identifier: GPL-2.0-or-later CFLAGS += -Wall -O2 -g -fsanitize=address -fsanitize=undefined -TEST_GEN_PROGS := openat2_test resolve_test rename_attack_test +LDLIBS += -lcap +TEST_GEN_PROGS := openat2_test resolve_test rename_attack_test omayexec_test include ../lib.mk diff --git a/tools/testing/selftests/openat2/config b/tools/testing/selftests/openat2/config new file mode 100644 index 000000000000..dd53c266bf52 --- /dev/null +++ b/tools/testing/selftests/openat2/config @@ -0,0 +1 @@ +CONFIG_SYSCTL=y diff --git a/tools/testing/selftests/openat2/helpers.h b/tools/testing/selftests/openat2/helpers.h index a6ea27344db2..1dcd3e1e2f38 100644 --- a/tools/testing/selftests/openat2/helpers.h +++ b/tools/testing/selftests/openat2/helpers.h @@ -9,6 +9,7 @@ #define _GNU_SOURCE #include +#include #include #include #include "../kselftest.h" diff --git a/tools/testing/selftests/openat2/omayexec_test.c b/tools/testing/selftests/openat2/omayexec_test.c new file mode 100644 index 000000000000..7052c852daf8 --- /dev/null +++ b/tools/testing/selftests/openat2/omayexec_test.c @@ -0,0 +1,330 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test O_MAYEXEC + * + * Copyright © 2018-2020 ANSSI + * + * Author: Mickaël Salaün + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "helpers.h" +#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_NE(NULL, caps); + ASSERT_EQ(0, cap_set_flag(caps, CAP_EFFECTIVE, 2, cap_val, + override ? CAP_SET : CAP_CLEAR)); + ASSERT_EQ(0, cap_set_proc(caps)); + EXPECT_EQ(0, 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_NE(NULL, caps); + ASSERT_EQ(0, cap_set_flag(caps, CAP_EFFECTIVE, 1, cap_val, + override ? CAP_SET : CAP_CLEAR)); + ASSERT_EQ(0, cap_set_proc(caps)); + EXPECT_EQ(0, cap_free(caps)); +} + +static void test_omx(struct __test_metadata *_metadata, + const char *const path, const int exec_allowed) +{ + struct open_how how = { + .flags = O_RDONLY | O_CLOEXEC, + }; + int fd; + + /* Opens without O_MAYEXEC. */ + fd = sys_openat2(AT_FDCWD, path, &how); + ASSERT_LE(0, fd); + EXPECT_EQ(0, close(fd)); + + how.flags |= O_MAYEXEC; + + /* Checks that O_MAYEXEC is ignored with open(2). */ + fd = open(path, how.flags); + ASSERT_LE(0, fd); + EXPECT_EQ(0, close(fd)); + + /* Checks that O_MAYEXEC is ignored with openat(2). */ + fd = openat(AT_FDCWD, path, how.flags); + ASSERT_LE(0, fd); + EXPECT_EQ(0, close(fd)); + + /* Opens with O_MAYEXEC. */ + fd = sys_openat2(AT_FDCWD, path, &how); + if (exec_allowed) { + ASSERT_LE(0, fd); + EXPECT_EQ(0, close(fd)); + } else { + ASSERT_EQ(-EACCES, fd); + } +} + +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) +{ + /* Tests as root. */ + ignore_dac(_metadata, 1); + test_omx_dir_file(_metadata, dir_path, file_path, exec_allowed); + + /* Tests 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_LE(0, fd); + len_value = strlen(value); + len_wrote = write(fd, value, len_value); + ASSERT_EQ(len_wrote, len_value); + EXPECT_EQ(0, close(fd)); +} + +static void create_workspace(struct __test_metadata *_metadata, + int mount_exec, int file_exec) +{ + int fd; + + /* + * Cleans previous workspace if any error previously happened (don't + * check errors). + */ + umount(BIN_DIR); + rmdir(BIN_DIR); + + /* Creates a clean mount point. */ + ASSERT_EQ(0, mkdir(BIN_DIR, 00700)); + ASSERT_EQ(0, mount("test", BIN_DIR, "tmpfs", + MS_MGC_VAL | (mount_exec ? 0 : MS_NOEXEC), + "mode=0700,size=4k")); + + /* Creates a test file. */ + fd = open(BIN_PATH, O_CREAT | O_RDONLY | O_CLOEXEC, + file_exec ? 00500 : 00400); + ASSERT_LE(0, fd); + EXPECT_EQ(0, close(fd)); + + /* Creates a test directory. */ + ASSERT_EQ(0, 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"); + + /* There is no need to unlink BIN_PATH nor DIR_PATH. */ + ASSERT_EQ(0, umount(BIN_DIR)); + ASSERT_EQ(0, 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) +{ + /* Enforces 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) +{ + /* Enforces 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) +{ + /* Enforces 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) +{ + /* Enforces 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) +{ + /* Enforces 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) +{ + /* Enforces 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) +{ + /* Enforces 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) +{ + /* Enforces 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) +{ + /* Enforces 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) +{ + /* Enforces 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) +{ + /* Enforces 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) +{ + /* Enforces 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_LE(0, fd); + len_wrote = write(fd, "0", 1); + ASSERT_EQ(len_wrote, -1); + EXPECT_EQ(0, close(fd)); + + ignore_mac(_metadata, 1); +} + +TEST_HARNESS_MAIN From patchwork Tue May 5 15:31:55 2020 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: 11529471 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 7C62F81 for ; Tue, 5 May 2020 15:33:14 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 6ED782078C for ; Tue, 5 May 2020 15:33:14 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1730266AbgEEPc6 (ORCPT ); Tue, 5 May 2020 11:32:58 -0400 Received: from smtp-8fac.mail.infomaniak.ch ([83.166.143.172]:42455 "EHLO smtp-8fac.mail.infomaniak.ch" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1730247AbgEEPc5 (ORCPT ); Tue, 5 May 2020 11:32:57 -0400 Received: from smtp-2-0000.mail.infomaniak.ch (unknown [10.5.36.107]) by smtp-3-3000.mail.infomaniak.ch (Postfix) with ESMTPS id 49GkHY2npdzlhp0K; Tue, 5 May 2020 17:32:25 +0200 (CEST) Received: from localhost (unknown [94.23.54.103]) by smtp-2-0000.mail.infomaniak.ch (Postfix) with ESMTPA id 49GkHX62J1zlqCkb; Tue, 5 May 2020 17:32:24 +0200 (CEST) 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 , Deven Bowers , Eric Chiang , Florian Weimer , James Morris , Jan Kara , Jann Horn , Jonathan Corbet , Kees Cook , Lakshmi Ramasubramanian , 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 , Steve Dower , Steve Grubb , Thibaut Sautereau , Vincent Strubel , kernel-hardening@lists.openwall.com, linux-api@vger.kernel.org, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org Subject: [PATCH v5 5/6] doc: Add documentation for the fs.open_mayexec_enforce sysctl Date: Tue, 5 May 2020 17:31:55 +0200 Message-Id: <20200505153156.925111-6-mic@digikod.net> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200505153156.925111-1-mic@digikod.net> References: <20200505153156.925111-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: owner-linux-security-module@vger.kernel.org Precedence: bulk List-ID: This sysctl enables to propagate executable permission to userspace thanks to the O_MAYEXEC flag. Signed-off-by: Mickaël Salaün Reviewed-by: Thibaut Sautereau Cc: Aleksa Sarai Cc: Al Viro Cc: Jonathan Corbet Cc: Kees Cook --- Changes since v3: * Switch back to O_MAYEXEC and highlight that it is only taken into account by openat2(2). Changes since v2: * Update documentation with the new RESOLVE_MAYEXEC. * Improve explanations, including concerns about LD_PRELOAD. Changes since v1: * Move from LSM/Yama to sysctl/fs . --- Documentation/admin-guide/sysctl/fs.rst | 44 +++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/Documentation/admin-guide/sysctl/fs.rst b/Documentation/admin-guide/sysctl/fs.rst index 2a45119e3331..d55615c36772 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,49 @@ system needs to prune the inode list instead of allocating more. +open_mayexec_enforce +-------------------- + +While being ignored by :manpage:`open(2)` and :manpage:`openat(2)`, the +``O_MAYEXEC`` flag can be passed to :manpage:`openat2(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, +or a dynamic linker to only load executable shared objects. One interesting +use case is to enforce a "write xor execute" policy through interpreters. + +The ability to restrict code execution must be thought as a system-wide policy, +which first starts by restricting mount points with the ``noexec`` option. +This option is also automatically applied to special filesystems such as /proc +. This prevents files on such mount points to be directly executed by the +kernel or mapped as executable memory (e.g. libraries). With script +interpreters using the ``O_MAYEXEC`` flag, the executable permission can then +be checked before reading commands from files. This makes it possible to +enforce the ``noexec`` at the interpreter level, and thus propagates this +security policy to scripts. To be fully effective, these interpreters also +need to handle the other ways to execute code: command line parameters (e.g., +option ``-e`` for Perl), module loading (e.g., option ``-m`` for Python), +stdin, file sourcing, environment variables, configuration files, etc. +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 are two complementary security policies: enforce the ``noexec`` mount +option, and 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: checks that the mount options for the underlying VFS + mount do not prevent execution. + +2 - File permission restriction: checks 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/openat2/omayexec_test.c +and at +https://github.com/clipos-archive/clipos4_portage-overlay/search?q=O_MAYEXEC . + + overflowgid & overflowuid ------------------------- From patchwork Tue May 5 15:31:56 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= X-Patchwork-Id: 11529467 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 A040C1392 for ; Tue, 5 May 2020 15:33:01 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 92B0F20838 for ; Tue, 5 May 2020 15:33:01 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1730290AbgEEPdA (ORCPT ); Tue, 5 May 2020 11:33:00 -0400 Received: from smtp-bc0a.mail.infomaniak.ch ([45.157.188.10]:59745 "EHLO smtp-bc0a.mail.infomaniak.ch" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1730004AbgEEPc7 (ORCPT ); Tue, 5 May 2020 11:32:59 -0400 Received: from smtp-3-0001.mail.infomaniak.ch (unknown [10.4.36.108]) by smtp-3-3000.mail.infomaniak.ch (Postfix) with ESMTPS id 49GkHZ5J2gzlhdsY; Tue, 5 May 2020 17:32:26 +0200 (CEST) Received: from localhost (unknown [94.23.54.103]) by smtp-3-0001.mail.infomaniak.ch (Postfix) with ESMTPA id 49GkHZ1fSRzlsT3N; Tue, 5 May 2020 17:32:26 +0200 (CEST) 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 , Deven Bowers , Eric Chiang , Florian Weimer , James Morris , Jan Kara , Jann Horn , Jonathan Corbet , Kees Cook , Lakshmi Ramasubramanian , 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 , Steve Dower , Steve Grubb , Thibaut Sautereau , Vincent Strubel , kernel-hardening@lists.openwall.com, linux-api@vger.kernel.org, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org Subject: [PATCH v5 6/6] ima: add policy support for the new file open MAY_OPENEXEC flag Date: Tue, 5 May 2020 17:31:56 +0200 Message-Id: <20200505153156.925111-7-mic@digikod.net> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200505153156.925111-1-mic@digikod.net> References: <20200505153156.925111-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: owner-linux-security-module@vger.kernel.org Precedence: bulk List-ID: From: Mimi Zohar The kernel has no way of differentiating between a file containing data or code being opened by an interpreter. The proposed O_MAYEXEC openat2(2) flag bridges this gap by defining and enabling the MAY_OPENEXEC flag. This patch adds IMA policy support for the new MAY_OPENEXEC flag. Example: measure func=FILE_CHECK mask=^MAY_OPENEXEC appraise func=FILE_CHECK appraise_type=imasig mask=^MAY_OPENEXEC Signed-off-by: Mimi Zohar Reviewed-by: Lakshmi Ramasubramanian Link: https://lore.kernel.org/r/1588167523-7866-3-git-send-email-zohar@linux.ibm.com --- Documentation/ABI/testing/ima_policy | 2 +- security/integrity/ima/ima_main.c | 3 ++- security/integrity/ima/ima_policy.c | 15 +++++++++++---- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Documentation/ABI/testing/ima_policy b/Documentation/ABI/testing/ima_policy index cd572912c593..caca46125fe0 100644 --- a/Documentation/ABI/testing/ima_policy +++ b/Documentation/ABI/testing/ima_policy @@ -31,7 +31,7 @@ Description: [KEXEC_KERNEL_CHECK] [KEXEC_INITRAMFS_CHECK] [KEXEC_CMDLINE] [KEY_CHECK] mask:= [[^]MAY_READ] [[^]MAY_WRITE] [[^]MAY_APPEND] - [[^]MAY_EXEC] + [[^]MAY_EXEC] [[^]MAY_OPENEXEC] fsmagic:= hex value fsuuid:= file system UUID (e.g 8bcbe394-4f13-4144-be8e-5aa9ea2ce2f6) uid:= decimal value diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 9d0abedeae77..c80cdaf13626 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -438,7 +438,8 @@ int ima_file_check(struct file *file, int mask) security_task_getsecid(current, &secid); return process_measurement(file, current_cred(), secid, NULL, 0, - mask & (MAY_READ | MAY_WRITE | MAY_EXEC | + mask & (MAY_READ | MAY_WRITE | + MAY_EXEC | MAY_OPENEXEC | MAY_APPEND), FILE_CHECK); } EXPORT_SYMBOL_GPL(ima_file_check); diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index c334e0dc6083..f54cbc55498d 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -406,7 +406,8 @@ static bool ima_match_keyring(struct ima_rule_entry *rule, * @cred: a pointer to a credentials structure for user validation * @secid: the secid of the task to be validated * @func: LIM hook identifier - * @mask: requested action (MAY_READ | MAY_WRITE | MAY_APPEND | MAY_EXEC) + * @mask: requested action (MAY_READ | MAY_WRITE | MAY_APPEND | MAY_EXEC | + * MAY_OPENEXEC) * @keyring: keyring name to check in policy for KEY_CHECK func * * Returns true on rule match, false on failure. @@ -527,7 +528,8 @@ static int get_subaction(struct ima_rule_entry *rule, enum ima_hooks func) * being made * @secid: LSM secid of the task to be validated * @func: IMA hook identifier - * @mask: requested action (MAY_READ | MAY_WRITE | MAY_APPEND | MAY_EXEC) + * @mask: requested action (MAY_READ | MAY_WRITE | MAY_APPEND | MAY_EXEC | + * MAY_OPENEXEC) * @pcr: set the pcr to extend * @template_desc: the template that should be used for this rule * @keyring: the keyring name, if given, to be used to check in the policy. @@ -1089,6 +1091,8 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) entry->mask = MAY_READ; else if (strcmp(from, "MAY_APPEND") == 0) entry->mask = MAY_APPEND; + else if (strcmp(from, "MAY_OPENEXEC") == 0) + entry->mask = MAY_OPENEXEC; else result = -EINVAL; if (!result) @@ -1420,14 +1424,15 @@ const char *const func_tokens[] = { #ifdef CONFIG_IMA_READ_POLICY enum { - mask_exec = 0, mask_write, mask_read, mask_append + mask_exec = 0, mask_write, mask_read, mask_append, mask_openexec }; static const char *const mask_tokens[] = { "^MAY_EXEC", "^MAY_WRITE", "^MAY_READ", - "^MAY_APPEND" + "^MAY_APPEND", + "^MAY_OPENEXEC" }; void *ima_policy_start(struct seq_file *m, loff_t *pos) @@ -1516,6 +1521,8 @@ int ima_policy_show(struct seq_file *m, void *v) seq_printf(m, pt(Opt_mask), mt(mask_read) + offset); if (entry->mask & MAY_APPEND) seq_printf(m, pt(Opt_mask), mt(mask_append) + offset); + if (entry->mask & MAY_OPENEXEC) + seq_printf(m, pt(Opt_mask), mt(mask_openexec) + offset); seq_puts(m, " "); }