github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/runsc/boot/procfs/dump.go (about)

     1  // Copyright 2022 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 procfs holds utilities for getting procfs information for sandboxed
    16  // processes.
    17  package procfs
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"strings"
    23  
    24  	"github.com/nicocha30/gvisor-ligolo/pkg/abi/linux"
    25  	"github.com/nicocha30/gvisor-ligolo/pkg/context"
    26  	"github.com/nicocha30/gvisor-ligolo/pkg/hostarch"
    27  	"github.com/nicocha30/gvisor-ligolo/pkg/log"
    28  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/fsimpl/proc"
    29  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/kernel"
    30  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/limits"
    31  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/mm"
    32  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/vfs"
    33  )
    34  
    35  // FDInfo contains information about an application file descriptor.
    36  type FDInfo struct {
    37  	// Number is the FD number.
    38  	Number int32 `json:"number"`
    39  	// Path is the path of the file that FD represents.
    40  	Path string `json:"path,omitempty"`
    41  	// Mode is the file mode.
    42  	Mode uint16 `json:"mode"`
    43  }
    44  
    45  // UIDGID contains information for /proc/[pid]/status/{uid,gid}.
    46  type UIDGID struct {
    47  	Real      uint32 `json:"real"`
    48  	Effective uint32 `json:"effective"`
    49  	Saved     uint32 `json:"saved"`
    50  }
    51  
    52  // Status contains information for /proc/[pid]/status.
    53  type Status struct {
    54  	Comm   string `json:"comm,omitempty"`
    55  	PID    int32  `json:"pid"`
    56  	PPID   int32  `json:"ppid"`
    57  	UID    UIDGID `json:"uid,omitempty"`
    58  	GID    UIDGID `json:"gid,omitempty"`
    59  	VMSize uint64 `json:"vm_size,omitempty"`
    60  	VMRSS  uint64 `json:"vm_rss,omitempty"`
    61  }
    62  
    63  // Stat contains information for /proc/[pid]/stat.
    64  type Stat struct {
    65  	PGID int32 `json:"pgid"`
    66  	SID  int32 `json:"sid"`
    67  }
    68  
    69  // Mapping contains information for /proc/[pid]/maps.
    70  type Mapping struct {
    71  	Address     hostarch.AddrRange  `json:"address,omitempty"`
    72  	Permissions hostarch.AccessType `json:"permissions"`
    73  	Private     string              `json:"private,omitempty"`
    74  	Offset      uint64              `json:"offset"`
    75  	DevMajor    uint32              `json:"deviceMajor,omitempty"`
    76  	DevMinor    uint32              `json:"deviceMinor,omitempty"`
    77  	Inode       uint64              `json:"inode,omitempty"`
    78  	Pathname    string              `json:"pathname,omitempty"`
    79  }
    80  
    81  // ProcessProcfsDump contains the procfs dump for one process. For more details
    82  // on fields that directly correspond to /proc fields, see proc(5).
    83  type ProcessProcfsDump struct {
    84  	// Exe is the symlink target of /proc/[pid]/exe.
    85  	Exe string `json:"exe,omitempty"`
    86  	// Args is /proc/[pid]/cmdline split into an array.
    87  	Args []string `json:"args,omitempty"`
    88  	// Env is /proc/[pid]/environ split into an array.
    89  	Env []string `json:"env,omitempty"`
    90  	// CWD is the symlink target of /proc/[pid]/cwd.
    91  	CWD string `json:"cwd,omitempty"`
    92  	// FDs contains the directory entries of /proc/[pid]/fd and also contains the
    93  	// symlink target for each FD.
    94  	FDs []FDInfo `json:"fdlist,omitempty"`
    95  	// StartTime is the process start time in nanoseconds since Unix epoch.
    96  	StartTime int64 `json:"clone_ts,omitempty"`
    97  	// Root is /proc/[pid]/root.
    98  	Root string `json:"root,omitempty"`
    99  	// Limits constains resource limits for this process. Currently only
   100  	// RLIMIT_NOFILE is supported.
   101  	Limits map[string]limits.Limit `json:"limits,omitempty"`
   102  	// Cgroup is /proc/[pid]/cgroup split into an array.
   103  	Cgroup []kernel.TaskCgroupEntry `json:"cgroup,omitempty"`
   104  	// Status is /proc/[pid]/status.
   105  	Status Status `json:"status,omitempty"`
   106  	// Stat is /proc/[pid]/stat.
   107  	Stat Stat `json:"stat,omitempty"`
   108  	// Maps is /proc/[pid]/maps.
   109  	Maps []Mapping `json:"maps,omitempty"`
   110  }
   111  
   112  // getMM returns t's MemoryManager. On success, the MemoryManager's users count
   113  // is incremented, and must be decremented by the caller when it is no longer
   114  // in use.
   115  func getMM(t *kernel.Task) *mm.MemoryManager {
   116  	var mm *mm.MemoryManager
   117  	t.WithMuLocked(func(*kernel.Task) {
   118  		mm = t.MemoryManager()
   119  	})
   120  	if mm == nil || !mm.IncUsers() {
   121  		return nil
   122  	}
   123  	return mm
   124  }
   125  
   126  func getExecutablePath(ctx context.Context, pid kernel.ThreadID, mm *mm.MemoryManager) string {
   127  	exec := mm.Executable()
   128  	if exec == nil {
   129  		log.Warningf("No executable found for PID %s", pid)
   130  		return ""
   131  	}
   132  	defer exec.DecRef(ctx)
   133  
   134  	return exec.MappedName(ctx)
   135  }
   136  
   137  func getMetadataArray(ctx context.Context, pid kernel.ThreadID, mm *mm.MemoryManager, metaType proc.MetadataType) []string {
   138  	buf := bytes.Buffer{}
   139  	if err := proc.GetMetadata(ctx, mm, &buf, metaType); err != nil {
   140  		log.Warningf("failed to get %v metadata for PID %s: %v", metaType, pid, err)
   141  		return nil
   142  	}
   143  	// As per proc(5), /proc/[pid]/cmdline may have "a further null byte after
   144  	// the last string". Similarly, for /proc/[pid]/environ "there may be a null
   145  	// byte at the end". So trim off the last null byte if it exists.
   146  	return strings.Split(strings.TrimSuffix(buf.String(), "\000"), "\000")
   147  }
   148  
   149  func getCWD(ctx context.Context, t *kernel.Task, pid kernel.ThreadID) string {
   150  	cwdDentry := t.FSContext().WorkingDirectory()
   151  	if !cwdDentry.Ok() {
   152  		log.Warningf("No CWD dentry found for PID %s", pid)
   153  		return ""
   154  	}
   155  
   156  	root := vfs.RootFromContext(ctx)
   157  	if !root.Ok() {
   158  		log.Warningf("no root could be found from context for PID %s", pid)
   159  		return ""
   160  	}
   161  	defer root.DecRef(ctx)
   162  
   163  	vfsObj := cwdDentry.Mount().Filesystem().VirtualFilesystem()
   164  	name, err := vfsObj.PathnameWithDeleted(ctx, root, cwdDentry)
   165  	if err != nil {
   166  		log.Warningf("PathnameWithDeleted failed to find CWD: %v", err)
   167  	}
   168  	return name
   169  }
   170  
   171  func getFDs(ctx context.Context, t *kernel.Task, pid kernel.ThreadID) []FDInfo {
   172  	type fdInfo struct {
   173  		fd *vfs.FileDescription
   174  		no int32
   175  	}
   176  	var fds []fdInfo
   177  	defer func() {
   178  		for _, fd := range fds {
   179  			fd.fd.DecRef(ctx)
   180  		}
   181  	}()
   182  
   183  	t.WithMuLocked(func(t *kernel.Task) {
   184  		if fdTable := t.FDTable(); fdTable != nil {
   185  			fdNos := fdTable.GetFDs(ctx)
   186  			fds = make([]fdInfo, 0, len(fdNos))
   187  			for _, fd := range fdNos {
   188  				file, _ := fdTable.Get(fd)
   189  				if file != nil {
   190  					fds = append(fds, fdInfo{fd: file, no: fd})
   191  				}
   192  			}
   193  		}
   194  	})
   195  
   196  	root := vfs.RootFromContext(ctx)
   197  	defer root.DecRef(ctx)
   198  
   199  	res := make([]FDInfo, 0, len(fds))
   200  	for _, fd := range fds {
   201  		path, err := t.Kernel().VFS().PathnameWithDeleted(ctx, root, fd.fd.VirtualDentry())
   202  		if err != nil {
   203  			log.Warningf("PathnameWithDeleted failed to find path for fd %d in PID %s: %v", fd.no, pid, err)
   204  			path = ""
   205  		}
   206  		mode := uint16(0)
   207  		if statx, err := fd.fd.Stat(ctx, vfs.StatOptions{Mask: linux.STATX_MODE}); err != nil {
   208  			log.Warningf("Stat(STATX_MODE) failed for fd %d in PID %s: %v", fd.no, pid, err)
   209  		} else {
   210  			mode = statx.Mode
   211  		}
   212  		res = append(res, FDInfo{Number: fd.no, Path: path, Mode: mode})
   213  	}
   214  	return res
   215  }
   216  
   217  func getRoot(t *kernel.Task, pid kernel.ThreadID) string {
   218  	realRoot := t.MountNamespace().Root()
   219  	root := t.FSContext().RootDirectory()
   220  	defer root.DecRef(t)
   221  	path, err := t.Kernel().VFS().PathnameWithDeleted(t, realRoot, root)
   222  	if err != nil {
   223  		log.Warningf("PathnameWithDeleted failed to find root path for PID %s: %v", pid, err)
   224  		return ""
   225  	}
   226  	return path
   227  }
   228  
   229  func getFDLimit(ctx context.Context, pid kernel.ThreadID) (limits.Limit, error) {
   230  	if limitSet := limits.FromContext(ctx); limitSet != nil {
   231  		return limitSet.Get(limits.NumberOfFiles), nil
   232  	}
   233  	return limits.Limit{}, fmt.Errorf("could not find limit set for pid %s", pid)
   234  }
   235  
   236  func getStatus(t *kernel.Task, mm *mm.MemoryManager, pid kernel.ThreadID, pidns *kernel.PIDNamespace) Status {
   237  	creds := t.Credentials()
   238  	uns := creds.UserNamespace
   239  	ppid := kernel.ThreadID(0)
   240  	if parent := t.Parent(); parent != nil {
   241  		ppid = pidns.IDOfThreadGroup(parent.ThreadGroup())
   242  	}
   243  	return Status{
   244  		Comm: t.Name(),
   245  		PID:  int32(pid),
   246  		PPID: int32(ppid),
   247  		UID: UIDGID{
   248  			Real:      uint32(creds.RealKUID.In(uns).OrOverflow()),
   249  			Effective: uint32(creds.EffectiveKUID.In(uns).OrOverflow()),
   250  			Saved:     uint32(creds.SavedKUID.In(uns).OrOverflow()),
   251  		},
   252  		GID: UIDGID{
   253  			Real:      uint32(creds.RealKGID.In(uns).OrOverflow()),
   254  			Effective: uint32(creds.EffectiveKGID.In(uns).OrOverflow()),
   255  			Saved:     uint32(creds.SavedKGID.In(uns).OrOverflow()),
   256  		},
   257  		VMSize: mm.VirtualMemorySize() >> 10,
   258  		VMRSS:  mm.ResidentSetSize() >> 10,
   259  	}
   260  }
   261  
   262  func getStat(t *kernel.Task, pid kernel.ThreadID, pidns *kernel.PIDNamespace) Stat {
   263  	return Stat{
   264  		PGID: int32(pidns.IDOfProcessGroup(t.ThreadGroup().ProcessGroup())),
   265  		SID:  int32(pidns.IDOfSession(t.ThreadGroup().Session())),
   266  	}
   267  }
   268  
   269  func getMappings(ctx context.Context, mm *mm.MemoryManager) []Mapping {
   270  	var maps []Mapping
   271  	mm.ReadMapsDataInto(ctx, func(start, end hostarch.Addr, permissions hostarch.AccessType, private string, offset uint64, devMajor, devMinor uint32, inode uint64, path string) {
   272  		maps = append(maps, Mapping{
   273  			Address: hostarch.AddrRange{
   274  				Start: start,
   275  				End:   end,
   276  			},
   277  			Permissions: permissions,
   278  			Private:     private,
   279  			Offset:      offset,
   280  			DevMajor:    devMajor,
   281  			DevMinor:    devMinor,
   282  			Inode:       inode,
   283  			Pathname:    path,
   284  		})
   285  	})
   286  
   287  	return maps
   288  }
   289  
   290  // Dump returns a procfs dump for process pid. t must be a task in process pid.
   291  func Dump(t *kernel.Task, pid kernel.ThreadID, pidns *kernel.PIDNamespace) (ProcessProcfsDump, error) {
   292  	ctx := t.AsyncContext()
   293  
   294  	mm := getMM(t)
   295  	if mm == nil {
   296  		return ProcessProcfsDump{}, fmt.Errorf("no MM found for PID %s", pid)
   297  	}
   298  	defer mm.DecUsers(ctx)
   299  
   300  	fdLimit, err := getFDLimit(ctx, pid)
   301  	if err != nil {
   302  		return ProcessProcfsDump{}, err
   303  	}
   304  
   305  	return ProcessProcfsDump{
   306  		Exe:       getExecutablePath(ctx, pid, mm),
   307  		Args:      getMetadataArray(ctx, pid, mm, proc.Cmdline),
   308  		Env:       getMetadataArray(ctx, pid, mm, proc.Environ),
   309  		CWD:       getCWD(ctx, t, pid),
   310  		FDs:       getFDs(ctx, t, pid),
   311  		StartTime: t.StartTime().Nanoseconds(),
   312  		Root:      getRoot(t, pid),
   313  		Limits: map[string]limits.Limit{
   314  			"RLIMIT_NOFILE": fdLimit,
   315  		},
   316  		// We don't need to worry about fake cgroup controllers as that is not
   317  		// supported in runsc.
   318  		Cgroup: t.GetCgroupEntries(),
   319  		Status: getStatus(t, mm, pid, pidns),
   320  		Stat:   getStat(t, pid, pidns),
   321  		Maps:   getMappings(ctx, mm),
   322  	}, nil
   323  }