aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/testsuite/ptrace.go
blob: ccf0900c1074a030e6be8ed912f0b429e1620f5b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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
}