gVisor: Read syscall seccheck enablement as part of per-syscall flags.

Prior to this CL, each of the 4 syscall points (raw enter,
enter, raw exit, exit) each resulted in 1 atomic read, on top of the existing
atomic read to read non-seccheck-related per-syscall flags.

This CL propagates seccheck's syscall-related points to the existing
per-syscall flags bitfield, such that this data can be loaded in a single
atomic read per syscall, rather than 5.

This shaves off a few precious nanoseconds (-3%) from the hot syscall path.

PiperOrigin-RevId: 463190096
This commit is contained in:
Etienne Perot
2022-07-25 15:24:56 -07:00
committed by gVisor bot
parent d542d45a28
commit a96af5dd61
4 changed files with 111 additions and 14 deletions
+56 -4
View File
@@ -113,6 +113,18 @@ const (
// ExternalAfterEnable enables the external hook after syscall execution.
ExternalAfterEnable
// SecCheckEnter represents a schematized/enter syscall seccheck event.
SecCheckEnter
// SecCheckExit represents a schematized/exit syscall seccheck event.
SecCheckExit
// SecCheckRawEnter represents raw/enter syscall seccheck event.
SecCheckRawEnter
// SecCheckRawExit represents raw/exit syscall seccheck event.
SecCheckRawExit
)
// StraceEnableBits combines both strace log and event flags.
@@ -142,7 +154,48 @@ type SyscallFlagsTable struct {
// max is the largest syscall number in table.
func (e *SyscallFlagsTable) init(table map[uintptr]Syscall) {
for num := range table {
e.enable[num] = atomicbitops.FromUint32(syscallPresent)
enableFlags := uint32(syscallPresent)
e.enable[num] = atomicbitops.FromUint32(enableFlags)
}
seccheck.Global.AddSyscallFlagListener(e)
e.UpdateSecCheck(&seccheck.Global)
}
// UpdateSecCheck implements seccheck.SyscallFlagListener.
//
// It is called when per-syscall seccheck event enablement changes.
func (e *SyscallFlagsTable) UpdateSecCheck(state *seccheck.State) {
e.mu.Lock()
defer e.mu.Unlock()
for sysno := uintptr(0); sysno < maxSyscallNum; sysno++ {
oldFlags := e.enable[sysno].Load()
if !bits.IsOn32(oldFlags, syscallPresent) {
continue
}
flags := oldFlags
if state.SyscallEnabled(seccheck.SyscallEnter, sysno) {
flags |= SecCheckEnter
} else {
flags &^= SecCheckEnter
}
if state.SyscallEnabled(seccheck.SyscallExit, sysno) {
flags |= SecCheckExit
} else {
flags &^= SecCheckExit
}
if state.SyscallEnabled(seccheck.SyscallRawEnter, sysno) {
flags |= SecCheckRawEnter
} else {
flags &^= SecCheckRawEnter
}
if state.SyscallEnabled(seccheck.SyscallRawExit, sysno) {
flags |= SecCheckRawExit
} else {
flags &^= SecCheckRawExit
}
if flags != oldFlags {
e.enable[sysno].Store(flags)
}
}
}
@@ -151,13 +204,12 @@ func (e *SyscallFlagsTable) Word(sysno uintptr) uint32 {
if sysno <= maxSyscallNum {
return e.enable[sysno].Load()
}
return e.missingEnable.Load()
}
// Enable sets enable bit bit for all syscalls based on s.
// Enable sets enable bit `bit` for all syscalls based on s.
//
// Syscalls missing from s are disabled.
// Syscalls missing from `s` are disabled.
//
// Syscalls missing from the initial table passed to Init cannot be added as
// individual syscalls. If present in s they will be ignored.
+6 -6
View File
@@ -90,7 +90,7 @@ 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) {
if bits.IsAnyOn32(fe, SecCheckRawEnter) {
info := pb.Syscall{
Sysno: uint64(sysno),
Arg1: args[0].Uint64(),
@@ -109,7 +109,7 @@ func (t *Task) executeSyscall(sysno uintptr, args arch.SyscallArguments) (rval u
return c.RawSyscall(t, fields, &info)
})
}
if seccheck.Global.SyscallEnabled(seccheck.SyscallEnter, sysno) {
if bits.IsAnyOn32(fe, SecCheckEnter) {
fields := seccheck.Global.GetFieldSet(seccheck.GetPointForSyscall(seccheck.SyscallEnter, sysno))
var ctxData *pb.ContextData
if !fields.Context.Empty() {
@@ -120,7 +120,7 @@ func (t *Task) executeSyscall(sysno uintptr, args arch.SyscallArguments) (rval u
Sysno: sysno,
Args: args,
}
cb := t.SyscallTable().LookupSyscallToProto(sysno)
cb := s.LookupSyscallToProto(sysno)
msg, msgType := cb(t, fields, ctxData, info)
seccheck.Global.SentToSinks(func(c seccheck.Sink) error {
return c.Syscall(t, fields, ctxData, msgType, msg)
@@ -158,7 +158,7 @@ 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) {
if bits.IsAnyOn32(fe, SecCheckRawExit) {
info := pb.Syscall{
Sysno: uint64(sysno),
Arg1: args[0].Uint64(),
@@ -181,7 +181,7 @@ func (t *Task) executeSyscall(sysno uintptr, args arch.SyscallArguments) (rval u
return c.RawSyscall(t, fields, &info)
})
}
if seccheck.Global.SyscallEnabled(seccheck.SyscallExit, sysno) {
if bits.IsAnyOn32(fe, SecCheckExit) {
fields := seccheck.Global.GetFieldSet(seccheck.GetPointForSyscall(seccheck.SyscallExit, sysno))
var ctxData *pb.ContextData
if !fields.Context.Empty() {
@@ -195,7 +195,7 @@ func (t *Task) executeSyscall(sysno uintptr, args arch.SyscallArguments) (rval u
Rval: rval,
Errno: ExtractErrno(err, int(sysno)),
}
cb := t.SyscallTable().LookupSyscallToProto(sysno)
cb := s.LookupSyscallToProto(sysno)
msg, msgType := cb(t, fields, ctxData, info)
seccheck.Global.SentToSinks(func(c seccheck.Sink) error {
return c.Syscall(t, fields, ctxData, msgType, msg)
+38 -4
View File
@@ -30,7 +30,8 @@ type Point uint
// PointX represents the checkpoint X.
const (
totalPoints = int(pointLengthBeforeSyscalls) + syscallPoints
numPointBitmaskUint32s = (totalPoints-1)/32 + 1
numPointsPerUint32 = 32
numPointBitmaskUint32s = (totalPoints-1)/numPointsPerUint32 + 1
)
// FieldSet contains all optional fields to be collected by a given Point.
@@ -209,6 +210,12 @@ type State struct {
// Mutation of sinks is serialized by registrationMu.
sinks []Sink
// syscallFlagListeners is the set of registered SyscallFlagListeners.
//
// They are notified when the enablement of a syscall point changes.
// Mutation of syscallFlagListeners is serialized by registrationMu.
syscallFlagListeners []SyscallFlagListener
pointFields map[Point]FieldSet
}
@@ -223,20 +230,38 @@ func (s *State) AppendSink(c Sink, reqs []PointReq) {
if s.pointFields == nil {
s.pointFields = make(map[Point]FieldSet)
}
updateSyscalls := false
for _, req := range reqs {
word, bit := req.Pt/32, req.Pt%32
word, bit := req.Pt/numPointsPerUint32, req.Pt%numPointsPerUint32
s.enabledPoints[word].Store(s.enabledPoints[word].RacyLoad() | (uint32(1) << bit))
if req.Pt >= pointLengthBeforeSyscalls {
updateSyscalls = true
}
s.pointFields[req.Pt] = req.Fields
}
if updateSyscalls {
for _, listener := range s.syscallFlagListeners {
listener.UpdateSecCheck(s)
}
}
}
func (s *State) clearSink() {
s.registrationMu.Lock()
defer s.registrationMu.Unlock()
updateSyscalls := false
for i := range s.enabledPoints {
s.enabledPoints[i].Store(0)
// We use i+1 here because we want to check the last bit that may have been changed within i.
if Point((i+1)*numPointsPerUint32) >= pointLengthBeforeSyscalls {
updateSyscalls = true
}
}
if updateSyscalls {
for _, listener := range s.syscallFlagListeners {
listener.UpdateSecCheck(s)
}
}
s.pointFields = nil
@@ -249,9 +274,18 @@ func (s *State) clearSink() {
}
}
// AddSyscallFlagListener adds a listener to the State.
//
// The listener will be notified whenever syscall point enablement changes.
func (s *State) AddSyscallFlagListener(listener SyscallFlagListener) {
s.registrationMu.Lock()
defer s.registrationMu.Unlock()
s.syscallFlagListeners = append(s.syscallFlagListeners, listener)
}
// Enabled returns true if any Sink is registered for the given checkpoint.
func (s *State) Enabled(p Point) bool {
word, bit := p/32, p%32
word, bit := p/numPointsPerUint32, p%numPointsPerUint32
if int(word) >= len(s.enabledPoints) {
return false
}
+11
View File
@@ -35,6 +35,17 @@ const (
syscallTypesCount
)
// SyscallFlagListener is an interface that is notified when syscall point enablement changes.
//
// It is used to notify the kernel's syscall table about syscall points, without introducing a
// direct dependency on it.
type SyscallFlagListener interface {
// UpdateSecCheck is called each time the system call point enablement may have changed.
// This is called with seccheck.State.mu held, so it is expected to be fast and not re-entrant
// with seccheck.State functions that attempt to re-lock it.
UpdateSecCheck(state *State)
}
const (
// Copied from kernel.maxSyscallNum to avoid reverse dependency.
syscallsMax = 2000