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 }