github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/pkg/sentry/seccheck/seccheck.go (about)

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