Add trace procfs command.

`runsc trace procfs` should dump a lot of relevant procfs information about
all the processes in the container. Falco will consume this.

For now the only procfs entry that the procfs dump returns is /proc/[pid]/exe.
Will incrementally add support for the remaining in separate changes.

Updates #4805

PiperOrigin-RevId: 449783478
This commit is contained in:
Ayush Ranjan
2022-05-19 10:49:49 -07:00
committed by gVisor bot
parent 62dcc6f5b0
commit cf039a68d8
11 changed files with 268 additions and 1 deletions
+1
View File
@@ -121,6 +121,7 @@ go_library(
"//runsc/boot/filter",
"//runsc/boot/platforms",
"//runsc/boot/pprof",
"//runsc/boot/procfs",
"//runsc/config",
"//runsc/specutils",
"//runsc/specutils/seccomp",
+22
View File
@@ -38,6 +38,7 @@ import (
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/urpc"
"gvisor.dev/gvisor/runsc/boot/pprof"
"gvisor.dev/gvisor/runsc/boot/procfs"
"gvisor.dev/gvisor/runsc/config"
"gvisor.dev/gvisor/runsc/specutils"
)
@@ -90,6 +91,9 @@ const (
// ContMgrListTraceSessions lists a trace session.
ContMgrListTraceSessions = "containerManager.ListTraceSessions"
// ContMgrProcfsDump dumps sandbox procfs state.
ContMgrProcfsDump = "containerManager.ProcfsDump"
)
const (
@@ -635,3 +639,21 @@ func (cm *containerManager) ListTraceSessions(_ *struct{}, out *[]seccheck.Sessi
seccheck.List(out)
return nil
}
// ProcfsDump dumps procfs state of the sandbox.
func (cm *containerManager) ProcfsDump(_ *struct{}, out *[]procfs.ProcessProcfsDump) error {
log.Debugf("containerManager.ProcfsDump")
ts := cm.l.k.TaskSet()
pidns := ts.Root
*out = make([]procfs.ProcessProcfsDump, 0, len(cm.l.processes))
for _, tg := range pidns.ThreadGroups() {
pid := pidns.IDOfThreadGroup(tg)
procDump, err := procfs.Dump(tg.Leader(), pid)
if err != nil {
log.Warningf("skipping procfs dump for PID %s: %v", pid, err)
continue
}
*out = append(*out, procDump)
}
return nil
}
+1 -1
View File
@@ -139,7 +139,7 @@ type Loader struct {
// processes are keyed with container ID and pid=0, while exec invocations
// have the corresponding pid set.
//
// processes is guardded by mu.
// processes is guarded by mu.
processes map[execID]*execProcess
// mountHints provides extra information about mounts for containers that
+15
View File
@@ -0,0 +1,15 @@
load("//tools:defs.bzl", "go_library")
package(licenses = ["notice"])
go_library(
name = "procfs",
srcs = ["dump.go"],
visibility = ["//runsc:__subpackages__"],
deps = [
"//pkg/context",
"//pkg/log",
"//pkg/sentry/kernel",
"//pkg/sentry/mm",
],
)
+75
View File
@@ -0,0 +1,75 @@
// Copyright 2022 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 procfs holds utilities for getting procfs information for sandboxed
// processes.
package procfs
import (
"fmt"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/mm"
)
// ProcessProcfsDump contains the procfs dump for one process.
type ProcessProcfsDump struct {
// PID is the process ID.
PID int32 `json:"pid,omitempty"`
// Exe is the symlink target of /proc/[pid]/exe.
Exe string `json:"exe,omitempty"`
}
// getMM returns t's MemoryManager. On success, the MemoryManager's users count
// is incremented, and must be decremented by the caller when it is no longer
// in use.
func getMM(t *kernel.Task) *mm.MemoryManager {
var mm *mm.MemoryManager
t.WithMuLocked(func(*kernel.Task) {
mm = t.MemoryManager()
})
if mm == nil || !mm.IncUsers() {
return nil
}
return mm
}
func getExecutablePath(ctx context.Context, pid kernel.ThreadID, mm *mm.MemoryManager) string {
exec := mm.Executable()
if exec == nil {
log.Warningf("No executable found for PID %s", pid)
return ""
}
defer exec.DecRef(ctx)
return exec.PathnameWithDeleted(ctx)
}
// 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()
mm := getMM(t)
if mm == nil {
return ProcessProcfsDump{}, fmt.Errorf("no MM found for PID %s", pid)
}
defer mm.DecUsers(ctx)
return ProcessProcfsDump{
PID: int32(pid),
Exe: getExecutablePath(ctx, pid, mm),
}, nil
}
+2
View File
@@ -9,12 +9,14 @@ go_library(
"delete.go",
"list.go",
"metadata.go",
"procfs.go",
"trace.go",
],
visibility = [
"//runsc:__subpackages__",
],
deps = [
"//pkg/log",
"//pkg/sentry/seccheck",
"//runsc/cmd/util",
"//runsc/config",
+89
View File
@@ -0,0 +1,89 @@
// Copyright 2022 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 trace
import (
"context"
"encoding/json"
"fmt"
"github.com/google/subcommands"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/runsc/cmd/util"
"gvisor.dev/gvisor/runsc/config"
"gvisor.dev/gvisor/runsc/container"
"gvisor.dev/gvisor/runsc/flag"
)
// procfs implements subcommands.Command for the "procfs" command.
type procfs struct {
}
// Name implements subcommands.Command.
func (*procfs) Name() string {
return "procfs"
}
// Synopsis implements subcommands.Command.
func (*procfs) Synopsis() string {
return "dump procfs state for sandbox"
}
// Usage implements subcommands.Command.
func (*procfs) Usage() string {
return `procfs <sandbox id> - get procfs dump for a trace session
`
}
// SetFlags implements subcommands.Command.
func (*procfs) SetFlags(*flag.FlagSet) {}
// Execute implements subcommands.Command.
func (*procfs) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
if f.NArg() != 1 {
f.Usage()
return subcommands.ExitUsageError
}
id := f.Arg(0)
conf := args[0].(*config.Config)
opts := container.LoadOpts{
SkipCheck: true,
RootContainer: true,
}
c, err := container.Load(conf.RootDir, container.FullID{ContainerID: id}, opts)
if err != nil {
util.Fatalf("loading sandbox: %v", err)
}
dump, err := c.Sandbox.ProcfsDump()
if err != nil {
util.Fatalf("procfs dump: %v", err)
}
fmt.Println("PROCFS DUMP")
for _, procDump := range dump {
out, err := json.Marshal(procDump)
if err != nil {
log.Warningf("json.Marshal failed to marshal %+v: %v", procDump, err)
continue
}
fmt.Println("")
fmt.Println(string(out))
}
return subcommands.ExitSuccess
}
+1
View File
@@ -65,5 +65,6 @@ func createCommander(f *flag.FlagSet) *subcommands.Commander {
cdr.Register(new(delete), "")
cdr.Register(new(list), "")
cdr.Register(new(metadata), "")
cdr.Register(new(procfs), "")
return cdr
}
+44
View File
@@ -297,3 +297,47 @@ func TestTraceForceCreate(t *testing.T) {
t.Errorf("wrong message type, want: %v, got: %v", want, pt.MsgType)
}
}
func TestProcfsDump(t *testing.T) {
spec, conf := sleepSpecConf(t)
_, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
if err != nil {
t.Fatalf("error setting up container: %v", err)
}
defer cleanup()
// Create and start the container.
args := Args{
ID: testutil.RandomContainerID(),
Spec: spec,
BundleDir: bundleDir,
}
cont, err := New(conf, args)
if err != nil {
t.Fatalf("error creating container: %v", err)
}
defer cont.Destroy()
if err := cont.Start(conf); err != nil {
t.Fatalf("error starting container: %v", err)
}
procfsDump, err := cont.Sandbox.ProcfsDump()
if err != nil {
t.Fatalf("ProcfsDump() failed: %v", err)
}
// Sleep should be the only process running in the container.
if len(procfsDump) != 1 {
t.Fatalf("got incorrect number of proc results: %+v", procfsDump)
}
// Sleep should be PID 1.
if procfsDump[0].PID != 1 {
t.Errorf("expected sleep process to be pid 1, got %d", procfsDump[0].PID)
}
// Check that bin/sleep is part of the executable path.
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)
}
}
+1
View File
@@ -30,6 +30,7 @@ go_library(
"//pkg/unet",
"//pkg/urpc",
"//runsc/boot",
"//runsc/boot/procfs",
"//runsc/cgroup",
"//runsc/config",
"//runsc/console",
+17
View File
@@ -46,6 +46,7 @@ import (
"gvisor.dev/gvisor/pkg/unet"
"gvisor.dev/gvisor/pkg/urpc"
"gvisor.dev/gvisor/runsc/boot"
"gvisor.dev/gvisor/runsc/boot/procfs"
"gvisor.dev/gvisor/runsc/cgroup"
"gvisor.dev/gvisor/runsc/config"
"gvisor.dev/gvisor/runsc/console"
@@ -453,6 +454,22 @@ func (s *Sandbox) ListTraceSessions() ([]seccheck.SessionConfig, error) {
return sessions, nil
}
// ProcfsDump collects and returns a procfs dump for the sandbox.
func (s *Sandbox) ProcfsDump() ([]procfs.ProcessProcfsDump, error) {
log.Debugf("Procfs dump %q", s.ID)
conn, err := s.sandboxConnect()
if err != nil {
return nil, err
}
defer conn.Close()
var procfsDump []procfs.ProcessProcfsDump
if err := conn.Call(boot.ContMgrProcfsDump, nil, &procfsDump); err != nil {
return nil, fmt.Errorf("getting sandbox %q stacks: %v", s.ID, err)
}
return procfsDump, nil
}
// NewCGroup returns the sandbox's Cgroup, or an error if it does not have one.
func (s *Sandbox) NewCGroup() (cgroup.Cgroup, error) {
return cgroup.NewFromPid(s.Pid.load(), false /* useSystemd */)