diff options
Diffstat (limited to 'internal/testsuite/ptrace.go')
| -rw-r--r-- | internal/testsuite/ptrace.go | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/internal/testsuite/ptrace.go b/internal/testsuite/ptrace.go new file mode 100644 index 00000000..ccf0900c --- /dev/null +++ b/internal/testsuite/ptrace.go @@ -0,0 +1,144 @@ +package testsuite + +import ( + "crypto/sha512" + "encoding/base64" + "errors" + "fmt" + "os" + "syscall" + "unsafe" +) + +const ( + // _PTRACE_ATTACH attaches to the process specified in pid. + _PTRACE_ATTACH = 16 + // _PTRACE_DETACH restarts the stopped tracee as for PTRACE_CONT, but first + // detaches from it. + _PTRACE_DETACH = 17 + + // _PTRACE_SECCOMP_GET_FILTER allows the tracer to dump the tracee's classic + // BPF filters. + _PTRACE_SECCOMP_GET_FILTER = 0x420c +) + +// ptrace wraps the ptrace syscall. +func ptrace( + op uintptr, + pid, addr int, + data unsafe.Pointer, +) (r uintptr, errno syscall.Errno) { + r, _, errno = syscall.Syscall6( + syscall.SYS_PTRACE, + op, + uintptr(pid), + uintptr(addr), + uintptr(data), + 0, 0, + ) + return +} + +// ptraceAttach attaches to the process referred to by pid. +func ptraceAttach(pid int) error { + if _, errno := ptrace(_PTRACE_ATTACH, pid, 0, nil); errno != 0 { + return os.NewSyscallError("PTRACE_ATTACH", errno) + } + + var status syscall.WaitStatus + for { + if _, err := syscall.Wait4( + pid, + &status, + syscall.WALL, + nil, + ); err != nil { + if errors.Is(err, syscall.EINTR) { + continue + } + return os.NewSyscallError("wait4", err) + } + switch { + case status.Stopped(): + return nil + + case status.Continued(): + continue + + case status.Signaled(): + return fmt.Errorf( + "tracee terminated by signal %s", + status.Signal(), + ) + + case status.Exited(): + return fmt.Errorf( + "tracee terminated unexpectedly with code %d", + status.ExitStatus(), + ) + } + } +} + +// ptraceDetach detaches from the attached process referred to by pid. +func ptraceDetach(pid int) error { + if _, errno := ptrace(_PTRACE_DETACH, pid, 0, nil); errno != 0 { + return os.NewSyscallError("PTRACE_DETACH", errno) + } + return nil +} + +// getFilter dumps the specified tracee's cBPF filter at the specified index +// and returns the resulting payload. T must be eight bytes long and must not +// contain pointers. +func getFilter(pid, index int) ([]syscall.SockFilter, error) { + var buf []syscall.SockFilter + if n, errno := ptrace( + _PTRACE_SECCOMP_GET_FILTER, + pid, index, nil, + ); errno != 0 { + return nil, os.NewSyscallError("PTRACE_SECCOMP_GET_FILTER", errno) + } else { + buf = make([]syscall.SockFilter, n) + } + if _, errno := ptrace( + _PTRACE_SECCOMP_GET_FILTER, + pid, index, unsafe.Pointer(&buf[0]), + ); errno != 0 { + return nil, os.NewSyscallError("PTRACE_SECCOMP_GET_FILTER", errno) + } + return buf, nil +} + +// CheckFilter checks the process at pid to have its first filter's contents +// match the specified sha512 checksum. +func CheckFilter(pid, index int, sum [sha512.Size]byte) (err error) { + if err = ptraceAttach(pid); err != nil { + return + } + defer func() { + if detachErr := ptraceDetach(pid); err == nil { + err = detachErr + } + }() + + var buf []syscall.SockFilter + h := sha512.New() + if buf, err = getFilter(pid, index); err != nil { + return + } else { + h.Write(unsafe.Slice( + (*byte)(unsafe.Pointer(&buf[0])), + uintptr(len(buf))*unsafe.Sizeof(buf[0]), + )) + } + + if got := h.Sum(nil); string(got) != string(sum[:]) { + return fmt.Errorf( + "bad filter\n\t got: %s\n\twant: %s", + base64.StdEncoding.EncodeToString(got), + base64.StdEncoding.EncodeToString(sum[:]), + ) + } + return +} |
