1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
|
// Copyright 2021 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 defines a structure for dynamically-configured security
// checks in the sentry.
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"
"gvisor.dev/gvisor/pkg/sync"
)
// A Point represents a checkpoint, a point at which a security check occurs.
type Point uint
// PointX represents the checkpoint X.
const (
totalPoints = int(pointLengthBeforeSyscalls) + syscallPoints
numPointsPerUint32 = 32
numPointBitmaskUint32s = (totalPoints-1)/numPointsPerUint32 + 1
)
// FieldSet contains all optional fields to be collected by a given Point.
type FieldSet struct {
// Local indicates which optional fields from the Point that needs to be
// collected, e.g. resolving path from an FD, or collecting a large field.
Local FieldMask
// Context indicates which optional fields from the Context that needs to be
// collected, e.g. PID, credentials, current time.
Context FieldMask
}
// Field represents the index of a single optional field to be collect for a
// Point.
type Field uint
// FieldMask is a bitmask with a single bit representing an optional field to be
// collected. The meaning of each bit varies per point. The mask is currently
// limited to 64 fields. If more are needed, FieldMask can be expanded to
// support additional fields.
type FieldMask struct {
mask uint64
}
// MakeFieldMask creates a FieldMask from a set of Fields.
func MakeFieldMask(fields ...Field) FieldMask {
var m FieldMask
for _, field := range fields {
m.Add(field)
}
return m
}
// Contains returns true if the mask contains the Field.
func (fm *FieldMask) Contains(field Field) bool {
return fm.mask&(1<<field) != 0
}
// Add adds a Field to the mask.
func (fm *FieldMask) Add(field Field) {
fm.mask |= 1 << field
}
// Remove removes a Field from the mask.
func (fm *FieldMask) Remove(field Field) {
fm.mask &^= 1 << field
}
// Empty returns true if no bits are set.
func (fm *FieldMask) Empty() bool {
return fm.mask == 0
}
// A Sink performs security checks at checkpoints.
//
// Each Sink method X is called at checkpoint X; if the method may return a
// non-nil error and does so, it causes the checked operation to fail
// immediately (without calling subsequent Sinks) and return the error. The
// info argument contains information relevant to the check. The mask argument
// indicates what fields in info are valid; the mask should usually be a
// superset of fields requested by the Sink's corresponding PointReq, but
// may be missing requested fields in some cases (e.g. if the Sink is
// registered concurrently with invocations of checkpoints).
type Sink interface {
// Name return the sink name.
Name() string
// Status returns the sink runtime status.
Status() SinkStatus
// Stop requests the sink to stop.
Stop()
Clone(ctx context.Context, fields FieldSet, info *pb.CloneInfo) error
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, pb.MessageType, proto.Message) error
RawSyscall(context.Context, FieldSet, *pb.Syscall) error
}
// SinkStatus represents stats about each Sink instance.
type SinkStatus struct {
// DroppedCount is the number of trace points dropped.
DroppedCount uint64
}
// SinkDefaults may be embedded by implementations of Sink to obtain
// no-op implementations of Sink methods that may be explicitly overridden.
type SinkDefaults struct{}
// Add functions missing in SinkDefaults to make it possible to check for the
// implementation below to catch missing functions more easily.
type sinkDefaultsImpl struct {
SinkDefaults
}
// Name implements Sink.Name.
func (sinkDefaultsImpl) Name() string { return "" }
var _ Sink = (*sinkDefaultsImpl)(nil)
// Status implements Sink.Status.
func (SinkDefaults) Status() SinkStatus {
return SinkStatus{}
}
// Stop implements Sink.Stop.
func (SinkDefaults) Stop() {}
// Clone implements Sink.Clone.
func (SinkDefaults) Clone(context.Context, FieldSet, *pb.CloneInfo) error {
return nil
}
// Execve implements Sink.Execve.
func (SinkDefaults) Execve(context.Context, FieldSet, *pb.ExecveInfo) error {
return nil
}
// ExitNotifyParent implements Sink.ExitNotifyParent.
func (SinkDefaults) ExitNotifyParent(context.Context, FieldSet, *pb.ExitNotifyParentInfo) error {
return nil
}
// ContainerStart implements Sink.ContainerStart.
func (SinkDefaults) ContainerStart(context.Context, FieldSet, *pb.Start) error {
return nil
}
// TaskExit implements Sink.TaskExit.
func (SinkDefaults) TaskExit(context.Context, FieldSet, *pb.TaskExit) error {
return nil
}
// RawSyscall implements Sink.RawSyscall.
func (SinkDefaults) RawSyscall(context.Context, FieldSet, *pb.Syscall) error {
return nil
}
// Syscall implements Sink.Syscall.
func (SinkDefaults) Syscall(context.Context, FieldSet, *pb.ContextData, pb.MessageType, proto.Message) error {
return nil
}
// PointReq indicates what Point a corresponding Sink runs at, and what
// information it requires at those Points.
type PointReq struct {
Pt Point
Fields FieldSet
}
// Global is the method receiver of all seccheck functions.
var Global State
// State is the type of global, and is separated out for testing.
type State struct {
// registrationMu serializes all changes to the set of registered Sinks
// for all checkpoints.
registrationMu sync.RWMutex
// enabledPoints is a bitmask of checkpoints for which at least one Sink
// is registered.
//
// Mutation of enabledPoints is serialized by registrationMu.
enabledPoints [numPointBitmaskUint32s]atomicbitops.Uint32
// registrationSeq supports store-free atomic reads of registeredSinks.
registrationSeq sync.SeqCount
// sinks is the set of all registered Sinks in order of execution.
//
// sinks is accessed using instantiations of SeqAtomic functions.
// 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
}
// AppendSink registers the given Sink to execute at checkpoints. The
// Sink will execute after all previously-registered sinks, and only if
// those Sinks return a nil error.
func (s *State) AppendSink(c Sink, reqs []PointReq) {
s.registrationMu.Lock()
defer s.registrationMu.Unlock()
s.appendSinkLocked(c)
if s.pointFields == nil {
s.pointFields = make(map[Point]FieldSet)
}
updateSyscalls := false
for _, req := range reqs {
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
oldSinks := s.getSinks()
s.registrationSeq.BeginWrite()
s.sinks = nil
s.registrationSeq.EndWrite()
for _, sink := range oldSinks {
sink.Stop()
}
}
// 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/numPointsPerUint32, p%numPointsPerUint32
if int(word) >= len(s.enabledPoints) {
return false
}
return s.enabledPoints[word].Load()&(uint32(1)<<bit) != 0
}
func (s *State) getSinks() []Sink {
return SeqAtomicLoadSinkSlice(&s.registrationSeq, &s.sinks)
}
// Preconditions: s.registrationMu must be locked.
func (s *State) appendSinkLocked(c Sink) {
s.registrationSeq.BeginWrite()
s.sinks = append(s.sinks, c)
s.registrationSeq.EndWrite()
}
// SentToSinks iterates over all sinks and calls fn for each one of them.
func (s *State) SentToSinks(fn func(c Sink) error) error {
for _, c := range s.getSinks() {
if err := fn(c); err != nil {
return err
}
}
return nil
}
// GetFieldSet returns the FieldSet that has been configured for a given Point.
func (s *State) GetFieldSet(p Point) FieldSet {
s.registrationMu.RLock()
defer s.registrationMu.RUnlock()
return s.pointFields[p]
}
|