diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-03-14 22:42:40 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-03-14 22:42:40 +0900 |
| commit | 2647a71be1f287e50011a65653b9e9c806627899 (patch) | |
| tree | 252dd8865632df348b7573ca6f31c04e4cb6c9e5 /helper | |
| parent | 7c60a4d8e8baa77b42789c041838fa82dd7d36f9 (diff) | |
seccomp: move out of helper
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'helper')
| -rw-r--r-- | helper/bwrap/config_test.go | 2 | ||||
| -rw-r--r-- | helper/bwrap/seccomp.go | 2 | ||||
| -rw-r--r-- | helper/seccomp/api.go | 71 | ||||
| -rw-r--r-- | helper/seccomp/export.go | 58 | ||||
| -rw-r--r-- | helper/seccomp/export_test.go | 139 | ||||
| -rw-r--r-- | helper/seccomp/seccomp-build.c | 302 | ||||
| -rw-r--r-- | helper/seccomp/seccomp-build.h | 23 | ||||
| -rw-r--r-- | helper/seccomp/seccomp.go | 123 | ||||
| -rw-r--r-- | helper/seccomp/seccomp_test.go | 65 |
9 files changed, 2 insertions, 783 deletions
diff --git a/helper/bwrap/config_test.go b/helper/bwrap/config_test.go index e0f14511..678f49de 100644 --- a/helper/bwrap/config_test.go +++ b/helper/bwrap/config_test.go @@ -8,7 +8,7 @@ import ( "git.gensokyo.uk/security/fortify/helper/bwrap" "git.gensokyo.uk/security/fortify/helper/proc" - "git.gensokyo.uk/security/fortify/helper/seccomp" + "git.gensokyo.uk/security/fortify/seccomp" ) func TestConfig_Args(t *testing.T) { diff --git a/helper/bwrap/seccomp.go b/helper/bwrap/seccomp.go index f74015fd..8e55171b 100644 --- a/helper/bwrap/seccomp.go +++ b/helper/bwrap/seccomp.go @@ -5,7 +5,7 @@ import ( "strconv" "git.gensokyo.uk/security/fortify/helper/proc" - "git.gensokyo.uk/security/fortify/helper/seccomp" + "git.gensokyo.uk/security/fortify/seccomp" ) type SyscallPolicy struct { diff --git a/helper/seccomp/api.go b/helper/seccomp/api.go deleted file mode 100644 index 697b09f3..00000000 --- a/helper/seccomp/api.go +++ /dev/null @@ -1,71 +0,0 @@ -package seccomp - -import ( - "context" - "errors" - "syscall" - - "git.gensokyo.uk/security/fortify/helper/proc" -) - -// New returns an inactive Encoder instance. -func New(opts SyscallOpts) *Encoder { return &Encoder{newExporter(opts)} } - -// Load loads a filter into the kernel. -func Load(opts SyscallOpts) error { return buildFilter(-1, opts) } - -/* -An Encoder writes a BPF program to an output stream. - -Methods of Encoder are not safe for concurrent use. - -An Encoder must not be copied after first use. -*/ -type Encoder struct { - *exporter -} - -func (e *Encoder) Read(p []byte) (n int, err error) { - if err = e.prepare(); err != nil { - return - } - return e.r.Read(p) -} - -func (e *Encoder) Close() error { - if e.r == nil { - return syscall.EINVAL - } - - // this hangs if the cgo thread fails to exit - return errors.Join(e.closeWrite(), <-e.exportErr) -} - -// NewFile returns an instance of exporter implementing [proc.File]. -func NewFile(opts SyscallOpts) proc.File { return &File{opts: opts} } - -// File implements [proc.File] and provides access to the read end of exporter pipe. -type File struct { - opts SyscallOpts - proc.BaseFile -} - -func (f *File) ErrCount() int { return 2 } -func (f *File) Fulfill(ctx context.Context, dispatchErr func(error)) error { - e := newExporter(f.opts) - if err := e.prepare(); err != nil { - return err - } - f.Set(e.r) - go func() { - select { - case err := <-e.exportErr: - dispatchErr(nil) - dispatchErr(err) - case <-ctx.Done(): - dispatchErr(e.closeWrite()) - dispatchErr(<-e.exportErr) - } - }() - return nil -} diff --git a/helper/seccomp/export.go b/helper/seccomp/export.go deleted file mode 100644 index 95684235..00000000 --- a/helper/seccomp/export.go +++ /dev/null @@ -1,58 +0,0 @@ -package seccomp - -import ( - "os" - "runtime" - "sync" -) - -type exporter struct { - opts SyscallOpts - r, w *os.File - - prepareOnce sync.Once - prepareErr error - closeOnce sync.Once - closeErr error - exportErr <-chan error -} - -func (e *exporter) prepare() error { - e.prepareOnce.Do(func() { - if r, w, err := os.Pipe(); err != nil { - e.prepareErr = err - return - } else { - e.r, e.w = r, w - } - - ec := make(chan error, 1) - go func(fd uintptr) { - ec <- buildFilter(int(fd), e.opts) - close(ec) - _ = e.closeWrite() - runtime.KeepAlive(e.w) - }(e.w.Fd()) - e.exportErr = ec - runtime.SetFinalizer(e, (*exporter).closeWrite) - }) - return e.prepareErr -} - -func (e *exporter) closeWrite() error { - e.closeOnce.Do(func() { - if e.w == nil { - panic("closeWrite called on invalid exporter") - } - e.closeErr = e.w.Close() - - // no need for a finalizer anymore - runtime.SetFinalizer(e, nil) - }) - - return e.closeErr -} - -func newExporter(opts SyscallOpts) *exporter { - return &exporter{opts: opts} -} diff --git a/helper/seccomp/export_test.go b/helper/seccomp/export_test.go deleted file mode 100644 index ff6f250e..00000000 --- a/helper/seccomp/export_test.go +++ /dev/null @@ -1,139 +0,0 @@ -package seccomp_test - -import ( - "crypto/sha512" - "errors" - "io" - "log" - "slices" - "syscall" - "testing" - - "git.gensokyo.uk/security/fortify/helper/seccomp" -) - -func TestExport(t *testing.T) { - testCases := []struct { - name string - opts seccomp.SyscallOpts - want []byte - wantErr bool - }{ - {"compat", 0, []byte{ - 0x95, 0xec, 0x69, 0xd0, 0x17, 0x73, 0x3e, 0x07, - 0x21, 0x60, 0xe0, 0xda, 0x80, 0xfd, 0xeb, 0xec, - 0xdf, 0x27, 0xae, 0x81, 0x66, 0xf5, 0xe2, 0xa7, - 0x31, 0x27, 0x0c, 0x98, 0xea, 0x2d, 0x29, 0x46, - 0xcb, 0x52, 0x31, 0x02, 0x90, 0x63, 0x66, 0x8a, - 0xf2, 0x15, 0x87, 0x91, 0x55, 0xda, 0x21, 0xac, - 0xa7, 0x9b, 0x07, 0x0e, 0x04, 0xc0, 0xee, 0x9a, - 0xcd, 0xf5, 0x8f, 0x55, 0xcf, 0xa8, 0x15, 0xa5, - }, false}, - {"base", seccomp.FlagExt, []byte{ - 0xdc, 0x7f, 0x2e, 0x1c, 0x5e, 0x82, 0x9b, 0x79, - 0xeb, 0xb7, 0xef, 0xc7, 0x59, 0x15, 0x0f, 0x54, - 0xa8, 0x3a, 0x75, 0xc8, 0xdf, 0x6f, 0xee, 0x4d, - 0xce, 0x5d, 0xad, 0xc4, 0x73, 0x6c, 0x58, 0x5d, - 0x4d, 0xee, 0xbf, 0xeb, 0x3c, 0x79, 0x69, 0xaf, - 0x3a, 0x07, 0x7e, 0x90, 0xb7, 0x7b, 0xb4, 0x74, - 0x1d, 0xb0, 0x5d, 0x90, 0x99, 0x7c, 0x86, 0x59, - 0xb9, 0x58, 0x91, 0x20, 0x6a, 0xc9, 0x95, 0x2d, - }, false}, - {"everything", seccomp.FlagExt | - seccomp.FlagDenyNS | seccomp.FlagDenyTTY | seccomp.FlagDenyDevel | - seccomp.FlagMultiarch | seccomp.FlagLinux32 | seccomp.FlagCan | - seccomp.FlagBluetooth, []byte{ - 0xe9, 0x9d, 0xd3, 0x45, 0xe1, 0x95, 0x41, 0x34, - 0x73, 0xd3, 0xcb, 0xee, 0x07, 0xb4, 0xed, 0x57, - 0xb9, 0x08, 0xbf, 0xa8, 0x9e, 0xa2, 0x07, 0x2f, - 0xe9, 0x34, 0x82, 0x84, 0x7f, 0x50, 0xb5, 0xb7, - 0x58, 0xda, 0x17, 0xe7, 0x4c, 0xa2, 0xbb, 0xc0, - 0x08, 0x13, 0xde, 0x49, 0xa2, 0xb9, 0xbf, 0x83, - 0x4c, 0x02, 0x4e, 0xd4, 0x88, 0x50, 0xbe, 0x69, - 0xb6, 0x8a, 0x9a, 0x4c, 0x5f, 0x53, 0xa9, 0xdb, - }, false}, - {"strict", seccomp.FlagExt | - seccomp.FlagDenyNS | seccomp.FlagDenyTTY | seccomp.FlagDenyDevel, []byte{ - 0xe8, 0x80, 0x29, 0x8d, 0xf2, 0xbd, 0x67, 0x51, - 0xd0, 0x04, 0x0f, 0xc2, 0x1b, 0xc0, 0xed, 0x4c, - 0x00, 0xf9, 0x5d, 0xc0, 0xd7, 0xba, 0x50, 0x6c, - 0x24, 0x4d, 0x8b, 0x8c, 0xf6, 0x86, 0x6d, 0xba, - 0x8e, 0xf4, 0xa3, 0x32, 0x96, 0xf2, 0x87, 0xb6, - 0x6c, 0xcc, 0xc1, 0xd7, 0x8e, 0x97, 0x02, 0x65, - 0x97, 0xf8, 0x4c, 0xc7, 0xde, 0xc1, 0x57, 0x3e, - 0x14, 0x89, 0x60, 0xfb, 0xd3, 0x5c, 0xd7, 0x35, - }, false}, - {"strict compat", 0 | - seccomp.FlagDenyNS | seccomp.FlagDenyTTY | seccomp.FlagDenyDevel, []byte{ - 0x39, 0x87, 0x1b, 0x93, 0xff, 0xaf, 0xc8, 0xb9, - 0x79, 0xfc, 0xed, 0xc0, 0xb0, 0xc3, 0x7b, 0x9e, - 0x03, 0x92, 0x2f, 0x5b, 0x02, 0x74, 0x8d, 0xc5, - 0xc3, 0xc1, 0x7c, 0x92, 0x52, 0x7f, 0x6e, 0x02, - 0x2e, 0xde, 0x1f, 0x48, 0xbf, 0xf5, 0x92, 0x46, - 0xea, 0x45, 0x2c, 0x0d, 0x1d, 0xe5, 0x48, 0x27, - 0x80, 0x8b, 0x1a, 0x6f, 0x84, 0xf3, 0x2b, 0xbd, - 0xe1, 0xaa, 0x02, 0xae, 0x30, 0xee, 0xdc, 0xfa, - }, false}, - } - - buf := make([]byte, 8) - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - seccomp.CPrintln = log.Println - t.Cleanup(func() { seccomp.CPrintln = nil }) - - e := seccomp.New(tc.opts) - digest := sha512.New() - - if _, err := io.CopyBuffer(digest, e, buf); (err != nil) != tc.wantErr { - t.Errorf("Exporter: error = %v, wantErr %v", err, tc.wantErr) - return - } - if err := e.Close(); err != nil { - t.Errorf("Close: error = %v", err) - return - } - if got := digest.Sum(nil); slices.Compare(got, tc.want) != 0 { - t.Fatalf("Export() hash = %x, want %x", - got, tc.want) - return - } - }) - } - - t.Run("close without use", func(t *testing.T) { - e := seccomp.New(0) - if err := e.Close(); !errors.Is(err, syscall.EINVAL) { - t.Errorf("Close: error = %v", err) - return - } - }) - - t.Run("close partial read", func(t *testing.T) { - e := seccomp.New(0) - if _, err := e.Read(make([]byte, 0)); err != nil { - t.Errorf("Read: error = %v", err) - return - } - if err := e.Close(); err == nil || !errors.Is(err, syscall.ECANCELED) || !errors.Is(err, syscall.EBADF) { - t.Errorf("Close: error = %v", err) - return - } - }) -} - -func BenchmarkExport(b *testing.B) { - buf := make([]byte, 8) - for i := 0; i < b.N; i++ { - e := seccomp.New(seccomp.FlagExt | - seccomp.FlagDenyNS | seccomp.FlagDenyTTY | seccomp.FlagDenyDevel | - seccomp.FlagMultiarch | seccomp.FlagLinux32 | seccomp.FlagCan | - seccomp.FlagBluetooth) - if _, err := io.CopyBuffer(io.Discard, e, buf); err != nil { - b.Fatalf("cannot export: %v", err) - } - if err := e.Close(); err != nil { - b.Fatalf("cannot close exporter: %v", err) - } - } -} diff --git a/helper/seccomp/seccomp-build.c b/helper/seccomp/seccomp-build.c deleted file mode 100644 index 4b2114f4..00000000 --- a/helper/seccomp/seccomp-build.c +++ /dev/null @@ -1,302 +0,0 @@ -#ifndef _GNU_SOURCE -#define _GNU_SOURCE // CLONE_NEWUSER -#endif - -#include "seccomp-build.h" -#include <stdlib.h> -#include <stdio.h> -#include <assert.h> -#include <errno.h> -#include <sys/syscall.h> -#include <sys/socket.h> -#include <sys/ioctl.h> -#include <sys/personality.h> -#include <sched.h> - -#if (SCMP_VER_MAJOR < 2) || \ - (SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 5) || \ - (SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR == 5 && SCMP_VER_MICRO < 1) -#error This package requires libseccomp >= v2.5.1 -#endif - -struct f_syscall_act { - int syscall; - int m_errno; - struct scmp_arg_cmp *arg; -}; - -#define LEN(arr) (sizeof(arr) / sizeof((arr)[0])) - -#define SECCOMP_RULESET_ADD(ruleset) do { \ - if (opts & F_VERBOSE) F_println("adding seccomp ruleset \"" #ruleset "\""); \ - for (int i = 0; i < LEN(ruleset); i++) { \ - assert(ruleset[i].m_errno == EPERM || ruleset[i].m_errno == ENOSYS); \ - \ - if (ruleset[i].arg) \ - *ret_p = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ruleset[i].m_errno), ruleset[i].syscall, 1, *ruleset[i].arg); \ - else \ - *ret_p = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ruleset[i].m_errno), ruleset[i].syscall, 0); \ - \ - if (*ret_p == -EFAULT) { \ - res = 4; \ - goto out; \ - } else if (*ret_p < 0) { \ - res = 5; \ - goto out; \ - } \ - } \ -} while (0) - -int32_t f_build_filter(int *ret_p, int fd, uint32_t arch, uint32_t multiarch, f_syscall_opts opts) { - int32_t res = 0; // refer to resErr for meaning - int allow_multiarch = opts & F_MULTIARCH; - int allowed_personality = PER_LINUX; - - if (opts & F_LINUX32) - allowed_personality = PER_LINUX32; - - // flatpak commit 4c3bf179e2e4a2a298cd1db1d045adaf3f564532 - - struct f_syscall_act deny_common[] = { - // Block dmesg - {SCMP_SYS(syslog), EPERM}, - // Useless old syscall - {SCMP_SYS(uselib), EPERM}, - // Don't allow disabling accounting - {SCMP_SYS(acct), EPERM}, - // Don't allow reading current quota use - {SCMP_SYS(quotactl), EPERM}, - - // Don't allow access to the kernel keyring - {SCMP_SYS(add_key), EPERM}, - {SCMP_SYS(keyctl), EPERM}, - {SCMP_SYS(request_key), EPERM}, - - // Scary VM/NUMA ops - {SCMP_SYS(move_pages), EPERM}, - {SCMP_SYS(mbind), EPERM}, - {SCMP_SYS(get_mempolicy), EPERM}, - {SCMP_SYS(set_mempolicy), EPERM}, - {SCMP_SYS(migrate_pages), EPERM}, - }; - - // fortify: project-specific extensions - struct f_syscall_act deny_common_ext[] = { - // system calls for changing the system clock - {SCMP_SYS(adjtimex), EPERM}, - {SCMP_SYS(clock_adjtime), EPERM}, - {SCMP_SYS(clock_adjtime64), EPERM}, - {SCMP_SYS(clock_settime), EPERM}, - {SCMP_SYS(clock_settime64), EPERM}, - {SCMP_SYS(settimeofday), EPERM}, - - // loading and unloading of kernel modules - {SCMP_SYS(delete_module), EPERM}, - {SCMP_SYS(finit_module), EPERM}, - {SCMP_SYS(init_module), EPERM}, - - // system calls for rebooting and reboot preparation - {SCMP_SYS(kexec_file_load), EPERM}, - {SCMP_SYS(kexec_load), EPERM}, - {SCMP_SYS(reboot), EPERM}, - - // system calls for enabling/disabling swap devices - {SCMP_SYS(swapoff), EPERM}, - {SCMP_SYS(swapon), EPERM}, - }; - - struct f_syscall_act deny_ns[] = { - // Don't allow subnamespace setups: - {SCMP_SYS(unshare), EPERM}, - {SCMP_SYS(setns), EPERM}, - {SCMP_SYS(mount), EPERM}, - {SCMP_SYS(umount), EPERM}, - {SCMP_SYS(umount2), EPERM}, - {SCMP_SYS(pivot_root), EPERM}, - {SCMP_SYS(chroot), EPERM}, -#if defined(__s390__) || defined(__s390x__) || defined(__CRIS__) - // Architectures with CONFIG_CLONE_BACKWARDS2: the child stack - // and flags arguments are reversed so the flags come second - {SCMP_SYS(clone), EPERM, &SCMP_A1(SCMP_CMP_MASKED_EQ, CLONE_NEWUSER, CLONE_NEWUSER)}, -#else - // Normally the flags come first - {SCMP_SYS(clone), EPERM, &SCMP_A0(SCMP_CMP_MASKED_EQ, CLONE_NEWUSER, CLONE_NEWUSER)}, -#endif - - // seccomp can't look into clone3()'s struct clone_args to check whether - // the flags are OK, so we have no choice but to block clone3(). - // Return ENOSYS so user-space will fall back to clone(). - // (CVE-2021-41133; see also https://github.com/moby/moby/commit/9f6b562d) - {SCMP_SYS(clone3), ENOSYS}, - - // New mount manipulation APIs can also change our VFS. There's no - // legitimate reason to do these in the sandbox, so block all of them - // rather than thinking about which ones might be dangerous. - // (CVE-2021-41133) - {SCMP_SYS(open_tree), ENOSYS}, - {SCMP_SYS(move_mount), ENOSYS}, - {SCMP_SYS(fsopen), ENOSYS}, - {SCMP_SYS(fsconfig), ENOSYS}, - {SCMP_SYS(fsmount), ENOSYS}, - {SCMP_SYS(fspick), ENOSYS}, - {SCMP_SYS(mount_setattr), ENOSYS}, - }; - - // fortify: project-specific extensions - struct f_syscall_act deny_ns_ext[] = { - // changing file ownership - {SCMP_SYS(chown), EPERM}, - {SCMP_SYS(chown32), EPERM}, - {SCMP_SYS(fchown), EPERM}, - {SCMP_SYS(fchown32), EPERM}, - {SCMP_SYS(fchownat), EPERM}, - {SCMP_SYS(lchown), EPERM}, - {SCMP_SYS(lchown32), EPERM}, - - // system calls for changing user ID and group ID credentials - {SCMP_SYS(setgid), EPERM}, - {SCMP_SYS(setgid32), EPERM}, - {SCMP_SYS(setgroups), EPERM}, - {SCMP_SYS(setgroups32), EPERM}, - {SCMP_SYS(setregid), EPERM}, - {SCMP_SYS(setregid32), EPERM}, - {SCMP_SYS(setresgid), EPERM}, - {SCMP_SYS(setresgid32), EPERM}, - {SCMP_SYS(setresuid), EPERM}, - {SCMP_SYS(setresuid32), EPERM}, - {SCMP_SYS(setreuid), EPERM}, - {SCMP_SYS(setreuid32), EPERM}, - {SCMP_SYS(setuid), EPERM}, - {SCMP_SYS(setuid32), EPERM}, - }; - - struct f_syscall_act deny_tty[] = { - // Don't allow faking input to the controlling tty (CVE-2017-5226) - {SCMP_SYS(ioctl), EPERM, &SCMP_A1(SCMP_CMP_MASKED_EQ, 0xFFFFFFFFu, (int)TIOCSTI)}, - // In the unlikely event that the controlling tty is a Linux virtual - // console (/dev/tty2 or similar), copy/paste operations have an effect - // similar to TIOCSTI (CVE-2023-28100) - {SCMP_SYS(ioctl), EPERM, &SCMP_A1(SCMP_CMP_MASKED_EQ, 0xFFFFFFFFu, (int)TIOCLINUX)}, - }; - - struct f_syscall_act deny_devel[] = { - // Profiling operations; we expect these to be done by tools from outside - // the sandbox. In particular perf has been the source of many CVEs. - {SCMP_SYS(perf_event_open), EPERM}, - // Don't allow you to switch to bsd emulation or whatnot - {SCMP_SYS(personality), EPERM, &SCMP_A0(SCMP_CMP_NE, allowed_personality)}, - - {SCMP_SYS(ptrace), EPERM} - }; - - struct f_syscall_act deny_emu[] = { - // modify_ldt is a historic source of interesting information leaks, - // so it's disabled as a hardening measure. - // However, it is required to run old 16-bit applications - // as well as some Wine patches, so it's allowed in multiarch. - {SCMP_SYS(modify_ldt), EPERM}, - }; - - // fortify: project-specific extensions - struct f_syscall_act deny_emu_ext[] = { - {SCMP_SYS(subpage_prot), ENOSYS}, - {SCMP_SYS(switch_endian), ENOSYS}, - {SCMP_SYS(vm86), ENOSYS}, - {SCMP_SYS(vm86old), ENOSYS}, - }; - - // Blocklist all but unix, inet, inet6 and netlink - struct - { - int family; - f_syscall_opts flags_mask; - } socket_family_allowlist[] = { - // NOTE: Keep in numerical order - { AF_UNSPEC, 0 }, - { AF_LOCAL, 0 }, - { AF_INET, 0 }, - { AF_INET6, 0 }, - { AF_NETLINK, 0 }, - { AF_CAN, F_CAN }, - { AF_BLUETOOTH, F_BLUETOOTH }, - }; - - scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ALLOW); - if (ctx == NULL) { - res = 1; - goto out; - } else - errno = 0; - - // We only really need to handle arches on multiarch systems. - // If only one arch is supported the default is fine - if (arch != 0) { - // This *adds* the target arch, instead of replacing the - // native one. This is not ideal, because we'd like to only - // allow the target arch, but we can't really disallow the - // native arch at this point, because then bubblewrap - // couldn't continue running. - *ret_p = seccomp_arch_add(ctx, arch); - if (*ret_p < 0 && *ret_p != -EEXIST) { - res = 2; - goto out; - } - - if (allow_multiarch && multiarch != 0) { - *ret_p = seccomp_arch_add(ctx, multiarch); - if (*ret_p < 0 && *ret_p != -EEXIST) { - res = 3; - goto out; - } - } - } - - SECCOMP_RULESET_ADD(deny_common); - if (opts & F_DENY_NS) SECCOMP_RULESET_ADD(deny_ns); - if (opts & F_DENY_TTY) SECCOMP_RULESET_ADD(deny_tty); - if (opts & F_DENY_DEVEL) SECCOMP_RULESET_ADD(deny_devel); - if (!allow_multiarch) SECCOMP_RULESET_ADD(deny_emu); - if (opts & F_EXT) { - SECCOMP_RULESET_ADD(deny_common_ext); - if (opts & F_DENY_NS) SECCOMP_RULESET_ADD(deny_ns_ext); - if (!allow_multiarch) SECCOMP_RULESET_ADD(deny_emu_ext); - } - - // Socket filtering doesn't work on e.g. i386, so ignore failures here - // However, we need to user seccomp_rule_add_exact to avoid libseccomp doing - // something else: https://github.com/seccomp/libseccomp/issues/8 - int last_allowed_family = -1; - for (int i = 0; i < LEN(socket_family_allowlist); i++) { - if (socket_family_allowlist[i].flags_mask != 0 && - (socket_family_allowlist[i].flags_mask & opts) != socket_family_allowlist[i].flags_mask) - continue; - - for (int disallowed = last_allowed_family + 1; disallowed < socket_family_allowlist[i].family; disallowed++) { - // Blocklist the in-between valid families - seccomp_rule_add_exact(ctx, SCMP_ACT_ERRNO(EAFNOSUPPORT), SCMP_SYS(socket), 1, SCMP_A0(SCMP_CMP_EQ, disallowed)); - } - last_allowed_family = socket_family_allowlist[i].family; - } - // Blocklist the rest - seccomp_rule_add_exact(ctx, SCMP_ACT_ERRNO(EAFNOSUPPORT), SCMP_SYS(socket), 1, SCMP_A0(SCMP_CMP_GE, last_allowed_family + 1)); - - if (fd < 0) { - *ret_p = seccomp_load(ctx); - if (*ret_p != 0) { - res = 7; - goto out; - } - } else { - *ret_p = seccomp_export_bpf(ctx, fd); - if (*ret_p != 0) { - res = 6; - goto out; - } - } - -out: - if (ctx) - seccomp_release(ctx); - - return res; -} diff --git a/helper/seccomp/seccomp-build.h b/helper/seccomp/seccomp-build.h deleted file mode 100644 index 7f1be761..00000000 --- a/helper/seccomp/seccomp-build.h +++ /dev/null @@ -1,23 +0,0 @@ -#include <stdint.h> -#include <seccomp.h> - -#if (SCMP_VER_MAJOR < 2) || \ - (SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 5) || \ - (SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR == 5 && SCMP_VER_MICRO < 1) -#error This package requires libseccomp >= v2.5.1 -#endif - -typedef enum { - F_VERBOSE = 1 << 0, - F_EXT = 1 << 1, - F_DENY_NS = 1 << 2, - F_DENY_TTY = 1 << 3, - F_DENY_DEVEL = 1 << 4, - F_MULTIARCH = 1 << 5, - F_LINUX32 = 1 << 6, - F_CAN = 1 << 7, - F_BLUETOOTH = 1 << 8, -} f_syscall_opts; - -extern void F_println(char *v); -int32_t f_build_filter(int *ret_p, int fd, uint32_t arch, uint32_t multiarch, f_syscall_opts opts);
\ No newline at end of file diff --git a/helper/seccomp/seccomp.go b/helper/seccomp/seccomp.go deleted file mode 100644 index 05c314c9..00000000 --- a/helper/seccomp/seccomp.go +++ /dev/null @@ -1,123 +0,0 @@ -package seccomp - -/* -#cgo linux pkg-config: --static libseccomp - -#include "seccomp-build.h" -*/ -import "C" -import ( - "errors" - "fmt" - "runtime" - "syscall" -) - -var CPrintln func(v ...any) - -// 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)) -} - -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", -} - -type SyscallOpts = C.f_syscall_opts - -const ( - flagVerbose SyscallOpts = C.F_VERBOSE - // FlagExt are project-specific extensions. - FlagExt SyscallOpts = C.F_EXT - // FlagDenyNS denies namespace setup syscalls. - FlagDenyNS SyscallOpts = C.F_DENY_NS - // FlagDenyTTY denies faking input. - FlagDenyTTY SyscallOpts = C.F_DENY_TTY - // FlagDenyDevel denies development-related syscalls. - FlagDenyDevel SyscallOpts = C.F_DENY_DEVEL - // FlagMultiarch allows multiarch/emulation. - FlagMultiarch SyscallOpts = C.F_MULTIARCH - // FlagLinux32 sets PER_LINUX32. - FlagLinux32 SyscallOpts = C.F_LINUX32 - // FlagCan allows AF_CAN. - FlagCan SyscallOpts = C.F_CAN - // FlagBluetooth allows AF_BLUETOOTH. - FlagBluetooth SyscallOpts = C.F_BLUETOOTH -) - -func buildFilter(fd int, opts SyscallOpts) error { - 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 - } - - // this removes repeated transitions between C and Go execution - // when producing log output via F_println and CPrintln is nil - if CPrintln != nil { - opts |= flagVerbose - } - - var ret C.int - res, err := C.f_build_filter(&ret, C.int(fd), arch, multiarch, opts) - if prefix := resPrefix[res]; prefix != "" { - return &LibraryError{ - prefix, - -syscall.Errno(ret), - err, - } - } - return err -} - -//export F_println -func F_println(v *C.char) { - if CPrintln != nil { - CPrintln(C.GoString(v)) - } -} diff --git a/helper/seccomp/seccomp_test.go b/helper/seccomp/seccomp_test.go deleted file mode 100644 index c04179c1..00000000 --- a/helper/seccomp/seccomp_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package seccomp_test - -import ( - "errors" - "runtime" - "syscall" - "testing" - - "git.gensokyo.uk/security/fortify/helper/seccomp" -) - -func TestLibraryError(t *testing.T) { - testCases := []struct { - name string - sample *seccomp.LibraryError - want string - wantIs bool - compare error - }{ - { - "full", - &seccomp.LibraryError{Prefix: "seccomp_export_bpf failed", Seccomp: syscall.ECANCELED, Errno: syscall.EBADF}, - "seccomp_export_bpf failed: operation canceled (bad file descriptor)", - true, - &seccomp.LibraryError{Prefix: "seccomp_export_bpf failed", Seccomp: syscall.ECANCELED, Errno: syscall.EBADF}, - }, - { - "errno only", - &seccomp.LibraryError{Prefix: "seccomp_init failed", Errno: syscall.ENOMEM}, - "seccomp_init failed: cannot allocate memory", - false, - nil, - }, - { - "seccomp only", - &seccomp.LibraryError{Prefix: "internal libseccomp failure", Seccomp: syscall.EFAULT}, - "internal libseccomp failure: bad address", - true, - syscall.EFAULT, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - if errors.Is(tc.sample, tc.compare) != tc.wantIs { - t.Errorf("errors.Is(%#v, %#v) did not return %v", - tc.sample, tc.compare, tc.wantIs) - } - - if got := tc.sample.Error(); got != tc.want { - t.Errorf("Error: %q, want %q", - got, tc.want) - } - }) - } - - t.Run("invalid", func(t *testing.T) { - wantPanic := "invalid libseccomp error" - defer func() { - if r := recover(); r != wantPanic { - t.Errorf("panic: %q, want %q", r, wantPanic) - } - }() - runtime.KeepAlive(new(seccomp.LibraryError).Error()) - }) -} |
