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
package bpf

import (
	"fmt"

	"golang.org/x/sys/unix"
)

// SeccompData mirrors the kernel's struct seccomp_data: the read-only input a
// seccomp filter inspects for each syscall.
type SeccompData struct {
	Nr   uint32
	Arch uint32
	IP   uint64
	Args [6]uint64
}

// word returns the 32-bit little-endian word at byte offset off, matching how a
// BPF_LD|BPF_W|BPF_ABS load reads struct seccomp_data.
func (d *SeccompData) word(off uint32) (uint32, error) {
	switch {
	case off == OffNr:
		return d.Nr, nil
	case off == OffArch:
		return d.Arch, nil
	case off == OffIP:
		return uint32(d.IP), nil
	case off == OffIP+4:
		return uint32(d.IP >> 32), nil
	case off >= offArgs && off < offArgs+8*6:
		rel := off - offArgs
		arg := d.Args[rel/8]
		if rel%8 == 0 {
			return uint32(arg), nil
		}
		return uint32(arg >> 32), nil
	default:
		return 0, fmt.Errorf("bpf: load from invalid seccomp_data offset %d", off)
	}
}

// Run executes a compiled seccomp program against the given input and returns
// the seccomp action (RET_ALLOW / RET_ERRNO|errno / RET_KILL_PROCESS / ...).
//
// It implements only the instruction subset the seccomp compiler emits; any
// other opcode is reported as an error, which guarantees the generator never
// drifts outside the verified subset.
func Run(prog []unix.SockFilter, data SeccompData) (uint32, error) {
	var a uint32 // accumulator; X register and scratch memory are unused by our codegen
	const maxSteps = 1 << 20
	for pc, steps := 0, 0; ; steps++ {
		if pc < 0 || pc >= len(prog) {
			return 0, fmt.Errorf("bpf: pc %d out of range [0,%d)", pc, len(prog))
		}
		if steps > maxSteps {
			return 0, fmt.Errorf("bpf: program did not terminate (possible loop)")
		}
		f := prog[pc]
		switch f.Code {
		case OpLdAbsW:
			w, err := data.word(f.K)
			if err != nil {
				return 0, err
			}
			a = w
			pc++
		case OpLdImmW:
			a = f.K
			pc++
		case OpAluAndK:
			a &= f.K
			pc++
		case OpJeqK:
			pc += 1 + branch(a == f.K, f.Jt, f.Jf)
		case OpJgeK:
			pc += 1 + branch(a >= f.K, f.Jt, f.Jf)
		case OpJsetK:
			pc += 1 + branch(a&f.K != 0, f.Jt, f.Jf)
		case OpJa:
			pc += 1 + int(f.K)
		case OpRetK:
			return f.K, nil
		default:
			return 0, fmt.Errorf("bpf: unsupported opcode 0x%04x at pc %d", f.Code, pc)
		}
	}
}

func branch(cond bool, jt, jf uint8) int {
	if cond {
		return int(jt)
	}
	return int(jf)
}