mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add support for syscall points
Each syscall provides 4 different points. There is a raw syscall point that contains the syscall number and all 6 arguments, nothing else. Some syscalls can provide a schematized version of the syscall by defining a function that converts the syscall into a proto representing the syscall. Each of these flavors have a point for enter and another for exit. In both cases, the exit event adds return value and errno (if any). Updates #4805 PiperOrigin-RevId: 445510907
This commit is contained in:
committed by
gVisor bot
parent
f9afde9b88
commit
575d76def2
@@ -291,6 +291,7 @@ go_library(
|
||||
"//pkg/tcpip/stack",
|
||||
"//pkg/usermem",
|
||||
"//pkg/waiter",
|
||||
"@org_golang_google_protobuf//proto:go_default_library",
|
||||
"@org_golang_x_sys//unix:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -17,11 +17,15 @@ package kernel
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
"gvisor.dev/gvisor/pkg/abi"
|
||||
"gvisor.dev/gvisor/pkg/atomicbitops"
|
||||
"gvisor.dev/gvisor/pkg/bits"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/hostarch"
|
||||
"gvisor.dev/gvisor/pkg/sentry/arch"
|
||||
"gvisor.dev/gvisor/pkg/sentry/seccheck"
|
||||
pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto"
|
||||
"gvisor.dev/gvisor/pkg/sync"
|
||||
)
|
||||
|
||||
@@ -31,7 +35,9 @@ const (
|
||||
// The types below create fast lookup slices for all syscalls. This maximum
|
||||
// serves as a sanity check that we don't allocate huge slices for a very large
|
||||
// syscall. This is checked during registration.
|
||||
// LINT.IfChange
|
||||
maxSyscallNum = 2000
|
||||
// LINT.ThenChange(../seccheck/syscall.go)
|
||||
)
|
||||
|
||||
// SyscallSupportLevel is a syscall support levels.
|
||||
@@ -77,6 +83,9 @@ type Syscall struct {
|
||||
Note string
|
||||
// URLs is set of URLs to any relevant bugs or issues.
|
||||
URLs []string
|
||||
// PointCallback is an optional callback that converts syscall arguments
|
||||
// to a proto that can be used with seccheck.Checker.
|
||||
PointCallback SyscallToProto
|
||||
}
|
||||
|
||||
// SyscallFn is a syscall implementation.
|
||||
@@ -242,6 +251,11 @@ type SyscallTable struct {
|
||||
// their numbers). It is used for fast look ups.
|
||||
lookup [maxSyscallNum + 1]SyscallFn
|
||||
|
||||
// pointCallbacks is a fixed-size array that holds SyscallToProto callbacks
|
||||
// (indexed by syscall numbers). It is used for fast lookups when
|
||||
// seccheck.Point is enabled for the syscall.
|
||||
pointCallbacks [maxSyscallNum + 1]SyscallToProto
|
||||
|
||||
// Emulate is a collection of instruction addresses to emulate. The
|
||||
// keys are addresses, and the values are system call numbers.
|
||||
Emulate map[hostarch.Addr]uintptr
|
||||
@@ -320,10 +334,13 @@ func (s *SyscallTable) Init() {
|
||||
s.Emulate = make(map[hostarch.Addr]uintptr)
|
||||
}
|
||||
|
||||
// Initialize the fast-lookup table.
|
||||
// Initialize the fast-lookup tables.
|
||||
for num, sc := range s.Table {
|
||||
s.lookup[num] = sc.Fn
|
||||
}
|
||||
for num, sc := range s.Table {
|
||||
s.pointCallbacks[num] = sc.PointCallback
|
||||
}
|
||||
|
||||
// Initialize all features.
|
||||
s.FeatureEnable.init(s.Table)
|
||||
@@ -369,3 +386,25 @@ func (s *SyscallTable) mapLookup(sysno uintptr) SyscallFn {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LookupSyscallToProto looks up the SyscallToProto callback for the given
|
||||
// syscall. It may return nil if none is registered.
|
||||
func (s *SyscallTable) LookupSyscallToProto(sysno uintptr) SyscallToProto {
|
||||
if sysno > maxSyscallNum {
|
||||
return nil
|
||||
}
|
||||
return s.pointCallbacks[sysno]
|
||||
}
|
||||
|
||||
// SyscallToProto is a callback function that converts generic syscall data to
|
||||
// schematized protobuf for the corresponding syscall.
|
||||
type SyscallToProto func(context.Context, seccheck.FieldSet, *pb.ContextData, SyscallInfo) proto.Message
|
||||
|
||||
// SyscallInfo provides generic information about the syscall.
|
||||
type SyscallInfo struct {
|
||||
Exit bool
|
||||
Sysno uintptr
|
||||
Args arch.SyscallArguments
|
||||
Rval uintptr
|
||||
Errno int
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/metric"
|
||||
"gvisor.dev/gvisor/pkg/sentry/arch"
|
||||
"gvisor.dev/gvisor/pkg/sentry/memmap"
|
||||
"gvisor.dev/gvisor/pkg/sentry/seccheck"
|
||||
pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto"
|
||||
)
|
||||
|
||||
// SyscallRestartBlock represents the restart block for a syscall restartable
|
||||
@@ -88,6 +90,44 @@ func (t *Task) executeSyscall(sysno uintptr, args arch.SyscallArguments) (rval u
|
||||
straceContext = s.Stracer.SyscallEnter(t, sysno, args, fe)
|
||||
}
|
||||
|
||||
if seccheck.Global.SyscallEnabled(seccheck.SyscallRawEnter, sysno) {
|
||||
info := pb.Syscall{
|
||||
Sysno: uint64(sysno),
|
||||
Arg1: args[0].Uint64(),
|
||||
Arg2: args[1].Uint64(),
|
||||
Arg3: args[2].Uint64(),
|
||||
Arg4: args[3].Uint64(),
|
||||
Arg5: args[4].Uint64(),
|
||||
Arg6: args[5].Uint64(),
|
||||
}
|
||||
fields := seccheck.Global.GetFieldSet(seccheck.GetPointForSyscall(seccheck.SyscallRawEnter, sysno))
|
||||
if !fields.Context.Empty() {
|
||||
info.ContextData = &pb.ContextData{}
|
||||
LoadSeccheckData(t, fields.Context, info.ContextData)
|
||||
}
|
||||
seccheck.Global.SendToCheckers(func(c seccheck.Checker) error {
|
||||
return c.RawSyscall(t, fields, &info)
|
||||
})
|
||||
}
|
||||
if seccheck.Global.SyscallEnabled(seccheck.SyscallEnter, sysno) {
|
||||
fields := seccheck.Global.GetFieldSet(seccheck.GetPointForSyscall(seccheck.SyscallEnter, sysno))
|
||||
var ctxData *pb.ContextData
|
||||
if !fields.Context.Empty() {
|
||||
ctxData = &pb.ContextData{}
|
||||
LoadSeccheckData(t, fields.Context, ctxData)
|
||||
}
|
||||
info := SyscallInfo{
|
||||
Sysno: sysno,
|
||||
Args: args,
|
||||
}
|
||||
// TODO(fvoznika): Make cb take a *Task instead of Context.
|
||||
cb := t.SyscallTable().LookupSyscallToProto(sysno)
|
||||
msg := cb(t, fields, ctxData, info)
|
||||
seccheck.Global.SendToCheckers(func(c seccheck.Checker) error {
|
||||
return c.Syscall(t, fields, ctxData, msg)
|
||||
})
|
||||
}
|
||||
|
||||
if bits.IsOn32(fe, ExternalBeforeEnable) && (s.ExternalFilterBefore == nil || s.ExternalFilterBefore(t, sysno, args)) {
|
||||
t.invokeExternal()
|
||||
// Ensure we check for stops, then invoke the syscall again.
|
||||
@@ -119,6 +159,50 @@ func (t *Task) executeSyscall(sysno uintptr, args arch.SyscallArguments) (rval u
|
||||
s.Stracer.SyscallExit(straceContext, t, sysno, rval, err)
|
||||
}
|
||||
|
||||
if seccheck.Global.SyscallEnabled(seccheck.SyscallRawExit, sysno) {
|
||||
info := pb.Syscall{
|
||||
Sysno: uint64(sysno),
|
||||
Arg1: args[0].Uint64(),
|
||||
Arg2: args[1].Uint64(),
|
||||
Arg3: args[2].Uint64(),
|
||||
Arg4: args[3].Uint64(),
|
||||
Arg5: args[4].Uint64(),
|
||||
Arg6: args[5].Uint64(),
|
||||
Exit: &pb.Exit{
|
||||
Result: int64(rval),
|
||||
Errorno: int64(ExtractErrno(err, int(sysno))),
|
||||
},
|
||||
}
|
||||
fields := seccheck.Global.GetFieldSet(seccheck.GetPointForSyscall(seccheck.SyscallRawEnter, sysno))
|
||||
if !fields.Context.Empty() {
|
||||
info.ContextData = &pb.ContextData{}
|
||||
LoadSeccheckData(t, fields.Context, info.ContextData)
|
||||
}
|
||||
seccheck.Global.SendToCheckers(func(c seccheck.Checker) error {
|
||||
return c.RawSyscall(t, fields, &info)
|
||||
})
|
||||
}
|
||||
if seccheck.Global.SyscallEnabled(seccheck.SyscallExit, sysno) {
|
||||
cb := t.SyscallTable().LookupSyscallToProto(sysno)
|
||||
fields := seccheck.Global.GetFieldSet(seccheck.GetPointForSyscall(seccheck.SyscallExit, sysno))
|
||||
var ctxData *pb.ContextData
|
||||
if !fields.Context.Empty() {
|
||||
ctxData = &pb.ContextData{}
|
||||
LoadSeccheckData(t, fields.Context, ctxData)
|
||||
}
|
||||
info := SyscallInfo{
|
||||
Exit: true,
|
||||
Sysno: sysno,
|
||||
Args: args,
|
||||
Rval: rval,
|
||||
Errno: ExtractErrno(err, int(sysno)),
|
||||
}
|
||||
msg := cb(t, fields, ctxData, info)
|
||||
seccheck.Global.SendToCheckers(func(c seccheck.Checker) error {
|
||||
return c.Syscall(t, fields, ctxData, msg)
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ go_library(
|
||||
"metadata.go",
|
||||
"seccheck.go",
|
||||
"seqatomic_checkerslice_unsafe.go",
|
||||
"syscall.go",
|
||||
],
|
||||
visibility = ["//:sandbox"],
|
||||
deps = [
|
||||
|
||||
@@ -185,3 +185,15 @@ func (r *Remote) ContainerStart(_ context.Context, _ seccheck.FieldSet, info *pb
|
||||
r.write(info)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RawSyscall implements seccheck.Checker.
|
||||
func (r *Remote) RawSyscall(_ context.Context, _ seccheck.FieldSet, info *pb.Syscall) error {
|
||||
r.write(info)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Syscall implements seccheck.Checker.
|
||||
func (r *Remote) Syscall(ctx context.Context, fields seccheck.FieldSet, ctxData *pb.ContextData, msg proto.Message) error {
|
||||
r.write(msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -21,6 +21,31 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/fd"
|
||||
)
|
||||
|
||||
// PointX represents the checkpoint X.
|
||||
const (
|
||||
PointCloneProcess Point = iota
|
||||
PointContainerStart
|
||||
PointExecve
|
||||
PointExitNotifyParent
|
||||
PointTaskExit
|
||||
|
||||
// Add new Points above this line.
|
||||
pointLengthBeforeSyscalls
|
||||
)
|
||||
|
||||
// FieldCtxtX represents a data field that comes from the Context.
|
||||
const (
|
||||
FieldCtxtContainerID Field = iota
|
||||
FieldCtxtCredentials
|
||||
FieldCtxtCwd
|
||||
FieldCtxtProcessName
|
||||
FieldCtxtThreadGroupID
|
||||
FieldCtxtThreadGroupStartTime
|
||||
FieldCtxtThreadID
|
||||
FieldCtxtThreadStartTime
|
||||
FieldCtxtTime
|
||||
)
|
||||
|
||||
const (
|
||||
// ContainerStartFieldEnv is an optional field to collect list of environment
|
||||
// variables set for the container start process.
|
||||
|
||||
@@ -10,5 +10,6 @@ proto_library(
|
||||
"common.proto",
|
||||
"container.proto",
|
||||
"sentry.proto",
|
||||
"syscall.proto",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// 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.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package gvisor.syscall;
|
||||
|
||||
import "pkg/sentry/seccheck/points/common.proto";
|
||||
|
||||
message Exit {
|
||||
int64 result = 1;
|
||||
int64 errorno = 2;
|
||||
}
|
||||
|
||||
message Syscall {
|
||||
gvisor.common.ContextData context_data = 1;
|
||||
Exit exit = 2;
|
||||
uint64 sysno = 4;
|
||||
uint64 arg1 = 5;
|
||||
uint64 arg2 = 6;
|
||||
uint64 arg3 = 7;
|
||||
uint64 arg4 = 8;
|
||||
uint64 arg5 = 9;
|
||||
uint64 arg6 = 10;
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
package seccheck
|
||||
|
||||
import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
"gvisor.dev/gvisor/pkg/atomicbitops"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto"
|
||||
@@ -28,28 +29,8 @@ type Point uint
|
||||
|
||||
// PointX represents the checkpoint X.
|
||||
const (
|
||||
PointCloneProcess Point = iota
|
||||
PointExecve
|
||||
PointExitNotifyParent
|
||||
PointContainerStart
|
||||
PointTaskExit
|
||||
// Add new Points above this line.
|
||||
pointLength
|
||||
|
||||
numPointBitmaskUint32s = (int(pointLength)-1)/32 + 1
|
||||
)
|
||||
|
||||
// FieldCtxtX represents a data field that comes from the Context.
|
||||
const (
|
||||
FieldCtxtTime Field = iota
|
||||
FieldCtxtThreadID
|
||||
FieldCtxtThreadStartTime
|
||||
FieldCtxtThreadGroupID
|
||||
FieldCtxtThreadGroupStartTime
|
||||
FieldCtxtContainerID
|
||||
FieldCtxtCredentials
|
||||
FieldCtxtCwd
|
||||
FieldCtxtProcessName
|
||||
totalPoints = int(pointLengthBeforeSyscalls) + syscallPoints
|
||||
numPointBitmaskUint32s = (totalPoints-1)/32 + 1
|
||||
)
|
||||
|
||||
// FieldSet contains all optional fields to be collected by a given Point.
|
||||
@@ -119,7 +100,11 @@ type Checker interface {
|
||||
Execve(ctx context.Context, fields FieldSet, info *pb.ExecveInfo) error
|
||||
ExitNotifyParent(ctx context.Context, fields FieldSet, info *pb.ExitNotifyParentInfo) error
|
||||
TaskExit(context.Context, FieldSet, *pb.TaskExit) error
|
||||
|
||||
ContainerStart(context.Context, FieldSet, *pb.Start) error
|
||||
|
||||
Syscall(context.Context, FieldSet, *pb.ContextData, proto.Message) error
|
||||
RawSyscall(context.Context, FieldSet, *pb.Syscall) error
|
||||
}
|
||||
|
||||
// CheckerDefaults may be embedded by implementations of Checker to obtain
|
||||
@@ -153,6 +138,16 @@ func (CheckerDefaults) TaskExit(context.Context, FieldSet, *pb.TaskExit) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RawSyscall implements Checker.RawSyscall.
|
||||
func (CheckerDefaults) RawSyscall(context.Context, FieldSet, *pb.Syscall) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Syscall implements Checker.Syscall.
|
||||
func (CheckerDefaults) Syscall(context.Context, FieldSet, *pb.ContextData, proto.Message) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PointReq indicates what Point a corresponding Checker runs at, and what
|
||||
// information it requires at those Points.
|
||||
type PointReq struct {
|
||||
@@ -209,6 +204,9 @@ func (s *State) AppendChecker(c Checker, reqs []PointReq) {
|
||||
// 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
|
||||
if int(word) >= len(s.enabledPoints) {
|
||||
return false
|
||||
}
|
||||
return s.enabledPoints[word].Load()&(uint32(1)<<bit) != 0
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// 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 seccheck
|
||||
|
||||
// SyscallType is an enum that denotes different types of syscall points. There
|
||||
// are 2 types of syscall point: fully-schematized and raw. Schematizes are
|
||||
// points that have syscall specific format, e.g. open => {path, flags, mode}.
|
||||
// Raw uses a generic schema that contains syscall number and 6 arguments. Each
|
||||
// of these type have a corresponding enter and exit points. Exit points include
|
||||
// return value and errno information.
|
||||
type SyscallType int
|
||||
|
||||
const (
|
||||
// SyscallEnter represents schematized/enter syscall.
|
||||
SyscallEnter SyscallType = iota
|
||||
// SyscallExit represents schematized/exit syscall.
|
||||
SyscallExit
|
||||
// SyscallRawEnter represents raw/enter syscall.
|
||||
SyscallRawEnter
|
||||
// SyscallRawExit represents raw/exit syscall.
|
||||
SyscallRawExit
|
||||
|
||||
syscallTypesCount
|
||||
)
|
||||
|
||||
const (
|
||||
// Copied from kernel.maxSyscallNum to avoid reverse dependency.
|
||||
syscallsMax = 2000
|
||||
syscallPoints = syscallsMax * int(syscallTypesCount)
|
||||
)
|
||||
|
||||
// GetPointForSyscall translates the syscall number to the corresponding Point.
|
||||
func GetPointForSyscall(typ SyscallType, sysno uintptr) Point {
|
||||
return Point(sysno)*Point(syscallTypesCount) + Point(typ) + pointLengthBeforeSyscalls
|
||||
}
|
||||
|
||||
// SyscallEnabled checks if the corresponding point for the syscall is enabled.
|
||||
func (s *State) SyscallEnabled(typ SyscallType, sysno uintptr) bool {
|
||||
return s.Enabled(GetPointForSyscall(typ, sysno))
|
||||
}
|
||||
Reference in New Issue
Block a user