When the RESOLVE_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. 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 RESOLVE_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... 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 <mic@xxxxxxxxxxx> Signed-off-by: Thibaut Sautereau <thibaut.sautereau@xxxxxxxxxxx> Signed-off-by: Vincent Strubel <vincent.strubel@xxxxxxxxxxx> Cc: Aleksa Sarai <cyphar@xxxxxxxxxx> Cc: Al Viro <viro@xxxxxxxxxxxxxxxxxx> Cc: Kees Cook <keescook@xxxxxxxxxxxx> --- 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/open.c | 6 ++++++ include/linux/fcntl.h | 2 +- include/linux/fs.h | 2 ++ include/uapi/linux/openat2.h | 6 ++++++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/fs/open.c b/fs/open.c index 719b320ede52..ca5a145761a2 100644 --- a/fs/open.c +++ b/fs/open.c @@ -1029,6 +1029,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 (how->resolve & RESOLVE_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..a37e213220ad 100644 --- a/include/linux/fcntl.h +++ b/include/linux/fcntl.h @@ -19,7 +19,7 @@ /* List of all valid flags for the how->resolve argument: */ #define VALID_RESOLVE_FLAGS \ (RESOLVE_NO_XDEV | RESOLVE_NO_MAGICLINKS | RESOLVE_NO_SYMLINKS | \ - RESOLVE_BENEATH | RESOLVE_IN_ROOT) + RESOLVE_BENEATH | RESOLVE_IN_ROOT | RESOLVE_MAYEXEC) /* List of all open_how "versions". */ #define OPEN_HOW_SIZE_VER0 24 /* sizeof first published struct */ diff --git a/include/linux/fs.h b/include/linux/fs.h index 4f6f59b4f22a..f5be4be7c01d 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 RESOLVE_MAYEXEC */ +#define MAY_OPENEXEC 0x00000100 /* * flags in file.f_mode. Note that FMODE_READ and FMODE_WRITE must correspond diff --git a/include/uapi/linux/openat2.h b/include/uapi/linux/openat2.h index 58b1eb711360..86ed0a2321c3 100644 --- a/include/uapi/linux/openat2.h +++ b/include/uapi/linux/openat2.h @@ -35,5 +35,11 @@ struct open_how { #define RESOLVE_IN_ROOT 0x10 /* Make all jumps to "/" and ".." be scoped inside the dirfd (similar to chroot(2)). */ +#define RESOLVE_MAYEXEC 0x20 /* Code execution from the target file is + intended, checks such permission. A + simple policy can be enforced + system-wide as explained in + Documentation/admin-guide/sysctl/fs.rst + */ #endif /* _UAPI_LINUX_OPENAT2_H */ -- 2.26.2