diff --git a/pkg/sentry/fsimpl/devpts/devpts.go b/pkg/sentry/fsimpl/devpts/devpts.go index f1f6c6781..f3e5b9c23 100644 --- a/pkg/sentry/fsimpl/devpts/devpts.go +++ b/pkg/sentry/fsimpl/devpts/devpts.go @@ -47,6 +47,13 @@ type FilesystemType struct { root *vfs.Dentry } +type fileSystemOpts struct { + mode linux.FileMode + ptmxMode linux.FileMode + uid auth.KUID + gid auth.KGID +} + // Name implements vfs.FilesystemType.Name. func (*FilesystemType) Name() string { return Name @@ -54,13 +61,79 @@ func (*FilesystemType) Name() string { // GetFilesystem implements vfs.FilesystemType.GetFilesystem. func (fstype *FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, source string, opts vfs.GetFilesystemOptions) (*vfs.Filesystem, *vfs.Dentry, error) { - // No data allowed. - if opts.Data != "" { + mopts := vfs.GenericParseMountOptions(opts.Data) + fsOpts := fileSystemOpts{ + mode: 0555, + ptmxMode: 0666, + uid: creds.EffectiveKUID, + gid: creds.EffectiveKGID, + } + if modeStr, ok := mopts["mode"]; ok { + delete(mopts, "mode") + mode, err := strconv.ParseUint(modeStr, 8, 32) + if err != nil { + ctx.Warningf("tmpfs.FilesystemType.GetFilesystem: invalid mode: %q", modeStr) + return nil, nil, linuxerr.EINVAL + } + fsOpts.mode = linux.FileMode(mode & 0777) + } + if modeStr, ok := mopts["ptmxmode"]; ok { + delete(mopts, "ptmxmode") + mode, err := strconv.ParseUint(modeStr, 8, 32) + if err != nil { + ctx.Warningf("tmpfs.FilesystemType.GetFilesystem: invalid ptmxmode: %q", modeStr) + return nil, nil, linuxerr.EINVAL + } + fsOpts.ptmxMode = linux.FileMode(mode & 0777) + } + if uidStr, ok := mopts["uid"]; ok { + delete(mopts, "uid") + uid, err := strconv.ParseUint(uidStr, 10, 32) + if err != nil { + ctx.Warningf("tmpfs.FilesystemType.GetFilesystem: invalid uid: %q", uidStr) + return nil, nil, linuxerr.EINVAL + } + kuid := creds.UserNamespace.MapToKUID(auth.UID(uid)) + if !kuid.Ok() { + ctx.Warningf("tmpfs.FilesystemType.GetFilesystem: unmapped uid: %d", uid) + return nil, nil, linuxerr.EINVAL + } + fsOpts.uid = kuid + } + if gidStr, ok := mopts["gid"]; ok { + delete(mopts, "gid") + gid, err := strconv.ParseUint(gidStr, 10, 32) + if err != nil { + ctx.Warningf("tmpfs.FilesystemType.GetFilesystem: invalid gid: %q", gidStr) + return nil, nil, linuxerr.EINVAL + } + kgid := creds.UserNamespace.MapToKGID(auth.GID(gid)) + if !kgid.Ok() { + ctx.Warningf("tmpfs.FilesystemType.GetFilesystem: unmapped gid: %d", gid) + return nil, nil, linuxerr.EINVAL + } + fsOpts.gid = kgid + } + newinstance := false + if _, ok := mopts["newinstance"]; ok { + newinstance = true + delete(mopts, "newinstance") + } + if len(mopts) != 0 { + ctx.Warningf("devpts.FilesystemType.GetFilesystem: unknown options: %v", mopts) return nil, nil, linuxerr.EINVAL } + if newinstance { + fs, root, err := fstype.newFilesystem(ctx, vfsObj, creds, fsOpts) + if err != nil { + return nil, nil, err + } + return fs.VFSFilesystem(), root.VFSDentry(), nil + } + fstype.initOnce.Do(func() { - fs, root, err := fstype.newFilesystem(ctx, vfsObj, creds) + fs, root, err := fstype.newFilesystem(ctx, vfsObj, creds, fsOpts) if err != nil { fstype.initErr = err return @@ -93,7 +166,7 @@ type filesystem struct { // newFilesystem creates a new devpts filesystem with root directory and ptmx // master inode. It returns the filesystem and root Dentry. -func (fstype *FilesystemType) newFilesystem(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials) (*filesystem, *kernfs.Dentry, error) { +func (fstype *FilesystemType) newFilesystem(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, opts fileSystemOpts) (*filesystem, *kernfs.Dentry, error) { devMinor, err := vfsObj.GetAnonBlockDevMinor() if err != nil { return nil, nil, err @@ -108,7 +181,7 @@ func (fstype *FilesystemType) newFilesystem(ctx context.Context, vfsObj *vfs.Vir root := &rootInode{ replicas: make(map[uint32]*replicaInode), } - root.InodeAttrs.Init(ctx, creds, linux.UNNAMED_MAJOR, devMinor, 1, linux.ModeDirectory|0555) + root.InodeAttrs.InitWithIDs(ctx, opts.uid, opts.gid, linux.UNNAMED_MAJOR, devMinor, 1, linux.ModeDirectory|opts.mode) root.OrderedChildren.Init(kernfs.OrderedChildrenOptions{}) root.InitRefs() @@ -120,7 +193,7 @@ func (fstype *FilesystemType) newFilesystem(ctx context.Context, vfsObj *vfs.Vir master := &masterInode{ root: root, } - master.InodeAttrs.Init(ctx, creds, linux.UNNAMED_MAJOR, devMinor, 2, linux.ModeCharacterDevice|0666) + master.InodeAttrs.InitWithIDs(ctx, opts.uid, opts.gid, linux.UNNAMED_MAJOR, devMinor, 2, linux.ModeCharacterDevice|opts.ptmxMode) // Add the master as a child of the root. links := root.OrderedChildren.Populate(map[string]kernfs.Inode{ diff --git a/pkg/sentry/fsimpl/kernfs/inode_impl_util.go b/pkg/sentry/fsimpl/kernfs/inode_impl_util.go index db54a5057..7cb23b668 100644 --- a/pkg/sentry/fsimpl/kernfs/inode_impl_util.go +++ b/pkg/sentry/fsimpl/kernfs/inode_impl_util.go @@ -191,6 +191,11 @@ type InodeAttrs struct { // Init initializes this InodeAttrs. func (a *InodeAttrs) Init(ctx context.Context, creds *auth.Credentials, devMajor, devMinor uint32, ino uint64, mode linux.FileMode) { + a.InitWithIDs(ctx, creds.EffectiveKUID, creds.EffectiveKGID, devMajor, devMinor, ino, mode) +} + +// InitWithIDs initializes this InodeAttrs. +func (a *InodeAttrs) InitWithIDs(ctx context.Context, uid auth.KUID, gid auth.KGID, devMajor, devMinor uint32, ino uint64, mode linux.FileMode) { if mode.FileType() == 0 { panic(fmt.Sprintf("No file type specified in 'mode' for InodeAttrs.Init(): mode=0%o", mode)) } @@ -203,8 +208,8 @@ func (a *InodeAttrs) Init(ctx context.Context, creds *auth.Credentials, devMajor a.devMinor = devMinor a.ino.Store(ino) a.mode.Store(uint32(mode)) - a.uid.Store(uint32(creds.EffectiveKUID)) - a.gid.Store(uint32(creds.EffectiveKGID)) + a.uid.Store(uint32(uid)) + a.gid.Store(uint32(gid)) a.nlink.Store(nlink) a.blockSize.Store(hostarch.PageSize) now := ktime.NowFromContext(ctx).Nanoseconds() diff --git a/runsc/boot/vfs.go b/runsc/boot/vfs.go index b1e5f5215..1f40982c0 100644 --- a/runsc/boot/vfs.go +++ b/runsc/boot/vfs.go @@ -95,7 +95,7 @@ func registerFilesystems(k *kernel.Kernel, info *containerInfo) error { AllowUserList: true, // TODO(b/29356795): Users may mount this once the terminals are in a // usable state. - AllowUserMount: false, + AllowUserMount: true, }) vfsObj.MustRegisterFilesystemType(devtmpfs.Name, &devtmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ AllowUserMount: true, diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index a40681a9c..3012952aa 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -1584,9 +1584,12 @@ cc_binary( "@com_google_absl//absl/time", gtest, "//test/util:cleanup", + "//test/util:fs_util", + "//test/util:mount_util", "//test/util:posix_error", "//test/util:pty_util", "//test/util:signal_util", + "//test/util:temp_path", "//test/util:test_main", "//test/util:test_util", "//test/util:thread_util", diff --git a/test/syscalls/linux/pty.cc b/test/syscalls/linux/pty.cc index bb34dc86e..132d71ac6 100644 --- a/test/syscalls/linux/pty.cc +++ b/test/syscalls/linux/pty.cc @@ -38,9 +38,13 @@ #include "test/util/capability_util.h" #include "test/util/cleanup.h" #include "test/util/file_descriptor.h" +#include "test/util/fs_util.h" +#include "test/util/linux_capability_util.h" +#include "test/util/mount_util.h" #include "test/util/posix_error.h" #include "test/util/pty_util.h" #include "test/util/signal_util.h" +#include "test/util/temp_path.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" @@ -585,6 +589,46 @@ TEST(BasicPtyTest, Getdents) { // their usage of the two modes. } +TEST(BasicPtyTest, NewInstance) { + 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("devpts_test", dir.path(), "devpts", 0, "newinstance", 0)); + auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const mount2 = ASSERT_NO_ERRNO_AND_VALUE( + Mount("devpts_test2", dir.path(), "devpts", 0, "newinstance", 0)); + + // Opening PTYs with O_TRUNC shouldn't cause an error, but calls to + // (f)truncate should. + FileDescriptor master = ASSERT_NO_ERRNO_AND_VALUE( + Open(JoinPath(dir.path(), "ptmx"), O_RDWR | O_TRUNC)); + int n = ASSERT_NO_ERRNO_AND_VALUE(ReplicaID(master)); + std::string spath2 = absl::StrCat(dir2.path(), "/", n); + ASSERT_THAT(open(spath2.c_str(), O_RDWR), SyscallFailsWithErrno(ENOENT)); + std::string spath = absl::StrCat(dir.path(), "/", n); + FileDescriptor replica = + ASSERT_NO_ERRNO_AND_VALUE(Open(spath.c_str(), O_RDWR | O_NOCTTY)); +} + +TEST(BasicPtyTest, SetMode) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); + + auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto mount = ASSERT_NO_ERRNO_AND_VALUE( + Mount("devpts_test", dir.path(), "devpts", 0, + "newinstance,mode=0600,ptmxmode=0620", 0)); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( + Open(JoinPath(dir.path()), O_RDONLY | O_DIRECTORY)); + mount.Release(); + + struct stat st; + ASSERT_THAT(fstat(fd.get(), &st), SyscallSucceeds()); + EXPECT_EQ(st.st_mode, 0600 | S_IFDIR); + ASSERT_THAT(fstatat(fd.get(), "ptmx", &st, 0), SyscallSucceeds()); + EXPECT_EQ(st.st_mode, 0620 | S_IFCHR); +} + class PtyTest : public ::testing::Test { protected: void SetUp() override {