mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -9,6 +9,7 @@ go_library(
|
||||
deps = [
|
||||
"//pkg/context",
|
||||
"//pkg/log",
|
||||
"//pkg/sentry/fsimpl/proc",
|
||||
"//pkg/sentry/kernel",
|
||||
"//pkg/sentry/mm",
|
||||
],
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user