Add runsc trace commands

The trace commands allows a user to manipulate trace sessions.

`runsc trace create <name> --config <file>` => creates a new trace session
`runsc trace delete <name>` => deletes an existing trace session
`runsc trace list` => lists all running trace sessions
`runsc trace metadata` => lists all point with their respective optional and
context fields

This allows trace sessions to be created/deleted on a running sandbox. Note
that the system currently only allows a single trace session to exist, named
'Default'. Attempts to manipulate other sessions will error out.

Updates #4805

PiperOrigin-RevId: 447815153
This commit is contained in:
Fabricio Voznika
2022-05-10 13:40:39 -07:00
committed by gVisor bot
parent 944b941f9d
commit f34e34b3c3
20 changed files with 884 additions and 37 deletions
@@ -5,7 +5,6 @@ package(licenses = ["notice"])
go_library(
name = "remote",
srcs = ["remote.go"],
marshal = True,
visibility = ["//:sandbox"],
deps = [
"//pkg/cleanup",
+10 -2
View File
@@ -28,7 +28,6 @@ import (
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/seccheck"
"gvisor.dev/gvisor/pkg/sentry/seccheck/checkers/remote/header"
pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto"
)
@@ -101,6 +100,15 @@ func New(_ map[string]interface{}, endpoint *fd.FD) (seccheck.Checker, error) {
return &Remote{endpoint: endpoint}, nil
}
// Stop implements seccheck.Checker.
func (r *Remote) Stop() {
if r.endpoint != nil {
// It's possible to race with Point firing, but in the worst case they will
// simply fail to be delivered.
r.endpoint.Close()
}
}
func (r *Remote) write(msg proto.Message, msgType pb.MessageType) {
out, err := proto.Marshal(msg)
if err != nil {
@@ -116,7 +124,7 @@ func (r *Remote) write(msg proto.Message, msgType pb.MessageType) {
// TODO(gvisor.dev/issue/4805): Change to non-blocking write. Count as dropped
// if write fails.
if _, err = unix.Writev(r.endpoint.FD(), [][]byte{hdrOut[:], out}); err != nil {
if _, err := unix.Writev(r.endpoint.FD(), [][]byte{hdrOut[:], out}); err != nil {
log.Debugf("write(%+v, %v): %v", msg, msgType, err)
return
}
@@ -169,6 +169,16 @@ func (s *Server) Count() int {
return len(s.points)
}
// Reset throws aways all points received so far and returns the number of
// points discarded.
func (s *Server) Reset() int {
s.mu.Lock()
defer s.mu.Unlock()
count := len(s.points)
s.points = nil
return count
}
// GetPoints returns all points that it has received.
func (s *Server) GetPoints() []Message {
s.mu.Lock()
+66 -12
View File
@@ -17,11 +17,21 @@ package seccheck
import (
"fmt"
"os"
"sync"
"gvisor.dev/gvisor/pkg/fd"
"gvisor.dev/gvisor/pkg/log"
)
// DefaultSessionName is the name of the only session that can exist in the
// system for now. When multiple sessions are supported, this can be removed.
const DefaultSessionName = "Default"
var (
sessionsMu = sync.Mutex{}
sessions = make(map[string]*State)
)
// SessionConfig describes a new session configuration. A session consists of a
// set of points to be enabled and sinks where the points are sent to.
type SessionConfig struct {
@@ -57,13 +67,18 @@ type SinkConfig struct {
FD *fd.FD `json:"-"`
}
// Configure reads the session configuration and applies it to the system.
func Configure(conf *SessionConfig) error {
log.Debugf("Configuring seccheck: %+v", conf)
state, err := findSession(conf.Name)
if err != nil {
return err
// Create reads the session configuration and applies it to the system.
func Create(conf *SessionConfig) error {
log.Debugf("Creating seccheck: %+v", conf)
sessionsMu.Lock()
defer sessionsMu.Unlock()
if _, ok := sessions[conf.Name]; ok {
return fmt.Errorf("session %q already exists", conf.Name)
}
if conf.Name != DefaultSessionName {
return fmt.Errorf(`only a single "Default" session is supported`)
}
state := &Global
var reqs []PointReq
for _, ptConfig := range conf.Points {
@@ -100,11 +115,31 @@ func Configure(conf *SessionConfig) error {
state.AppendChecker(checker, reqs)
}
sessions[conf.Name] = state
return nil
}
// SetupSink runs the setup step for a given sink.
func SetupSink(config SinkConfig) (*os.File, error) {
// SetupSinks runs the setup step of all sinks in the configuration.
func SetupSinks(sinks []SinkConfig) ([]*os.File, error) {
var files []*os.File
for _, sink := range sinks {
sinkFile, err := setupSink(sink)
if err != nil {
if !sink.IgnoreSetupError {
return nil, err
}
log.Warningf("Ignoring sink setup failure: %v", err)
// Set sinkFile is nil and append it to the list to ensure the file
// order is preserved.
sinkFile = nil
}
files = append(files, sinkFile)
}
return files, nil
}
// setupSink runs the setup step for a given sink.
func setupSink(config SinkConfig) (*os.File, error) {
sink, err := findSinkDesc(config.Name)
if err != nil {
return nil, err
@@ -115,11 +150,30 @@ func SetupSink(config SinkConfig) (*os.File, error) {
return sink.Setup(config.Config)
}
func findSession(name string) (*State, error) {
if name != "Default" {
return nil, fmt.Errorf(`only a single "Default" session is supported`)
// Delete deletes an existing session.
func Delete(name string) error {
sessionsMu.Lock()
defer sessionsMu.Unlock()
session := sessions[name]
if session == nil {
return fmt.Errorf("session %q not found", name)
}
session.clearCheckers()
delete(sessions, name)
return nil
}
// List lists all existing sessions.
func List(out *[]SessionConfig) {
sessionsMu.Lock()
defer sessionsMu.Unlock()
for name := range sessions {
// Only report session name. Consider adding rest of the fields as needed.
*out = append(*out, SessionConfig{Name: name})
}
return &Global, nil
}
func findPointDesc(name string) (PointDesc, error) {
+24
View File
@@ -96,6 +96,9 @@ func (fm *FieldMask) Empty() bool {
// may be missing requested fields in some cases (e.g. if the Checker is
// registered concurrently with invocations of checkpoints).
type Checker interface {
// Stop requests the checker to stop.
Stop()
Clone(ctx context.Context, fields FieldSet, info *pb.CloneInfo) error
Execve(ctx context.Context, fields FieldSet, info *pb.ExecveInfo) error
ExitNotifyParent(ctx context.Context, fields FieldSet, info *pb.ExitNotifyParentInfo) error
@@ -113,6 +116,9 @@ type CheckerDefaults struct{}
var _ Checker = (*CheckerDefaults)(nil)
// Stop implements Checker.Stop.
func (CheckerDefaults) Stop() {}
// Clone implements Checker.Clone.
func (CheckerDefaults) Clone(context.Context, FieldSet, *pb.CloneInfo) error {
return nil
@@ -201,6 +207,24 @@ func (s *State) AppendChecker(c Checker, reqs []PointReq) {
}
}
func (s *State) clearCheckers() {
s.registrationMu.Lock()
defer s.registrationMu.Unlock()
for i := range s.enabledPoints {
s.enabledPoints[i].Store(0)
}
s.pointFields = nil
oldCheckers := s.getCheckers()
s.registrationSeq.BeginWrite()
s.checkers = nil
s.registrationSeq.EndWrite()
for _, checker := range oldCheckers {
checker.Stop()
}
}
// Enabled returns true if any Checker is registered for the given checkpoint.
func (s *State) Enabled(p Point) bool {
word, bit := p/32, p%32
+44
View File
@@ -29,6 +29,7 @@ import (
controlpb "gvisor.dev/gvisor/pkg/sentry/control/control_go_proto"
"gvisor.dev/gvisor/pkg/sentry/fs"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/seccheck"
"gvisor.dev/gvisor/pkg/sentry/socket/netstack"
"gvisor.dev/gvisor/pkg/sentry/state"
"gvisor.dev/gvisor/pkg/sentry/time"
@@ -80,6 +81,15 @@ const (
// ContMgrRootContainerStart starts a new sandbox with a root container.
ContMgrRootContainerStart = "containerManager.StartRoot"
// ContMgrCreateTraceSession starts a trace session.
ContMgrCreateTraceSession = "containerManager.CreateTraceSession"
// ContMgrDeleteTraceSession deletes a trace session.
ContMgrDeleteTraceSession = "containerManager.DeleteTraceSession"
// ContMgrListTraceSessions lists a trace session.
ContMgrListTraceSessions = "containerManager.ListTraceSessions"
)
const (
@@ -590,3 +600,37 @@ func (cm *containerManager) Signal(args *SignalArgs, _ *struct{}) error {
log.Debugf("containerManager.Signal: cid: %s, PID: %d, signal: %d, mode: %v", args.CID, args.PID, args.Signo, args.Mode)
return cm.l.signal(args.CID, args.PID, args.Signo, args.Mode)
}
// CreateTraceSessionArgs are arguments to the CreateTraceSession method.
type CreateTraceSessionArgs struct {
Config seccheck.SessionConfig
urpc.FilePayload
}
// CreateTraceSession creates a new trace session.
func (cm *containerManager) CreateTraceSession(args *CreateTraceSessionArgs, _ *struct{}) error {
log.Debugf("containerManager.CreateTraceSession: config: %+v", args.Config)
for i, sinkFile := range args.Files {
if sinkFile != nil {
fd, err := fd.NewFromFile(sinkFile)
if err != nil {
return err
}
args.Config.Sinks[i].FD = fd
}
}
return seccheck.Create(&args.Config)
}
// DeleteTraceSession deletes an existing trace session.
func (cm *containerManager) DeleteTraceSession(name *string, _ *struct{}) error {
log.Debugf("containerManager.DeleteTraceSession: name: %q", *name)
return seccheck.Delete(*name)
}
// ListTraceSessions lists trace sessions.
func (cm *containerManager) ListTraceSessions(_ *struct{}, out *[]seccheck.SessionConfig) error {
log.Debugf("containerManager.ListTraceSessions")
seccheck.List(out)
return nil
}
+4 -20
View File
@@ -20,7 +20,6 @@ import (
"os"
"gvisor.dev/gvisor/pkg/fd"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/seccheck"
// Register supported of checkers.
@@ -41,7 +40,7 @@ func setupSeccheck(configFD int, sinkFDs []int) error {
if err != nil {
return err
}
return initConf.configure(sinkFDs)
return initConf.create(sinkFDs)
}
// LoadInitConfig loads an InitConfig struct from a json formatted file.
@@ -66,29 +65,14 @@ func loadInitConfig(reader io.Reader) (*InitConfig, error) {
// Setup performs the actions defined in the InitConfig, e.g. setup seccheck
// session.
func (c *InitConfig) Setup() ([]*os.File, error) {
var files []*os.File
for _, sink := range c.TraceSession.Sinks {
sinkFile, err := seccheck.SetupSink(sink)
if err != nil {
if !sink.IgnoreSetupError {
return nil, err
}
log.Warningf("Ignoring sink setup failure: %v", err)
// Ensure sinkFile is nil and append it to the list to ensure the file
// order is preserved.
sinkFile = nil
}
files = append(files, sinkFile)
}
return files, nil
return seccheck.SetupSinks(c.TraceSession.Sinks)
}
func (c *InitConfig) configure(sinkFDs []int) error {
func (c *InitConfig) create(sinkFDs []int) error {
for i, sinkFD := range sinkFDs {
if sinkFD >= 0 {
c.TraceSession.Sinks[i].FD = fd.New(sinkFD)
}
}
return seccheck.Configure(&c.TraceSession)
return seccheck.Create(&c.TraceSession)
}
+1
View File
@@ -16,6 +16,7 @@ go_library(
"//pkg/refsvfs2",
"//pkg/sentry/platform",
"//runsc/cmd",
"//runsc/cmd/trace",
"//runsc/cmd/util",
"//runsc/config",
"//runsc/flag",
+3 -1
View File
@@ -33,6 +33,7 @@ import (
"gvisor.dev/gvisor/pkg/refsvfs2"
"gvisor.dev/gvisor/pkg/sentry/platform"
"gvisor.dev/gvisor/runsc/cmd"
"gvisor.dev/gvisor/runsc/cmd/trace"
"gvisor.dev/gvisor/runsc/cmd/util"
"gvisor.dev/gvisor/runsc/config"
"gvisor.dev/gvisor/runsc/flag"
@@ -83,11 +84,12 @@ func Main(version string) {
subcommands.Register(new(cmd.VerityPrepare), "")
subcommands.Register(new(cmd.Wait), "")
// Installation helpers.
// Helpers.
const helperGroup = "helpers"
subcommands.Register(new(cmd.Install), helperGroup)
subcommands.Register(new(cmd.Mitigate), helperGroup)
subcommands.Register(new(cmd.Uninstall), helperGroup)
subcommands.Register(new(trace.Trace), helperGroup)
const debugGroup = "debug"
subcommands.Register(new(cmd.Debug), debugGroup)
+25
View File
@@ -0,0 +1,25 @@
load("//tools:defs.bzl", "go_library")
package(licenses = ["notice"])
go_library(
name = "trace",
srcs = [
"create.go",
"delete.go",
"list.go",
"metadata.go",
"trace.go",
],
visibility = [
"//runsc:__subpackages__",
],
deps = [
"//pkg/sentry/seccheck",
"//runsc/cmd/util",
"//runsc/config",
"//runsc/container",
"//runsc/flag",
"@com_github_google_subcommands//:go_default_library",
],
)
+95
View File
@@ -0,0 +1,95 @@
// Copyright 2020 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"
"os"
"github.com/google/subcommands"
"gvisor.dev/gvisor/pkg/sentry/seccheck"
"gvisor.dev/gvisor/runsc/cmd/util"
"gvisor.dev/gvisor/runsc/config"
"gvisor.dev/gvisor/runsc/container"
"gvisor.dev/gvisor/runsc/flag"
)
// create implements subcommands.Command for the "create" command.
type create struct {
config string
}
// Name implements subcommands.Command.
func (*create) Name() string {
return "create"
}
// Synopsis implements subcommands.Command.
func (*create) Synopsis() string {
return "create a trace session"
}
// Usage implements subcommands.Command.
func (*create) Usage() string {
return `create [flags] <sandbox id> - create a trace session
`
}
// SetFlags implements subcommands.Command.
func (l *create) SetFlags(f *flag.FlagSet) {
f.StringVar(&l.config, "config", "", "path to the JSON file that describes the session being created")
}
// Execute implements subcommands.Command.
func (l *create) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
if f.NArg() != 1 {
f.Usage()
return subcommands.ExitUsageError
}
if len(l.config) == 0 {
f.Usage()
return util.Errorf("missing path to configuration file, please set --config=[path]")
}
file, err := os.Open(l.config)
if err != nil {
return util.Errorf(err.Error())
}
defer file.Close()
decoder := json.NewDecoder(file)
sessionConfig := &seccheck.SessionConfig{}
if err := decoder.Decode(sessionConfig); err != nil {
return util.Errorf("invalid configuration file: %v", err)
}
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)
}
if err := c.Sandbox.CreateTraceSession(sessionConfig); err != nil {
util.Fatalf("creating session: %v", err)
}
return subcommands.ExitSuccess
}
+80
View File
@@ -0,0 +1,80 @@
// Copyright 2020 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"
"github.com/google/subcommands"
"gvisor.dev/gvisor/runsc/cmd/util"
"gvisor.dev/gvisor/runsc/config"
"gvisor.dev/gvisor/runsc/container"
"gvisor.dev/gvisor/runsc/flag"
)
// delete implements subcommands.Command for the "delete" command.
type delete struct {
name string
}
// Name implements subcommands.Command.
func (*delete) Name() string {
return "delete"
}
// Synopsis implements subcommands.Command.
func (*delete) Synopsis() string {
return "delete a trace session"
}
// Usage implements subcommands.Command.
func (*delete) Usage() string {
return `delete [flags] <sandbox id> - delete a trace session
`
}
// SetFlags implements subcommands.Command.
func (l *delete) SetFlags(f *flag.FlagSet) {
f.StringVar(&l.name, "name", "", "name of session to be deleted")
}
// Execute implements subcommands.Command.
func (l *delete) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
if f.NArg() != 1 {
f.Usage()
return subcommands.ExitUsageError
}
if len(l.name) == 0 {
f.Usage()
return util.Errorf("missing session name, please set --name")
}
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)
}
if err := c.Sandbox.DeleteTraceSession(l.name); err != nil {
util.Fatalf("deleting session: %v", err)
}
return subcommands.ExitSuccess
}
+78
View File
@@ -0,0 +1,78 @@
// 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"
"fmt"
"github.com/google/subcommands"
"gvisor.dev/gvisor/runsc/cmd/util"
"gvisor.dev/gvisor/runsc/config"
"gvisor.dev/gvisor/runsc/container"
"gvisor.dev/gvisor/runsc/flag"
)
// list implements subcommands.Command for the "list" command.
type list struct{}
// Name implements subcommands.Command.
func (*list) Name() string {
return "list"
}
// Synopsis implements subcommands.Command.
func (*list) Synopsis() string {
return "list all trace sessions"
}
// Usage implements subcommands.Command.
func (*list) Usage() string {
return `list - list all trace sessions
`
}
// SetFlags implements subcommands.Command.
func (*list) SetFlags(*flag.FlagSet) {}
// Execute implements subcommands.Command.
func (l *list) 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)
}
sessions, err := c.Sandbox.ListTraceSessions()
if err != nil {
util.Fatalf("listing sessions: %v", err)
}
fmt.Printf("SESSIONS (%d)\n", len(sessions))
for _, session := range sessions {
fmt.Printf("%q\n", session.Name)
}
return subcommands.ExitSuccess
}
+76
View File
@@ -0,0 +1,76 @@
// 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"
"fmt"
"sort"
"strings"
"github.com/google/subcommands"
"gvisor.dev/gvisor/pkg/sentry/seccheck"
"gvisor.dev/gvisor/runsc/flag"
)
// metadata implements subcommands.Command for the "metadata" command.
type metadata struct{}
// Name implements subcommands.Command.
func (*metadata) Name() string {
return "metadata"
}
// Synopsis implements subcommands.Command.
func (*metadata) Synopsis() string {
return "list all trace points configuration information"
}
// Usage implements subcommands.Command.
func (*metadata) Usage() string {
return `metadata - list all trace points configuration information
`
}
// SetFlags implements subcommands.Command.
func (*metadata) SetFlags(*flag.FlagSet) {}
// Execute implements subcommands.Command.
func (l *metadata) Execute(context.Context, *flag.FlagSet, ...interface{}) subcommands.ExitStatus {
// Sort to keep related points together.
points := make([]seccheck.PointDesc, 0, len(seccheck.Points))
for _, pt := range seccheck.Points {
points = append(points, pt)
}
sort.Slice(points, func(i int, j int) bool {
return points[i].Name < points[j].Name
})
fmt.Printf("POINTS (%d)\n", len(seccheck.Points))
for _, pt := range points {
optFields := fieldNames(pt.OptionalFields)
ctxFields := fieldNames(pt.ContextFields)
fmt.Printf("Name: %s, optional fields: [%s], context fields: [%s]\n", pt.Name, strings.Join(optFields, "|"), strings.Join(ctxFields, "|"))
}
return subcommands.ExitSuccess
}
func fieldNames(fields []seccheck.FieldDesc) []string {
names := make([]string, 0, len(fields))
for _, f := range fields {
names = append(names, f.Name)
}
return names
}
+69
View File
@@ -0,0 +1,69 @@
// 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 provides subcommands for the trace command.
package trace
import (
"bytes"
"context"
"github.com/google/subcommands"
"gvisor.dev/gvisor/runsc/flag"
)
// Trace implements subcommands.Command for the "trace" command.
type Trace struct{}
// Name implements subcommands.Command.
func (*Trace) Name() string {
return "trace"
}
// Synopsis implements subcommands.Command.
func (*Trace) Synopsis() string {
return "manages trace sessions for a given sandbox"
}
// Usage implements subcommands.Command.
func (*Trace) Usage() string {
buf := bytes.Buffer{}
buf.WriteString("Usage: trace <flags> <subcommand> <subcommand args>\n\n")
cdr := createCommander(&flag.FlagSet{})
cdr.VisitGroups(func(grp *subcommands.CommandGroup) {
cdr.ExplainGroup(&buf, grp)
})
return buf.String()
}
// SetFlags implements subcommands.Command.
func (*Trace) SetFlags(f *flag.FlagSet) {}
// Execute implements subcommands.Command.
func (*Trace) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
return createCommander(f).Execute(ctx, args...)
}
func createCommander(f *flag.FlagSet) *subcommands.Commander {
cdr := subcommands.NewCommander(f, "trace")
cdr.Register(cdr.HelpCommand(), "")
cdr.Register(cdr.FlagsCommand(), "")
cdr.Register(new(create), "")
cdr.Register(new(delete), "")
cdr.Register(new(list), "")
cdr.Register(new(metadata), "")
return cdr
}
+5
View File
@@ -43,6 +43,7 @@ go_test(
"container_test.go",
"multi_container_test.go",
"shared_volume_test.go",
"trace_test.go",
],
# Only run the default platform for the tsan test, which should
# be compatible. For non-tsan builds, run all platforms.
@@ -70,6 +71,9 @@ go_test(
"//pkg/sentry/kernel",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/platform",
"//pkg/sentry/seccheck",
"//pkg/sentry/seccheck/checkers/remote/test",
"//pkg/sentry/seccheck/points:points_go_proto",
"//pkg/sync",
"//pkg/test/testutil",
"//pkg/unet",
@@ -81,6 +85,7 @@ go_test(
"@com_github_cenkalti_backoff//:go_default_library",
"@com_github_kr_pty//:go_default_library",
"@com_github_opencontainers_runtime_spec//specs-go:go_default_library",
"@org_golang_google_protobuf//proto:go_default_library",
"@org_golang_x_sys//unix:go_default_library",
],
)
+9
View File
@@ -38,6 +38,11 @@ type LoadOpts struct {
// SkipCheck tells Load() to skip checking if container is runnning.
SkipCheck bool
// RootContainer when true matches the search only with the root container of
// a sandbox. This is used when looking for a sandbox given that root
// container and sandbox share the same ID.
RootContainer bool
}
// Load loads a container with the given id from a metadata file. "id" may
@@ -77,6 +82,10 @@ func Load(rootDir string, id FullID, opts LoadOpts) (*Container, error) {
return nil, fmt.Errorf("reading container metadata file %q: %v", state.statePath(), err)
}
if opts.RootContainer && c.ID != c.Sandbox.ID {
return nil, fmt.Errorf("ID %q doesn't belong to a sandbox", id)
}
if !opts.SkipCheck {
// If the status is "Running" or "Created", check that the sandbox/container
// is still running, setting it to Stopped if not.
+219
View File
@@ -0,0 +1,219 @@
// 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 container
import (
"encoding/json"
"io/ioutil"
"testing"
"time"
"google.golang.org/protobuf/proto"
"gvisor.dev/gvisor/pkg/sentry/seccheck"
"gvisor.dev/gvisor/pkg/sentry/seccheck/checkers/remote/test"
pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto"
"gvisor.dev/gvisor/pkg/test/testutil"
"gvisor.dev/gvisor/runsc/boot"
)
// Test that setting up a trace session configuration in PodInitConfig creates
// a session before container creation.
func TestTraceStartup(t *testing.T) {
// Test on all configurations to ensure that point can be sent to an outside
// process in all cases. Rest of the tests don't require all configs.
for name, conf := range configs(t, false /* noOverlay */) {
t.Run(name, func(t *testing.T) {
server, err := test.NewServer()
if err != nil {
t.Fatalf("newServer(): %v", err)
}
defer server.Close()
podInitConfig, err := ioutil.TempFile(testutil.TmpDir(), "config")
if err != nil {
t.Fatalf("error creating tmp file: %v", err)
}
defer podInitConfig.Close()
initConfig := boot.InitConfig{
TraceSession: seccheck.SessionConfig{
Name: seccheck.DefaultSessionName,
Points: []seccheck.PointConfig{
{
Name: "container/start",
ContextFields: []string{"container_id"},
},
},
Sinks: []seccheck.SinkConfig{
{
Name: "remote",
Config: map[string]interface{}{
"endpoint": server.Path,
},
},
},
},
}
encoder := json.NewEncoder(podInitConfig)
if err := encoder.Encode(&initConfig); err != nil {
t.Fatalf("JSON encode: %v", err)
}
conf.PodInitConfig = podInitConfig.Name()
spec := testutil.NewSpecWithArgs("/bin/true")
if err := run(spec, conf); err != nil {
t.Fatalf("Error running container: %v", err)
}
// Wait for the point to be received and then check that fields match.
if err := server.WaitForCount(1); err != nil {
t.Fatalf("WaitForCount(1): %v", err)
}
pt := server.GetPoints()[0]
if want := pb.MessageType_MESSAGE_CONTAINER_START; pt.MsgType != want {
t.Errorf("wrong message type, want: %v, got: %v", want, pt.MsgType)
}
got := &pb.Start{}
if err := proto.Unmarshal(pt.Msg, got); err != nil {
t.Errorf("proto.Unmarshal(Start): %v", err)
}
if want := "/bin/true"; len(got.Args) != 1 || want != got.Args[0] {
t.Errorf("container.Start.Args, want: %q, got: %q", want, got.Args)
}
if want, got := got.Id, got.ContextData.ContainerId; want != got {
t.Errorf("Mismatched container ID, want: %v, got: %v", want, got)
}
})
}
}
func TestTraceLifecycle(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)
}
// Check that no session are created.
if sessions, err := cont.Sandbox.ListTraceSessions(); err != nil {
t.Fatalf("ListTraceSessions(): %v", err)
} else if len(sessions) != 0 {
t.Fatalf("no session should exist, got: %+v", sessions)
}
// Create a new trace session on the fly.
server, err := test.NewServer()
if err != nil {
t.Fatalf("newServer(): %v", err)
}
defer server.Close()
session := seccheck.SessionConfig{
Name: "Default",
Points: []seccheck.PointConfig{
{
Name: "sentry/task_exit",
ContextFields: []string{"container_id"},
},
},
Sinks: []seccheck.SinkConfig{
{
Name: "remote",
Config: map[string]interface{}{
"endpoint": server.Path,
},
},
},
}
if err := cont.Sandbox.CreateTraceSession(&session); err != nil {
t.Fatalf("CreateTraceSession(): %v", err)
}
// Trigger the configured point and want to receive it in the server.
if ws, err := execute(conf, cont, "/bin/true"); err != nil || ws != 0 {
t.Fatalf("exec: true, ws: %v, err: %v", ws, err)
}
if err := server.WaitForCount(1); err != nil {
t.Fatalf("WaitForCount(1): %v", err)
}
pt := server.GetPoints()[0]
if want := pb.MessageType_MESSAGE_SENTRY_TASK_EXIT; pt.MsgType != want {
t.Errorf("wrong message type, want: %v, got: %v", want, pt.MsgType)
}
got := &pb.TaskExit{}
if err := proto.Unmarshal(pt.Msg, got); err != nil {
t.Errorf("proto.Unmarshal(TaskExit): %v", err)
}
if got.ExitStatus != 0 {
t.Errorf("Wrong TaskExit.ExitStatus, want: 0, got: %+v", got)
}
if want, got := cont.ID, got.ContextData.ContainerId; want != got {
t.Errorf("Wrong TaskExit.ContextData.ContainerId, want: %v, got: %v", want, got)
}
// Check that no more points were received and reset the server for the
// remaining tests.
if want, got := 1, server.Reset(); want != got {
t.Errorf("wrong number of points, want: %d, got: %d", want, got)
}
// List and check that trace session is reported.
sessions, err := cont.Sandbox.ListTraceSessions()
if err != nil {
t.Fatalf("ListTraceSessions(): %v", err)
}
if len(sessions) != 1 {
t.Fatalf("expected a single session, got: %+v", sessions)
}
if got := sessions[0].Name; seccheck.DefaultSessionName != got {
t.Errorf("wrong session, want: %v, got: %v", seccheck.DefaultSessionName, got)
}
if err := cont.Sandbox.DeleteTraceSession("Default"); err != nil {
t.Fatalf("DeleteTraceSession(): %v", err)
}
// Check that session was indeed deleted.
if sessions, err := cont.Sandbox.ListTraceSessions(); err != nil {
t.Fatalf("ListTraceSessions(): %v", err)
} else if len(sessions) != 0 {
t.Fatalf("no session should exist, got: %+v", sessions)
}
// Trigger the point again and check that it's not received.
if ws, err := execute(conf, cont, "/bin/true"); err != nil || ws != 0 {
t.Fatalf("exec: true, ws: %v, err: %v", ws, err)
}
time.Sleep(time.Second) // give some time to receive the point.
if server.Count() > 0 {
t.Errorf("point received after session was deleted: %+v", server.GetPoints())
}
}
+1
View File
@@ -23,6 +23,7 @@ go_library(
"//pkg/log",
"//pkg/sentry/control",
"//pkg/sentry/platform",
"//pkg/sentry/seccheck",
"//pkg/sync",
"//pkg/tcpip/header",
"//pkg/tcpip/stack",
+65 -1
View File
@@ -41,6 +41,7 @@ import (
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/control"
"gvisor.dev/gvisor/pkg/sentry/platform"
"gvisor.dev/gvisor/pkg/sentry/seccheck"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/unet"
"gvisor.dev/gvisor/pkg/urpc"
@@ -196,7 +197,7 @@ func New(conf *config.Config, args *Args) (*Sandbox, error) {
if len(conf.PodInitConfig) > 0 {
initConf, err := boot.LoadInitConfig(conf.PodInitConfig)
if err != nil {
return nil, err
return nil, fmt.Errorf("loading init config file: %w", err)
}
args.SinkFiles, err = initConf.Setup()
if err != nil {
@@ -388,6 +389,69 @@ func (s *Sandbox) Processes(cid string) ([]*control.Process, error) {
return pl, nil
}
// CreateTraceSession creates a new trace session.
func (s *Sandbox) CreateTraceSession(config *seccheck.SessionConfig) error {
log.Debugf("Creating trace session in sandbox %q", s.ID)
sinkFiles, err := seccheck.SetupSinks(config.Sinks)
if err != nil {
return err
}
defer func() {
for _, f := range sinkFiles {
_ = f.Close()
}
}()
conn, err := s.sandboxConnect()
if err != nil {
return err
}
defer conn.Close()
arg := boot.CreateTraceSessionArgs{
Config: *config,
FilePayload: urpc.FilePayload{
Files: sinkFiles,
},
}
if err := conn.Call(boot.ContMgrCreateTraceSession, &arg, nil); err != nil {
return fmt.Errorf("creating trace session: %w", err)
}
return nil
}
// DeleteTraceSession deletes an existing trace session.
func (s *Sandbox) DeleteTraceSession(name string) error {
log.Debugf("Deleting trace session %q in sandbox %q", name, s.ID)
conn, err := s.sandboxConnect()
if err != nil {
return err
}
defer conn.Close()
if err := conn.Call(boot.ContMgrDeleteTraceSession, name, nil); err != nil {
return fmt.Errorf("deleting trace session: %w", err)
}
return nil
}
// ListTraceSessions lists all trace sessions.
func (s *Sandbox) ListTraceSessions() ([]seccheck.SessionConfig, error) {
log.Debugf("Listing trace sessions in sandbox %q", s.ID)
conn, err := s.sandboxConnect()
if err != nil {
return nil, err
}
defer conn.Close()
var sessions []seccheck.SessionConfig
if err := conn.Call(boot.ContMgrListTraceSessions, nil, &sessions); err != nil {
return nil, fmt.Errorf("listing trace session: %w", err)
}
return sessions, 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 */)