Message ID | 20200714181638.45751-6-mic@digikod.net (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | Add support for O_MAYEXEC | expand |
Hi, On 7/14/20 11:16 AM, Mickaël Salaün wrote: > --- > Documentation/admin-guide/sysctl/fs.rst | 45 +++++++++++++++++++++++++ > fs/namei.c | 29 +++++++++++++--- > include/linux/fs.h | 1 + > kernel/sysctl.c | 12 +++++-- > 4 files changed, 80 insertions(+), 7 deletions(-) > > diff --git a/Documentation/admin-guide/sysctl/fs.rst b/Documentation/admin-guide/sysctl/fs.rst > index 2a45119e3331..02ec384b8bbf 100644 > --- a/Documentation/admin-guide/sysctl/fs.rst > +++ b/Documentation/admin-guide/sysctl/fs.rst Reviewed-by: Randy Dunlap <rdunlap@infradead.org> with one tiny nit: > @@ -165,6 +166,50 @@ system needs to prune the inode list instead of allocating > +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 Can you move that period from the beginning of the line to the end of the previous line? > +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. thanks.
On Tue, Jul 14, 2020 at 08:16:36PM +0200, Mickaël Salaün wrote: > @@ -2849,7 +2855,7 @@ static int may_open(const struct path *path, int acc_mode, int flag) > case S_IFLNK: > return -ELOOP; > case S_IFDIR: > - if (acc_mode & (MAY_WRITE | MAY_EXEC)) > + if (acc_mode & (MAY_WRITE | MAY_EXEC | MAY_OPENEXEC)) > return -EISDIR; > break; (I need to figure out where "open for reading" rejects S_IFDIR, since it's clearly not here...) > case S_IFBLK: > @@ -2859,13 +2865,26 @@ static int may_open(const struct path *path, int acc_mode, int flag) > fallthrough; > case S_IFIFO: > case S_IFSOCK: > - if (acc_mode & MAY_EXEC) > + if (acc_mode & (MAY_EXEC | MAY_OPENEXEC)) > return -EACCES; > flag &= ~O_TRUNC; > break; This will immediately break a system that runs code with MAY_OPENEXEC set but reads from a block, char, fifo, or socket, even in the case of a sysadmin leaving the "file" sysctl disabled. > case S_IFREG: > - if ((acc_mode & MAY_EXEC) && path_noexec(path)) > - return -EACCES; > + if (path_noexec(path)) { > + if (acc_mode & MAY_EXEC) > + return -EACCES; > + if ((acc_mode & MAY_OPENEXEC) && > + (sysctl_open_mayexec_enforce & OPEN_MAYEXEC_ENFORCE_MOUNT)) > + return -EACCES; > + } > + if ((acc_mode & MAY_OPENEXEC) && > + (sysctl_open_mayexec_enforce & OPEN_MAYEXEC_ENFORCE_FILE)) > + /* > + * Because acc_mode may change here, the next and only > + * use of acc_mode should then be by the following call > + * to inode_permission(). > + */ > + acc_mode |= MAY_EXEC; > break; > } Likely very minor, but I'd like to avoid the path_noexec() call in the fast-path (it dereferences a couple pointers where as doing bit tests on acc_mode is fast). Given that and the above observations, I think that may_open() likely needs to start with: if (acc_mode & MAY_OPENEXEC) { /* Reject all file types when mount enforcement set. */ if ((sysctl_open_mayexec_enforce & OPEN_MAYEXEC_ENFORCE_MOUNT) && path_noexec(path)) return -EACCES; /* Treat the same as MAY_EXEC. */ if (sysctl_open_mayexec_enforce & OPEN_MAYEXEC_ENFORCE_FILE)) acc_mode |= MAY_EXEC; } (Though I'm not 100% sure that path_noexec() is safe to be called for all file types: i.e. path->mnt and path->-mnt->mnt_sb *always* non-NULL?) This change would also imply that OPEN_MAYEXEC_ENFORCE_FILE *includes* OPEN_MAYEXEC_ENFORCE_MOUNT (i.e. the sysctl should not be a bitfield), since path_noexec() would get checked for S_ISREG. I can't come up with a rationale where one would want OPEN_MAYEXEC_ENFORCE_FILE but _not_ OPEN_MAYEXEC_ENFORCE_MOUNT? (I can absolutely see wanting only OPEN_MAYEXEC_ENFORCE_MOUNT, or suddenly one has to go mark every loaded thing with the exec bit and most distros haven't done this to, for example, shared libraries. But setting the exec bit and then NOT wanting to enforce the mount check seems... not sensible?) Outside of this change, yes, I like this now -- it's much cleaner because we have all the checks in the same place where they belong. :) > diff --git a/kernel/sysctl.c b/kernel/sysctl.c > index db1ce7af2563..5008a2566e79 100644 > --- a/kernel/sysctl.c > +++ b/kernel/sysctl.c > @@ -113,6 +113,7 @@ static int sixty = 60; > > static int __maybe_unused neg_one = -1; > static int __maybe_unused two = 2; > +static int __maybe_unused three = 3; > static int __maybe_unused four = 4; > static unsigned long zero_ul; > static unsigned long one_ul = 1; Oh, are these still here? I thought they got removed (or at least made const). Where did that series go? Hmpf, see sysctl_vals, but yes, for now, this is fine. > @@ -888,7 +889,6 @@ static int proc_taint(struct ctl_table *table, int write, > return err; > } > > -#ifdef CONFIG_PRINTK > static int proc_dointvec_minmax_sysadmin(struct ctl_table *table, int write, > void *buffer, size_t *lenp, loff_t *ppos) > { > @@ -897,7 +897,6 @@ static int proc_dointvec_minmax_sysadmin(struct ctl_table *table, int write, > > return proc_dointvec_minmax(table, write, buffer, lenp, ppos); > } > -#endif > > /** > * struct do_proc_dointvec_minmax_conv_param - proc_dointvec_minmax() range checking structure > @@ -3264,6 +3263,15 @@ static struct ctl_table fs_table[] = { > .extra1 = SYSCTL_ZERO, > .extra2 = &two, > }, > + { > + .procname = "open_mayexec_enforce", > + .data = &sysctl_open_mayexec_enforce, > + .maxlen = sizeof(int), > + .mode = 0600, > + .proc_handler = proc_dointvec_minmax_sysadmin, > + .extra1 = SYSCTL_ZERO, > + .extra2 = &three, > + }, > #if defined(CONFIG_BINFMT_MISC) || defined(CONFIG_BINFMT_MISC_MODULE) > { > .procname = "binfmt_misc", > -- > 2.27.0 >
On 15/07/2020 22:37, Kees Cook wrote: > On Tue, Jul 14, 2020 at 08:16:36PM +0200, Mickaël Salaün wrote: >> @@ -2849,7 +2855,7 @@ static int may_open(const struct path *path, int acc_mode, int flag) >> case S_IFLNK: >> return -ELOOP; >> case S_IFDIR: >> - if (acc_mode & (MAY_WRITE | MAY_EXEC)) >> + if (acc_mode & (MAY_WRITE | MAY_EXEC | MAY_OPENEXEC)) >> return -EISDIR; >> break; > > (I need to figure out where "open for reading" rejects S_IFDIR, since > it's clearly not here...) > >> case S_IFBLK: >> @@ -2859,13 +2865,26 @@ static int may_open(const struct path *path, int acc_mode, int flag) >> fallthrough; >> case S_IFIFO: >> case S_IFSOCK: >> - if (acc_mode & MAY_EXEC) >> + if (acc_mode & (MAY_EXEC | MAY_OPENEXEC)) >> return -EACCES; >> flag &= ~O_TRUNC; >> break; > > This will immediately break a system that runs code with MAY_OPENEXEC > set but reads from a block, char, fifo, or socket, even in the case of > a sysadmin leaving the "file" sysctl disabled. As documented, O_MAYEXEC is for regular files. The only legitimate use case seems to be with pipes, which should probably be allowed when enforcement is disabled. > >> case S_IFREG: >> - if ((acc_mode & MAY_EXEC) && path_noexec(path)) >> - return -EACCES; >> + if (path_noexec(path)) { >> + if (acc_mode & MAY_EXEC) >> + return -EACCES; >> + if ((acc_mode & MAY_OPENEXEC) && >> + (sysctl_open_mayexec_enforce & OPEN_MAYEXEC_ENFORCE_MOUNT)) >> + return -EACCES; >> + } >> + if ((acc_mode & MAY_OPENEXEC) && >> + (sysctl_open_mayexec_enforce & OPEN_MAYEXEC_ENFORCE_FILE)) >> + /* >> + * Because acc_mode may change here, the next and only >> + * use of acc_mode should then be by the following call >> + * to inode_permission(). >> + */ >> + acc_mode |= MAY_EXEC; >> break; >> } > > Likely very minor, but I'd like to avoid the path_noexec() call in the > fast-path (it dereferences a couple pointers where as doing bit tests on > acc_mode is fast). > > Given that and the above observations, I think that may_open() likely > needs to start with: > > if (acc_mode & MAY_OPENEXEC) { > /* Reject all file types when mount enforcement set. */ > if ((sysctl_open_mayexec_enforce & OPEN_MAYEXEC_ENFORCE_MOUNT) && > path_noexec(path)) > return -EACCES; > /* Treat the same as MAY_EXEC. */ > if (sysctl_open_mayexec_enforce & OPEN_MAYEXEC_ENFORCE_FILE)) > acc_mode |= MAY_EXEC; > } OK > > (Though I'm not 100% sure that path_noexec() is safe to be called for > all file types: i.e. path->mnt and path->-mnt->mnt_sb *always* non-NULL?) path->mnt should always be non-NULL: https://lore.kernel.org/lkml/20200317164709.GA23230@ZenIV.linux.org.uk/ > > This change would also imply that OPEN_MAYEXEC_ENFORCE_FILE *includes* > OPEN_MAYEXEC_ENFORCE_MOUNT (i.e. the sysctl should not be a bitfield), > since path_noexec() would get checked for S_ISREG. I can't come up with > a rationale where one would want OPEN_MAYEXEC_ENFORCE_FILE but _not_ > OPEN_MAYEXEC_ENFORCE_MOUNT? I don't see why it is an inclusion. > > (I can absolutely see wanting only OPEN_MAYEXEC_ENFORCE_MOUNT, or > suddenly one has to go mark every loaded thing with the exec bit and > most distros haven't done this to, for example, shared libraries. But > setting the exec bit and then NOT wanting to enforce the mount check > seems... not sensible?) > > Outside of this change, yes, I like this now -- it's much cleaner > because we have all the checks in the same place where they belong. :) > >> diff --git a/kernel/sysctl.c b/kernel/sysctl.c >> index db1ce7af2563..5008a2566e79 100644 >> --- a/kernel/sysctl.c >> +++ b/kernel/sysctl.c >> @@ -113,6 +113,7 @@ static int sixty = 60; >> >> static int __maybe_unused neg_one = -1; >> static int __maybe_unused two = 2; >> +static int __maybe_unused three = 3; >> static int __maybe_unused four = 4; >> static unsigned long zero_ul; >> static unsigned long one_ul = 1; > > Oh, are these still here? I thought they got removed (or at least made > const). Where did that series go? Hmpf, see sysctl_vals, but yes, for > now, this is fine. > >> @@ -888,7 +889,6 @@ static int proc_taint(struct ctl_table *table, int write, >> return err; >> } >> >> -#ifdef CONFIG_PRINTK >> static int proc_dointvec_minmax_sysadmin(struct ctl_table *table, int write, >> void *buffer, size_t *lenp, loff_t *ppos) >> { >> @@ -897,7 +897,6 @@ static int proc_dointvec_minmax_sysadmin(struct ctl_table *table, int write, >> >> return proc_dointvec_minmax(table, write, buffer, lenp, ppos); >> } >> -#endif >> >> /** >> * struct do_proc_dointvec_minmax_conv_param - proc_dointvec_minmax() range checking structure >> @@ -3264,6 +3263,15 @@ static struct ctl_table fs_table[] = { >> .extra1 = SYSCTL_ZERO, >> .extra2 = &two, >> }, >> + { >> + .procname = "open_mayexec_enforce", >> + .data = &sysctl_open_mayexec_enforce, >> + .maxlen = sizeof(int), >> + .mode = 0600, >> + .proc_handler = proc_dointvec_minmax_sysadmin, >> + .extra1 = SYSCTL_ZERO, >> + .extra2 = &three, >> + }, >> #if defined(CONFIG_BINFMT_MISC) || defined(CONFIG_BINFMT_MISC_MODULE) >> { >> .procname = "binfmt_misc", >> -- >> 2.27.0 >> >
On 14/07/2020 20:40, Randy Dunlap wrote: > Hi, > > On 7/14/20 11:16 AM, Mickaël Salaün wrote: > >> --- >> Documentation/admin-guide/sysctl/fs.rst | 45 +++++++++++++++++++++++++ >> fs/namei.c | 29 +++++++++++++--- >> include/linux/fs.h | 1 + >> kernel/sysctl.c | 12 +++++-- >> 4 files changed, 80 insertions(+), 7 deletions(-) >> >> diff --git a/Documentation/admin-guide/sysctl/fs.rst b/Documentation/admin-guide/sysctl/fs.rst >> index 2a45119e3331..02ec384b8bbf 100644 >> --- a/Documentation/admin-guide/sysctl/fs.rst >> +++ b/Documentation/admin-guide/sysctl/fs.rst > > Reviewed-by: Randy Dunlap <rdunlap@infradead.org> > > with one tiny nit: > >> @@ -165,6 +166,50 @@ system needs to prune the inode list instead of allocating >> +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 > > Can you move that period from the beginning of the line to the end of the > previous line? OK, done. Thanks! > >> +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. > > thanks. >
On Thu, Jul 16, 2020 at 04:39:14PM +0200, Mickaël Salaün wrote: > > On 15/07/2020 22:37, Kees Cook wrote: > > On Tue, Jul 14, 2020 at 08:16:36PM +0200, Mickaël Salaün wrote: > >> @@ -2849,7 +2855,7 @@ static int may_open(const struct path *path, int acc_mode, int flag) > >> case S_IFLNK: > >> return -ELOOP; > >> case S_IFDIR: > >> - if (acc_mode & (MAY_WRITE | MAY_EXEC)) > >> + if (acc_mode & (MAY_WRITE | MAY_EXEC | MAY_OPENEXEC)) > >> return -EISDIR; > >> break; > > > > (I need to figure out where "open for reading" rejects S_IFDIR, since > > it's clearly not here...) Doesn't it come from generic_read_dir() in fs/libfs.c? > > > >> case S_IFBLK: > >> @@ -2859,13 +2865,26 @@ static int may_open(const struct path *path, int acc_mode, int flag) > >> fallthrough; > >> case S_IFIFO: > >> case S_IFSOCK: > >> - if (acc_mode & MAY_EXEC) > >> + if (acc_mode & (MAY_EXEC | MAY_OPENEXEC)) > >> return -EACCES; > >> flag &= ~O_TRUNC; > >> break; > > > > This will immediately break a system that runs code with MAY_OPENEXEC > > set but reads from a block, char, fifo, or socket, even in the case of > > a sysadmin leaving the "file" sysctl disabled. > > As documented, O_MAYEXEC is for regular files. The only legitimate use > case seems to be with pipes, which should probably be allowed when > enforcement is disabled. By the way Kees, while we fix that for the next series, do you think it would be relevant, at least for the sake of clarity, to add a WARN_ON_ONCE(acc_mode & MAY_OPENEXEC) for the S_IFSOCK case, since a socket cannot be open anyway?
On 22/07/2020 18:16, Thibaut Sautereau wrote: > On Thu, Jul 16, 2020 at 04:39:14PM +0200, Mickaël Salaün wrote: >> >> On 15/07/2020 22:37, Kees Cook wrote: >>> On Tue, Jul 14, 2020 at 08:16:36PM +0200, Mickaël Salaün wrote: >>>> @@ -2849,7 +2855,7 @@ static int may_open(const struct path *path, int acc_mode, int flag) >>>> case S_IFLNK: >>>> return -ELOOP; >>>> case S_IFDIR: >>>> - if (acc_mode & (MAY_WRITE | MAY_EXEC)) >>>> + if (acc_mode & (MAY_WRITE | MAY_EXEC | MAY_OPENEXEC)) >>>> return -EISDIR; >>>> break; >>> >>> (I need to figure out where "open for reading" rejects S_IFDIR, since >>> it's clearly not here...) > > Doesn't it come from generic_read_dir() in fs/libfs.c? > >>> >>>> case S_IFBLK: >>>> @@ -2859,13 +2865,26 @@ static int may_open(const struct path *path, int acc_mode, int flag) >>>> fallthrough; >>>> case S_IFIFO: >>>> case S_IFSOCK: >>>> - if (acc_mode & MAY_EXEC) >>>> + if (acc_mode & (MAY_EXEC | MAY_OPENEXEC)) >>>> return -EACCES; >>>> flag &= ~O_TRUNC; >>>> break; >>> >>> This will immediately break a system that runs code with MAY_OPENEXEC >>> set but reads from a block, char, fifo, or socket, even in the case of >>> a sysadmin leaving the "file" sysctl disabled. >> >> As documented, O_MAYEXEC is for regular files. The only legitimate use >> case seems to be with pipes, which should probably be allowed when >> enforcement is disabled. > > By the way Kees, while we fix that for the next series, do you think it > would be relevant, at least for the sake of clarity, to add a > WARN_ON_ONCE(acc_mode & MAY_OPENEXEC) for the S_IFSOCK case, since a > socket cannot be open anyway? > We just did some more tests (for the next patch series) and it turns out that may_open() can return EACCES before another part returns ENXIO. As a reminder, the next series will deny access to block devices, character devices, fifo and socket when opened with O_MAYEXEC *and* if any policy is enforced (via the sysctl). The question is then: do we prefer to return EACCES when a policy is enforced (on a socket), or do we stick to the ENXIO? The EACCES approach will be more consistent with devices and fifo handling, and seems safer (belt and suspenders) thought.
On Wed, Jul 22, 2020 at 09:04:28PM +0200, Mickaël Salaün wrote: > > On 22/07/2020 18:16, Thibaut Sautereau wrote: > > On Thu, Jul 16, 2020 at 04:39:14PM +0200, Mickaël Salaün wrote: > >> > >> On 15/07/2020 22:37, Kees Cook wrote: > >>> On Tue, Jul 14, 2020 at 08:16:36PM +0200, Mickaël Salaün wrote: > >>>> @@ -2849,7 +2855,7 @@ static int may_open(const struct path *path, int acc_mode, int flag) > >>>> case S_IFLNK: > >>>> return -ELOOP; > >>>> case S_IFDIR: > >>>> - if (acc_mode & (MAY_WRITE | MAY_EXEC)) > >>>> + if (acc_mode & (MAY_WRITE | MAY_EXEC | MAY_OPENEXEC)) > >>>> return -EISDIR; > >>>> break; > >>> > >>> (I need to figure out where "open for reading" rejects S_IFDIR, since > >>> it's clearly not here...) > > > > Doesn't it come from generic_read_dir() in fs/libfs.c? > > > >>> > >>>> case S_IFBLK: > >>>> @@ -2859,13 +2865,26 @@ static int may_open(const struct path *path, int acc_mode, int flag) > >>>> fallthrough; > >>>> case S_IFIFO: > >>>> case S_IFSOCK: > >>>> - if (acc_mode & MAY_EXEC) > >>>> + if (acc_mode & (MAY_EXEC | MAY_OPENEXEC)) > >>>> return -EACCES; > >>>> flag &= ~O_TRUNC; > >>>> break; > >>> > >>> This will immediately break a system that runs code with MAY_OPENEXEC > >>> set but reads from a block, char, fifo, or socket, even in the case of > >>> a sysadmin leaving the "file" sysctl disabled. > >> > >> As documented, O_MAYEXEC is for regular files. The only legitimate use > >> case seems to be with pipes, which should probably be allowed when > >> enforcement is disabled. > > > > By the way Kees, while we fix that for the next series, do you think it > > would be relevant, at least for the sake of clarity, to add a > > WARN_ON_ONCE(acc_mode & MAY_OPENEXEC) for the S_IFSOCK case, since a > > socket cannot be open anyway? If it's a state that userspace should never be able to reach, then yes, I think a WARN_ON_ONCE() would be nice. > We just did some more tests (for the next patch series) and it turns out > that may_open() can return EACCES before another part returns ENXIO. > > As a reminder, the next series will deny access to block devices, > character devices, fifo and socket when opened with O_MAYEXEC *and* if > any policy is enforced (via the sysctl). > > The question is then: do we prefer to return EACCES when a policy is > enforced (on a socket), or do we stick to the ENXIO? The EACCES approach > will be more consistent with devices and fifo handling, and seems safer > (belt and suspenders) thought. I think EACCES is correct for these cases, since it's a new flag, etc.
diff --git a/Documentation/admin-guide/sysctl/fs.rst b/Documentation/admin-guide/sysctl/fs.rst index 2a45119e3331..02ec384b8bbf 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,50 @@ 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_SYS_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 interpreter patches (for the original O_MAYEXEC version) may be found at +https://github.com/clipos-archive/clipos4_portage-overlay/search?q=O_MAYEXEC . +See also an overview article: https://lwn.net/Articles/820000/ . + + overflowgid & overflowuid ------------------------- diff --git a/fs/namei.c b/fs/namei.c index ddc9b25540fe..9a9166e5ddd3 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" @@ -425,10 +426,15 @@ static int sb_permission(struct super_block *sb, struct inode *inode, int mask) return 0; } +#define OPEN_MAYEXEC_ENFORCE_MOUNT BIT(0) +#define OPEN_MAYEXEC_ENFORCE_FILE BIT(1) + +int sysctl_open_mayexec_enforce __read_mostly; + /** * 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) * * 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 @@ -2849,7 +2855,7 @@ static int may_open(const struct path *path, int acc_mode, int flag) case S_IFLNK: return -ELOOP; case S_IFDIR: - if (acc_mode & (MAY_WRITE | MAY_EXEC)) + if (acc_mode & (MAY_WRITE | MAY_EXEC | MAY_OPENEXEC)) return -EISDIR; break; case S_IFBLK: @@ -2859,13 +2865,26 @@ static int may_open(const struct path *path, int acc_mode, int flag) fallthrough; case S_IFIFO: case S_IFSOCK: - if (acc_mode & MAY_EXEC) + if (acc_mode & (MAY_EXEC | MAY_OPENEXEC)) return -EACCES; flag &= ~O_TRUNC; break; case S_IFREG: - if ((acc_mode & MAY_EXEC) && path_noexec(path)) - return -EACCES; + if (path_noexec(path)) { + if (acc_mode & MAY_EXEC) + return -EACCES; + if ((acc_mode & MAY_OPENEXEC) && + (sysctl_open_mayexec_enforce & OPEN_MAYEXEC_ENFORCE_MOUNT)) + return -EACCES; + } + if ((acc_mode & MAY_OPENEXEC) && + (sysctl_open_mayexec_enforce & OPEN_MAYEXEC_ENFORCE_FILE)) + /* + * Because acc_mode may change here, the next and only + * use of acc_mode should then be by the following call + * to inode_permission(). + */ + acc_mode |= MAY_EXEC; break; } diff --git a/include/linux/fs.h b/include/linux/fs.h index 56f835c9a87a..071f37707ccc 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -83,6 +83,7 @@ extern int sysctl_protected_symlinks; extern int sysctl_protected_hardlinks; extern int sysctl_protected_fifos; extern int sysctl_protected_regular; +extern int sysctl_open_mayexec_enforce; typedef __kernel_rwf_t rwf_t; diff --git a/kernel/sysctl.c b/kernel/sysctl.c index db1ce7af2563..5008a2566e79 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -113,6 +113,7 @@ static int sixty = 60; static int __maybe_unused neg_one = -1; static int __maybe_unused two = 2; +static int __maybe_unused three = 3; static int __maybe_unused four = 4; static unsigned long zero_ul; static unsigned long one_ul = 1; @@ -888,7 +889,6 @@ static int proc_taint(struct ctl_table *table, int write, return err; } -#ifdef CONFIG_PRINTK static int proc_dointvec_minmax_sysadmin(struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { @@ -897,7 +897,6 @@ static int proc_dointvec_minmax_sysadmin(struct ctl_table *table, int write, return proc_dointvec_minmax(table, write, buffer, lenp, ppos); } -#endif /** * struct do_proc_dointvec_minmax_conv_param - proc_dointvec_minmax() range checking structure @@ -3264,6 +3263,15 @@ static struct ctl_table fs_table[] = { .extra1 = SYSCTL_ZERO, .extra2 = &two, }, + { + .procname = "open_mayexec_enforce", + .data = &sysctl_open_mayexec_enforce, + .maxlen = sizeof(int), + .mode = 0600, + .proc_handler = proc_dointvec_minmax_sysadmin, + .extra1 = SYSCTL_ZERO, + .extra2 = &three, + }, #if defined(CONFIG_BINFMT_MISC) || defined(CONFIG_BINFMT_MISC_MODULE) { .procname = "binfmt_misc",