github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sentry/seccheck/sinks/remote/remote.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 remote defines a seccheck.Sink that serializes points to a remote
    16  // process. Points are serialized using the protobuf format, asynchronously.
    17  package remote
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"io"
    23  	"os"
    24  	"time"
    25  
    26  	"golang.org/x/sys/unix"
    27  	"google.golang.org/protobuf/proto"
    28  	"github.com/nicocha30/gvisor-ligolo/pkg/atomicbitops"
    29  	"github.com/nicocha30/gvisor-ligolo/pkg/cleanup"
    30  	"github.com/nicocha30/gvisor-ligolo/pkg/context"
    31  	"github.com/nicocha30/gvisor-ligolo/pkg/fd"
    32  	"github.com/nicocha30/gvisor-ligolo/pkg/log"
    33  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/seccheck"
    34  	pb "github.com/nicocha30/gvisor-ligolo/pkg/sentry/seccheck/points/points_go_proto"
    35  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/seccheck/sinks/remote/wire"
    36  )
    37  
    38  const name = "remote"
    39  
    40  func init() {
    41  	seccheck.RegisterSink(seccheck.SinkDesc{
    42  		Name:  name,
    43  		Setup: setupSink,
    44  		New:   new,
    45  	})
    46  }
    47  
    48  // remote sends a serialized point to a remote process asynchronously over a
    49  // SOCK_SEQPACKET Unix-domain socket. Each message corresponds to a single
    50  // serialized point proto, preceded by a standard header. If the point cannot
    51  // be sent, e.g. buffer full, the point is dropped on the floor to avoid
    52  // delaying/hanging indefinitely the application.
    53  type remote struct {
    54  	endpoint *fd.FD
    55  
    56  	droppedCount atomicbitops.Uint32
    57  
    58  	retries        int
    59  	initialBackoff time.Duration
    60  	maxBackoff     time.Duration
    61  }
    62  
    63  var _ seccheck.Sink = (*remote)(nil)
    64  
    65  // setupSink starts the connection to the remote process and returns a file that
    66  // can be used to communicate with it. The caller is responsible to close to
    67  // file.
    68  func setupSink(config map[string]any) (*os.File, error) {
    69  	addrOpaque, ok := config["endpoint"]
    70  	if !ok {
    71  		return nil, fmt.Errorf("endpoint not present in configuration")
    72  	}
    73  	addr, ok := addrOpaque.(string)
    74  	if !ok {
    75  		return nil, fmt.Errorf("endpoint %q is not a string", addrOpaque)
    76  	}
    77  	return setup(addr)
    78  }
    79  
    80  func setup(path string) (*os.File, error) {
    81  	log.Debugf("Remote sink connecting to %q", path)
    82  	socket, err := unix.Socket(unix.AF_UNIX, unix.SOCK_SEQPACKET, 0)
    83  	if err != nil {
    84  		return nil, fmt.Errorf("socket(AF_UNIX, SOCK_SEQPACKET, 0): %w", err)
    85  	}
    86  	f := os.NewFile(uintptr(socket), path)
    87  	cu := cleanup.Make(func() {
    88  		_ = f.Close()
    89  	})
    90  	defer cu.Clean()
    91  
    92  	addr := unix.SockaddrUnix{Name: path}
    93  	if err := unix.Connect(int(f.Fd()), &addr); err != nil {
    94  		return nil, fmt.Errorf("connect(%q): %w", path, err)
    95  	}
    96  
    97  	// Perform handshake. See common.proto for details about the protocol.
    98  	hsOut := pb.Handshake{Version: wire.CurrentVersion}
    99  	out, err := proto.Marshal(&hsOut)
   100  	if err != nil {
   101  		return nil, fmt.Errorf("marshalling handshake message: %w", err)
   102  	}
   103  	if _, err := f.Write(out); err != nil {
   104  		return nil, fmt.Errorf("sending handshake message: %w", err)
   105  	}
   106  
   107  	in := make([]byte, 10240)
   108  	read, err := f.Read(in)
   109  	if err != nil && !errors.Is(err, io.EOF) {
   110  		return nil, fmt.Errorf("reading handshake message: %w", err)
   111  	}
   112  	// Protect against the handshake becoming larger than the buffer allocated
   113  	// for it.
   114  	if read == len(in) {
   115  		return nil, fmt.Errorf("handshake message too big")
   116  	}
   117  	hsIn := pb.Handshake{}
   118  	if err := proto.Unmarshal(in[:read], &hsIn); err != nil {
   119  		return nil, fmt.Errorf("unmarshalling handshake message: %w", err)
   120  	}
   121  
   122  	// Check that remote version can be supported.
   123  	const minSupportedVersion = 1
   124  	if hsIn.Version < minSupportedVersion {
   125  		return nil, fmt.Errorf("remote version (%d) is smaller than minimum supported (%d)", hsIn.Version, minSupportedVersion)
   126  	}
   127  
   128  	if err := unix.SetNonblock(int(f.Fd()), true); err != nil {
   129  		return nil, err
   130  	}
   131  
   132  	cu.Release()
   133  	return f, nil
   134  }
   135  
   136  func parseDuration(config map[string]any, name string) (bool, time.Duration, error) {
   137  	opaque, ok := config[name]
   138  	if !ok {
   139  		return false, 0, nil
   140  	}
   141  	duration, ok := opaque.(string)
   142  	if !ok {
   143  		return false, 0, fmt.Errorf("%s %v is not an string", name, opaque)
   144  	}
   145  	rv, err := time.ParseDuration(duration)
   146  	if err != nil {
   147  		return false, 0, err
   148  	}
   149  	return true, rv, nil
   150  }
   151  
   152  // new creates a new Remote sink.
   153  func new(config map[string]any, endpoint *fd.FD) (seccheck.Sink, error) {
   154  	if endpoint == nil {
   155  		return nil, fmt.Errorf("remote sink requires an endpoint")
   156  	}
   157  	r := &remote{
   158  		endpoint:       endpoint,
   159  		initialBackoff: 25 * time.Microsecond,
   160  		maxBackoff:     10 * time.Millisecond,
   161  	}
   162  	if retriesOpaque, ok := config["retries"]; ok {
   163  		retries, ok := retriesOpaque.(float64)
   164  		if !ok {
   165  			return nil, fmt.Errorf("retries %q is not an int", retriesOpaque)
   166  		}
   167  		r.retries = int(retries)
   168  		if float64(r.retries) != retries {
   169  			return nil, fmt.Errorf("retries %q is not an int", retriesOpaque)
   170  		}
   171  	}
   172  	if ok, backoff, err := parseDuration(config, "backoff"); err != nil {
   173  		return nil, err
   174  	} else if ok {
   175  		r.initialBackoff = backoff
   176  	}
   177  	if ok, backoff, err := parseDuration(config, "backoff_max"); err != nil {
   178  		return nil, err
   179  	} else if ok {
   180  		r.maxBackoff = backoff
   181  	}
   182  	if r.initialBackoff > r.maxBackoff {
   183  		return nil, fmt.Errorf("initial backoff (%v) cannot be larger than max backoff (%v)", r.initialBackoff, r.maxBackoff)
   184  	}
   185  
   186  	log.Debugf("Remote sink created, endpoint FD: %d, %+v", r.endpoint.FD(), r)
   187  	return r, nil
   188  }
   189  
   190  func (*remote) Name() string {
   191  	return name
   192  }
   193  
   194  func (r *remote) Status() seccheck.SinkStatus {
   195  	return seccheck.SinkStatus{
   196  		DroppedCount: uint64(r.droppedCount.Load()),
   197  	}
   198  }
   199  
   200  // Stop implements seccheck.Sink.
   201  func (r *remote) Stop() {
   202  	if r.endpoint != nil {
   203  		// It's possible to race with Point firing, but in the worst case they will
   204  		// simply fail to be delivered.
   205  		r.endpoint.Close()
   206  	}
   207  }
   208  
   209  func (r *remote) write(msg proto.Message, msgType pb.MessageType) {
   210  	out, err := proto.Marshal(msg)
   211  	if err != nil {
   212  		log.Debugf("Marshal(%+v): %v", msg, err)
   213  		return
   214  	}
   215  	hdr := wire.Header{
   216  		HeaderSize:   uint16(wire.HeaderStructSize),
   217  		DroppedCount: r.droppedCount.Load(),
   218  		MessageType:  uint16(msgType),
   219  	}
   220  	var hdrOut [wire.HeaderStructSize]byte
   221  	hdr.MarshalUnsafe(hdrOut[:])
   222  
   223  	backoff := r.initialBackoff
   224  	for i := 0; ; i++ {
   225  		_, err := unix.Writev(r.endpoint.FD(), [][]byte{hdrOut[:], out})
   226  		if err == nil {
   227  			// Write succeeded, we're done!
   228  			return
   229  		}
   230  		if !errors.Is(err, unix.EAGAIN) || i >= r.retries {
   231  			log.Debugf("Write failed, dropping point: %v", err)
   232  			r.droppedCount.Add(1)
   233  			return
   234  		}
   235  		log.Debugf("Write failed, retrying (%d/%d) in %v: %v", i+1, r.retries, backoff, err)
   236  		time.Sleep(backoff)
   237  		backoff *= 2
   238  		if r.maxBackoff > 0 && backoff > r.maxBackoff {
   239  			backoff = r.maxBackoff
   240  		}
   241  	}
   242  }
   243  
   244  // Clone implements seccheck.Sink.
   245  func (r *remote) Clone(_ context.Context, _ seccheck.FieldSet, info *pb.CloneInfo) error {
   246  	r.write(info, pb.MessageType_MESSAGE_SENTRY_CLONE)
   247  	return nil
   248  }
   249  
   250  // Execve implements seccheck.Sink.
   251  func (r *remote) Execve(_ context.Context, _ seccheck.FieldSet, info *pb.ExecveInfo) error {
   252  	r.write(info, pb.MessageType_MESSAGE_SENTRY_EXEC)
   253  	return nil
   254  }
   255  
   256  // ExitNotifyParent implements seccheck.Sink.
   257  func (r *remote) ExitNotifyParent(_ context.Context, _ seccheck.FieldSet, info *pb.ExitNotifyParentInfo) error {
   258  	r.write(info, pb.MessageType_MESSAGE_SENTRY_EXIT_NOTIFY_PARENT)
   259  	return nil
   260  }
   261  
   262  // TaskExit implements seccheck.Sink.
   263  func (r *remote) TaskExit(_ context.Context, _ seccheck.FieldSet, info *pb.TaskExit) error {
   264  	r.write(info, pb.MessageType_MESSAGE_SENTRY_TASK_EXIT)
   265  	return nil
   266  }
   267  
   268  // ContainerStart implements seccheck.Sink.
   269  func (r *remote) ContainerStart(_ context.Context, _ seccheck.FieldSet, info *pb.Start) error {
   270  	r.write(info, pb.MessageType_MESSAGE_CONTAINER_START)
   271  	return nil
   272  }
   273  
   274  // RawSyscall implements seccheck.Sink.
   275  func (r *remote) RawSyscall(_ context.Context, _ seccheck.FieldSet, info *pb.Syscall) error {
   276  	r.write(info, pb.MessageType_MESSAGE_SYSCALL_RAW)
   277  	return nil
   278  }
   279  
   280  // Syscall implements seccheck.Sink.
   281  func (r *remote) Syscall(ctx context.Context, fields seccheck.FieldSet, ctxData *pb.ContextData, msgType pb.MessageType, msg proto.Message) error {
   282  	r.write(msg, msgType)
   283  	return nil
   284  }