github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/proc/fds.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package proc
    16  
    17  import (
    18  	"fmt"
    19  	"sort"
    20  	"strconv"
    21  
    22  	"github.com/SagerNet/gvisor/pkg/context"
    23  	"github.com/SagerNet/gvisor/pkg/sentry/fs"
    24  	"github.com/SagerNet/gvisor/pkg/sentry/fs/fsutil"
    25  	"github.com/SagerNet/gvisor/pkg/sentry/fs/proc/device"
    26  	"github.com/SagerNet/gvisor/pkg/sentry/fs/ramfs"
    27  	"github.com/SagerNet/gvisor/pkg/sentry/kernel"
    28  	"github.com/SagerNet/gvisor/pkg/syserror"
    29  )
    30  
    31  // LINT.IfChange
    32  
    33  // walkDescriptors finds the descriptor (file-flag pair) for the fd identified
    34  // by p, and calls the toInodeOperations callback with that descriptor.  This is a helper
    35  // method for implementing fs.InodeOperations.Lookup.
    36  func walkDescriptors(t *kernel.Task, p string, toInode func(*fs.File, kernel.FDFlags) *fs.Inode) (*fs.Inode, error) {
    37  	n, err := strconv.ParseUint(p, 10, 64)
    38  	if err != nil {
    39  		// Not found.
    40  		return nil, syserror.ENOENT
    41  	}
    42  
    43  	var file *fs.File
    44  	var fdFlags kernel.FDFlags
    45  	t.WithMuLocked(func(t *kernel.Task) {
    46  		if fdTable := t.FDTable(); fdTable != nil {
    47  			file, fdFlags = fdTable.Get(int32(n))
    48  		}
    49  	})
    50  	if file == nil {
    51  		return nil, syserror.ENOENT
    52  	}
    53  	return toInode(file, fdFlags), nil
    54  }
    55  
    56  // readDescriptors reads fds in the task starting at offset, and calls the
    57  // toDentAttr callback for each to get a DentAttr, which it then emits. This is
    58  // a helper for implementing fs.InodeOperations.Readdir.
    59  func readDescriptors(ctx context.Context, t *kernel.Task, c *fs.DirCtx, offset int64, toDentAttr func(int) fs.DentAttr) (int64, error) {
    60  	var fds []int32
    61  	t.WithMuLocked(func(t *kernel.Task) {
    62  		if fdTable := t.FDTable(); fdTable != nil {
    63  			fds = fdTable.GetFDs(ctx)
    64  		}
    65  	})
    66  
    67  	// Find the appropriate starting point.
    68  	idx := sort.Search(len(fds), func(i int) bool { return fds[i] >= int32(offset) })
    69  	if idx == len(fds) {
    70  		return offset, nil
    71  	}
    72  	fds = fds[idx:]
    73  
    74  	// Serialize all FDs.
    75  	for _, fd := range fds {
    76  		name := strconv.FormatUint(uint64(fd), 10)
    77  		if err := c.DirEmit(name, toDentAttr(int(fd))); err != nil {
    78  			// Returned offset is the next fd to serialize.
    79  			return int64(fd), err
    80  		}
    81  	}
    82  	// We serialized them all.  Next offset should be higher than last
    83  	// serialized fd.
    84  	return int64(fds[len(fds)-1] + 1), nil
    85  }
    86  
    87  // fd implements fs.InodeOperations for a file in /proc/TID/fd/.
    88  type fd struct {
    89  	ramfs.Symlink
    90  	file *fs.File
    91  }
    92  
    93  var _ fs.InodeOperations = (*fd)(nil)
    94  
    95  // newFd returns a new fd based on an existing file.
    96  //
    97  // This inherits one reference to the file.
    98  func newFd(ctx context.Context, t *kernel.Task, f *fs.File, msrc *fs.MountSource) *fs.Inode {
    99  	fd := &fd{
   100  		// RootOwner overridden by taskOwnedInodeOps.UnstableAttrs().
   101  		Symlink: *ramfs.NewSymlink(ctx, fs.RootOwner, ""),
   102  		file:    f,
   103  	}
   104  	return newProcInode(ctx, fd, msrc, fs.Symlink, t)
   105  }
   106  
   107  // GetFile returns the fs.File backing this fd.  The dirent and flags
   108  // arguments are ignored.
   109  func (f *fd) GetFile(context.Context, *fs.Dirent, fs.FileFlags) (*fs.File, error) {
   110  	// Take a reference on the fs.File.
   111  	f.file.IncRef()
   112  	return f.file, nil
   113  }
   114  
   115  // Readlink returns the current target.
   116  func (f *fd) Readlink(ctx context.Context, _ *fs.Inode) (string, error) {
   117  	root := fs.RootFromContext(ctx)
   118  	if root != nil {
   119  		defer root.DecRef(ctx)
   120  	}
   121  	n, _ := f.file.Dirent.FullName(root)
   122  	return n, nil
   123  }
   124  
   125  // Getlink implements fs.InodeOperations.Getlink.
   126  func (f *fd) Getlink(context.Context, *fs.Inode) (*fs.Dirent, error) {
   127  	f.file.Dirent.IncRef()
   128  	return f.file.Dirent, nil
   129  }
   130  
   131  // Truncate is ignored.
   132  func (f *fd) Truncate(context.Context, *fs.Inode, int64) error {
   133  	return nil
   134  }
   135  
   136  func (f *fd) Release(ctx context.Context) {
   137  	f.Symlink.Release(ctx)
   138  	f.file.DecRef(ctx)
   139  }
   140  
   141  // fdDir is an InodeOperations for /proc/TID/fd.
   142  //
   143  // +stateify savable
   144  type fdDir struct {
   145  	ramfs.Dir
   146  
   147  	// We hold a reference on the task's FDTable but only keep an indirect
   148  	// task pointer to avoid Dirent loading circularity caused by the
   149  	// table's back pointers into the dirent tree.
   150  	t *kernel.Task
   151  }
   152  
   153  var _ fs.InodeOperations = (*fdDir)(nil)
   154  
   155  // newFdDir creates a new fdDir.
   156  func newFdDir(ctx context.Context, t *kernel.Task, msrc *fs.MountSource) *fs.Inode {
   157  	f := &fdDir{
   158  		Dir: *ramfs.NewDir(ctx, nil, fs.RootOwner, fs.FilePermissions{User: fs.PermMask{Read: true, Execute: true}}),
   159  		t:   t,
   160  	}
   161  	return newProcInode(ctx, f, msrc, fs.SpecialDirectory, t)
   162  }
   163  
   164  // Check implements InodeOperations.Check.
   165  //
   166  // This is to match Linux, which uses a special permission handler to guarantee
   167  // that a process can still access /proc/self/fd after it has executed
   168  // setuid. See fs/proc/fd.c:proc_fd_permission.
   169  func (f *fdDir) Check(ctx context.Context, inode *fs.Inode, req fs.PermMask) bool {
   170  	if fs.ContextCanAccessFile(ctx, inode, req) {
   171  		return true
   172  	}
   173  	if t := kernel.TaskFromContext(ctx); t != nil {
   174  		// Allow access if the task trying to access it is in the
   175  		// thread group corresponding to this directory.
   176  		if f.t.ThreadGroup() == t.ThreadGroup() {
   177  			return true
   178  		}
   179  	}
   180  	return false
   181  }
   182  
   183  // Lookup loads an Inode in /proc/TID/fd into a Dirent.
   184  func (f *fdDir) Lookup(ctx context.Context, dir *fs.Inode, p string) (*fs.Dirent, error) {
   185  	n, err := walkDescriptors(f.t, p, func(file *fs.File, _ kernel.FDFlags) *fs.Inode {
   186  		return newFd(ctx, f.t, file, dir.MountSource)
   187  	})
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  	return fs.NewDirent(ctx, n, p), nil
   192  }
   193  
   194  // GetFile implements fs.FileOperations.GetFile.
   195  func (f *fdDir) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) {
   196  	fops := &fdDirFile{
   197  		isInfoFile: false,
   198  		t:          f.t,
   199  	}
   200  	return fs.NewFile(ctx, dirent, flags, fops), nil
   201  }
   202  
   203  // +stateify savable
   204  type fdDirFile struct {
   205  	fsutil.DirFileOperations        `state:"nosave"`
   206  	fsutil.FileUseInodeUnstableAttr `state:"nosave"`
   207  
   208  	isInfoFile bool
   209  
   210  	t *kernel.Task
   211  }
   212  
   213  var _ fs.FileOperations = (*fdDirFile)(nil)
   214  
   215  // Readdir implements fs.FileOperations.Readdir.
   216  func (f *fdDirFile) Readdir(ctx context.Context, file *fs.File, ser fs.DentrySerializer) (int64, error) {
   217  	dirCtx := &fs.DirCtx{
   218  		Serializer: ser,
   219  	}
   220  	typ := fs.RegularFile
   221  	if f.isInfoFile {
   222  		typ = fs.Symlink
   223  	}
   224  	return readDescriptors(ctx, f.t, dirCtx, file.Offset(), func(fd int) fs.DentAttr {
   225  		return fs.GenericDentAttr(typ, device.ProcDevice)
   226  	})
   227  }
   228  
   229  // fdInfoDir implements /proc/TID/fdinfo.  It embeds an fdDir, but overrides
   230  // Lookup and Readdir.
   231  //
   232  // +stateify savable
   233  type fdInfoDir struct {
   234  	ramfs.Dir
   235  
   236  	t *kernel.Task
   237  }
   238  
   239  // newFdInfoDir creates a new fdInfoDir.
   240  func newFdInfoDir(ctx context.Context, t *kernel.Task, msrc *fs.MountSource) *fs.Inode {
   241  	fdid := &fdInfoDir{
   242  		Dir: *ramfs.NewDir(ctx, nil, fs.RootOwner, fs.FilePermsFromMode(0500)),
   243  		t:   t,
   244  	}
   245  	return newProcInode(ctx, fdid, msrc, fs.SpecialDirectory, t)
   246  }
   247  
   248  // Lookup loads an fd in /proc/TID/fdinfo into a Dirent.
   249  func (fdid *fdInfoDir) Lookup(ctx context.Context, dir *fs.Inode, p string) (*fs.Dirent, error) {
   250  	inode, err := walkDescriptors(fdid.t, p, func(file *fs.File, fdFlags kernel.FDFlags) *fs.Inode {
   251  		// TODO(b/121266871): Using a static inode here means that the
   252  		// data can be out-of-date if, for instance, the flags on the
   253  		// FD change before we read this file. We should switch to
   254  		// generating the data on Read(). Also, we should include pos,
   255  		// locks, and other data.  For now we only have flags.
   256  		// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
   257  		flags := file.Flags().ToLinux() | fdFlags.ToLinuxFileFlags()
   258  		file.DecRef(ctx)
   259  		contents := []byte(fmt.Sprintf("flags:\t0%o\n", flags))
   260  		return newStaticProcInode(ctx, dir.MountSource, contents)
   261  	})
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  	return fs.NewDirent(ctx, inode, p), nil
   266  }
   267  
   268  // GetFile implements fs.FileOperations.GetFile.
   269  func (fdid *fdInfoDir) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) {
   270  	fops := &fdDirFile{
   271  		isInfoFile: true,
   272  		t:          fdid.t,
   273  	}
   274  	return fs.NewFile(ctx, dirent, flags, fops), nil
   275  }
   276  
   277  // LINT.ThenChange(../../fsimpl/proc/task_files.go)