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:
Fabricio Voznika
2022-04-29 14:49:40 -07:00
committed by gVisor bot
parent f9afde9b88
commit 575d76def2
10 changed files with 272 additions and 23 deletions
+1
View File
@@ -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",
],
)
+40 -1
View File
@@ -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
}
+84
View File
@@ -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
}
+1
View File
@@ -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
}
+25
View File
@@ -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.
+1
View File
@@ -10,5 +10,6 @@ proto_library(
"common.proto",
"container.proto",
"sentry.proto",
"syscall.proto",
],
)
+36
View File
@@ -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;
}
+20 -22
View File
@@ -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
}
+52
View File
@@ -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))
}