github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/pkg/sentry/fsimpl/proc/tasks.go (about)

     1  // Copyright 2019 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  	"bytes"
    19  	"sort"
    20  	"strconv"
    21  
    22  	"github.com/ttpreport/gvisor-ligolo/pkg/abi/linux"
    23  	"github.com/ttpreport/gvisor-ligolo/pkg/context"
    24  	"github.com/ttpreport/gvisor-ligolo/pkg/errors/linuxerr"
    25  	"github.com/ttpreport/gvisor-ligolo/pkg/sentry/fsimpl/kernfs"
    26  	"github.com/ttpreport/gvisor-ligolo/pkg/sentry/kernel"
    27  	"github.com/ttpreport/gvisor-ligolo/pkg/sentry/kernel/auth"
    28  	"github.com/ttpreport/gvisor-ligolo/pkg/sentry/vfs"
    29  )
    30  
    31  const (
    32  	selfName       = "self"
    33  	threadSelfName = "thread-self"
    34  )
    35  
    36  // tasksInode represents the inode for /proc/ directory.
    37  //
    38  // +stateify savable
    39  type tasksInode struct {
    40  	implStatFS
    41  	kernfs.InodeAlwaysValid
    42  	kernfs.InodeAttrs
    43  	kernfs.InodeDirectoryNoNewChildren
    44  	kernfs.InodeNotAnonymous
    45  	kernfs.InodeNotSymlink
    46  	kernfs.InodeTemporary // This holds no meaning as this inode can't be Looked up and is always valid.
    47  	kernfs.InodeWatches
    48  	kernfs.OrderedChildren
    49  	tasksInodeRefs
    50  
    51  	locks vfs.FileLocks
    52  
    53  	fs    *filesystem
    54  	pidns *kernel.PIDNamespace
    55  
    56  	// '/proc/self' and '/proc/thread-self' have custom directory offsets in
    57  	// Linux. So handle them outside of OrderedChildren.
    58  
    59  	// fakeCgroupControllers is a map of controller name to directory in the
    60  	// cgroup hierarchy. These controllers are immutable and will be listed
    61  	// in /proc/pid/cgroup if not nil.
    62  	fakeCgroupControllers map[string]string
    63  }
    64  
    65  var _ kernfs.Inode = (*tasksInode)(nil)
    66  
    67  func (fs *filesystem) newTasksInode(ctx context.Context, k *kernel.Kernel, pidns *kernel.PIDNamespace, fakeCgroupControllers map[string]string) *tasksInode {
    68  	root := auth.NewRootCredentials(pidns.UserNamespace())
    69  	contents := map[string]kernfs.Inode{
    70  		"cmdline":        fs.newInode(ctx, root, 0444, &cmdLineData{}),
    71  		"cpuinfo":        fs.newInode(ctx, root, 0444, newStaticFileSetStat(cpuInfoData(k))),
    72  		"filesystems":    fs.newInode(ctx, root, 0444, &filesystemsData{}),
    73  		"loadavg":        fs.newInode(ctx, root, 0444, &loadavgData{}),
    74  		"sys":            fs.newSysDir(ctx, root, k),
    75  		"meminfo":        fs.newInode(ctx, root, 0444, &meminfoData{}),
    76  		"mounts":         kernfs.NewStaticSymlink(ctx, root, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), "self/mounts"),
    77  		"net":            kernfs.NewStaticSymlink(ctx, root, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), "self/net"),
    78  		"sentry-meminfo": fs.newInode(ctx, root, 0444, &sentryMeminfoData{}),
    79  		"stat":           fs.newInode(ctx, root, 0444, &statData{}),
    80  		"uptime":         fs.newInode(ctx, root, 0444, &uptimeData{}),
    81  		"version":        fs.newInode(ctx, root, 0444, &versionData{}),
    82  	}
    83  	// If fakeCgroupControllers are provided, don't create a cgroupfs backed
    84  	// /proc/cgroup as it will not match the fake controllers.
    85  	if len(fakeCgroupControllers) == 0 {
    86  		contents["cgroups"] = fs.newInode(ctx, root, 0444, &cgroupsData{})
    87  	}
    88  
    89  	inode := &tasksInode{
    90  		pidns:                 pidns,
    91  		fs:                    fs,
    92  		fakeCgroupControllers: fakeCgroupControllers,
    93  	}
    94  	inode.InodeAttrs.Init(ctx, root, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), linux.ModeDirectory|0555)
    95  	inode.InitRefs()
    96  
    97  	inode.OrderedChildren.Init(kernfs.OrderedChildrenOptions{})
    98  	links := inode.OrderedChildren.Populate(contents)
    99  	inode.IncLinks(links)
   100  
   101  	return inode
   102  }
   103  
   104  // Lookup implements kernfs.inodeDirectory.Lookup.
   105  func (i *tasksInode) Lookup(ctx context.Context, name string) (kernfs.Inode, error) {
   106  	// Check if a static entry was looked up.
   107  	if d, err := i.OrderedChildren.Lookup(ctx, name); err == nil {
   108  		return d, nil
   109  	}
   110  
   111  	// Not a static entry. Try to lookup a corresponding task.
   112  	tid, err := strconv.ParseUint(name, 10, 64)
   113  	if err != nil {
   114  		root := auth.NewRootCredentials(i.pidns.UserNamespace())
   115  		// If it failed to parse, check if it's one of the special handled files.
   116  		switch name {
   117  		case selfName:
   118  			return i.newSelfSymlink(ctx, root), nil
   119  		case threadSelfName:
   120  			return i.newThreadSelfSymlink(ctx, root), nil
   121  		}
   122  		return nil, linuxerr.ENOENT
   123  	}
   124  
   125  	task := i.pidns.TaskWithID(kernel.ThreadID(tid))
   126  	if task == nil {
   127  		return nil, linuxerr.ENOENT
   128  	}
   129  
   130  	return i.fs.newTaskInode(ctx, task, i.pidns, true, i.fakeCgroupControllers)
   131  }
   132  
   133  // IterDirents implements kernfs.inodeDirectory.IterDirents.
   134  func (i *tasksInode) IterDirents(ctx context.Context, mnt *vfs.Mount, cb vfs.IterDirentsCallback, offset, _ int64) (int64, error) {
   135  	// fs/proc/internal.h: #define FIRST_PROCESS_ENTRY 256
   136  	const FIRST_PROCESS_ENTRY = 256
   137  
   138  	// Use maxTaskID to shortcut searches that will result in 0 entries.
   139  	const maxTaskID = kernel.TasksLimit + 1
   140  	if offset >= maxTaskID {
   141  		return offset, nil
   142  	}
   143  
   144  	// According to Linux (fs/proc/base.c:proc_pid_readdir()), process directories
   145  	// start at offset FIRST_PROCESS_ENTRY with '/proc/self', followed by
   146  	// '/proc/thread-self' and then '/proc/[pid]'.
   147  	if offset < FIRST_PROCESS_ENTRY {
   148  		offset = FIRST_PROCESS_ENTRY
   149  	}
   150  
   151  	if offset == FIRST_PROCESS_ENTRY {
   152  		dirent := vfs.Dirent{
   153  			Name:    selfName,
   154  			Type:    linux.DT_LNK,
   155  			Ino:     i.fs.NextIno(),
   156  			NextOff: offset + 1,
   157  		}
   158  		if err := cb.Handle(dirent); err != nil {
   159  			return offset, err
   160  		}
   161  		offset++
   162  	}
   163  	if offset == FIRST_PROCESS_ENTRY+1 {
   164  		dirent := vfs.Dirent{
   165  			Name:    threadSelfName,
   166  			Type:    linux.DT_LNK,
   167  			Ino:     i.fs.NextIno(),
   168  			NextOff: offset + 1,
   169  		}
   170  		if err := cb.Handle(dirent); err != nil {
   171  			return offset, err
   172  		}
   173  		offset++
   174  	}
   175  
   176  	// Collect all tasks that TGIDs are greater than the offset specified. Per
   177  	// Linux we only include in directory listings if it's the leader. But for
   178  	// whatever crazy reason, you can still walk to the given node.
   179  	var tids []int
   180  	startTid := offset - FIRST_PROCESS_ENTRY - 2
   181  	for _, tg := range i.pidns.ThreadGroups() {
   182  		tid := i.pidns.IDOfThreadGroup(tg)
   183  		if int64(tid) < startTid {
   184  			continue
   185  		}
   186  		if leader := tg.Leader(); leader != nil {
   187  			tids = append(tids, int(tid))
   188  		}
   189  	}
   190  
   191  	if len(tids) == 0 {
   192  		return offset, nil
   193  	}
   194  
   195  	sort.Ints(tids)
   196  	for _, tid := range tids {
   197  		dirent := vfs.Dirent{
   198  			Name:    strconv.FormatUint(uint64(tid), 10),
   199  			Type:    linux.DT_DIR,
   200  			Ino:     i.fs.NextIno(),
   201  			NextOff: FIRST_PROCESS_ENTRY + 2 + int64(tid) + 1,
   202  		}
   203  		if err := cb.Handle(dirent); err != nil {
   204  			return offset, err
   205  		}
   206  		offset++
   207  	}
   208  	return maxTaskID, nil
   209  }
   210  
   211  // Open implements kernfs.Inode.Open.
   212  func (i *tasksInode) Open(ctx context.Context, rp *vfs.ResolvingPath, d *kernfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
   213  	fd, err := kernfs.NewGenericDirectoryFD(rp.Mount(), d, &i.OrderedChildren, &i.locks, &opts, kernfs.GenericDirectoryFDOptions{
   214  		SeekEnd: kernfs.SeekEndZero,
   215  	})
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  	return fd.VFSFileDescription(), nil
   220  }
   221  
   222  func (i *tasksInode) Stat(ctx context.Context, vsfs *vfs.Filesystem, opts vfs.StatOptions) (linux.Statx, error) {
   223  	stat, err := i.InodeAttrs.Stat(ctx, vsfs, opts)
   224  	if err != nil {
   225  		return linux.Statx{}, err
   226  	}
   227  
   228  	if opts.Mask&linux.STATX_NLINK != 0 {
   229  		// Add dynamic children to link count.
   230  		for _, tg := range i.pidns.ThreadGroups() {
   231  			if leader := tg.Leader(); leader != nil {
   232  				stat.Nlink++
   233  			}
   234  		}
   235  	}
   236  
   237  	return stat, nil
   238  }
   239  
   240  // DecRef implements kernfs.Inode.DecRef.
   241  func (i *tasksInode) DecRef(ctx context.Context) {
   242  	i.tasksInodeRefs.DecRef(func() { i.Destroy(ctx) })
   243  }
   244  
   245  // staticFileSetStat implements a special static file that allows inode
   246  // attributes to be set. This is to support /proc files that are readonly, but
   247  // allow attributes to be set.
   248  //
   249  // +stateify savable
   250  type staticFileSetStat struct {
   251  	dynamicBytesFileSetAttr
   252  	vfs.StaticData
   253  }
   254  
   255  var _ dynamicInode = (*staticFileSetStat)(nil)
   256  
   257  func newStaticFileSetStat(data string) *staticFileSetStat {
   258  	return &staticFileSetStat{StaticData: vfs.StaticData{Data: data}}
   259  }
   260  
   261  func cpuInfoData(k *kernel.Kernel) string {
   262  	features := k.FeatureSet()
   263  	var buf bytes.Buffer
   264  	for i, max := uint(0), k.ApplicationCores(); i < max; i++ {
   265  		features.WriteCPUInfoTo(i, &buf)
   266  	}
   267  	return buf.String()
   268  }
   269  
   270  func ipcData(v uint64) dynamicInode {
   271  	return newStaticFile(strconv.FormatUint(v, 10))
   272  }