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 }