Add remaining procfs files

Closes #1195

PiperOrigin-RevId: 300867055
This commit is contained in:
Fabricio Voznika
2020-03-13 18:57:07 -07:00
committed by gVisor bot
parent 829beebf0b
commit 45a8ae240d
13 changed files with 709 additions and 80 deletions
@@ -554,3 +554,11 @@ func (s *StaticDirectory) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, opts vfs
fd.Init(rp.Mount(), vfsd, &s.OrderedChildren, &opts)
return fd.VFSFileDescription(), nil
}
// AlwaysValid partially implements kernfs.inodeDynamicLookup.
type AlwaysValid struct{}
// Valid implements kernfs.inodeDynamicLookup.
func (*AlwaysValid) Valid(context.Context) bool {
return true
}
+1 -1
View File
@@ -319,7 +319,7 @@ type inodeMetadata interface {
// CheckPermissions checks that creds may access this inode for the
// requested access type, per the the rules of
// fs/namei.c:generic_permission().
CheckPermissions(ctx context.Context, creds *auth.Credentials, atx vfs.AccessTypes) error
CheckPermissions(ctx context.Context, creds *auth.Credentials, ats vfs.AccessTypes) error
// Mode returns the (struct stat)::st_mode value for this inode. This is
// separated from Stat for performance.
+4
View File
@@ -8,6 +8,7 @@ go_library(
"filesystem.go",
"subtasks.go",
"task.go",
"task_fds.go",
"task_files.go",
"task_net.go",
"tasks.go",
@@ -19,8 +20,10 @@ go_library(
"//pkg/abi/linux",
"//pkg/context",
"//pkg/log",
"//pkg/refs",
"//pkg/safemem",
"//pkg/sentry/fs",
"//pkg/sentry/fsbridge",
"//pkg/sentry/fsimpl/kernfs",
"//pkg/sentry/inet",
"//pkg/sentry/kernel",
@@ -53,6 +56,7 @@ go_test(
"//pkg/fspath",
"//pkg/sentry/contexttest",
"//pkg/sentry/fsimpl/testutil",
"//pkg/sentry/fsimpl/tmpfs",
"//pkg/sentry/inet",
"//pkg/sentry/kernel",
"//pkg/sentry/kernel/auth",
+1 -5
View File
@@ -34,6 +34,7 @@ type subtasksInode struct {
kernfs.InodeDirectoryNoNewChildren
kernfs.InodeAttrs
kernfs.OrderedChildren
kernfs.AlwaysValid
task *kernel.Task
pidns *kernel.PIDNamespace
@@ -61,11 +62,6 @@ func newSubtasks(task *kernel.Task, pidns *kernel.PIDNamespace, inoGen InoGenera
return dentry
}
// Valid implements kernfs.inodeDynamicLookup.
func (i *subtasksInode) Valid(ctx context.Context) bool {
return true
}
// Lookup implements kernfs.inodeDynamicLookup.
func (i *subtasksInode) Lookup(ctx context.Context, name string) (*vfs.Dentry, error) {
tid, err := strconv.ParseUint(name, 10, 32)
+13 -13
View File
@@ -45,19 +45,19 @@ var _ kernfs.Inode = (*taskInode)(nil)
func newTaskInode(inoGen InoGenerator, task *kernel.Task, pidns *kernel.PIDNamespace, isThreadGroup bool, cgroupControllers map[string]string) *kernfs.Dentry {
contents := map[string]*kernfs.Dentry{
"auxv": newTaskOwnedFile(task, inoGen.NextIno(), 0444, &auxvData{task: task}),
"cmdline": newTaskOwnedFile(task, inoGen.NextIno(), 0444, &cmdlineData{task: task, arg: cmdlineDataArg}),
"comm": newComm(task, inoGen.NextIno(), 0444),
"environ": newTaskOwnedFile(task, inoGen.NextIno(), 0444, &cmdlineData{task: task, arg: environDataArg}),
//"exe": newExe(t, msrc),
//"fd": newFdDir(t, msrc),
//"fdinfo": newFdInfoDir(t, msrc),
"gid_map": newTaskOwnedFile(task, inoGen.NextIno(), 0644, &idMapData{task: task, gids: true}),
"io": newTaskOwnedFile(task, inoGen.NextIno(), 0400, newIO(task, isThreadGroup)),
"maps": newTaskOwnedFile(task, inoGen.NextIno(), 0444, &mapsData{task: task}),
//"mountinfo": seqfile.NewSeqFileInode(t, &mountInfoFile{t: t}, msrc),
//"mounts": seqfile.NewSeqFileInode(t, &mountsFile{t: t}, msrc),
"net": newTaskNetDir(task, inoGen),
"auxv": newTaskOwnedFile(task, inoGen.NextIno(), 0444, &auxvData{task: task}),
"cmdline": newTaskOwnedFile(task, inoGen.NextIno(), 0444, &cmdlineData{task: task, arg: cmdlineDataArg}),
"comm": newComm(task, inoGen.NextIno(), 0444),
"environ": newTaskOwnedFile(task, inoGen.NextIno(), 0444, &cmdlineData{task: task, arg: environDataArg}),
"exe": newExeSymlink(task, inoGen.NextIno()),
"fd": newFDDirInode(task, inoGen),
"fdinfo": newFDInfoDirInode(task, inoGen),
"gid_map": newTaskOwnedFile(task, inoGen.NextIno(), 0644, &idMapData{task: task, gids: true}),
"io": newTaskOwnedFile(task, inoGen.NextIno(), 0400, newIO(task, isThreadGroup)),
"maps": newTaskOwnedFile(task, inoGen.NextIno(), 0444, &mapsData{task: task}),
"mountinfo": newTaskOwnedFile(task, inoGen.NextIno(), 0444, &mountInfoData{task: task}),
"mounts": newTaskOwnedFile(task, inoGen.NextIno(), 0444, &mountsData{task: task}),
"net": newTaskNetDir(task, inoGen),
"ns": newTaskOwnedDir(task, inoGen.NextIno(), 0511, map[string]*kernfs.Dentry{
"net": newNamespaceSymlink(task, inoGen.NextIno(), "net"),
"pid": newNamespaceSymlink(task, inoGen.NextIno(), "pid"),
+287
View File
@@ -0,0 +1,287 @@
// Copyright 2020 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 (
"bytes"
"fmt"
"sort"
"strconv"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/refs"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/syserror"
)
type fdDir struct {
inoGen InoGenerator
task *kernel.Task
// When produceSymlinks is set, dirents produces for the FDs are reported
// as symlink. Otherwise, they are reported as regular files.
produceSymlink bool
}
func (i *fdDir) lookup(name string) (*vfs.FileDescription, kernel.FDFlags, error) {
fd, err := strconv.ParseUint(name, 10, 64)
if err != nil {
return nil, kernel.FDFlags{}, syserror.ENOENT
}
var (
file *vfs.FileDescription
flags kernel.FDFlags
)
i.task.WithMuLocked(func(t *kernel.Task) {
if fdTable := t.FDTable(); fdTable != nil {
file, flags = fdTable.GetVFS2(int32(fd))
}
})
if file == nil {
return nil, kernel.FDFlags{}, syserror.ENOENT
}
return file, flags, nil
}
// IterDirents implements kernfs.inodeDynamicLookup.
func (i *fdDir) IterDirents(ctx context.Context, cb vfs.IterDirentsCallback, absOffset, relOffset int64) (int64, error) {
var fds []int32
i.task.WithMuLocked(func(t *kernel.Task) {
if fdTable := t.FDTable(); fdTable != nil {
fds = fdTable.GetFDs()
}
})
offset := absOffset + relOffset
typ := uint8(linux.DT_REG)
if i.produceSymlink {
typ = linux.DT_LNK
}
// Find the appropriate starting point.
idx := sort.Search(len(fds), func(i int) bool { return fds[i] >= int32(relOffset) })
if idx >= len(fds) {
return offset, nil
}
for _, fd := range fds[idx:] {
dirent := vfs.Dirent{
Name: strconv.FormatUint(uint64(fd), 10),
Type: typ,
Ino: i.inoGen.NextIno(),
NextOff: offset + 1,
}
if err := cb.Handle(dirent); err != nil {
return offset, err
}
offset++
}
return offset, nil
}
// fdDirInode represents the inode for /proc/[pid]/fd directory.
//
// +stateify savable
type fdDirInode struct {
kernfs.InodeNotSymlink
kernfs.InodeDirectoryNoNewChildren
kernfs.InodeAttrs
kernfs.OrderedChildren
kernfs.AlwaysValid
fdDir
}
var _ kernfs.Inode = (*fdDirInode)(nil)
func newFDDirInode(task *kernel.Task, inoGen InoGenerator) *kernfs.Dentry {
inode := &fdDirInode{
fdDir: fdDir{
inoGen: inoGen,
task: task,
produceSymlink: true,
},
}
inode.InodeAttrs.Init(task.Credentials(), inoGen.NextIno(), linux.ModeDirectory|0555)
dentry := &kernfs.Dentry{}
dentry.Init(inode)
inode.OrderedChildren.Init(kernfs.OrderedChildrenOptions{})
return dentry
}
// Lookup implements kernfs.inodeDynamicLookup.
func (i *fdDirInode) Lookup(ctx context.Context, name string) (*vfs.Dentry, error) {
file, _, err := i.lookup(name)
if err != nil {
return nil, err
}
taskDentry := newFDSymlink(i.task.Credentials(), file, i.inoGen.NextIno())
return taskDentry.VFSDentry(), nil
}
// Open implements kernfs.Inode.
func (i *fdDirInode) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
fd := &kernfs.GenericDirectoryFD{}
fd.Init(rp.Mount(), vfsd, &i.OrderedChildren, &opts)
return fd.VFSFileDescription(), nil
}
// CheckPermissions implements kernfs.Inode.
//
// 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 (i *fdDirInode) CheckPermissions(ctx context.Context, creds *auth.Credentials, ats vfs.AccessTypes) error {
err := i.InodeAttrs.CheckPermissions(ctx, creds, ats)
if err == nil {
// Access granted, no extra check needed.
return nil
}
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 i.task.ThreadGroup() == t.ThreadGroup() {
// Access granted (overridden).
return nil
}
}
return err
}
// fdSymlink is an symlink for the /proc/[pid]/fd/[fd] file.
//
// +stateify savable
type fdSymlink struct {
refs.AtomicRefCount
kernfs.InodeAttrs
kernfs.InodeSymlink
file *vfs.FileDescription
}
var _ kernfs.Inode = (*fdSymlink)(nil)
func newFDSymlink(creds *auth.Credentials, file *vfs.FileDescription, ino uint64) *kernfs.Dentry {
file.IncRef()
inode := &fdSymlink{file: file}
inode.Init(creds, ino, linux.ModeSymlink|0777)
d := &kernfs.Dentry{}
d.Init(inode)
return d
}
func (s *fdSymlink) Readlink(ctx context.Context) (string, error) {
root := vfs.RootFromContext(ctx)
defer root.DecRef()
vfsObj := s.file.VirtualDentry().Mount().Filesystem().VirtualFilesystem()
return vfsObj.PathnameWithDeleted(ctx, root, s.file.VirtualDentry())
}
func (s *fdSymlink) DecRef() {
s.AtomicRefCount.DecRefWithDestructor(func() {
s.Destroy()
})
}
func (s *fdSymlink) Destroy() {
s.file.DecRef()
}
// fdInfoDirInode represents the inode for /proc/[pid]/fdinfo directory.
//
// +stateify savable
type fdInfoDirInode struct {
kernfs.InodeNotSymlink
kernfs.InodeDirectoryNoNewChildren
kernfs.InodeAttrs
kernfs.OrderedChildren
kernfs.AlwaysValid
fdDir
}
var _ kernfs.Inode = (*fdInfoDirInode)(nil)
func newFDInfoDirInode(task *kernel.Task, inoGen InoGenerator) *kernfs.Dentry {
inode := &fdInfoDirInode{
fdDir: fdDir{
inoGen: inoGen,
task: task,
},
}
inode.InodeAttrs.Init(task.Credentials(), inoGen.NextIno(), linux.ModeDirectory|0555)
dentry := &kernfs.Dentry{}
dentry.Init(inode)
inode.OrderedChildren.Init(kernfs.OrderedChildrenOptions{})
return dentry
}
// Lookup implements kernfs.inodeDynamicLookup.
func (i *fdInfoDirInode) Lookup(ctx context.Context, name string) (*vfs.Dentry, error) {
file, flags, err := i.lookup(name)
if err != nil {
return nil, err
}
data := &fdInfoData{file: file, flags: flags}
dentry := newTaskOwnedFile(i.task, i.inoGen.NextIno(), 0444, data)
return dentry.VFSDentry(), nil
}
// Open implements kernfs.Inode.
func (i *fdInfoDirInode) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
fd := &kernfs.GenericDirectoryFD{}
fd.Init(rp.Mount(), vfsd, &i.OrderedChildren, &opts)
return fd.VFSFileDescription(), nil
}
// fdInfoData implements vfs.DynamicBytesSource for /proc/[pid]/fdinfo/[fd].
//
// +stateify savable
type fdInfoData struct {
kernfs.DynamicBytesFile
refs.AtomicRefCount
file *vfs.FileDescription
flags kernel.FDFlags
}
var _ dynamicInode = (*fdInfoData)(nil)
func (d *fdInfoData) DecRef() {
d.AtomicRefCount.DecRefWithDestructor(d.destroy)
}
func (d *fdInfoData) destroy() {
d.file.DecRef()
}
// Generate implements vfs.DynamicBytesSource.Generate.
func (d *fdInfoData) Generate(ctx context.Context, buf *bytes.Buffer) error {
// TODO(b/121266871): Include pos, locks, and other data. For now we only
// have flags.
// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
flags := uint(d.file.StatusFlags()) | d.flags.ToLinuxFileFlags()
fmt.Fprintf(buf, "flags:\t0%o\n", flags)
return nil
}
+250 -1
View File
@@ -18,10 +18,14 @@ import (
"bytes"
"fmt"
"io"
"sort"
"strings"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/safemem"
"gvisor.dev/gvisor/pkg/sentry/fs"
"gvisor.dev/gvisor/pkg/sentry/fsbridge"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
@@ -496,7 +500,7 @@ func (s *statusData) Generate(ctx context.Context, buf *bytes.Buffer) error {
return nil
}
// ioUsage is the /proc/<pid>/io and /proc/<pid>/task/<tid>/io data provider.
// ioUsage is the /proc/[pid]/io and /proc/[pid]/task/[tid]/io data provider.
type ioUsage interface {
// IOUsage returns the io usage data.
IOUsage() *usage.IO
@@ -570,3 +574,248 @@ func (o *oomScoreAdj) Write(ctx context.Context, src usermem.IOSequence, offset
return n, nil
}
// exeSymlink is an symlink for the /proc/[pid]/exe file.
//
// +stateify savable
type exeSymlink struct {
kernfs.InodeAttrs
kernfs.InodeNoopRefCount
kernfs.InodeSymlink
task *kernel.Task
}
var _ kernfs.Inode = (*exeSymlink)(nil)
func newExeSymlink(task *kernel.Task, ino uint64) *kernfs.Dentry {
inode := &exeSymlink{task: task}
inode.Init(task.Credentials(), ino, linux.ModeSymlink|0777)
d := &kernfs.Dentry{}
d.Init(inode)
return d
}
// Readlink implements kernfs.Inode.
func (s *exeSymlink) Readlink(ctx context.Context) (string, error) {
if !kernel.ContextCanTrace(ctx, s.task, false) {
return "", syserror.EACCES
}
// Pull out the executable for /proc/[pid]/exe.
exec, err := s.executable()
if err != nil {
return "", err
}
defer exec.DecRef()
return exec.PathnameWithDeleted(ctx), nil
}
func (s *exeSymlink) executable() (file fsbridge.File, err error) {
s.task.WithMuLocked(func(t *kernel.Task) {
mm := t.MemoryManager()
if mm == nil {
// TODO(b/34851096): Check shouldn't allow Readlink once the
// Task is zombied.
err = syserror.EACCES
return
}
// The MemoryManager may be destroyed, in which case
// MemoryManager.destroy will simply set the executable to nil
// (with locks held).
file = mm.Executable()
if file == nil {
err = syserror.ENOENT
}
})
return
}
// forEachMountSource runs f for the process root mount and each mount that is
// a descendant of the root.
func forEachMount(t *kernel.Task, fn func(string, *fs.Mount)) {
var fsctx *kernel.FSContext
t.WithMuLocked(func(t *kernel.Task) {
fsctx = t.FSContext()
})
if fsctx == nil {
// The task has been destroyed. Nothing to show here.
return
}
// All mount points must be relative to the rootDir, and mounts outside
// will be excluded.
rootDir := fsctx.RootDirectory()
if rootDir == nil {
// The task has been destroyed. Nothing to show here.
return
}
defer rootDir.DecRef()
mnt := t.MountNamespace().FindMount(rootDir)
if mnt == nil {
// Has it just been unmounted?
return
}
ms := t.MountNamespace().AllMountsUnder(mnt)
sort.Slice(ms, func(i, j int) bool {
return ms[i].ID < ms[j].ID
})
for _, m := range ms {
mroot := m.Root()
if mroot == nil {
continue // No longer valid.
}
mountPath, desc := mroot.FullName(rootDir)
mroot.DecRef()
if !desc {
// MountSources that are not descendants of the chroot jail are ignored.
continue
}
fn(mountPath, m)
}
}
// mountInfoData is used to implement /proc/[pid]/mountinfo.
//
// +stateify savable
type mountInfoData struct {
kernfs.DynamicBytesFile
task *kernel.Task
}
var _ dynamicInode = (*mountInfoData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (i *mountInfoData) Generate(ctx context.Context, buf *bytes.Buffer) error {
forEachMount(i.task, func(mountPath string, m *fs.Mount) {
mroot := m.Root()
if mroot == nil {
return // No longer valid.
}
defer mroot.DecRef()
// Format:
// 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
// (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11)
// (1) MountSource ID.
fmt.Fprintf(buf, "%d ", m.ID)
// (2) Parent ID (or this ID if there is no parent).
pID := m.ID
if !m.IsRoot() && !m.IsUndo() {
pID = m.ParentID
}
fmt.Fprintf(buf, "%d ", pID)
// (3) Major:Minor device ID. We don't have a superblock, so we
// just use the root inode device number.
sa := mroot.Inode.StableAttr
fmt.Fprintf(buf, "%d:%d ", sa.DeviceFileMajor, sa.DeviceFileMinor)
// (4) Root: the pathname of the directory in the filesystem
// which forms the root of this mount.
//
// NOTE(b/78135857): This will always be "/" until we implement
// bind mounts.
fmt.Fprintf(buf, "/ ")
// (5) Mount point (relative to process root).
fmt.Fprintf(buf, "%s ", mountPath)
// (6) Mount options.
flags := mroot.Inode.MountSource.Flags
opts := "rw"
if flags.ReadOnly {
opts = "ro"
}
if flags.NoAtime {
opts += ",noatime"
}
if flags.NoExec {
opts += ",noexec"
}
fmt.Fprintf(buf, "%s ", opts)
// (7) Optional fields: zero or more fields of the form "tag[:value]".
// (8) Separator: the end of the optional fields is marked by a single hyphen.
fmt.Fprintf(buf, "- ")
// (9) Filesystem type.
fmt.Fprintf(buf, "%s ", mroot.Inode.MountSource.FilesystemType)
// (10) Mount source: filesystem-specific information or "none".
fmt.Fprintf(buf, "none ")
// (11) Superblock options, and final newline.
fmt.Fprintf(buf, "%s\n", superBlockOpts(mountPath, mroot.Inode.MountSource))
})
return nil
}
func superBlockOpts(mountPath string, msrc *fs.MountSource) string {
// gVisor doesn't (yet) have a concept of super block options, so we
// use the ro/rw bit from the mount flag.
opts := "rw"
if msrc.Flags.ReadOnly {
opts = "ro"
}
// NOTE(b/147673608): If the mount is a cgroup, we also need to include
// the cgroup name in the options. For now we just read that from the
// path.
// TODO(gvisor.dev/issues/190): Once gVisor has full cgroup support, we
// should get this value from the cgroup itself, and not rely on the
// path.
if msrc.FilesystemType == "cgroup" {
splitPath := strings.Split(mountPath, "/")
cgroupType := splitPath[len(splitPath)-1]
opts += "," + cgroupType
}
return opts
}
// mountsData is used to implement /proc/[pid]/mounts.
//
// +stateify savable
type mountsData struct {
kernfs.DynamicBytesFile
task *kernel.Task
}
var _ dynamicInode = (*mountInfoData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (i *mountsData) Generate(ctx context.Context, buf *bytes.Buffer) error {
forEachMount(i.task, func(mountPath string, m *fs.Mount) {
// Format:
// <special device or remote filesystem> <mount point> <filesystem type> <mount options> <needs dump> <fsck order>
//
// We use the filesystem name as the first field, since there
// is no real block device we can point to, and we also should
// not expose anything about the remote filesystem.
//
// Only ro/rw option is supported for now.
//
// The "needs dump"and fsck flags are always 0, which is allowed.
root := m.Root()
if root == nil {
return // No longer valid.
}
defer root.DecRef()
flags := root.Inode.MountSource.Flags
opts := "rw"
if flags.ReadOnly {
opts = "ro"
}
fmt.Fprintf(buf, "%s %s %s %s %d %d\n", "none", mountPath, root.Inode.MountSource.FilesystemType, opts, 0, 0)
})
return nil
}
+13 -17
View File
@@ -46,6 +46,7 @@ type tasksInode struct {
kernfs.InodeDirectoryNoNewChildren
kernfs.InodeAttrs
kernfs.OrderedChildren
kernfs.AlwaysValid
inoGen InoGenerator
pidns *kernel.PIDNamespace
@@ -66,23 +67,23 @@ var _ kernfs.Inode = (*tasksInode)(nil)
func newTasksInode(inoGen InoGenerator, k *kernel.Kernel, pidns *kernel.PIDNamespace, cgroupControllers map[string]string) (*tasksInode, *kernfs.Dentry) {
root := auth.NewRootCredentials(pidns.UserNamespace())
contents := map[string]*kernfs.Dentry{
"cpuinfo": newDentry(root, inoGen.NextIno(), 0444, newStaticFile(cpuInfoData(k))),
//"filesystems": newDentry(root, inoGen.NextIno(), 0444, &filesystemsData{}),
"loadavg": newDentry(root, inoGen.NextIno(), 0444, &loadavgData{}),
"sys": newSysDir(root, inoGen, k),
"meminfo": newDentry(root, inoGen.NextIno(), 0444, &meminfoData{}),
"mounts": kernfs.NewStaticSymlink(root, inoGen.NextIno(), "self/mounts"),
"net": kernfs.NewStaticSymlink(root, inoGen.NextIno(), "self/net"),
"stat": newDentry(root, inoGen.NextIno(), 0444, &statData{k: k}),
"uptime": newDentry(root, inoGen.NextIno(), 0444, &uptimeData{}),
"version": newDentry(root, inoGen.NextIno(), 0444, &versionData{k: k}),
"cpuinfo": newDentry(root, inoGen.NextIno(), 0444, newStaticFile(cpuInfoData(k))),
"filesystems": newDentry(root, inoGen.NextIno(), 0444, &filesystemsData{}),
"loadavg": newDentry(root, inoGen.NextIno(), 0444, &loadavgData{}),
"sys": newSysDir(root, inoGen, k),
"meminfo": newDentry(root, inoGen.NextIno(), 0444, &meminfoData{}),
"mounts": kernfs.NewStaticSymlink(root, inoGen.NextIno(), "self/mounts"),
"net": kernfs.NewStaticSymlink(root, inoGen.NextIno(), "self/net"),
"stat": newDentry(root, inoGen.NextIno(), 0444, &statData{}),
"uptime": newDentry(root, inoGen.NextIno(), 0444, &uptimeData{}),
"version": newDentry(root, inoGen.NextIno(), 0444, &versionData{}),
}
inode := &tasksInode{
pidns: pidns,
inoGen: inoGen,
selfSymlink: newSelfSymlink(root, inoGen.NextIno(), 0444, pidns).VFSDentry(),
threadSelfSymlink: newThreadSelfSymlink(root, inoGen.NextIno(), 0444, pidns).VFSDentry(),
selfSymlink: newSelfSymlink(root, inoGen.NextIno(), pidns).VFSDentry(),
threadSelfSymlink: newThreadSelfSymlink(root, inoGen.NextIno(), pidns).VFSDentry(),
cgroupControllers: cgroupControllers,
}
inode.InodeAttrs.Init(root, inoGen.NextIno(), linux.ModeDirectory|0555)
@@ -121,11 +122,6 @@ func (i *tasksInode) Lookup(ctx context.Context, name string) (*vfs.Dentry, erro
return taskDentry.VFSDentry(), nil
}
// Valid implements kernfs.inodeDynamicLookup.
func (i *tasksInode) Valid(ctx context.Context) bool {
return true
}
// IterDirents implements kernfs.inodeDynamicLookup.
func (i *tasksInode) IterDirents(ctx context.Context, cb vfs.IterDirentsCallback, offset, _ int64) (int64, error) {
// fs/proc/internal.h: #define FIRST_PROCESS_ENTRY 256
+31 -21
View File
@@ -40,9 +40,9 @@ type selfSymlink struct {
var _ kernfs.Inode = (*selfSymlink)(nil)
func newSelfSymlink(creds *auth.Credentials, ino uint64, perm linux.FileMode, pidns *kernel.PIDNamespace) *kernfs.Dentry {
func newSelfSymlink(creds *auth.Credentials, ino uint64, pidns *kernel.PIDNamespace) *kernfs.Dentry {
inode := &selfSymlink{pidns: pidns}
inode.Init(creds, ino, linux.ModeSymlink|perm)
inode.Init(creds, ino, linux.ModeSymlink|0777)
d := &kernfs.Dentry{}
d.Init(inode)
@@ -72,9 +72,9 @@ type threadSelfSymlink struct {
var _ kernfs.Inode = (*threadSelfSymlink)(nil)
func newThreadSelfSymlink(creds *auth.Credentials, ino uint64, perm linux.FileMode, pidns *kernel.PIDNamespace) *kernfs.Dentry {
func newThreadSelfSymlink(creds *auth.Credentials, ino uint64, pidns *kernel.PIDNamespace) *kernfs.Dentry {
inode := &threadSelfSymlink{pidns: pidns}
inode.Init(creds, ino, linux.ModeSymlink|perm)
inode.Init(creds, ino, linux.ModeSymlink|0777)
d := &kernfs.Dentry{}
d.Init(inode)
@@ -138,21 +138,19 @@ func (c cpuStats) String() string {
// +stateify savable
type statData struct {
kernfs.DynamicBytesFile
// k is the owning Kernel.
k *kernel.Kernel
}
var _ dynamicInode = (*statData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (s *statData) Generate(ctx context.Context, buf *bytes.Buffer) error {
func (*statData) Generate(ctx context.Context, buf *bytes.Buffer) error {
// TODO(b/37226836): We currently export only zero CPU stats. We could
// at least provide some aggregate stats.
var cpu cpuStats
fmt.Fprintf(buf, "cpu %s\n", cpu)
for c, max := uint(0), s.k.ApplicationCores(); c < max; c++ {
k := kernel.KernelFromContext(ctx)
for c, max := uint(0), k.ApplicationCores(); c < max; c++ {
fmt.Fprintf(buf, "cpu%d %s\n", c, cpu)
}
@@ -176,7 +174,7 @@ func (s *statData) Generate(ctx context.Context, buf *bytes.Buffer) error {
fmt.Fprintf(buf, "ctxt 0\n")
// CLOCK_REALTIME timestamp from boot, in seconds.
fmt.Fprintf(buf, "btime %d\n", s.k.Timekeeper().BootTime().Seconds())
fmt.Fprintf(buf, "btime %d\n", k.Timekeeper().BootTime().Seconds())
// Total number of clones.
// TODO(b/37226836): Count this.
@@ -209,7 +207,7 @@ type loadavgData struct {
var _ dynamicInode = (*loadavgData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (d *loadavgData) Generate(ctx context.Context, buf *bytes.Buffer) error {
func (*loadavgData) Generate(ctx context.Context, buf *bytes.Buffer) error {
// TODO(b/62345059): Include real data in fields.
// Column 1-3: CPU and IO utilization of the last 1, 5, and 10 minute periods.
// Column 4-5: currently running processes and the total number of processes.
@@ -223,16 +221,14 @@ func (d *loadavgData) Generate(ctx context.Context, buf *bytes.Buffer) error {
// +stateify savable
type meminfoData struct {
kernfs.DynamicBytesFile
// k is the owning Kernel.
k *kernel.Kernel
}
var _ dynamicInode = (*meminfoData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (d *meminfoData) Generate(ctx context.Context, buf *bytes.Buffer) error {
mf := d.k.MemoryFile()
func (*meminfoData) Generate(ctx context.Context, buf *bytes.Buffer) error {
k := kernel.KernelFromContext(ctx)
mf := k.MemoryFile()
mf.UpdateUsage()
snapshot, totalUsage := usage.MemoryAccounting.Copy()
totalSize := usage.TotalMemory(mf.TotalSize(), totalUsage)
@@ -295,16 +291,14 @@ func (*uptimeData) Generate(ctx context.Context, buf *bytes.Buffer) error {
// +stateify savable
type versionData struct {
kernfs.DynamicBytesFile
// k is the owning Kernel.
k *kernel.Kernel
}
var _ dynamicInode = (*versionData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (v *versionData) Generate(ctx context.Context, buf *bytes.Buffer) error {
init := v.k.GlobalInit()
func (*versionData) Generate(ctx context.Context, buf *bytes.Buffer) error {
k := kernel.KernelFromContext(ctx)
init := k.GlobalInit()
if init == nil {
// Attempted to read before the init Task is created. This can
// only occur during startup, which should never need to read
@@ -335,3 +329,19 @@ func (v *versionData) Generate(ctx context.Context, buf *bytes.Buffer) error {
fmt.Fprintf(buf, "%s version %s %s\n", ver.Sysname, ver.Release, ver.Version)
return nil
}
// filesystemsData backs /proc/filesystems.
//
// +stateify savable
type filesystemsData struct {
kernfs.DynamicBytesFile
}
var _ dynamicInode = (*filesystemsData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (d *filesystemsData) Generate(ctx context.Context, buf *bytes.Buffer) error {
k := kernel.KernelFromContext(ctx)
k.VFS().GenerateProcFilesystems(buf)
return nil
}
+68 -19
View File
@@ -25,6 +25,7 @@ import (
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/fspath"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/testutil"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/vfs"
@@ -47,6 +48,7 @@ var (
var (
tasksStaticFiles = map[string]testutil.DirentType{
"cpuinfo": linux.DT_REG,
"filesystems": linux.DT_REG,
"loadavg": linux.DT_REG,
"meminfo": linux.DT_REG,
"mounts": linux.DT_LNK,
@@ -68,9 +70,14 @@ var (
"cmdline": linux.DT_REG,
"comm": linux.DT_REG,
"environ": linux.DT_REG,
"exe": linux.DT_LNK,
"fd": linux.DT_DIR,
"fdinfo": linux.DT_DIR,
"gid_map": linux.DT_REG,
"io": linux.DT_REG,
"maps": linux.DT_REG,
"mountinfo": linux.DT_REG,
"mounts": linux.DT_REG,
"net": linux.DT_DIR,
"ns": linux.DT_DIR,
"oom_score": linux.DT_REG,
@@ -96,17 +103,37 @@ func setup(t *testing.T) *testutil.System {
k.VFS().MustRegisterFilesystemType(Name, &FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
AllowUserMount: true,
})
fsOpts := vfs.GetFilesystemOptions{
InternalData: &InternalData{
Cgroups: map[string]string{
"cpuset": "/foo/cpuset",
"memory": "/foo/memory",
mntns, err := k.VFS().NewMountNamespace(ctx, creds, "", tmpfs.Name, &vfs.GetFilesystemOptions{})
if err != nil {
t.Fatalf("NewMountNamespace(): %v", err)
}
pop := &vfs.PathOperation{
Root: mntns.Root(),
Start: mntns.Root(),
Path: fspath.Parse("/proc"),
}
if err := k.VFS().MkdirAt(ctx, creds, pop, &vfs.MkdirOptions{Mode: 0777}); err != nil {
t.Fatalf("MkDir(/proc): %v", err)
}
pop = &vfs.PathOperation{
Root: mntns.Root(),
Start: mntns.Root(),
Path: fspath.Parse("/proc"),
}
mntOpts := &vfs.MountOptions{
GetFilesystemOptions: vfs.GetFilesystemOptions{
InternalData: &InternalData{
Cgroups: map[string]string{
"cpuset": "/foo/cpuset",
"memory": "/foo/memory",
},
},
},
}
mntns, err := k.VFS().NewMountNamespace(ctx, creds, "", Name, &fsOpts)
if err != nil {
t.Fatalf("NewMountNamespace(): %v", err)
if err := k.VFS().MountAt(ctx, creds, "", pop, Name, mntOpts); err != nil {
t.Fatalf("MountAt(/proc): %v", err)
}
return testutil.NewSystem(ctx, t, k.VFS(), mntns)
}
@@ -115,7 +142,7 @@ func TestTasksEmpty(t *testing.T) {
s := setup(t)
defer s.Destroy()
collector := s.ListDirents(s.PathOpAtRoot("/"))
collector := s.ListDirents(s.PathOpAtRoot("/proc"))
s.AssertAllDirentTypes(collector, tasksStaticFiles)
s.AssertDirentOffsets(collector, tasksStaticFilesNextOffs)
}
@@ -141,7 +168,7 @@ func TestTasks(t *testing.T) {
expectedDirents[fmt.Sprintf("%d", i+1)] = linux.DT_DIR
}
collector := s.ListDirents(s.PathOpAtRoot("/"))
collector := s.ListDirents(s.PathOpAtRoot("/proc"))
s.AssertAllDirentTypes(collector, expectedDirents)
s.AssertDirentOffsets(collector, tasksStaticFilesNextOffs)
@@ -181,7 +208,7 @@ func TestTasks(t *testing.T) {
}
// Test lookup.
for _, path := range []string{"/1", "/2"} {
for _, path := range []string{"/proc/1", "/proc/2"} {
fd, err := s.VFS.OpenAt(
s.Ctx,
s.Creds,
@@ -191,6 +218,7 @@ func TestTasks(t *testing.T) {
if err != nil {
t.Fatalf("vfsfs.OpenAt(%q) failed: %v", path, err)
}
defer fd.DecRef()
buf := make([]byte, 1)
bufIOSeq := usermem.BytesIOSequence(buf)
if _, err := fd.Read(s.Ctx, bufIOSeq, vfs.ReadOptions{}); err != syserror.EISDIR {
@@ -201,10 +229,10 @@ func TestTasks(t *testing.T) {
if _, err := s.VFS.OpenAt(
s.Ctx,
s.Creds,
s.PathOpAtRoot("/9999"),
s.PathOpAtRoot("/proc/9999"),
&vfs.OpenOptions{},
); err != syserror.ENOENT {
t.Fatalf("wrong error from vfsfs.OpenAt(/9999): %v", err)
t.Fatalf("wrong error from vfsfs.OpenAt(/proc/9999): %v", err)
}
}
@@ -302,12 +330,13 @@ func TestTasksOffset(t *testing.T) {
fd, err := s.VFS.OpenAt(
s.Ctx,
s.Creds,
s.PathOpAtRoot("/"),
s.PathOpAtRoot("/proc"),
&vfs.OpenOptions{},
)
if err != nil {
t.Fatalf("vfsfs.OpenAt(/) failed: %v", err)
}
defer fd.DecRef()
if _, err := fd.Seek(s.Ctx, tc.offset, linux.SEEK_SET); err != nil {
t.Fatalf("Seek(%d, SEEK_SET): %v", tc.offset, err)
}
@@ -344,7 +373,7 @@ func TestTask(t *testing.T) {
t.Fatalf("CreateTask(): %v", err)
}
collector := s.ListDirents(s.PathOpAtRoot("/1"))
collector := s.ListDirents(s.PathOpAtRoot("/proc/1"))
s.AssertAllDirentTypes(collector, taskStaticFiles)
}
@@ -362,14 +391,14 @@ func TestProcSelf(t *testing.T) {
collector := s.WithTemporaryContext(task).ListDirents(&vfs.PathOperation{
Root: s.Root,
Start: s.Root,
Path: fspath.Parse("/self/"),
Path: fspath.Parse("/proc/self/"),
FollowFinalSymlink: true,
})
s.AssertAllDirentTypes(collector, taskStaticFiles)
}
func iterateDir(ctx context.Context, t *testing.T, s *testutil.System, fd *vfs.FileDescription) {
t.Logf("Iterating: /proc%s", fd.MappedName(ctx))
t.Logf("Iterating: %s", fd.MappedName(ctx))
var collector testutil.DirentCollector
if err := fd.IterDirents(ctx, &collector); err != nil {
@@ -412,6 +441,7 @@ func iterateDir(ctx context.Context, t *testing.T, s *testutil.System, fd *vfs.F
t.Errorf("vfsfs.OpenAt(%v) failed: %v", childPath, err)
continue
}
defer child.DecRef()
stat, err := child.Stat(ctx, vfs.StatOptions{})
if err != nil {
t.Errorf("Stat(%v) failed: %v", childPath, err)
@@ -432,6 +462,22 @@ func TestTree(t *testing.T) {
defer s.Destroy()
k := kernel.KernelFromContext(s.Ctx)
pop := &vfs.PathOperation{
Root: s.Root,
Start: s.Root,
Path: fspath.Parse("test-file"),
}
opts := &vfs.OpenOptions{
Flags: linux.O_RDONLY | linux.O_CREAT,
Mode: 0777,
}
file, err := s.VFS.OpenAt(s.Ctx, s.Creds, pop, opts)
if err != nil {
t.Fatalf("failed to create test file: %v", err)
}
defer file.DecRef()
var tasks []*kernel.Task
for i := 0; i < 5; i++ {
tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
@@ -439,6 +485,8 @@ func TestTree(t *testing.T) {
if err != nil {
t.Fatalf("CreateTask(): %v", err)
}
// Add file to populate /proc/[pid]/fd and fdinfo directories.
task.FDTable().NewFDVFS2(task, 0, file, kernel.FDFlags{})
tasks = append(tasks, task)
}
@@ -446,11 +494,12 @@ func TestTree(t *testing.T) {
fd, err := s.VFS.OpenAt(
ctx,
auth.CredentialsFromContext(s.Ctx),
&vfs.PathOperation{Root: s.Root, Start: s.Root, Path: fspath.Parse("/")},
&vfs.PathOperation{Root: s.Root, Start: s.Root, Path: fspath.Parse("/proc")},
&vfs.OpenOptions{},
)
if err != nil {
t.Fatalf("vfsfs.OpenAt(/) failed: %v", err)
t.Fatalf("vfsfs.OpenAt(/proc) failed: %v", err)
}
iterateDir(ctx, t, s, fd)
fd.DecRef()
}
+2
View File
@@ -16,12 +16,14 @@ go_library(
"//pkg/cpuid",
"//pkg/fspath",
"//pkg/memutil",
"//pkg/sentry/fsbridge",
"//pkg/sentry/fsimpl/tmpfs",
"//pkg/sentry/kernel",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/kernel/sched",
"//pkg/sentry/limits",
"//pkg/sentry/loader",
"//pkg/sentry/mm",
"//pkg/sentry/pgalloc",
"//pkg/sentry/platform",
"//pkg/sentry/platform/kvm",
+26 -1
View File
@@ -23,13 +23,16 @@ import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/cpuid"
"gvisor.dev/gvisor/pkg/fspath"
"gvisor.dev/gvisor/pkg/memutil"
"gvisor.dev/gvisor/pkg/sentry/fsbridge"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/kernel/sched"
"gvisor.dev/gvisor/pkg/sentry/limits"
"gvisor.dev/gvisor/pkg/sentry/loader"
"gvisor.dev/gvisor/pkg/sentry/mm"
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
"gvisor.dev/gvisor/pkg/sentry/platform"
"gvisor.dev/gvisor/pkg/sentry/time"
@@ -123,10 +126,17 @@ func Boot() (*kernel.Kernel, error) {
// CreateTask creates a new bare bones task for tests.
func CreateTask(ctx context.Context, name string, tc *kernel.ThreadGroup, mntns *vfs.MountNamespace, root, cwd vfs.VirtualDentry) (*kernel.Task, error) {
k := kernel.KernelFromContext(ctx)
exe, err := newFakeExecutable(ctx, k.VFS(), auth.CredentialsFromContext(ctx), root)
if err != nil {
return nil, err
}
m := mm.NewMemoryManager(k, k, k.SleepForAddressSpaceActivation)
m.SetExecutable(fsbridge.NewVFSFile(exe))
config := &kernel.TaskConfig{
Kernel: k,
ThreadGroup: tc,
TaskContext: &kernel.TaskContext{Name: name},
TaskContext: &kernel.TaskContext{Name: name, MemoryManager: m},
Credentials: auth.CredentialsFromContext(ctx),
NetworkNamespace: k.RootNetworkNamespace(),
AllowedCPUMask: sched.NewFullCPUSet(k.ApplicationCores()),
@@ -135,10 +145,25 @@ func CreateTask(ctx context.Context, name string, tc *kernel.ThreadGroup, mntns
AbstractSocketNamespace: kernel.NewAbstractSocketNamespace(),
MountNamespaceVFS2: mntns,
FSContext: kernel.NewFSContextVFS2(root, cwd, 0022),
FDTable: k.NewFDTable(),
}
return k.TaskSet().NewTask(config)
}
func newFakeExecutable(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, root vfs.VirtualDentry) (*vfs.FileDescription, error) {
const name = "executable"
pop := &vfs.PathOperation{
Root: root,
Start: root,
Path: fspath.Parse(name),
}
opts := &vfs.OpenOptions{
Flags: linux.O_RDONLY | linux.O_CREAT,
Mode: 0777,
}
return vfsObj.OpenAt(ctx, creds, pop, opts)
}
func createMemoryFile() (*pgalloc.MemoryFile, error) {
const memfileName = "test-memory"
memfd, err := memutil.CreateMemFD(memfileName, 0)
+5 -2
View File
@@ -191,7 +191,7 @@ func (f *FDTable) Size() int {
return int(size)
}
// forEach iterates over all non-nil files.
// forEach iterates over all non-nil files in sorted order.
//
// It is the caller's responsibility to acquire an appropriate lock.
func (f *FDTable) forEach(fn func(fd int32, file *fs.File, fileVFS2 *vfs.FileDescription, flags FDFlags)) {
@@ -458,7 +458,10 @@ func (f *FDTable) GetVFS2(fd int32) (*vfs.FileDescription, FDFlags) {
}
}
// GetFDs returns a list of valid fds.
// GetFDs returns a sorted list of valid fds.
//
// Precondition: The caller must be running on the task goroutine, or Task.mu
// must be locked.
func (f *FDTable) GetFDs() []int32 {
fds := make([]int32, 0, int(atomic.LoadInt32(&f.used)))
f.forEach(func(fd int32, _ *fs.File, _ *vfs.FileDescription, _ FDFlags) {