diff --git a/pkg/sentry/kernel/syscalls.go b/pkg/sentry/kernel/syscalls.go index 2c7f2bae5..96696dd7a 100644 --- a/pkg/sentry/kernel/syscalls.go +++ b/pkg/sentry/kernel/syscalls.go @@ -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. diff --git a/pkg/sentry/kernel/task_syscall.go b/pkg/sentry/kernel/task_syscall.go index 10f89793c..8b875cce5 100644 --- a/pkg/sentry/kernel/task_syscall.go +++ b/pkg/sentry/kernel/task_syscall.go @@ -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) diff --git a/pkg/sentry/seccheck/seccheck.go b/pkg/sentry/seccheck/seccheck.go index e56300a6b..784ac3652 100644 --- a/pkg/sentry/seccheck/seccheck.go +++ b/pkg/sentry/seccheck/seccheck.go @@ -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 } diff --git a/pkg/sentry/seccheck/syscall.go b/pkg/sentry/seccheck/syscall.go index 4dea53546..cdfec0b55 100644 --- a/pkg/sentry/seccheck/syscall.go +++ b/pkg/sentry/seccheck/syscall.go @@ -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