mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
seccheck: Trace sibling pipe processes in a client lib
PiperOrigin-RevId: 964771164
This commit is contained in:
@@ -8,13 +8,16 @@ go_library(
|
||||
name = "scsdk",
|
||||
srcs = [
|
||||
"client.go",
|
||||
"events.go",
|
||||
"fs.go",
|
||||
"tracker.go",
|
||||
],
|
||||
visibility = ["//:sandbox"],
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/control/client",
|
||||
"//pkg/sentry/control",
|
||||
"//pkg/sentry/seccheck/points:points_go_proto",
|
||||
"//pkg/urpc",
|
||||
"@org_golang_x_sys//unix:go_default_library",
|
||||
],
|
||||
@@ -32,3 +35,13 @@ go_test(
|
||||
"@org_golang_x_sys//unix:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "tracker_test",
|
||||
size = "small",
|
||||
srcs = ["tracker_test.go"],
|
||||
library = ":scsdk",
|
||||
deps = [
|
||||
"//pkg/sentry/seccheck/points:points_go_proto",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright 2026 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 scsdk
|
||||
|
||||
import (
|
||||
pointspb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto"
|
||||
)
|
||||
|
||||
// ProcessContext uniquely identifies a thread-group/process and its environment
|
||||
// at the time of an event.
|
||||
type ProcessContext struct {
|
||||
ThreadGroupID int32
|
||||
ThreadGroupStartTimeNs int64
|
||||
ContainerID string
|
||||
ProcessName string
|
||||
ParentThreadGroupID int32
|
||||
}
|
||||
|
||||
// ExecutionEvent provides a Go presentation of the ExecveInfo protobuf.
|
||||
type ExecutionEvent struct {
|
||||
Context ProcessContext
|
||||
BinaryPath string
|
||||
Argv []string
|
||||
Env []string
|
||||
|
||||
// Sibling pipes for enriching the event.
|
||||
PipeInputSiblingContext *ProcessContext
|
||||
PipeOutputSiblingContext *ProcessContext
|
||||
}
|
||||
|
||||
// ToProcessContext translates a raw ContextData protobuf into the ProcessContext struct.
|
||||
func ToProcessContext(ctx *pointspb.ContextData) ProcessContext {
|
||||
if ctx == nil {
|
||||
return ProcessContext{}
|
||||
}
|
||||
return ProcessContext{
|
||||
ThreadGroupID: ctx.GetThreadGroupId(),
|
||||
ThreadGroupStartTimeNs: ctx.GetThreadGroupStartTimeNs(),
|
||||
ContainerID: ctx.GetContainerId(),
|
||||
ProcessName: ctx.GetProcessName(),
|
||||
ParentThreadGroupID: ctx.GetParentThreadGroupId(),
|
||||
}
|
||||
}
|
||||
|
||||
// ToProcessContextPtr translates a raw ContextData protobuf into the ProcessContext struct,
|
||||
// returning nil if the input is nil.
|
||||
func ToProcessContextPtr(ctx *pointspb.ContextData) *ProcessContext {
|
||||
if ctx == nil {
|
||||
return nil
|
||||
}
|
||||
c := ToProcessContext(ctx)
|
||||
return &c
|
||||
}
|
||||
|
||||
// ToExecutionEvent translates a raw ExecveInfo protobuf into the ExecutionEvent struct.
|
||||
// It handles all the defensive nil-checking so SDK consumers don't have to.
|
||||
func ToExecutionEvent(execve *pointspb.ExecveInfo) *ExecutionEvent {
|
||||
if execve == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ev := &ExecutionEvent{
|
||||
Context: ToProcessContext(execve.GetContextData()),
|
||||
BinaryPath: execve.GetBinaryPath(),
|
||||
}
|
||||
|
||||
if len(execve.GetArgv()) > 0 {
|
||||
ev.Argv = append([]string(nil), execve.GetArgv()...)
|
||||
}
|
||||
if len(execve.GetEnv()) > 0 {
|
||||
ev.Env = append([]string(nil), execve.GetEnv()...)
|
||||
}
|
||||
|
||||
if pipeIn := execve.GetPipeInputProc(); pipeIn != nil {
|
||||
ev.PipeInputSiblingContext = ToProcessContextPtr(pipeIn.GetContextData())
|
||||
}
|
||||
if pipeOut := execve.GetPipeOutputProc(); pipeOut != nil {
|
||||
ev.PipeOutputSiblingContext = ToProcessContextPtr(pipeOut.GetContextData())
|
||||
}
|
||||
|
||||
return ev
|
||||
}
|
||||
|
||||
// TaskExitEvent provides a Go presentation of the TaskExit protobuf.
|
||||
type TaskExitEvent struct {
|
||||
Context ProcessContext
|
||||
ExitStatus int32
|
||||
}
|
||||
|
||||
// ToTaskExitEvent translates a raw TaskExit protobuf into the SDK struct.
|
||||
func ToTaskExitEvent(exit *pointspb.TaskExit) *TaskExitEvent {
|
||||
if exit == nil {
|
||||
return nil
|
||||
}
|
||||
return &TaskExitEvent{
|
||||
Context: ToProcessContext(exit.GetContextData()),
|
||||
ExitStatus: exit.GetExitStatus(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
// Copyright 2026 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.
|
||||
|
||||
// ProcessTracker can be used by a trace consumer to track historical process metadata (PID, start
|
||||
// time, binary path, argv, and env) and automatically enriches ExecveInfo events with sibling
|
||||
// process information (PipeInputSibling and PipeOutputSibling) when processes are connected via
|
||||
// stdin or stdout pipes.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// // Initialize a ProcessTracker in your monitoring daemon:
|
||||
// pt := scsdk.NewProcessTracker()
|
||||
//
|
||||
// // Whenever a protobuf message arrives (from Unix socket, gRPC, or file):
|
||||
// switch msg := protoMsg.(type) {
|
||||
// case *pb.ExecveInfo:
|
||||
// // Map the raw protobuf to a SDK event:
|
||||
// execEvent := scsdk.ToExecutionEvent(msg)
|
||||
// // ProcessExecve updates the tracker table and returns an EnrichedExecveEvent
|
||||
// // where PipeInputSibling and PipeOutputSibling are automatically resolved.
|
||||
// enriched := pt.ProcessExecve(execEvent)
|
||||
// fmt.Printf("Process executed: %s (PID %d, Argv: %v)\n",
|
||||
// enriched.Process.BinaryPath, enriched.Process.Key.ThreadGroupID, enriched.Process.Argv)
|
||||
//
|
||||
// if sib := enriched.PipeInputSibling; sib != nil {
|
||||
// fmt.Printf(" <- Piped input from sibling: %s (PID %d, Argv: %v)\n",
|
||||
// sib.BinaryPath, sib.Key.ThreadGroupID, sib.Argv)
|
||||
// }
|
||||
// if sib := enriched.PipeOutputSibling; sib != nil {
|
||||
// fmt.Printf(" -> Piped output to sibling: %s (PID %d, Argv: %v)\n",
|
||||
// sib.BinaryPath, sib.Key.ThreadGroupID, sib.Argv)
|
||||
// }
|
||||
//
|
||||
// case *pb.TaskExit:
|
||||
// exitEvent := scsdk.ToTaskExitEvent(msg)
|
||||
// // ProcessTaskExit cleans up tracked state when a thread group exits.
|
||||
// pt.ProcessTaskExit(exitEvent)
|
||||
// }
|
||||
|
||||
package scsdk
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ProcessKey uniquely identifies a task/thread-group across time, safe against PID recycling.
|
||||
type ProcessKey struct {
|
||||
ThreadGroupID int32
|
||||
ThreadGroupStartTimeNs int64
|
||||
}
|
||||
|
||||
// ProcessInfo contains tracked state for a process inside a gVisor sandbox.
|
||||
type ProcessInfo struct {
|
||||
Key ProcessKey
|
||||
ContainerID string
|
||||
ProcessName string
|
||||
BinaryPath string
|
||||
Argv []string
|
||||
Env []string
|
||||
ParentThreadGroupID int32
|
||||
}
|
||||
|
||||
// EnrichedExecveEvent contains the ExecveInfo protobuf event and automatically resolved
|
||||
// sibling process information for stdin/stdout pipes.
|
||||
//
|
||||
// By decoupling string storage from the PipeProcInfo proto message,
|
||||
// PipeInputSibling and PipeOutputSibling provide full binary/argv details without
|
||||
// kernel-side string copying overhead.
|
||||
type EnrichedExecveEvent struct {
|
||||
Event *ExecutionEvent
|
||||
Process *ProcessInfo
|
||||
PipeInputSibling *ProcessInfo
|
||||
PipeOutputSibling *ProcessInfo
|
||||
}
|
||||
|
||||
// ProcessTracker manages the lifecycle of process metadata across Seccheck events.
|
||||
type ProcessTracker struct {
|
||||
mu sync.RWMutex
|
||||
processes map[ProcessKey]*ProcessInfo
|
||||
}
|
||||
|
||||
// NewProcessTracker creates an initialized ProcessTracker.
|
||||
func NewProcessTracker() *ProcessTracker {
|
||||
return &ProcessTracker{
|
||||
processes: make(map[ProcessKey]*ProcessInfo),
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup returns the ProcessInfo matching key, or nil if untracked.
|
||||
func (pt *ProcessTracker) Lookup(key ProcessKey) *ProcessInfo {
|
||||
pt.mu.RLock()
|
||||
defer pt.mu.RUnlock()
|
||||
return pt.processes[key]
|
||||
}
|
||||
|
||||
// LookupFromContext creates a ProcessKey from a ProcessContext and returns the tracked process.
|
||||
func (pt *ProcessTracker) LookupFromContext(ctx ProcessContext) *ProcessInfo {
|
||||
key := ProcessKey{
|
||||
ThreadGroupID: ctx.ThreadGroupID,
|
||||
ThreadGroupStartTimeNs: ctx.ThreadGroupStartTimeNs,
|
||||
}
|
||||
return pt.Lookup(key)
|
||||
}
|
||||
|
||||
// RegisterOrUpdate records process metadata from an ExecutionEvent.
|
||||
func (pt *ProcessTracker) RegisterOrUpdate(execve *ExecutionEvent) *ProcessInfo {
|
||||
key := ProcessKey{
|
||||
ThreadGroupID: execve.Context.ThreadGroupID,
|
||||
ThreadGroupStartTimeNs: execve.Context.ThreadGroupStartTimeNs,
|
||||
}
|
||||
|
||||
pt.mu.Lock()
|
||||
defer pt.mu.Unlock()
|
||||
|
||||
info, exists := pt.processes[key]
|
||||
if !exists {
|
||||
info = &ProcessInfo{Key: key}
|
||||
pt.processes[key] = info
|
||||
}
|
||||
info.ContainerID = execve.Context.ContainerID
|
||||
info.ProcessName = execve.Context.ProcessName
|
||||
info.ParentThreadGroupID = execve.Context.ParentThreadGroupID
|
||||
info.BinaryPath = execve.BinaryPath
|
||||
info.Argv = append([]string(nil), execve.Argv...)
|
||||
info.Env = append([]string(nil), execve.Env...)
|
||||
return info
|
||||
}
|
||||
|
||||
// Unregister deletes process metadata upon task exit given its context.
|
||||
func (pt *ProcessTracker) Unregister(ctx ProcessContext) {
|
||||
key := ProcessKey{
|
||||
ThreadGroupID: ctx.ThreadGroupID,
|
||||
ThreadGroupStartTimeNs: ctx.ThreadGroupStartTimeNs,
|
||||
}
|
||||
pt.mu.Lock()
|
||||
defer pt.mu.Unlock()
|
||||
delete(pt.processes, key)
|
||||
}
|
||||
|
||||
// ProcessExecve processes an ExecutionEvent, updates state, and returns an
|
||||
// enriched event.
|
||||
//
|
||||
// Because PipeProcInfo only transports ContextData, we resolve full sibling
|
||||
// process info (binary, argv) by querying our ProcessTracker using the TGID and StartTime.
|
||||
func (pt *ProcessTracker) ProcessExecve(execve *ExecutionEvent) *EnrichedExecveEvent {
|
||||
if execve == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update our tracker with the current process's binary, argv, and env.
|
||||
currentProc := pt.RegisterOrUpdate(execve)
|
||||
|
||||
// Resolve sibling process information via ContextData keys from PipeInputProc/PipeOutputProc.
|
||||
var inSibling, outSibling *ProcessInfo
|
||||
if execve.PipeInputSiblingContext != nil {
|
||||
inSibling = pt.LookupFromContext(*execve.PipeInputSiblingContext)
|
||||
}
|
||||
if execve.PipeOutputSiblingContext != nil {
|
||||
outSibling = pt.LookupFromContext(*execve.PipeOutputSiblingContext)
|
||||
}
|
||||
|
||||
return &EnrichedExecveEvent{
|
||||
Event: execve,
|
||||
Process: currentProc,
|
||||
PipeInputSibling: inSibling,
|
||||
PipeOutputSibling: outSibling,
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessTaskExit processes an TaskExitEvent and cleans up tracker state.
|
||||
func (pt *ProcessTracker) ProcessTaskExit(exit *TaskExitEvent) {
|
||||
if exit != nil {
|
||||
pt.Unregister(exit.Context)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright 2026 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 scsdk
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto"
|
||||
)
|
||||
|
||||
func TestProcessTrackerAndPipeEnrichment(t *testing.T) {
|
||||
pt := NewProcessTracker()
|
||||
|
||||
// Simulate an ExecveInfo event for process A ("cat foo").
|
||||
// Design Decision: Only standard ExecveInfo events carry the full strings (BinaryPath, Argv).
|
||||
execA := &pb.ExecveInfo{
|
||||
ContextData: &pb.ContextData{
|
||||
ThreadGroupId: 100,
|
||||
ThreadGroupStartTimeNs: 1000,
|
||||
ContainerId: "test-container",
|
||||
ProcessName: "cat",
|
||||
},
|
||||
BinaryPath: "/bin/cat",
|
||||
Argv: []string{"cat", "foo"},
|
||||
}
|
||||
|
||||
eventA := pt.ProcessExecve(ToExecutionEvent(execA))
|
||||
if eventA.Process == nil {
|
||||
t.Fatalf("got nil, want ProcessInfo for cat")
|
||||
}
|
||||
if eventA.Process.BinaryPath != "/bin/cat" {
|
||||
t.Errorf("got binary %q, want %q", eventA.Process.BinaryPath, "/bin/cat")
|
||||
}
|
||||
|
||||
// Simulate an ExecveInfo event for process B ("grep bar") which receives piped input from A.
|
||||
// Design Decision: Notice that PipeInputProc ONLY contains ContextData (TGID=100, StartTime=1000)
|
||||
// without any string fields, eliminating kernel string copy overhead.
|
||||
execB := &pb.ExecveInfo{
|
||||
ContextData: &pb.ContextData{
|
||||
ThreadGroupId: 101,
|
||||
ThreadGroupStartTimeNs: 1010,
|
||||
ContainerId: "test-container",
|
||||
ProcessName: "grep",
|
||||
},
|
||||
BinaryPath: "/bin/grep",
|
||||
Argv: []string{"grep", "bar"},
|
||||
PipeInputProc: &pb.PipeProcInfo{
|
||||
ContextData: &pb.ContextData{
|
||||
ThreadGroupId: 100,
|
||||
ThreadGroupStartTimeNs: 1000,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
eventB := pt.ProcessExecve(ToExecutionEvent(execB))
|
||||
if eventB.PipeInputSibling == nil {
|
||||
t.Fatalf("got nil, want PipeInputSibling to be resolved from tracker")
|
||||
}
|
||||
if eventB.PipeInputSibling.BinaryPath != "/bin/cat" {
|
||||
t.Errorf("got sibling binary %q, want %q", eventB.PipeInputSibling.BinaryPath, "/bin/cat")
|
||||
}
|
||||
if len(eventB.PipeInputSibling.Argv) != 2 || eventB.PipeInputSibling.Argv[1] != "foo" {
|
||||
t.Errorf("got sibling argv %v, want [cat foo]", eventB.PipeInputSibling.Argv)
|
||||
}
|
||||
|
||||
// Simulate TaskExit for process A and verify cleanup.
|
||||
pt.ProcessTaskExit(ToTaskExitEvent(&pb.TaskExit{
|
||||
ContextData: &pb.ContextData{
|
||||
ThreadGroupId: 100,
|
||||
ThreadGroupStartTimeNs: 1000,
|
||||
},
|
||||
}))
|
||||
keyA := ProcessKey{ThreadGroupID: 100, ThreadGroupStartTimeNs: 1000}
|
||||
if info := pt.Lookup(keyA); info != nil {
|
||||
t.Errorf("got %v, want process A to be unregistered after exit", info)
|
||||
}
|
||||
}
|
||||
@@ -402,6 +402,7 @@ go_library(
|
||||
"//pkg/sentry/kernel/ipc",
|
||||
"//pkg/sentry/kernel/mq",
|
||||
"//pkg/sentry/kernel/msgqueue",
|
||||
"//pkg/sentry/kernel/pipe",
|
||||
"//pkg/sentry/kernel/sched",
|
||||
"//pkg/sentry/kernel/semaphore",
|
||||
"//pkg/sentry/kernel/shm",
|
||||
|
||||
@@ -47,6 +47,11 @@ func NewVFSPipe(isNamed bool, sizeBytes int64) *VFSPipe {
|
||||
return &vp
|
||||
}
|
||||
|
||||
// Pipe returns the underlying Pipe object.
|
||||
func (vp *VFSPipe) Pipe() *Pipe {
|
||||
return &vp.pipe
|
||||
}
|
||||
|
||||
// ReaderWriterPair returns read-only and write-only FDs for vp.
|
||||
//
|
||||
// Preconditions: statusFlags should not contain an open access mode.
|
||||
@@ -175,6 +180,11 @@ type VFSPipeFD struct {
|
||||
lastAddr hostarch.Addr
|
||||
}
|
||||
|
||||
// Pipe returns the underlying Pipe object.
|
||||
func (fd *VFSPipeFD) Pipe() *Pipe {
|
||||
return fd.pipe
|
||||
}
|
||||
|
||||
// Release implements vfs.FileDescriptionImpl.Release.
|
||||
func (fd *VFSPipeFD) Release(context.Context) {
|
||||
var event waiter.EventMask
|
||||
|
||||
@@ -71,6 +71,7 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/overlay"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/pipe"
|
||||
"gvisor.dev/gvisor/pkg/sentry/loader"
|
||||
"gvisor.dev/gvisor/pkg/sentry/mm"
|
||||
"gvisor.dev/gvisor/pkg/sentry/seccheck"
|
||||
@@ -536,6 +537,70 @@ func execveSeccheckInfo(t *Task, argv, env []string, executable *vfs.FileDescrip
|
||||
info.Stderr = execveFdInfo(t, 2)
|
||||
}
|
||||
|
||||
if fields.Local.Contains(seccheck.FieldSentryExecvePipeProcInfo) {
|
||||
// Check whether standard input (fd 0) or standard output (fd 1)
|
||||
// is connected to a pipe by extracting the underlying Pipe object.
|
||||
var pipeIn, pipeOut *pipe.Pipe
|
||||
if fileIn := t.GetFile(0); fileIn != nil {
|
||||
if pfd, ok := fileIn.Impl().(*pipe.VFSPipeFD); ok {
|
||||
pipeIn = pfd.Pipe()
|
||||
}
|
||||
fileIn.DecRef(t)
|
||||
}
|
||||
if fileOut := t.GetFile(1); fileOut != nil {
|
||||
if pfd, ok := fileOut.Impl().(*pipe.VFSPipeFD); ok {
|
||||
pipeOut = pfd.Pipe()
|
||||
}
|
||||
fileOut.DecRef(t)
|
||||
}
|
||||
|
||||
// Inspect sibling tasks under the same parent to find processes connected to the
|
||||
// opposite end of stdin (pipeIn) or stdout (pipeOut).
|
||||
if pipeIn != nil || pipeOut != nil {
|
||||
// Only inspect the parent if pipeIn or pipeOut are present. Calling
|
||||
// t.Parent() grabs a read lock on the PID namespace owner, which we want to avoid
|
||||
// on the fast path if no pipes are connected.
|
||||
if parent := t.Parent(); parent != nil {
|
||||
for child := range parent.Children() {
|
||||
if child == t {
|
||||
continue
|
||||
}
|
||||
// Early termination: stop scanning siblings as soon as all applicable
|
||||
// pipe peers (stdin and/or stdout) have been found or are not pipes.
|
||||
if (pipeIn == nil || info.PipeInputProc != nil) && (pipeOut == nil || info.PipeOutputProc != nil) {
|
||||
break
|
||||
}
|
||||
|
||||
fdt := child.FDTable()
|
||||
if fdt == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if the sibling writes to t's stdin pipe via stdout (fd 1).
|
||||
if pipeIn != nil && info.PipeInputProc == nil {
|
||||
if childOut, _ := fdt.Get(1); childOut != nil {
|
||||
pfd, ok := childOut.Impl().(*pipe.VFSPipeFD)
|
||||
if ok && pfd.Pipe() == pipeIn {
|
||||
info.PipeInputProc = pipeProcInfo(child, fields.Context)
|
||||
}
|
||||
childOut.DecRef(t)
|
||||
}
|
||||
}
|
||||
// Check if the sibling reads from t's stdout pipe via stdin (fd 0).
|
||||
if pipeOut != nil && info.PipeOutputProc == nil {
|
||||
if childIn, _ := fdt.Get(0); childIn != nil {
|
||||
pfd, ok := childIn.Impl().(*pipe.VFSPipeFD)
|
||||
if ok && pfd.Pipe() == pipeOut {
|
||||
info.PipeOutputProc = pipeProcInfo(child, fields.Context)
|
||||
}
|
||||
childIn.DecRef(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !fields.Context.Empty() {
|
||||
info.ContextData = &pb.ContextData{}
|
||||
LoadSeccheckData(t, fields.Context, info.ContextData)
|
||||
@@ -719,3 +784,18 @@ func (t *Task) releaseExecveCredsLocks() {
|
||||
t.execveCredsMutexUnlock()
|
||||
t.FSContext().allowSharing()
|
||||
}
|
||||
|
||||
// pipeProcInfo collects Seccheck process information for a sibling task connected via a pipe.
|
||||
//
|
||||
// To avoid returning heavy strings (like argv or binary path) on every execve trace point, we only
|
||||
// populate the lightweight ContextData. A stateful seccheck client library can
|
||||
// correlate the sibling's thread_group_id and thread_group_start_time_ns with prior Execve/Clone
|
||||
// events to enrich the trace with full binary_path and argv without kernel overhead.
|
||||
func pipeProcInfo(child *Task, contextMask seccheck.FieldMask) *pb.PipeProcInfo {
|
||||
res := &pb.PipeProcInfo{}
|
||||
if !contextMask.Empty() {
|
||||
res.ContextData = &pb.ContextData{}
|
||||
LoadSeccheckData(child, contextMask, res.ContextData)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -75,6 +75,10 @@ const (
|
||||
// FieldSentryExecveFdInfo is an optional field to collect information
|
||||
// about standard file descriptors (stdin, stdout, stderr).
|
||||
FieldSentryExecveFdInfo
|
||||
|
||||
// FieldSentryExecvePipeProcInfo is an optional field to collect lightweight
|
||||
// information about sibling processes connected via pipes.
|
||||
FieldSentryExecvePipeProcInfo
|
||||
)
|
||||
|
||||
// Points is a map with all the trace points registered in the system.
|
||||
@@ -275,6 +279,10 @@ func genericInit() {
|
||||
ID: FieldSentryExecveFdInfo,
|
||||
Name: "fd_info",
|
||||
},
|
||||
{
|
||||
ID: FieldSentryExecvePipeProcInfo,
|
||||
Name: "pipe_proc_info",
|
||||
},
|
||||
},
|
||||
ContextFields: defaultContextFields,
|
||||
})
|
||||
|
||||
@@ -110,6 +110,21 @@ message ExecveInfo {
|
||||
// AT_EXECFN). Unlike BinaryPath, this preserves the original relative or
|
||||
// unresolved path argument before symlink or interpreter script resolution.
|
||||
string execfn = 17;
|
||||
|
||||
// PipeProcInfo contains lightweight process information of a sibling process
|
||||
// connected via pipe on stdin/stdout.
|
||||
PipeProcInfo pipe_input_proc = 21;
|
||||
PipeProcInfo pipe_output_proc = 22;
|
||||
}
|
||||
|
||||
// PipeProcInfo contains lightweight process information of a sibling process
|
||||
// connected via pipe.
|
||||
//
|
||||
// Only ContextData is provided here so that trace consumers can uniquely
|
||||
// correlate the sibling process via thread_group_id and
|
||||
// thread_group_start_time_ns without passing repetitive strings over the wire.
|
||||
message PipeProcInfo {
|
||||
gvisor.common.ContextData context_data = 1;
|
||||
}
|
||||
|
||||
message ExitNotifyParentInfo {
|
||||
|
||||
@@ -79,6 +79,7 @@ go_test(
|
||||
"//pkg/cleanup",
|
||||
"//pkg/hostos",
|
||||
"//pkg/log",
|
||||
"//pkg/seccheck/scsdk",
|
||||
"//pkg/sentry/checkpoint",
|
||||
"//pkg/sentry/control",
|
||||
"//pkg/sentry/fscheckpoint",
|
||||
@@ -90,7 +91,6 @@ go_test(
|
||||
"//pkg/sentry/platform",
|
||||
"//pkg/sentry/seccheck",
|
||||
"//pkg/sentry/seccheck/points:points_go_proto",
|
||||
"//pkg/sentry/seccheck/scsdk",
|
||||
"//pkg/sentry/seccheck/sinks/remote/test",
|
||||
"//pkg/sentry/state/checkpointfiles",
|
||||
"//pkg/state/statefile",
|
||||
|
||||
@@ -24,11 +24,11 @@ import (
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"golang.org/x/sys/unix"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"gvisor.dev/gvisor/pkg/seccheck/scsdk"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/limits"
|
||||
"gvisor.dev/gvisor/pkg/sentry/seccheck"
|
||||
pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto"
|
||||
"gvisor.dev/gvisor/pkg/sentry/seccheck/scsdk"
|
||||
"gvisor.dev/gvisor/pkg/sentry/seccheck/sinks/remote/test"
|
||||
"gvisor.dev/gvisor/pkg/test/testutil"
|
||||
"gvisor.dev/gvisor/runsc/boot"
|
||||
|
||||
Reference in New Issue
Block a user