summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/fs/proc/fds.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/sentry/fs/proc/fds.go')
-rw-r--r--pkg/sentry/fs/proc/fds.go285
1 files changed, 285 insertions, 0 deletions
diff --git a/pkg/sentry/fs/proc/fds.go b/pkg/sentry/fs/proc/fds.go
new file mode 100644
index 000000000..744b31c74
--- /dev/null
+++ b/pkg/sentry/fs/proc/fds.go
@@ -0,0 +1,285 @@
+// Copyright 2018 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package proc
+
+import (
+ "fmt"
+ "sort"
+ "strconv"
+
+ "gvisor.googlesource.com/gvisor/pkg/sentry/context"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/fs"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/fs/fsutil"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/fs/proc/device"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/fs/ramfs"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/kernel"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/kernel/kdefs"
+ "gvisor.googlesource.com/gvisor/pkg/syserror"
+)
+
+// walkDescriptors finds the descriptor (file-flag pair) for the fd identified
+// by p, and calls the toInodeOperations callback with that descriptor. This is a helper
+// method for implementing fs.InodeOperations.Lookup.
+func walkDescriptors(t *kernel.Task, p string, toInode func(*fs.File, kernel.FDFlags) *fs.Inode) (*fs.Inode, error) {
+ n, err := strconv.ParseUint(p, 10, 64)
+ if err != nil {
+ // Not found.
+ return nil, syserror.ENOENT
+ }
+
+ var file *fs.File
+ var fdFlags kernel.FDFlags
+ t.WithMuLocked(func(t *kernel.Task) {
+ if fdm := t.FDMap(); fdm != nil {
+ file, fdFlags = fdm.GetDescriptor(kdefs.FD(n))
+ }
+ })
+ if file == nil {
+ return nil, syserror.ENOENT
+ }
+ return toInode(file, fdFlags), nil
+}
+
+// readDescriptors reads fds in the task starting at offset, and calls the
+// toDentAttr callback for each to get a DentAttr, which it then emits. This is
+// a helper for implementing fs.InodeOperations.Readdir.
+func readDescriptors(t *kernel.Task, c *fs.DirCtx, offset int64, toDentAttr func(int) fs.DentAttr) (int64, error) {
+ var fds kernel.FDs
+ t.WithMuLocked(func(t *kernel.Task) {
+ if fdm := t.FDMap(); fdm != nil {
+ fds = fdm.GetFDs()
+ }
+ })
+
+ fdInts := make([]int, 0, len(fds))
+ for _, fd := range fds {
+ fdInts = append(fdInts, int(fd))
+ }
+
+ // Find the fd to start at.
+ idx := sort.SearchInts(fdInts, int(offset))
+ if idx == len(fdInts) {
+ return offset, nil
+ }
+ fdInts = fdInts[idx:]
+
+ var fd int
+ for _, fd = range fdInts {
+ name := strconv.FormatUint(uint64(fd), 10)
+ if err := c.DirEmit(name, toDentAttr(fd)); err != nil {
+ // Returned offset is the next fd to serialize.
+ return int64(fd), err
+ }
+ }
+ // We serialized them all. Next offset should be higher than last
+ // serialized fd.
+ return int64(fd + 1), nil
+}
+
+// fd implements fs.InodeOperations for a file in /proc/TID/fd/.
+type fd struct {
+ ramfs.Symlink
+ file *fs.File
+}
+
+var _ fs.InodeOperations = (*fd)(nil)
+
+// newFd returns a new fd based on an existing file.
+//
+// This inherits one reference to the file.
+func newFd(t *kernel.Task, f *fs.File, msrc *fs.MountSource) *fs.Inode {
+ fd := &fd{
+ // RootOwner overridden by taskOwnedInodeOps.UnstableAttrs().
+ Symlink: *ramfs.NewSymlink(t, fs.RootOwner, ""),
+ file: f,
+ }
+ return newProcInode(fd, msrc, fs.Symlink, t)
+}
+
+// GetFile returns the fs.File backing this fd. The dirent and flags
+// arguments are ignored.
+func (f *fd) GetFile(context.Context, *fs.Dirent, fs.FileFlags) (*fs.File, error) {
+ // Take a reference on the fs.File.
+ f.file.IncRef()
+ return f.file, nil
+}
+
+// Readlink returns the current target.
+func (f *fd) Readlink(ctx context.Context, _ *fs.Inode) (string, error) {
+ root := fs.RootFromContext(ctx)
+ if root != nil {
+ defer root.DecRef()
+ }
+ n, _ := f.file.Dirent.FullName(root)
+ return n, nil
+}
+
+// Getlink implements fs.InodeOperations.Getlink.
+func (f *fd) Getlink(context.Context, *fs.Inode) (*fs.Dirent, error) {
+ f.file.Dirent.IncRef()
+ return f.file.Dirent, nil
+}
+
+// Truncate is ignored.
+func (f *fd) Truncate(context.Context, *fs.Inode, int64) error {
+ return nil
+}
+
+func (f *fd) Release(ctx context.Context) {
+ f.Symlink.Release(ctx)
+ f.file.DecRef()
+}
+
+// Close releases the reference on the file.
+func (f *fd) Close() error {
+ f.file.DecRef()
+ return nil
+}
+
+// fdDir is an InodeOperations for /proc/TID/fd.
+//
+// +stateify savable
+type fdDir struct {
+ ramfs.Dir
+
+ // We hold a reference on the task's fdmap but only keep an indirect
+ // task pointer to avoid Dirent loading circularity caused by fdmap's
+ // potential back pointers into the dirent tree.
+ t *kernel.Task
+}
+
+var _ fs.InodeOperations = (*fdDir)(nil)
+
+// newFdDir creates a new fdDir.
+func newFdDir(t *kernel.Task, msrc *fs.MountSource) *fs.Inode {
+ f := &fdDir{
+ Dir: *ramfs.NewDir(t, nil, fs.RootOwner, fs.FilePermissions{User: fs.PermMask{Read: true, Execute: true}}),
+ t: t,
+ }
+ return newProcInode(f, msrc, fs.SpecialDirectory, t)
+}
+
+// Check implements InodeOperations.Check.
+//
+// This is to match Linux, which uses a special permission handler to guarantee
+// that a process can still access /proc/self/fd after it has executed
+// setuid. See fs/proc/fd.c:proc_fd_permission.
+func (f *fdDir) Check(ctx context.Context, inode *fs.Inode, req fs.PermMask) bool {
+ if fs.ContextCanAccessFile(ctx, inode, req) {
+ return true
+ }
+ if t := kernel.TaskFromContext(ctx); t != nil {
+ // Allow access if the task trying to access it is in the
+ // thread group corresponding to this directory.
+ if f.t.ThreadGroup() == t.ThreadGroup() {
+ return true
+ }
+ }
+ return false
+}
+
+// Lookup loads an Inode in /proc/TID/fd into a Dirent.
+func (f *fdDir) Lookup(ctx context.Context, dir *fs.Inode, p string) (*fs.Dirent, error) {
+ n, err := walkDescriptors(f.t, p, func(file *fs.File, _ kernel.FDFlags) *fs.Inode {
+ return newFd(f.t, file, dir.MountSource)
+ })
+ if err != nil {
+ return nil, err
+ }
+ return fs.NewDirent(n, p), nil
+}
+
+// GetFile implements fs.FileOperations.GetFile.
+func (f *fdDir) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) {
+ fops := &fdDirFile{
+ isInfoFile: false,
+ t: f.t,
+ }
+ return fs.NewFile(ctx, dirent, flags, fops), nil
+}
+
+// +stateify savable
+type fdDirFile struct {
+ fsutil.DirFileOperations `state:"nosave"`
+ fsutil.FileUseInodeUnstableAttr `state:"nosave"`
+
+ isInfoFile bool
+
+ t *kernel.Task
+}
+
+var _ fs.FileOperations = (*fdDirFile)(nil)
+
+// Readdir implements fs.FileOperations.Readdir.
+func (f *fdDirFile) Readdir(ctx context.Context, file *fs.File, ser fs.DentrySerializer) (int64, error) {
+ dirCtx := &fs.DirCtx{
+ Serializer: ser,
+ }
+ typ := fs.RegularFile
+ if f.isInfoFile {
+ typ = fs.Symlink
+ }
+ return readDescriptors(f.t, dirCtx, file.Offset(), func(fd int) fs.DentAttr {
+ return fs.GenericDentAttr(typ, device.ProcDevice)
+ })
+}
+
+// fdInfoDir implements /proc/TID/fdinfo. It embeds an fdDir, but overrides
+// Lookup and Readdir.
+//
+// +stateify savable
+type fdInfoDir struct {
+ ramfs.Dir
+
+ t *kernel.Task
+}
+
+// newFdInfoDir creates a new fdInfoDir.
+func newFdInfoDir(t *kernel.Task, msrc *fs.MountSource) *fs.Inode {
+ fdid := &fdInfoDir{
+ Dir: *ramfs.NewDir(t, nil, fs.RootOwner, fs.FilePermsFromMode(0500)),
+ t: t,
+ }
+ return newProcInode(fdid, msrc, fs.SpecialDirectory, t)
+}
+
+// Lookup loads an fd in /proc/TID/fdinfo into a Dirent.
+func (fdid *fdInfoDir) Lookup(ctx context.Context, dir *fs.Inode, p string) (*fs.Dirent, error) {
+ inode, err := walkDescriptors(fdid.t, p, func(file *fs.File, fdFlags kernel.FDFlags) *fs.Inode {
+ // TODO(b/121266871): Using a static inode here means that the
+ // data can be out-of-date if, for instance, the flags on the
+ // FD change before we read this file. We should switch to
+ // generating the data on Read(). Also, we should include pos,
+ // locks, and other data. For now we only have flags.
+ // See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
+ flags := file.Flags().ToLinux() | fdFlags.ToLinuxFileFlags()
+ file.DecRef()
+ contents := []byte(fmt.Sprintf("flags:\t0%o\n", flags))
+ return newStaticProcInode(ctx, dir.MountSource, contents)
+ })
+ if err != nil {
+ return nil, err
+ }
+ return fs.NewDirent(inode, p), nil
+}
+
+// GetFile implements fs.FileOperations.GetFile.
+func (fdid *fdInfoDir) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) {
+ fops := &fdDirFile{
+ isInfoFile: true,
+ t: fdid.t,
+ }
+ return fs.NewFile(ctx, dirent, flags, fops), nil
+}