[PATCH v5 3/6] fs: Enable to enforce noexec mounts or file exec through O_MAYEXEC

Kees Cook keescook at chromium.org
Tue May 12 21:48:48 UTC 2020


On Tue, May 05, 2020 at 05:31:53PM +0200, Mickaël Salaün wrote:
> 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.

Some language tailoring. I might change the first sentence to:

Allow for the enforcement of the O_MAYEXEC openat2(2) flag.

> 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.

OMAYEXEC feels like the wrong name here. Maybe something closer to the
sysctl name? CONFIG_OPEN_MAYEXEC?

And I think it's not needed to have 3 configs for this. That's a lot of
mess for a corner case option. I think I would model this after other
sysctl CONFIGs, and just call this CONFIG_OPEN_MAYEXEC_DEFAULT.

Is _disabling_ the sysctl needed? This patch gets much smaller without
the ..._STATIC bit. (And can we avoid "static", it means different
things to different people. How about invert the logic and call it
CONFIG_OPEN_MAYEXEC_SYSCTL?)

Further notes below...

> [...]
> 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 <linux/bitops.h>
>  #include <linux/init_task.h>
>  #include <linux/uaccess.h>
> +#include <linux/sysctl.h>
>  
>  #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

Like the CONFIG, I'd stay close to the sysctl, OPEN_MAYEXEC_ENFORCE_...

> +#define OMAYEXEC_ENFORCE_MOUNT	(1 << 0)
> +#define OMAYEXEC_ENFORCE_FILE	(1 << 1)

Please use BIT(0), BIT(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 */


If you keep CONFIG_OPEN_MAYEXEC_SYSCTL, you could do this in namei.h:

#ifdef CONFIG_OPEN_MAYEXEC_SYSCTL
#define __sysctl_writable	__read_mostly
#else
#define __sysctl_write		const
#endif

Then with my proposed change to the enforce CONFIG, all of this is
reduced to simply:

int open_mayexec_enforce __sysctl_writable = CONFIG_OPEN_MAYEXEC_DEFAULT;

> +
> +/*
> + * 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

I don't think any of this is needed. There are no complex bit field
interactions to check for. The sysctl is min=0, max=3. The only thing
special here is checking CAP_MAC_ADMIN. I would just add
proc_dointvec_minmax_macadmin(), like we have for ..._minmax_sysadmin().

> +
> +/**
> + * 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;
> +}

More naming nits: I think this should be called may_openexec() to match
the other may_*() functions.

> +
>  /**
>   * 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

Now there's no need to wrap this in ifdef.

>  
>  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,

This can just be min/max of 0/3 with a new macadmin handler.

> +	},
> +#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"
> -- 
> 2.26.2
> 

Otherwise, yeah, the intent here looks good to me.

-- 
Kees Cook



More information about the Linux-security-module-archive mailing list