mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
Reading a ZAP result writes every field its layout states, the file payload included, so files attached before decoding were overwritten with none. A frame whose header is not ZAP is refused at the magic rather than read as a length. Co-authored-by: Hanzo Dev <[email protected]>
1040 lines
31 KiB
Go
1040 lines
31 KiB
Go
// Copyright 2022 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 trace provides end-to-end integration tests for `runsc trace`.
|
|
package trace
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"golang.org/x/sys/unix"
|
|
"google.golang.org/protobuf/proto"
|
|
"gvisor.dev/gvisor/pkg/sentry/seccheck"
|
|
pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto"
|
|
"gvisor.dev/gvisor/pkg/sentry/seccheck/sinks/remote/test"
|
|
"gvisor.dev/gvisor/pkg/test/testutil"
|
|
"gvisor.dev/gvisor/test/trace/config"
|
|
)
|
|
|
|
var cutoffTime time.Time
|
|
|
|
type checkers struct {
|
|
checker func(test.Message) error
|
|
count int
|
|
}
|
|
|
|
// TestAll enables all trace points in the system with all optional and context
|
|
// fields enabled. Then it runs a workload that will trigger those points and
|
|
// run some basic validation over the points generated.
|
|
func TestAll(t *testing.T) {
|
|
server, err := test.NewServer()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
runsc, err := testutil.FindRunsc()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
builder := config.Builder{}
|
|
if err := builder.LoadAllPoints(runsc); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
builder.AddSink(seccheck.SinkConfig{
|
|
Name: "remote",
|
|
Config: json.RawMessage(fmt.Sprintf(`{"endpoint": %q}`, server.Endpoint)),
|
|
})
|
|
|
|
cfgFile, err := os.CreateTemp(testutil.TmpDir(), "config")
|
|
if err != nil {
|
|
t.Fatalf("error creating tmp file: %v", err)
|
|
}
|
|
defer cfgFile.Close()
|
|
if err := builder.WriteInitConfig(cfgFile); err != nil {
|
|
t.Fatalf("writing config file: %v", err)
|
|
}
|
|
|
|
workload, err := testutil.FindFile("test/trace/workload/workload")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// No trace point should have a time lesser than this.
|
|
cutoffTime = time.Now()
|
|
cmd := exec.Command(
|
|
runsc,
|
|
"--debug", "--alsologtostderr", // Debug logging for troubleshooting
|
|
"--rootless", "--network=none", "--TESTONLY-unsafe-nonroot", // Disable features that we don't care
|
|
"--pod-init-config", cfgFile.Name(),
|
|
"do", workload)
|
|
out, err := cmd.CombinedOutput()
|
|
t.Log(string(out))
|
|
if err != nil {
|
|
t.Fatalf("runsc do: %v", err)
|
|
}
|
|
|
|
// Wait until the sandbox disconnects to ensure all points were gathered.
|
|
server.WaitForNoClients()
|
|
|
|
matchers := matchPoints(t, server.GetPoints())
|
|
extraMatchers(t, server.GetPoints(), matchers)
|
|
validatePoints(t, server.GetPoints(), matchers)
|
|
}
|
|
|
|
func matchPoints(t *testing.T, msgs []test.Message) map[pb.MessageType]*checkers {
|
|
// Register functions that verify each available point.
|
|
matchers := map[pb.MessageType]*checkers{
|
|
pb.MessageType_MESSAGE_CONTAINER_START: {checker: checkContainerStart},
|
|
pb.MessageType_MESSAGE_SENTRY_CLONE: {checker: checkSentryClone},
|
|
pb.MessageType_MESSAGE_SENTRY_EXEC: {checker: checkSentryExec},
|
|
pb.MessageType_MESSAGE_SENTRY_EXIT_NOTIFY_PARENT: {checker: checkSentryExitNotifyParent},
|
|
pb.MessageType_MESSAGE_SENTRY_TASK_EXIT: {checker: checkSentryTaskExit},
|
|
pb.MessageType_MESSAGE_SENTRY_MMAP: {checker: checkSentryMmap},
|
|
pb.MessageType_MESSAGE_SYSCALL_CLOSE: {checker: checkSyscallClose},
|
|
pb.MessageType_MESSAGE_SYSCALL_CONNECT: {checker: checkSyscallConnect},
|
|
pb.MessageType_MESSAGE_SYSCALL_EXECVE: {checker: checkSyscallExecve},
|
|
pb.MessageType_MESSAGE_SYSCALL_OPEN: {checker: checkSyscallOpen},
|
|
pb.MessageType_MESSAGE_SYSCALL_RAW: {checker: checkSyscallRaw},
|
|
pb.MessageType_MESSAGE_SYSCALL_READ: {checker: checkSyscallRead},
|
|
pb.MessageType_MESSAGE_SYSCALL_SOCKET: {checker: checkSyscallSocket},
|
|
pb.MessageType_MESSAGE_SYSCALL_WRITE: {checker: checkSyscallWrite},
|
|
pb.MessageType_MESSAGE_SYSCALL_CHDIR: {checker: checkSyscallChdir},
|
|
pb.MessageType_MESSAGE_SYSCALL_SETID: {checker: checkSyscallSetid},
|
|
pb.MessageType_MESSAGE_SYSCALL_SETRESID: {checker: checkSyscallSetresid},
|
|
pb.MessageType_MESSAGE_SYSCALL_CHROOT: {checker: checkSyscallChroot},
|
|
pb.MessageType_MESSAGE_SYSCALL_DUP: {checker: checkSyscallDup},
|
|
pb.MessageType_MESSAGE_SYSCALL_PRLIMIT64: {checker: checkSyscallPrlimit64},
|
|
pb.MessageType_MESSAGE_SYSCALL_EVENTFD: {checker: checkSyscallEventfd},
|
|
pb.MessageType_MESSAGE_SYSCALL_SIGNALFD: {checker: checkSyscallSignalfd},
|
|
pb.MessageType_MESSAGE_SYSCALL_BIND: {checker: checkSyscallBind},
|
|
pb.MessageType_MESSAGE_SYSCALL_ACCEPT: {checker: checkSyscallAccept},
|
|
pb.MessageType_MESSAGE_SYSCALL_FCNTL: {checker: checkSyscallFcntl},
|
|
pb.MessageType_MESSAGE_SYSCALL_PIPE: {checker: checkSyscallPipe},
|
|
pb.MessageType_MESSAGE_SYSCALL_TIMERFD_CREATE: {checker: checkSyscallTimerfdCreate},
|
|
pb.MessageType_MESSAGE_SYSCALL_TIMERFD_SETTIME: {checker: checkSyscallTimerfdSettime},
|
|
pb.MessageType_MESSAGE_SYSCALL_TIMERFD_GETTIME: {checker: checkSyscallTimerfdGettime},
|
|
pb.MessageType_MESSAGE_SYSCALL_INOTIFY_INIT: {checker: checkSyscallInotifyInit},
|
|
pb.MessageType_MESSAGE_SYSCALL_INOTIFY_ADD_WATCH: {checker: checkSyscallInotifyInitAddWatch},
|
|
pb.MessageType_MESSAGE_SYSCALL_INOTIFY_RM_WATCH: {checker: checkSyscallInotifyInitRmWatch},
|
|
pb.MessageType_MESSAGE_SYSCALL_CLONE: {checker: checkSyscallClone},
|
|
pb.MessageType_MESSAGE_SYSCALL_MMAP: {checker: checkSyscallMmap},
|
|
pb.MessageType_MESSAGE_SYSCALL_LISTEN: {checker: checkSyscallListen},
|
|
}
|
|
return matchers
|
|
}
|
|
|
|
func validatePoints(t *testing.T, msgs []test.Message, matchers map[pb.MessageType]*checkers) {
|
|
for _, msg := range msgs {
|
|
t.Logf("Processing message type %v", msg.MsgType)
|
|
if handler := matchers[msg.MsgType]; handler == nil {
|
|
// All points generated should have a corresponding matcher.
|
|
t.Errorf("No matcher for message type %v", msg.MsgType)
|
|
} else {
|
|
handler.count++
|
|
if err := handler.checker(msg); err != nil {
|
|
t.Errorf("message type %v: %v", msg.MsgType, err)
|
|
}
|
|
}
|
|
}
|
|
for msgType, match := range matchers {
|
|
t.Logf("Processed %d messages for %v", match.count, msgType)
|
|
if match.count == 0 {
|
|
// All matchers should be triggered at least once to ensure points are
|
|
// firing with the workload.
|
|
t.Errorf("no point was generated for %v", msgType)
|
|
}
|
|
}
|
|
}
|
|
|
|
func checkTimeNs(ns int64) error {
|
|
if ns <= int64(cutoffTime.Nanosecond()) {
|
|
return fmt.Errorf("time: got: %d (%v), should not be less than %d (%v)", ns, time.Unix(0, ns), cutoffTime.Nanosecond(), cutoffTime)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type contextDataOpts struct {
|
|
skipCwd bool
|
|
}
|
|
|
|
func checkContextData(data *pb.ContextData) error {
|
|
return checkContextDataOpts(data, contextDataOpts{})
|
|
}
|
|
|
|
func checkContextDataOpts(data *pb.ContextData, opts contextDataOpts) error {
|
|
if data == nil {
|
|
return fmt.Errorf("ContextData should not be nil")
|
|
}
|
|
if !strings.HasPrefix(data.ContainerId, "runsc-") {
|
|
return fmt.Errorf("invalid container ID %q", data.ContainerId)
|
|
}
|
|
|
|
if err := checkTimeNs(data.TimeNs); err != nil {
|
|
return err
|
|
}
|
|
if err := checkTimeNs(data.ThreadStartTimeNs); err != nil {
|
|
return err
|
|
}
|
|
if data.ThreadStartTimeNs > data.TimeNs {
|
|
return fmt.Errorf("thread_start_time should not be greater than point time: %d (%v), got: %d (%v)", data.TimeNs, time.Unix(0, data.TimeNs), data.ThreadStartTimeNs, time.Unix(0, data.ThreadStartTimeNs))
|
|
}
|
|
if err := checkTimeNs(data.ThreadGroupStartTimeNs); err != nil {
|
|
return err
|
|
}
|
|
if data.ThreadGroupStartTimeNs > data.TimeNs {
|
|
return fmt.Errorf("thread_group_start_time should not be greater than point time: %d (%v), got: %d (%v)", data.TimeNs, time.Unix(0, data.TimeNs), data.ThreadGroupStartTimeNs, time.Unix(0, data.ThreadGroupStartTimeNs))
|
|
}
|
|
|
|
if data.ThreadId <= 0 {
|
|
return fmt.Errorf("invalid thread_id: %v", data.ThreadId)
|
|
}
|
|
if data.ThreadGroupId <= 0 {
|
|
return fmt.Errorf("invalid thread_group_id: %v", data.ThreadGroupId)
|
|
}
|
|
if data.ParentThreadGroupId < 0 {
|
|
return fmt.Errorf("invalid parent_thread_group_id: %v", data.ParentThreadGroupId)
|
|
}
|
|
if !opts.skipCwd && len(data.Cwd) == 0 {
|
|
return fmt.Errorf("invalid cwd: %v", data.Cwd)
|
|
}
|
|
if len(data.ProcessName) == 0 {
|
|
return fmt.Errorf("invalid process_name: %v", data.ProcessName)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkContainerStart(msg test.Message) error {
|
|
p := pb.Start{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if !strings.HasPrefix(p.Id, "runsc-") {
|
|
return fmt.Errorf("invalid container ID %q", p.Id)
|
|
}
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return fmt.Errorf("Getwd(): %v", err)
|
|
}
|
|
if cwd != p.Cwd {
|
|
return fmt.Errorf("invalid cwd, got: %q, want: %q", p.Cwd, cwd)
|
|
}
|
|
if len(p.Args) == 0 {
|
|
return fmt.Errorf("empty args")
|
|
}
|
|
if len(p.Env) == 0 {
|
|
return fmt.Errorf("empty env")
|
|
}
|
|
for _, e := range p.Env {
|
|
if !strings.ContainsRune(e, '=') {
|
|
return fmt.Errorf("malformed env: %q", e)
|
|
}
|
|
}
|
|
if p.Terminal {
|
|
return fmt.Errorf("terminal should be off")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkSentryTaskExit(msg test.Message) error {
|
|
p := pb.TaskExit{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkSentryMmap(msg test.Message) error {
|
|
p := pb.MmapInfo{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
// Print out mmap events
|
|
if p.IsInitialMmap || p.MappedPath != "" {
|
|
fmt.Printf("Mmap event: path=%q, ino=%d, mode=%o, uid=%d, gid=%d, initial=%v\n", p.MappedPath, p.MappedIno, p.MappedMode, p.MappedUid, p.MappedGid, p.IsInitialMmap)
|
|
}
|
|
if p.MappedPath != "" && (p.MappedCtime == nil || (p.MappedCtime.Sec == 0 && p.MappedCtime.Nsec == 0)) {
|
|
return fmt.Errorf("MappedCtime should not be empty for mapped file: %q", p.MappedPath)
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallRaw(msg test.Message) error {
|
|
p := pb.Syscall{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
// Sanity check that Sysno is within valid range. If sysno could be larger
|
|
// than the value below, update it accordingly.
|
|
if p.Sysno > 500 {
|
|
return fmt.Errorf("invalid syscall number %d", p.Sysno)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallClose(msg test.Message) error {
|
|
p := pb.Close{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if p.Fd < 0 {
|
|
// Although negative FD is possible, it doesn't happen in the test.
|
|
return fmt.Errorf("closing negative FD: %d", p.Fd)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallMmap(msg test.Message) error {
|
|
var p pb.Mmap
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallOpen(msg test.Message) error {
|
|
p := pb.Open{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallRead(msg test.Message) error {
|
|
p := pb.Read{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if p.Fd < 0 {
|
|
// Although negative FD is possible, it doesn't happen in the test.
|
|
return fmt.Errorf("read negative FD: %d", p.Fd)
|
|
}
|
|
if p.HasOffset {
|
|
// Workload always uses 20 for read offsets (account for partial reads).
|
|
if lower, upper := int64(20), int64(120); p.Offset < lower && p.Offset > upper {
|
|
return fmt.Errorf("invalid offset, got: %d, want: [%d, %d]", p.Offset, lower, upper)
|
|
}
|
|
} else if p.Offset != 0 {
|
|
return fmt.Errorf("offset should be 0: %+v", &p)
|
|
}
|
|
if p.Flags != 0 && p.Flags != unix.RWF_HIPRI {
|
|
return fmt.Errorf("invalid flag value, got: %+x, want: 0 || RWF_HIPRI", p.Flags)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallWrite(msg test.Message) error {
|
|
p := pb.Write{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if p.Fd < 0 {
|
|
// Although negative FD is possible, it doesn't happen in the test.
|
|
return fmt.Errorf("write negative FD: %d", p.Fd)
|
|
}
|
|
if p.HasOffset {
|
|
// Workload always uses 10 for write offsets (account for partial writes).
|
|
if lower, upper := int64(10), int64(110); p.Offset < lower && p.Offset > upper {
|
|
return fmt.Errorf("invalid offset, got: %d, want: [%d, %d]", p.Offset, lower, upper)
|
|
}
|
|
} else if p.Offset != 0 {
|
|
return fmt.Errorf("offset should be 0: %+v", &p)
|
|
}
|
|
if p.Flags != 0 && p.Flags != unix.RWF_HIPRI {
|
|
return fmt.Errorf("invalid flag value, got: %+x, want: 0 || RWF_HIPRI", p.Flags)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkSentryClone(msg test.Message) error {
|
|
p := pb.CloneInfo{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if p.CreatedThreadId < 0 {
|
|
return fmt.Errorf("invalid TID: %d", p.CreatedThreadId)
|
|
}
|
|
if p.CreatedThreadGroupId < 0 {
|
|
return fmt.Errorf("invalid TGID: %d", p.CreatedThreadGroupId)
|
|
}
|
|
if p.CreatedThreadStartTimeNs < 0 {
|
|
return fmt.Errorf("invalid TID: %d", p.CreatedThreadId)
|
|
}
|
|
return checkTimeNs(p.CreatedThreadStartTimeNs)
|
|
}
|
|
|
|
func checkSentryExec(msg test.Message) error {
|
|
p := pb.ExecveInfo{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
|
|
if want := "/bin/true"; !strings.Contains(p.BinaryPath, want) && p.Argv[0] != "test_memfd" {
|
|
return fmt.Errorf("wrong BinaryPath, got: %q, want substring: %q", p.BinaryPath, want)
|
|
}
|
|
if len(p.Argv) == 0 {
|
|
return fmt.Errorf("empty Argv")
|
|
}
|
|
|
|
// workload.cc fires distinct execution workloads:
|
|
// 1. A test checking execve syscall resolution using a symlink ("test_binary_name").
|
|
// 2. A test covering execveat testing from a memfd ("test_memfd").
|
|
// 3. A test covering execveat testing ("/bin/true").
|
|
switch p.Argv[0] {
|
|
case "test_binary_name":
|
|
// In this case, we expect the following distinct path values:
|
|
// 1. BinaryPath: The fully resolved system path mapped by Sentry
|
|
// (points to /bin/true).
|
|
// 2. Execfn: The exact invariant path string given by caller
|
|
// (points to unresolved /tmp/test_symlink).
|
|
// 3. Argv[0]: The arbitrary caller-provided process argument
|
|
// (points to synthetic "test_binary_name").
|
|
if want := "/tmp/test_symlink"; p.Execfn != want {
|
|
return fmt.Errorf("wrong Execfn, got: %q, want: %q", p.Execfn, want)
|
|
}
|
|
if p.BinaryPath == p.Execfn {
|
|
return fmt.Errorf("BinaryPath (%q) should differ from Execfn (%q)", p.BinaryPath, p.Execfn)
|
|
}
|
|
case "test_memfd":
|
|
// For memfd files, BinaryPath is the path in the mount namespace of the memfd file descriptor.
|
|
// This path is `/dev/fd/<fd>` for a memfd file descriptor.
|
|
if !strings.HasPrefix(p.BinaryPath, "/dev/fd/") && !strings.HasPrefix(p.BinaryPath, "/proc/") {
|
|
return fmt.Errorf("wrong BinaryPath for memfd, got: %q", p.BinaryPath)
|
|
}
|
|
default:
|
|
if !strings.Contains(p.BinaryPath, p.Argv[0]) {
|
|
return fmt.Errorf("wrong Argv[0], got: %q, want substring: %q", p.Argv[0], p.BinaryPath)
|
|
}
|
|
}
|
|
if len(p.Env) == 0 {
|
|
return fmt.Errorf("empty Env")
|
|
}
|
|
if want := "TEST=123"; want != p.Env[0] {
|
|
return fmt.Errorf("wrong Env[0], got: %q, want: %q", p.Env[0], want)
|
|
}
|
|
if (p.BinaryMode & 0111) == 0 {
|
|
return fmt.Errorf("executing non-executable file, mode: %#o (%#x)", p.BinaryMode, p.BinaryMode)
|
|
}
|
|
const nobody = 65534
|
|
if p.Argv[0] == "test_memfd" {
|
|
// test_memfd is created by the test runner itself (root).
|
|
if p.BinaryUid != 0 {
|
|
return fmt.Errorf("BinaryUid for memfd, got: %d, want: 0", p.BinaryUid)
|
|
}
|
|
if p.BinaryGid != 0 {
|
|
return fmt.Errorf("BinaryGid for memfd, got: %d, want: 0", p.BinaryGid)
|
|
}
|
|
} else {
|
|
// For host binaries (like /bin/true, normally 0:0 on the host), rootless user
|
|
// namespaces map the unmapped host root onto nobody (65534). Depending on the
|
|
// test environment's topology (e.g., CI nodes running as root natively), it
|
|
// will be captured as either 0 or 65534.
|
|
if p.BinaryUid != 0 && p.BinaryUid != nobody {
|
|
return fmt.Errorf("BinaryUid, got: %d, want: 0 or %d", p.BinaryUid, nobody)
|
|
}
|
|
if p.BinaryGid != 0 && p.BinaryGid != nobody {
|
|
return fmt.Errorf("BinaryGid, got: %d, want: 0 or %d", p.BinaryGid, nobody)
|
|
}
|
|
}
|
|
if p.BinaryIno == 0 {
|
|
return fmt.Errorf("BinaryIno should not be 0")
|
|
}
|
|
if p.BinaryCtime == nil || (p.BinaryCtime.Sec == 0 && p.BinaryCtime.Nsec == 0) {
|
|
return fmt.Errorf("BinaryCtime should not be empty")
|
|
}
|
|
|
|
// Get SHA256 from the binary and compare it with the one from the event.
|
|
binaryPathForSha256 := p.BinaryPath
|
|
if p.Argv[0] == "test_memfd" {
|
|
// The workload creates a memfd and copies /bin/true into it. Since the
|
|
// memfd is not accessible from here, we use /bin/true to calculate the
|
|
// expected SHA256.
|
|
binaryPathForSha256 = "/bin/true"
|
|
}
|
|
out, err := exec.Command("sha256sum", binaryPathForSha256).CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("Not able to calculate SHA256sum: %v", err)
|
|
}
|
|
want, _, _ := strings.Cut(string(out), " ")
|
|
|
|
got := ""
|
|
for _, b := range p.BinarySha256 {
|
|
got += fmt.Sprintf("%02x", b)
|
|
}
|
|
if want != got {
|
|
return fmt.Errorf("BinarySHA256, got: %q, want: %q", got, want)
|
|
}
|
|
|
|
if p.BinaryOverlayfsUpper {
|
|
return fmt.Errorf("BinaryOverlayfsUpper, got: true, want: false")
|
|
}
|
|
if p.Argv[0] == "test_memfd" {
|
|
// memfd is not on overlayfs and is in-memory.
|
|
if p.BinaryOverlayfsLower {
|
|
return fmt.Errorf("BinaryOverlayfsLower, got: true, want: false")
|
|
}
|
|
if !p.BinaryInMemfd {
|
|
return fmt.Errorf("BinaryInMemfd, got: false, want: true")
|
|
}
|
|
} else {
|
|
// Other binaries are on the rootfs, which is an overlayfs mount.
|
|
if !p.BinaryOverlayfsLower {
|
|
return fmt.Errorf("BinaryOverlayfsLower, got: false, want: true")
|
|
}
|
|
if p.BinaryInMemfd {
|
|
return fmt.Errorf("BinaryInMemfd, got: true, want: false")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallExecve(msg test.Message) error {
|
|
p := pb.Execve{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if len(p.Argv) == 0 {
|
|
return fmt.Errorf("empty Argv")
|
|
}
|
|
// We handle separate execs from workload.cc:
|
|
// 1. ForkAndExec, which tests execve relative to "/tmp/test_symlink" using "test_binary_name".
|
|
// 2. ForkAndExecveatMemfd, which tests execveat against a memfd using "test_memfd".
|
|
// 3. ForkAndExecveat, which tests execveat against "/bin/true" relative to its path.
|
|
switch p.Argv[0] {
|
|
case "test_binary_name":
|
|
// PointExecve doesn't populate Fd, so it defaults to 0.
|
|
if p.Fd != 0 {
|
|
return fmt.Errorf("execve invalid FD: %d", p.Fd)
|
|
}
|
|
if want := "/tmp/test_symlink"; want != p.Pathname {
|
|
return fmt.Errorf("wrong Pathname, got: %q, want: %q", p.Pathname, want)
|
|
}
|
|
case "test_memfd":
|
|
// test_memfd uses execveat(fd, "", ..., AT_EMPTY_PATH).
|
|
if p.Fd < 3 {
|
|
return fmt.Errorf("execve invalid FD: %d", p.Fd)
|
|
}
|
|
if want := ""; want != p.Pathname {
|
|
return fmt.Errorf("wrong Pathname, got: %q, want: %q", p.Pathname, want)
|
|
}
|
|
default:
|
|
// PointExecveat gets a dirfd that is explicitly opened by the workload so it is >= 3.
|
|
if p.Fd < 3 {
|
|
return fmt.Errorf("execve invalid FD: %d", p.Fd)
|
|
}
|
|
if want := "/"; want != p.FdPath {
|
|
return fmt.Errorf("wrong FdPath, got: %q, want: %q", p.FdPath, want)
|
|
}
|
|
if want := "/bin/true"; want != p.Pathname {
|
|
return fmt.Errorf("wrong Pathname, got: %q, want: %q", p.Pathname, want)
|
|
}
|
|
if p.Argv[0] != p.Pathname {
|
|
return fmt.Errorf("wrong Argv[0], got: %q, want: %q", p.Argv[0], p.Pathname)
|
|
}
|
|
}
|
|
if len(p.Envv) == 0 {
|
|
return fmt.Errorf("empty Envv")
|
|
}
|
|
if want := "TEST=123"; want != p.Envv[0] {
|
|
return fmt.Errorf("wrong Envv[0], got: %q, want: %q", p.Envv[0], want)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkSentryExitNotifyParent(msg test.Message) error {
|
|
p := pb.ExitNotifyParentInfo{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
// cwd is empty because the task has already been destroyed when the point
|
|
// fires.
|
|
opts := contextDataOpts{skipCwd: true}
|
|
if err := checkContextDataOpts(p.ContextData, opts); err != nil {
|
|
return err
|
|
}
|
|
if p.ExitStatus != 0 {
|
|
return fmt.Errorf("wrong ExitStatus, got: %d, want: 0", p.ExitStatus)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallConnect(msg test.Message) error {
|
|
p := pb.Connect{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if p.Fd < 3 {
|
|
return fmt.Errorf("invalid FD: %d", p.Fd)
|
|
}
|
|
if want := "socket:"; !strings.HasPrefix(p.FdPath, want) {
|
|
return fmt.Errorf("wrong FdPath, got: %q, want prefix: %q", p.FdPath, want)
|
|
}
|
|
if len(p.Address) == 0 {
|
|
return fmt.Errorf("empty address: %q", string(p.Address))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallSocket(msg test.Message) error {
|
|
p := pb.Socket{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if want := unix.AF_UNIX; int32(want) != p.Domain {
|
|
return fmt.Errorf("wrong Domain, got: %v, want: %v", p.Domain, want)
|
|
}
|
|
if want := unix.SOCK_STREAM; int32(want) != p.Type {
|
|
return fmt.Errorf("wrong Type, got: %v, want: %v", p.Type, want)
|
|
}
|
|
if want := int32(0); want != p.Protocol {
|
|
return fmt.Errorf("wrong Protocol, got: %v, want: %v", p.Protocol, want)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallSetid(msg test.Message) error {
|
|
p := pb.Setid{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if p.Id != 0 {
|
|
return fmt.Errorf("invalid id: %d", p.Id)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallSetresid(msg test.Message) error {
|
|
p := pb.Setresid{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if p.GetRid() != 0 {
|
|
return fmt.Errorf("invalid rid: %d", p.Rid)
|
|
}
|
|
if p.GetEid() != 0 {
|
|
return fmt.Errorf("invalid eid: %d", p.Eid)
|
|
}
|
|
if p.GetSid() != 0 {
|
|
return fmt.Errorf("invalid sid: %d", p.Sid)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallChdir(msg test.Message) error {
|
|
p := pb.Chdir{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if p.Fd < 3 && p.Fd != unix.AT_FDCWD { // Constant used for all file-related syscalls.
|
|
return fmt.Errorf("invalid FD: %d", p.Fd)
|
|
}
|
|
if want := "trace_test.abc"; !strings.Contains(p.Pathname, want) {
|
|
return fmt.Errorf("wrong Pathname, got: %q, want: %q", p.Pathname, want)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallDup(msg test.Message) error {
|
|
p := pb.Dup{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if p.OldFd < 0 {
|
|
return fmt.Errorf("invalid fd: %d", p.OldFd)
|
|
}
|
|
if p.NewFd < 0 {
|
|
return fmt.Errorf("invalid fd: %d", p.NewFd)
|
|
}
|
|
if p.Flags != unix.O_CLOEXEC && p.Flags != 0 {
|
|
return fmt.Errorf("invalid flag got: %v", p.Flags)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallPrlimit64(msg test.Message) error {
|
|
p := pb.Prlimit{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if p.Pid < 0 {
|
|
return fmt.Errorf("invalid pid: %d", p.Pid)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallEventfd(msg test.Message) error {
|
|
p := pb.Eventfd{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if p.Val < 0 {
|
|
return fmt.Errorf("invalid pid: %d", p.Val)
|
|
}
|
|
if p.Flags != unix.EFD_NONBLOCK && p.Flags != 0 {
|
|
return fmt.Errorf("invalid flag got: %d, ", p.Flags)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallBind(msg test.Message) error {
|
|
p := pb.Bind{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if p.Fd < 0 {
|
|
return fmt.Errorf("invalid fd: %d", p.Fd)
|
|
}
|
|
if p.FdPath == " " {
|
|
return fmt.Errorf("invalid path: %v", p.FdPath)
|
|
}
|
|
if len(p.Address) == 0 {
|
|
return fmt.Errorf("invalid address: %d", p.Address)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallAccept(msg test.Message) error {
|
|
p := pb.Accept{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if p.Fd < 0 {
|
|
return fmt.Errorf("invalid fd: %d", p.Fd)
|
|
}
|
|
if p.FdPath == "" {
|
|
return fmt.Errorf("invalid path: %v", p.FdPath)
|
|
}
|
|
if len(p.Address) != 0 {
|
|
return fmt.Errorf("invalid address: %d, %v", p.Address, p.Sysno)
|
|
}
|
|
if p.Flags != 0 && p.Flags != unix.SOCK_CLOEXEC {
|
|
return fmt.Errorf("invalid flag got: %d", p.Flags)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallListen(msg test.Message) error {
|
|
p := pb.Listen{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if p.Fd < 0 {
|
|
return fmt.Errorf("invalid fd: %d", p.Fd)
|
|
}
|
|
if p.FdPath == "" {
|
|
return fmt.Errorf("invalid path: %v", p.FdPath)
|
|
}
|
|
if len(p.Address) == 0 {
|
|
return fmt.Errorf("invalid address: %d", len(p.Address))
|
|
}
|
|
if p.SocketFamily != int32(unix.AF_UNIX) {
|
|
return fmt.Errorf("invalid socket family got: %d", p.SocketFamily)
|
|
}
|
|
if p.SocketType != int32(unix.SOCK_STREAM) && p.SocketType != int32(unix.SOCK_SEQPACKET) {
|
|
return fmt.Errorf("invalid socket type got: %d", p.SocketType)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallChroot(msg test.Message) error {
|
|
p := pb.Chroot{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if want := "trace_test.abc"; !strings.Contains(p.Pathname, want) {
|
|
return fmt.Errorf("wrong pathname, got: %q, want substring: %q", p.Pathname, want)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallFcntl(msg test.Message) error {
|
|
p := pb.Fcntl{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if p.Fd < 0 {
|
|
return fmt.Errorf("invalid fd: %d", p.Fd)
|
|
}
|
|
if p.Cmd != unix.F_GETFL {
|
|
return fmt.Errorf("invalid cmd: got: %v, want: F_GETFL", p.Cmd)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallPipe(msg test.Message) error {
|
|
p := pb.Pipe{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if p.Reader < 0 {
|
|
return fmt.Errorf("invalid reader fd: %d", p.Reader)
|
|
}
|
|
if p.Writer < 0 {
|
|
return fmt.Errorf("invalid writer fd: %d", p.Writer)
|
|
}
|
|
if p.Flags != unix.O_CLOEXEC && p.Flags != 0 {
|
|
return fmt.Errorf("invalid flag got: %v", p.Flags)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallSignalfd(msg test.Message) error {
|
|
p := pb.Signalfd{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if p.Fd != -1 {
|
|
return fmt.Errorf("invalid fd: %d", p.Fd)
|
|
}
|
|
if p.Sigset != 0 && p.Sigset != uint64(unix.SIGILL) {
|
|
return fmt.Errorf("invalid signal got: %v", p.Sigset)
|
|
}
|
|
return checkSyscallSignalfdFlags(p.Flags)
|
|
}
|
|
|
|
func checkSyscallTimerfdCreate(msg test.Message) error {
|
|
p := pb.TimerfdCreate{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if p.ClockId != unix.CLOCK_REALTIME {
|
|
return fmt.Errorf("invalid clockid: %d", p.ClockId)
|
|
}
|
|
if p.Flags != 0 {
|
|
return fmt.Errorf("invalid flag got: %v", p.Flags)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallTimerfdSettime(msg test.Message) error {
|
|
p := pb.TimerfdSetTime{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if p.Fd < 0 {
|
|
return fmt.Errorf("invalid clockid: %d", p.Fd)
|
|
}
|
|
if p.FdPath == "" {
|
|
return fmt.Errorf("invalid path: %q", p.FdPath)
|
|
}
|
|
if p.Flags != unix.TFD_TIMER_ABSTIME {
|
|
return fmt.Errorf("invalid flag got: %v", p.Flags)
|
|
}
|
|
if p.OldValue != nil {
|
|
return fmt.Errorf("invalid oldvalue: %v", p.OldValue.String())
|
|
}
|
|
if p.NewValue == nil {
|
|
return fmt.Errorf("invalid oldvalue: %v", p.OldValue.String())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallTimerfdGettime(msg test.Message) error {
|
|
p := pb.TimerfdGetTime{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if p.Fd < 0 {
|
|
return fmt.Errorf("invalid clockid: %d", p.Fd)
|
|
}
|
|
if p.FdPath == "" {
|
|
return fmt.Errorf("invalid path: %q", p.FdPath)
|
|
}
|
|
if p.CurValue == nil {
|
|
return fmt.Errorf("invalid oldvalue: %v", p.CurValue.String())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallClone(msg test.Message) error {
|
|
p := pb.Clone{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
// Flags used by default in system calls that use clone(2) in the underlying.
|
|
rawFlags := unix.CLONE_CHILD_CLEARTID | unix.CLONE_CHILD_SETTID | uint64(unix.SIGCHLD)
|
|
// Flags used for clone(2) syscall in workload.cc
|
|
cloneFlags := uint64(unix.SIGCHLD) | unix.CLONE_VFORK | unix.CLONE_FILES
|
|
if p.Flags != uint64(rawFlags) && p.Flags != cloneFlags {
|
|
return fmt.Errorf("invalid flag got: %v", p.Flags)
|
|
}
|
|
if (p.Flags == uint64(rawFlags) && p.Stack != 0) || (p.Flags == cloneFlags && p.Stack == 0) {
|
|
return fmt.Errorf("invalid stack got: %v", p.Stack)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallInotifyInit(msg test.Message) error {
|
|
p := pb.InotifyInit{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if !(p.Flags == 0 || p.Flags == unix.IN_NONBLOCK) {
|
|
return fmt.Errorf("invalid flag got: %v", p.Flags)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallInotifyInitAddWatch(msg test.Message) error {
|
|
p := pb.InotifyAddWatch{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if p.Fd < 0 {
|
|
return fmt.Errorf("invalid fd: %d", p.Fd)
|
|
}
|
|
if p.FdPath == "" {
|
|
return fmt.Errorf("invalid path: %v", p.FdPath)
|
|
}
|
|
if want := "timer_trace_test.abc"; !strings.Contains(p.Pathname, want) {
|
|
return fmt.Errorf("wrong pathname, got: %q, want: %q", p.Pathname, want)
|
|
}
|
|
if want := unix.IN_NONBLOCK; want != int(p.Mask) {
|
|
return fmt.Errorf("invalid mask: got: %v, want: %v", p.Mask, want)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkSyscallInotifyInitRmWatch(msg test.Message) error {
|
|
p := pb.InotifyRmWatch{}
|
|
if err := proto.Unmarshal(msg.Msg, &p); err != nil {
|
|
return err
|
|
}
|
|
if err := checkContextData(p.ContextData); err != nil {
|
|
return err
|
|
}
|
|
if p.Fd < 0 {
|
|
return fmt.Errorf("invalid fd: %d", p.Fd)
|
|
}
|
|
if p.FdPath == "" {
|
|
return fmt.Errorf("invalid path: %q", p.FdPath)
|
|
}
|
|
if p.Wd < 0 {
|
|
return fmt.Errorf("invalid wd: %d", p.Wd)
|
|
}
|
|
return nil
|
|
}
|