mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ go_library(
|
||||
"//pkg/unet",
|
||||
"//pkg/urpc",
|
||||
"//runsc/boot",
|
||||
"//runsc/boot/procfs",
|
||||
"//runsc/cgroup",
|
||||
"//runsc/config",
|
||||
"//runsc/console",
|
||||
|
||||
@@ -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 */)
|
||||
|
||||
Reference in New Issue
Block a user