mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
242 lines
6.1 KiB
Go
242 lines
6.1 KiB
Go
// 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 kernel
|
|
|
|
import (
|
|
"testing"
|
|
_ "unsafe"
|
|
|
|
"google.golang.org/protobuf/proto"
|
|
"gvisor.dev/gvisor/pkg/sentry/arch"
|
|
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
|
"gvisor.dev/gvisor/pkg/sentry/seccheck"
|
|
|
|
pointspb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto"
|
|
)
|
|
|
|
type myNullSink struct {
|
|
seccheck.SinkDefaults
|
|
}
|
|
|
|
func (myNullSink) Name() string { return "null" }
|
|
|
|
// BenchmarkExecuteSyscall measures the internal overhead of seccheck data
|
|
// collection during syscall execution.
|
|
//
|
|
// Unlike end-to-end benchmarks, this benchmark runs the executeSyscall loop with a dummy task and
|
|
// a null sink. This isolates the CPU and memory allocation costs of payload generation
|
|
// in LoadSeccheckData (e.g., CWD, Time, Credentials) from all IPC and context switch noise.
|
|
//
|
|
// To run the benchmark:
|
|
//
|
|
// make test TARGETS="//pkg/sentry/kernel:kernel_test" \
|
|
// OPTIONS="--test_arg=-test.bench=BenchmarkExecuteSyscall --test_arg=-test.benchmem --test_output=all --nocache_test_results"
|
|
func BenchmarkExecuteSyscall(b *testing.B) {
|
|
testCases := []struct {
|
|
name string
|
|
fm func() seccheck.FieldMask
|
|
}{
|
|
{
|
|
name: "Empty",
|
|
fm: func() seccheck.FieldMask { return seccheck.FieldMask{} },
|
|
},
|
|
{
|
|
// Tests a trivial sub-nanosecond struct property read that doesn't allocate memory.
|
|
name: "ThreadStartTime",
|
|
fm: func() seccheck.FieldMask {
|
|
fm := seccheck.FieldMask{}
|
|
fm.Add(seccheck.FieldCtxtThreadStartTime)
|
|
return fm
|
|
},
|
|
},
|
|
{
|
|
// Tests the interface overhead of reaching into timekeeper and invoking clock functions.
|
|
name: "Time",
|
|
fm: func() seccheck.FieldMask {
|
|
fm := seccheck.FieldMask{}
|
|
fm.Add(seccheck.FieldCtxtTime)
|
|
return fm
|
|
},
|
|
},
|
|
{
|
|
// Tests locking overhead (task.k.tasks.mu.RLock()) and sets the groundwork for tracking the most
|
|
// expensive path in gVisor: VFS traversal and string-building.
|
|
name: "Cwd",
|
|
fm: func() seccheck.FieldMask {
|
|
fm := seccheck.FieldMask{}
|
|
fm.Add(seccheck.FieldCtxtCwd)
|
|
return fm
|
|
},
|
|
},
|
|
{
|
|
// Tests protobuf nesting. Calling task.Credentials().LoadSeccheckData(...)
|
|
// dynamically builds out nested structs (euid, suid, fsuid, groups, etc.)
|
|
// and performs heavier allocations.
|
|
name: "Credentials",
|
|
fm: func() seccheck.FieldMask {
|
|
fm := seccheck.FieldMask{}
|
|
fm.Add(seccheck.FieldCtxtCredentials)
|
|
return fm
|
|
},
|
|
},
|
|
{
|
|
name: "ThreadID",
|
|
fm: func() seccheck.FieldMask {
|
|
fm := seccheck.FieldMask{}
|
|
fm.Add(seccheck.FieldCtxtThreadID)
|
|
return fm
|
|
},
|
|
},
|
|
{
|
|
name: "ThreadGroupID",
|
|
fm: func() seccheck.FieldMask {
|
|
fm := seccheck.FieldMask{}
|
|
fm.Add(seccheck.FieldCtxtThreadGroupID)
|
|
return fm
|
|
},
|
|
},
|
|
{
|
|
name: "ThreadGroupStartTime",
|
|
fm: func() seccheck.FieldMask {
|
|
fm := seccheck.FieldMask{}
|
|
fm.Add(seccheck.FieldCtxtThreadGroupStartTime)
|
|
return fm
|
|
},
|
|
},
|
|
{
|
|
name: "ContainerID",
|
|
fm: func() seccheck.FieldMask {
|
|
fm := seccheck.FieldMask{}
|
|
fm.Add(seccheck.FieldCtxtContainerID)
|
|
return fm
|
|
},
|
|
},
|
|
{
|
|
name: "ProcessName",
|
|
fm: func() seccheck.FieldMask {
|
|
fm := seccheck.FieldMask{}
|
|
fm.Add(seccheck.FieldCtxtProcessName)
|
|
return fm
|
|
},
|
|
},
|
|
{
|
|
name: "ParentThreadGroupID",
|
|
fm: func() seccheck.FieldMask {
|
|
fm := seccheck.FieldMask{}
|
|
fm.Add(seccheck.FieldCtxtParentThreadGroupID)
|
|
return fm
|
|
},
|
|
},
|
|
{
|
|
name: "IsExecSession",
|
|
fm: func() seccheck.FieldMask {
|
|
fm := seccheck.FieldMask{}
|
|
fm.Add(seccheck.FieldCtxtIsExecSession)
|
|
return fm
|
|
},
|
|
},
|
|
{
|
|
name: "All",
|
|
fm: func() seccheck.FieldMask {
|
|
fm := seccheck.FieldMask{}
|
|
fm.Add(seccheck.FieldCtxtTime)
|
|
fm.Add(seccheck.FieldCtxtThreadID)
|
|
fm.Add(seccheck.FieldCtxtThreadStartTime)
|
|
fm.Add(seccheck.FieldCtxtThreadGroupID)
|
|
fm.Add(seccheck.FieldCtxtThreadGroupStartTime)
|
|
fm.Add(seccheck.FieldCtxtContainerID)
|
|
fm.Add(seccheck.FieldCtxtCredentials)
|
|
fm.Add(seccheck.FieldCtxtCwd)
|
|
fm.Add(seccheck.FieldCtxtProcessName)
|
|
fm.Add(seccheck.FieldCtxtParentThreadGroupID)
|
|
fm.Add(seccheck.FieldCtxtIsExecSession)
|
|
return fm
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
b.Run(tc.name, func(b *testing.B) {
|
|
seccheck.Global = seccheck.State{}
|
|
seccheck.Initialize()
|
|
|
|
pt := seccheck.GetPointForSyscall(seccheck.SyscallEnter, 0)
|
|
reqs := []seccheck.PointReq{
|
|
{
|
|
Pt: pt,
|
|
Fields: seccheck.FieldSet{
|
|
Context: tc.fm(),
|
|
},
|
|
},
|
|
}
|
|
seccheck.Global.AppendSink(myNullSink{}, reqs)
|
|
|
|
_, params := stateTestClocklessTimekeeper(b)
|
|
tk := NewTimekeeper()
|
|
tk.SetClocks(&mockClocks{}, params)
|
|
|
|
k := &Kernel{
|
|
timekeeper: tk,
|
|
tasks: &TaskSet{},
|
|
}
|
|
|
|
creds := auth.NewRootCredentials(auth.NewRootUserNamespace())
|
|
|
|
task := &Task{
|
|
k: k,
|
|
}
|
|
|
|
st := &SyscallTable{
|
|
Missing: func(task *Task, sysno uintptr, args arch.SyscallArguments) (uintptr, error) {
|
|
return 0, nil
|
|
},
|
|
}
|
|
st.pointCallbacks[0] = func(task *Task, fields seccheck.FieldSet, ctxData *pointspb.ContextData, info SyscallInfo) (proto.Message, pointspb.MessageType) {
|
|
return nil, pointspb.MessageType(0)
|
|
}
|
|
|
|
tg := &ThreadGroup{
|
|
threadGroupNode: threadGroupNode{
|
|
leader: task,
|
|
},
|
|
}
|
|
task.tg = tg
|
|
|
|
pidns := &PIDNamespace{
|
|
tids: map[*Task]ThreadID{task: 1},
|
|
tgids: map[*ThreadGroup]ThreadID{tg: 1},
|
|
}
|
|
k.tasks.Root = pidns
|
|
|
|
ti := TaskImage{
|
|
st: st,
|
|
}
|
|
task.image = ti
|
|
task.creds.Store(creds)
|
|
|
|
cwd := &FSContext{}
|
|
task.fsContext.Store(cwd)
|
|
|
|
task.SyscallTable().FeatureEnable.EnableAll(SecCheckEnter)
|
|
|
|
var args arch.SyscallArguments
|
|
|
|
for b.Loop() {
|
|
task.executeSyscall(0, args)
|
|
}
|
|
})
|
|
}
|
|
}
|