diff options
Diffstat (limited to 'pkg/sentry/fs/proc/fds.go')
-rw-r--r-- | pkg/sentry/fs/proc/fds.go | 258 |
1 files changed, 258 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..2eca9ac31 --- /dev/null +++ b/pkg/sentry/fs/proc/fds.go @@ -0,0 +1,258 @@ +// Copyright 2018 Google Inc. +// +// 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/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/sentry/usermem" + "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 flags kernel.FDFlags + t.WithMuLocked(func(t *kernel.Task) { + if fdm := t.FDMap(); fdm != nil { + file, flags = fdm.GetDescriptor(kdefs.FD(n)) + } + }) + if file == nil { + return nil, syserror.ENOENT + } + return toInode(file, flags), 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 int, toDentAttr func(int) fs.DentAttr) (int, 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, 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 fd, err + } + } + // We serialized them all. Next offset should be higher than last + // serialized fd. + return fd + 1, nil +} + +// fd is a single file in /proc/TID/fd/. +type fd struct { + ramfs.Symlink + *fs.File +} + +// 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{File: f} + // RootOwner by default, is overridden in UnstableAttr() + fd.InitSymlink(t, fs.RootOwner, "") + return newFile(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) + defer root.DecRef() + n, _ := f.Dirent.FullName(root) + return n, nil +} + +// Getlink implements fs.InodeOperations.Getlink. +func (f *fd) Getlink(context.Context, *fs.Inode) (*fs.Dirent, error) { + f.Dirent.IncRef() + return f.Dirent, nil +} + +// Truncate is ignored. +func (f *fd) Truncate(context.Context, *fs.Inode, int64) error { + return nil +} + +// Close releases the reference on the file. +func (f *fd) Close() error { + f.DecRef() + return nil +} + +// fdDir implements /proc/TID/fd. +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 +} + +// newFdDir creates a new fdDir. +func newFdDir(t *kernel.Task, msrc *fs.MountSource) *fs.Inode { + f := &fdDir{t: t} + f.InitDir(t, nil, fs.RootOwner, fs.FilePermissions{User: fs.PermMask{Read: true, Execute: true}}) + return newFile(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. + // + // N.B. Technically, in Linux 3.11, this compares what would be + // the equivalent of task pointers. However, this was fixed + // later in 54708d2858e7 ("proc: actually make + // proc_fd_permission() thread-friendly"). + 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 +} + +// DeprecatedReaddir lists fds in /proc/TID/fd. +func (f *fdDir) DeprecatedReaddir(ctx context.Context, dirCtx *fs.DirCtx, offset int) (int, error) { + return readDescriptors(f.t, dirCtx, offset, func(fd int) fs.DentAttr { + return fs.GenericDentAttr(fs.Symlink, device.ProcDevice) + }) +} + +// fdInfo is a single file in /proc/TID/fdinfo/. +type fdInfo struct { + ramfs.File + + flags kernel.FDFlags +} + +// newFdInfo returns a new fdInfo based on an existing file. +func newFdInfo(t *kernel.Task, _ *fs.File, flags kernel.FDFlags, msrc *fs.MountSource) *fs.Inode { + fdi := &fdInfo{flags: flags} + fdi.InitFile(t, fs.RootOwner, fs.FilePermissions{User: fs.PermMask{Read: true}}) + // TODO: Get pos, locks, and other data. For now we only + // have flags. + // See https://www.kernel.org/doc/Documentation/filesystems/proc.txt + fdi.Append([]byte(fmt.Sprintf("flags: %08o\n", flags))) + return newFile(fdi, msrc, fs.SpecialFile, t) +} + +// DeprecatedPwritev implements fs.HandleOperations.DeprecatedPwritev. +func (*fdInfo) DeprecatedPwritev(ctx context.Context, src usermem.IOSequence, offset int64) (int64, error) { + return 0, ramfs.ErrInvalidOp +} + +// Truncate implements fs.InodeOperations.Truncate. +func (*fdInfo) Truncate(ctx context.Context, inode *fs.Inode, size int64) error { + return ramfs.ErrInvalidOp +} + +// fdInfoDir implements /proc/TID/fdinfo. It embeds an fdDir, but overrides +// Lookup and Readdir. +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{t: t} + fdid.InitDir(t, nil, fs.RootOwner, fs.FilePermsFromMode(0500)) + return newFile(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) { + n, err := walkDescriptors(fdid.t, p, func(file *fs.File, flags kernel.FDFlags) *fs.Inode { + return newFdInfo(fdid.t, file, flags, dir.MountSource) + }) + if err != nil { + return nil, err + } + return fs.NewDirent(n, p), nil +} + +// DeprecatedReaddir lists fds in /proc/TID/fdinfo. +func (fdid *fdInfoDir) DeprecatedReaddir(ctx context.Context, dirCtx *fs.DirCtx, offset int) (int, error) { + return readDescriptors(fdid.t, dirCtx, offset, func(fd int) fs.DentAttr { + return fs.GenericDentAttr(fs.RegularFile, device.ProcDevice) + }) +} |