summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrei Vagin <avagin@google.com>2019-04-04 17:42:51 -0700
committerShentubot <shentubot@google.com>2019-04-04 17:43:53 -0700
commit88409e983c463b6d9c8085e7fdbe7ff45b3c5184 (patch)
treef5ba3e9b1c67a7641a8d4d7c4106bd5bc5c2dcf1
parent75a5ccf5d98876c26305da0feff20e4a148027ec (diff)
gvisor: Add support for the MS_NOEXEC mount option
https://github.com/google/gvisor/issues/145 PiperOrigin-RevId: 242044115 Change-Id: I8f140fe05e32ecd438b6be218e224e4b7fe05878
-rw-r--r--pkg/sentry/fs/context.go5
-rw-r--r--pkg/sentry/fs/filesystems.go4
-rw-r--r--pkg/sentry/fs/proc/mounts.go3
-rw-r--r--pkg/sentry/syscalls/linux/sys_mount.go5
-rw-r--r--runsc/boot/fs.go2
-rw-r--r--runsc/specutils/fs.go5
-rw-r--r--test/syscalls/linux/BUILD1
-rw-r--r--test/syscalls/linux/mount.cc18
8 files changed, 39 insertions, 4 deletions
diff --git a/pkg/sentry/fs/context.go b/pkg/sentry/fs/context.go
index 1775d3486..c0e6075e4 100644
--- a/pkg/sentry/fs/context.go
+++ b/pkg/sentry/fs/context.go
@@ -46,6 +46,11 @@ func ContextCanAccessFile(ctx context.Context, inode *Inode, reqPerms PermMask)
p = uattr.Perms.Group
}
+ // Do not allow programs to be executed if MS_NOEXEC is set.
+ if IsFile(inode.StableAttr) && reqPerms.Execute && inode.MountSource.Flags.NoExec {
+ return false
+ }
+
// Are permissions satisfied without capability checks?
if p.SupersetOf(reqPerms) {
return true
diff --git a/pkg/sentry/fs/filesystems.go b/pkg/sentry/fs/filesystems.go
index aa664b973..a6b27c402 100644
--- a/pkg/sentry/fs/filesystems.go
+++ b/pkg/sentry/fs/filesystems.go
@@ -140,6 +140,10 @@ type MountSourceFlags struct {
// cache, even when the platform supports direct mapped I/O. This
// doesn't correspond to any Linux mount options.
ForcePageCache bool
+
+ // NoExec corresponds to mount(2)'s "MS_NOEXEC" and indicates that
+ // binaries from this file system can't be executed.
+ NoExec bool
}
// GenericMountSourceOptions splits a string containing comma separated tokens of the
diff --git a/pkg/sentry/fs/proc/mounts.go b/pkg/sentry/fs/proc/mounts.go
index 7111e5c0f..1e62af8c6 100644
--- a/pkg/sentry/fs/proc/mounts.go
+++ b/pkg/sentry/fs/proc/mounts.go
@@ -129,6 +129,9 @@ func (mif *mountInfoFile) ReadSeqFileData(ctx context.Context, handle seqfile.Se
if m.Flags.NoAtime {
opts += ",noatime"
}
+ if m.Flags.NoExec {
+ opts += ",noexec"
+ }
fmt.Fprintf(&buf, "%s ", opts)
// (7) Optional fields: zero or more fields of the form "tag[:value]".
diff --git a/pkg/sentry/syscalls/linux/sys_mount.go b/pkg/sentry/syscalls/linux/sys_mount.go
index 6b8d75d24..e110a553f 100644
--- a/pkg/sentry/syscalls/linux/sys_mount.go
+++ b/pkg/sentry/syscalls/linux/sys_mount.go
@@ -75,7 +75,7 @@ func Mount(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
// Silently allow MS_NOSUID, since we don't implement set-id bits
// anyway.
- const unsupportedFlags = linux.MS_NODEV | linux.MS_NOEXEC |
+ const unsupportedFlags = linux.MS_NODEV |
linux.MS_NODIRATIME | linux.MS_STRICTATIME
// Linux just allows passing any flags to mount(2) - it won't fail when
@@ -100,6 +100,9 @@ func Mount(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
if flags&linux.MS_RDONLY == linux.MS_RDONLY {
superFlags.ReadOnly = true
}
+ if flags&linux.MS_NOEXEC == linux.MS_NOEXEC {
+ superFlags.NoExec = true
+ }
rootInode, err := rsys.Mount(t, sourcePath, superFlags, data, nil)
if err != nil {
diff --git a/runsc/boot/fs.go b/runsc/boot/fs.go
index 25e23c09b..8dfb6dce6 100644
--- a/runsc/boot/fs.go
+++ b/runsc/boot/fs.go
@@ -482,6 +482,8 @@ func mountFlags(opts []string) fs.MountSourceFlags {
mf.ReadOnly = true
case "noatime":
mf.NoAtime = true
+ case "noexec":
+ mf.NoExec = true
default:
log.Warningf("ignoring unknown mount option %q", o)
}
diff --git a/runsc/specutils/fs.go b/runsc/specutils/fs.go
index aa17d4eb9..98c3b19c0 100644
--- a/runsc/specutils/fs.go
+++ b/runsc/specutils/fs.go
@@ -39,6 +39,7 @@ var optionsMap = map[string]mapping{
"diratime": {set: false, val: syscall.MS_NODIRATIME},
"dirsync": {set: true, val: syscall.MS_DIRSYNC},
"exec": {set: false, val: syscall.MS_NOEXEC},
+ "noexec": {set: true, val: syscall.MS_NOEXEC},
"iversion": {set: true, val: syscall.MS_I_VERSION},
"loud": {set: false, val: syscall.MS_SILENT},
"mand": {set: true, val: syscall.MS_MANDLOCK},
@@ -76,9 +77,7 @@ var propOptionsMap = map[string]mapping{
// invalidOptions list options not allowed.
// - shared: sandbox must be isolated from the host. Propagating mount changes
// from the sandbox to the host breaks the isolation.
-// - noexec: not yet supported. Don't ignore it since it could break
-// in-sandbox security.
-var invalidOptions = []string{"shared", "rshared", "noexec"}
+var invalidOptions = []string{"shared", "rshared"}
// OptionsToFlags converts mount options to syscall flags.
func OptionsToFlags(opts []string) uint32 {
diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD
index 1e386193b..38faba267 100644
--- a/test/syscalls/linux/BUILD
+++ b/test/syscalls/linux/BUILD
@@ -1080,6 +1080,7 @@ cc_binary(
"//test/util:file_descriptor",
"//test/util:fs_util",
"//test/util:mount_util",
+ "//test/util:multiprocess_util",
"//test/util:posix_error",
"//test/util:temp_path",
"//test/util:test_main",
diff --git a/test/syscalls/linux/mount.cc b/test/syscalls/linux/mount.cc
index 6bb4287a3..201b83e87 100644
--- a/test/syscalls/linux/mount.cc
+++ b/test/syscalls/linux/mount.cc
@@ -31,6 +31,7 @@
#include "test/util/file_descriptor.h"
#include "test/util/fs_util.h"
#include "test/util/mount_util.h"
+#include "test/util/multiprocess_util.h"
#include "test/util/posix_error.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
@@ -277,6 +278,23 @@ TEST(MountTest, MountNoAtime) {
EXPECT_EQ(before, after);
}
+TEST(MountTest, MountNoExec) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
+
+ auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
+ Mount("", dir.path(), "tmpfs", MS_NOEXEC, "mode=0777", 0));
+
+ std::string const contents = "No no no, don't follow the instructions!";
+ auto const file = ASSERT_NO_ERRNO_AND_VALUE(
+ TempPath::CreateFileWith(dir.path(), contents, 0777));
+
+ int execve_errno;
+ ASSERT_NO_ERRNO_AND_VALUE(
+ ForkAndExec(file.path(), {}, {}, nullptr, &execve_errno));
+ EXPECT_EQ(execve_errno, EACCES);
+}
+
TEST(MountTest, RenameRemoveMountPoint) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));