File: seccheck.go

package info (click to toggle)
golang-gvisor-gvisor 0.0~20240729.0-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 21,276 kB
  • sloc: asm: 3,361; ansic: 1,197; cpp: 348; makefile: 92; python: 89; sh: 83
file content (321 lines) | stat: -rw-r--r-- 9,968 bytes parent folder | download | duplicates (4)
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]
}