aboutsummaryrefslogtreecommitdiffhomepage
path: root/sandbox/seccomp/libseccomp.go
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-07-01 20:23:33 +0900
committerOphestra <cat@gensokyo.uk>2025-07-01 22:11:32 +0900
commit1a8840bebc673672235b6e10b1b9386f24751757 (patch)
treed1e6772bfd685e2162d047e3640bd3ee55c1a1f7 /sandbox/seccomp/libseccomp.go
parent1fb453dffe4c83866fedfa4590be30ec65e815ff (diff)
sandbox/seccomp: resolve rules natively
This enables loading syscall filter policies from external cross-platform config files. This also removes a significant amount of C code. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'sandbox/seccomp/libseccomp.go')
-rw-r--r--sandbox/seccomp/libseccomp.go183
1 files changed, 183 insertions, 0 deletions
diff --git a/sandbox/seccomp/libseccomp.go b/sandbox/seccomp/libseccomp.go
new file mode 100644
index 00000000..58ae8599
--- /dev/null
+++ b/sandbox/seccomp/libseccomp.go
@@ -0,0 +1,183 @@
+package seccomp
+
+/*
+#cgo linux pkg-config: --static libseccomp
+
+#include <libseccomp-helper.h>
+*/
+import "C"
+import (
+ "errors"
+ "fmt"
+ "runtime"
+ "syscall"
+ "unsafe"
+)
+
+var (
+ ErrInvalidRules = errors.New("invalid native rules slice")
+)
+
+// LibraryError represents a libseccomp error.
+type LibraryError struct {
+ Prefix string
+ Seccomp syscall.Errno
+ 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))
+}
+
+// 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 C.int
+ // Errno is the errno value to return when the condition is satisfied.
+ Errno C.int
+ // Arg is the optional struct scmp_arg_cmp passed to libseccomp.
+ Arg *ScmpArgCmp
+}
+
+type PrepareFlag = C.hakurei_prepare_flag
+
+const (
+ // AllowMultiarch allows multiarch/emulation.
+ AllowMultiarch PrepareFlag = C.HAKUREI_PREPARE_MULTIARCH
+ // AllowCAN allows AF_CAN.
+ AllowCAN PrepareFlag = C.HAKUREI_PREPARE_CAN
+ // AllowBluetooth allows AF_BLUETOOTH.
+ AllowBluetooth PrepareFlag = C.HAKUREI_PREPARE_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 failed",
+ 7: "seccomp_load failed",
+}
+
+// Prepare streams filter contents to fd, or installs it to the current process if fd < 0.
+func Prepare(fd int, rules []NativeRule, flags PrepareFlag) 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
+
+ rulesPinner := new(runtime.Pinner)
+ for i := range rules {
+ rule := &rules[i]
+ rulesPinner.Pin(rule)
+ if rule.Arg != nil {
+ rulesPinner.Pin(rule.Arg)
+ }
+ }
+ res, err := C.hakurei_prepare_filter(
+ &ret, C.int(fd),
+ arch, multiarch,
+ (*C.struct_hakurei_syscall_rule)(unsafe.Pointer(&rules[0])),
+ C.size_t(len(rules)),
+ flags,
+ )
+ rulesPinner.Unpin()
+
+ if prefix := resPrefix[res]; prefix != "" {
+ return &LibraryError{
+ prefix,
+ -syscall.Errno(ret),
+ err,
+ }
+ }
+ return err
+}
+
+// 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
+
+ datum_a, datum_b ScmpDatum
+}
+
+// only used for testing
+func syscallResolveName(s string) (trap int) {
+ v := C.CString(s)
+ trap = int(C.seccomp_syscall_resolve_name(v))
+ C.free(unsafe.Pointer(v))
+
+ return
+}