Add support for /prod/[pid]/cmdline and /proc/[pid]/environ to trace procfs.

This change exports some logic in fsimpl/proc so that runsc/boot can use it.

Added tests for these fields.

Updates #4805

PiperOrigin-RevId: 449794552
This commit is contained in:
Ayush Ranjan
2022-05-19 11:30:22 -07:00
committed by gVisor bot
parent cf039a68d8
commit 6fdeba8d78
5 changed files with 99 additions and 48 deletions
+2 -2
View File
@@ -54,10 +54,10 @@ func (fs *filesystem) newTaskInode(ctx context.Context, task *kernel.Task, pidns
contents := map[string]kernfs.Inode{
"auxv": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &auxvData{task: task}),
"cmdline": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &cmdlineData{task: task, arg: cmdlineDataArg}),
"cmdline": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &metadataData{task: task, metaType: Cmdline}),
"comm": fs.newComm(ctx, task, fs.NextIno(), 0444),
"cwd": fs.newCwdSymlink(ctx, task, fs.NextIno()),
"environ": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &cmdlineData{task: task, arg: environDataArg}),
"environ": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &metadataData{task: task, metaType: Environ}),
"exe": fs.newExeSymlink(ctx, task, fs.NextIno()),
"fd": fs.newFDDirInode(ctx, task),
"fdinfo": fs.newFDInfoDirInode(ctx, task),
+53 -44
View File
@@ -133,56 +133,35 @@ func (d *auxvData) Generate(ctx context.Context, buf *bytes.Buffer) error {
return nil
}
// execArgType enumerates the types of exec arguments that are exposed through
// proc.
type execArgType int
// MetadataType enumerates the types of metadata that is exposed through proc.
type MetadataType int
const (
cmdlineDataArg execArgType = iota
environDataArg
// Cmdline represents /proc/[pid]/cmdline.
Cmdline MetadataType = iota
// Environ represents /proc/[pid]/environ.
Environ
)
// cmdlineData implements vfs.DynamicBytesSource for /proc/[pid]/cmdline.
//
// +stateify savable
type cmdlineData struct {
kernfs.DynamicBytesFile
task *kernel.Task
// arg is the type of exec argument this file contains.
arg execArgType
}
var _ dynamicInode = (*cmdlineData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (d *cmdlineData) Generate(ctx context.Context, buf *bytes.Buffer) error {
if d.task.ExitState() == kernel.TaskExitDead {
return linuxerr.ESRCH
}
m, err := getMMIncRef(d.task)
if err != nil {
// Return empty file.
return nil
}
defer m.DecUsers(ctx)
// GetMetadata fetches the process's metadata of type t and writes it into
// buf. The process is identified by mm.
func GetMetadata(ctx context.Context, mm *mm.MemoryManager, buf *bytes.Buffer, t MetadataType) error {
// Figure out the bounds of the exec arg we are trying to read.
var ar hostarch.AddrRange
switch d.arg {
case cmdlineDataArg:
switch t {
case Cmdline:
ar = hostarch.AddrRange{
Start: m.ArgvStart(),
End: m.ArgvEnd(),
Start: mm.ArgvStart(),
End: mm.ArgvEnd(),
}
case environDataArg:
case Environ:
ar = hostarch.AddrRange{
Start: m.EnvvStart(),
End: m.EnvvEnd(),
Start: mm.EnvvStart(),
End: mm.EnvvEnd(),
}
default:
panic(fmt.Sprintf("unknown exec arg type %v", d.arg))
panic(fmt.Sprintf("unknown exec arg type %v", t))
}
if ar.Start == 0 || ar.End == 0 {
// Don't attempt to read before the start/end are set up.
@@ -193,7 +172,7 @@ func (d *cmdlineData) Generate(ctx context.Context, buf *bytes.Buffer) error {
// until Linux 4.9 (272ddc8b3735 "proc: don't use FOLL_FORCE for reading
// cmdline and environment").
writer := &bufferWriter{buf: buf}
if n, err := m.CopyInTo(ctx, hostarch.AddrRangeSeqOf(ar), writer, usermem.IOOpts{}); n == 0 || err != nil {
if n, err := mm.CopyInTo(ctx, hostarch.AddrRangeSeqOf(ar), writer, usermem.IOOpts{}); n == 0 || err != nil {
// Nothing to copy or something went wrong.
return err
}
@@ -201,7 +180,7 @@ func (d *cmdlineData) Generate(ctx context.Context, buf *bytes.Buffer) error {
// On Linux, if the NULL byte at the end of the argument vector has been
// overwritten, it continues reading the environment vector as part of
// the argument vector.
if d.arg == cmdlineDataArg && buf.Bytes()[buf.Len()-1] != 0 {
if t == Cmdline && buf.Bytes()[buf.Len()-1] != 0 {
if end := bytes.IndexByte(buf.Bytes(), 0); end != -1 {
// If we found a NULL character somewhere else in argv, truncate the
// return up to the NULL terminator (including it).
@@ -211,8 +190,8 @@ func (d *cmdlineData) Generate(ctx context.Context, buf *bytes.Buffer) error {
// There is no NULL terminator in the string, return into envp.
arEnvv := hostarch.AddrRange{
Start: m.EnvvStart(),
End: m.EnvvEnd(),
Start: mm.EnvvStart(),
End: mm.EnvvEnd(),
}
// Upstream limits the returned amount to one page of slop.
@@ -231,7 +210,7 @@ func (d *cmdlineData) Generate(ctx context.Context, buf *bytes.Buffer) error {
}
arEnvv.End = end
}
if _, err := m.CopyInTo(ctx, hostarch.AddrRangeSeqOf(arEnvv), writer, usermem.IOOpts{}); err != nil {
if _, err := mm.CopyInTo(ctx, hostarch.AddrRangeSeqOf(arEnvv), writer, usermem.IOOpts{}); err != nil {
return err
}
@@ -246,6 +225,36 @@ func (d *cmdlineData) Generate(ctx context.Context, buf *bytes.Buffer) error {
return nil
}
// metadataData implements vfs.DynamicBytesSource for proc metadata fields like:
// - /proc/[pid]/cmdline
// - /proc/[pid]/environ
//
// +stateify savable
type metadataData struct {
kernfs.DynamicBytesFile
task *kernel.Task
// arg is the type of exec argument this file contains.
metaType MetadataType
}
var _ dynamicInode = (*metadataData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (d *metadataData) Generate(ctx context.Context, buf *bytes.Buffer) error {
if d.task.ExitState() == kernel.TaskExitDead {
return linuxerr.ESRCH
}
m, err := getMMIncRef(d.task)
if err != nil {
// Return empty file.
return nil
}
defer m.DecUsers(ctx)
return GetMetadata(ctx, m, buf, d.metaType)
}
// +stateify savable
type commInode struct {
kernfs.DynamicBytesFile
+1
View File
@@ -9,6 +9,7 @@ go_library(
deps = [
"//pkg/context",
"//pkg/log",
"//pkg/sentry/fsimpl/proc",
"//pkg/sentry/kernel",
"//pkg/sentry/mm",
],
+23 -2
View File
@@ -17,10 +17,13 @@
package procfs
import (
"bytes"
"fmt"
"strings"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/proc"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/mm"
)
@@ -31,6 +34,10 @@ type ProcessProcfsDump struct {
PID int32 `json:"pid,omitempty"`
// Exe is the symlink target of /proc/[pid]/exe.
Exe string `json:"exe,omitempty"`
// Args is /proc/[pid]/cmdline split into an array.
Args []string `json:"args,omitempty"`
// Env is /proc/[pid]/environ split into an array.
Env []string `json:"env,omitempty"`
}
// getMM returns t's MemoryManager. On success, the MemoryManager's users count
@@ -58,6 +65,18 @@ func getExecutablePath(ctx context.Context, pid kernel.ThreadID, mm *mm.MemoryMa
return exec.PathnameWithDeleted(ctx)
}
func getMetadataArray(ctx context.Context, pid kernel.ThreadID, mm *mm.MemoryManager, metaType proc.MetadataType) []string {
buf := bytes.Buffer{}
if err := proc.GetMetadata(ctx, mm, &buf, metaType); err != nil {
log.Warningf("failed to get %v metadata for PID %s: %v", metaType, pid, err)
return nil
}
// As per proc(5), /proc/[pid]/cmdline may have "a further null byte after
// the last string". Similarly, for /proc/[pid]/environ "there may be a null
// byte at the end". So trim off the last null byte if it exists.
return strings.Split(strings.TrimSuffix(buf.String(), "\000"), "\000")
}
// Dump returns a procfs dump for process pid. t must be a task in process pid.
func Dump(t *kernel.Task, pid kernel.ThreadID) (ProcessProcfsDump, error) {
ctx := t.AsyncContext()
@@ -69,7 +88,9 @@ func Dump(t *kernel.Task, pid kernel.ThreadID) (ProcessProcfsDump, error) {
defer mm.DecUsers(ctx)
return ProcessProcfsDump{
PID: int32(pid),
Exe: getExecutablePath(ctx, pid, mm),
PID: int32(pid),
Exe: getExecutablePath(ctx, pid, mm),
Args: getMetadataArray(ctx, pid, mm, proc.Cmdline),
Env: getMetadataArray(ctx, pid, mm, proc.Environ),
}, nil
}
+20
View File
@@ -300,6 +300,8 @@ func TestTraceForceCreate(t *testing.T) {
func TestProcfsDump(t *testing.T) {
spec, conf := sleepSpecConf(t)
testEnv := "GVISOR_IS_GREAT=true"
spec.Process.Env = append(spec.Process.Env, testEnv)
_, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
if err != nil {
t.Fatalf("error setting up container: %v", err)
@@ -340,4 +342,22 @@ func TestProcfsDump(t *testing.T) {
if wantExeSubStr := "bin/sleep"; !strings.HasSuffix(procfsDump[0].Exe, wantExeSubStr) {
t.Errorf("expected %q to be part of execuable path %q", wantExeSubStr, procfsDump[0].Exe)
}
if len(procfsDump[0].Args) != 2 {
t.Errorf("expected 2 args, but got %+v", procfsDump[0].Args)
} else {
if procfsDump[0].Args[0] != "sleep" || procfsDump[0].Args[1] != "1000" {
t.Errorf("expected args %q but got %+v", "sleep 1000", procfsDump[0].Args)
}
}
testEnvFound := false
for _, env := range procfsDump[0].Env {
if env == testEnv {
testEnvFound = true
}
}
if !testEnvFound {
t.Errorf("expected to find %q env but did not find it, got env %+v", testEnv, procfsDump[0].Env)
}
}