hakurei/container/seccomp/libseccomp.go
Ophestra 123d7fbfd5
All checks were successful
Test / Create distribution (push) Successful in 34s
Test / Sandbox (push) Successful in 2m11s
Test / Sandbox (race detector) (push) Successful in 4m2s
Test / Hpkg (push) Successful in 4m19s
Test / Hakurei (race detector) (push) Successful in 4m47s
Test / Hakurei (push) Successful in 2m13s
Test / Flake checks (push) Successful in 1m32s
container/seccomp: remove export pipe
This was only useful when wrapping bwrap.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-10-13 18:51:35 +09:00

236 lines
6.1 KiB
Go

package seccomp
/*
#cgo linux pkg-config: --static libseccomp
#include "libseccomp-helper.h"
#include <sys/personality.h>
*/
import "C"
import (
"errors"
"fmt"
"runtime"
"runtime/cgo"
"syscall"
"unsafe"
)
// ErrInvalidRules is returned for a zero-length rules slice.
var ErrInvalidRules = errors.New("invalid native rules slice")
// LibraryError represents a libseccomp error.
type LibraryError struct {
// User facing description of the libseccomp function returning the error.
Prefix string
// Negated errno value returned by libseccomp.
Seccomp syscall.Errno
// Global errno value on return.
Errno error
}
func (e *LibraryError) Error() string {
if e.Seccomp == 0 {
if e.Errno == nil {
panic("invalid libseccomp error")
}
return fmt.Sprintf("%s: %s", e.Prefix, e.Errno)
}
if e.Errno == nil {
return fmt.Sprintf("%s: %s", e.Prefix, e.Seccomp)
}
return fmt.Sprintf("%s: %s (%s)", e.Prefix, e.Seccomp, e.Errno)
}
func (e *LibraryError) Is(err error) bool {
if e == nil {
return err == nil
}
if ef, ok := err.(*LibraryError); ok {
return *e == *ef
}
return (e.Seccomp != 0 && errors.Is(err, e.Seccomp)) ||
(e.Errno != nil && errors.Is(err, e.Errno))
}
type (
// ScmpSyscall represents a syscall number passed to libseccomp via [NativeRule.Syscall].
ScmpSyscall = C.int
// ScmpErrno represents an errno value passed to libseccomp via [NativeRule.Errno].
ScmpErrno = C.int
)
// A NativeRule specifies an arch-specific action taken by seccomp under certain conditions.
type NativeRule struct {
// Syscall is the arch-dependent syscall number to act against.
Syscall ScmpSyscall
// Errno is the errno value to return when the condition is satisfied.
Errno ScmpErrno
// Arg is the optional struct scmp_arg_cmp passed to libseccomp.
Arg *ScmpArgCmp
}
type ExportFlag = C.hakurei_export_flag
const (
// AllowMultiarch allows multiarch/emulation.
AllowMultiarch ExportFlag = C.HAKUREI_EXPORT_MULTIARCH
// AllowCAN allows AF_CAN.
AllowCAN ExportFlag = C.HAKUREI_EXPORT_CAN
// AllowBluetooth allows AF_BLUETOOTH.
AllowBluetooth ExportFlag = C.HAKUREI_EXPORT_BLUETOOTH
)
var resPrefix = [...]string{
0: "",
1: "seccomp_init failed",
2: "seccomp_arch_add failed",
3: "seccomp_arch_add failed (multiarch)",
4: "internal libseccomp failure",
5: "seccomp_rule_add failed",
6: "seccomp_export_bpf_mem failed",
7: "seccomp_load failed",
}
// cbAllocateBuffer is the function signature for the function handle passed to hakurei_export_filter
// which allocates the buffer that the resulting bpf program is copied into, and writes its slice header
// to a value held by the caller.
type cbAllocateBuffer = func(len C.size_t) (buf unsafe.Pointer)
//export hakurei_scmp_allocate
func hakurei_scmp_allocate(f C.uintptr_t, len C.size_t) (buf unsafe.Pointer) {
return cgo.Handle(f).Value().(cbAllocateBuffer)(len)
}
// makeFilter generates a bpf program from a slice of [NativeRule] and writes the resulting byte slice to p.
// The filter is installed to the current process if p is nil.
func makeFilter(rules []NativeRule, flags ExportFlag, p *[]byte) error {
if len(rules) == 0 {
return ErrInvalidRules
}
var (
arch C.uint32_t = 0
multiarch C.uint32_t = 0
)
switch runtime.GOARCH {
case "386":
arch = C.SCMP_ARCH_X86
case "amd64":
arch = C.SCMP_ARCH_X86_64
multiarch = C.SCMP_ARCH_X86
case "arm":
arch = C.SCMP_ARCH_ARM
case "arm64":
arch = C.SCMP_ARCH_AARCH64
multiarch = C.SCMP_ARCH_ARM
}
var ret C.int
var scmpPinner runtime.Pinner
for i := range rules {
rule := &rules[i]
scmpPinner.Pin(rule)
if rule.Arg != nil {
scmpPinner.Pin(rule.Arg)
}
}
var allocateP cgo.Handle
if p != nil {
allocateP = cgo.NewHandle(func(len C.size_t) (buf unsafe.Pointer) {
// this is so the slice header gets a Go pointer
*p = make([]byte, len)
buf = unsafe.Pointer(unsafe.SliceData(*p))
scmpPinner.Pin(buf)
return
})
}
res, err := C.hakurei_scmp_make_filter(
&ret, C.uintptr_t(allocateP),
arch, multiarch,
(*C.struct_hakurei_syscall_rule)(unsafe.Pointer(&rules[0])),
C.size_t(len(rules)),
flags,
)
scmpPinner.Unpin()
if p != nil {
allocateP.Delete()
}
if prefix := resPrefix[res]; prefix != "" {
return &LibraryError{prefix, syscall.Errno(-ret), err}
}
return err
}
// Export generates a bpf program from a slice of [NativeRule].
// Errors returned by libseccomp is wrapped in [LibraryError].
func Export(rules []NativeRule, flags ExportFlag) (data []byte, err error) {
err = makeFilter(rules, flags, &data)
return
}
// Load generates a bpf program from a slice of [NativeRule] and enforces it on the current process.
// Errors returned by libseccomp is wrapped in [LibraryError].
func Load(rules []NativeRule, flags ExportFlag) error { return makeFilter(rules, flags, nil) }
// ScmpCompare is the equivalent of scmp_compare;
// Comparison operators
type ScmpCompare = C.enum_scmp_compare
const (
_SCMP_CMP_MIN = C._SCMP_CMP_MIN
// not equal
SCMP_CMP_NE = C.SCMP_CMP_NE
// less than
SCMP_CMP_LT = C.SCMP_CMP_LT
// less than or equal
SCMP_CMP_LE = C.SCMP_CMP_LE
// equal
SCMP_CMP_EQ = C.SCMP_CMP_EQ
// greater than or equal
SCMP_CMP_GE = C.SCMP_CMP_GE
// greater than
SCMP_CMP_GT = C.SCMP_CMP_GT
// masked equality
SCMP_CMP_MASKED_EQ = C.SCMP_CMP_MASKED_EQ
_SCMP_CMP_MAX = C._SCMP_CMP_MAX
)
// ScmpDatum is the equivalent of scmp_datum_t;
// Argument datum
type ScmpDatum uint64
// ScmpArgCmp is the equivalent of struct scmp_arg_cmp;
// Argument / Value comparison definition
type ScmpArgCmp struct {
// argument number, starting at 0
Arg C.uint
// the comparison op, e.g. SCMP_CMP_*
Op ScmpCompare
DatumA, DatumB ScmpDatum
}
const (
// PersonaLinux is passed in a [ScmpDatum] for filtering calls to syscall.SYS_PERSONALITY.
PersonaLinux = C.PER_LINUX
// PersonaLinux32 is passed in a [ScmpDatum] for filtering calls to syscall.SYS_PERSONALITY.
PersonaLinux32 = C.PER_LINUX32
)
// syscallResolveName resolves a syscall number by name via seccomp_syscall_resolve_name.
// This function is only for testing the lookup tables and included here for convenience.
func syscallResolveName(s string) (trap int) {
v := C.CString(s)
trap = int(C.seccomp_syscall_resolve_name(v))
C.free(unsafe.Pointer(v))
return
}