aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/proc
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-01-22 01:51:10 +0900
committerOphestra <cat@gensokyo.uk>2025-01-22 01:52:57 +0900
commit9a239fa1a5ad2ff248ca7a9d39342f66926c9fef (patch)
tree09efadb2ddf4eaeaf153d7e4b7d73e1b20d2fc1a /internal/proc
parent82029948e6d8d047edc02ccff354e16c419e5742 (diff)
helper/bwrap: integrate seccomp into helper interface
This makes API usage much cleaner, and encapsulates all bwrap arguments in argsWt. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/proc')
-rw-r--r--internal/proc/priv/shim/export.c254
-rw-r--r--internal/proc/priv/shim/export.h22
-rw-r--r--internal/proc/priv/shim/main.go120
-rw-r--r--internal/proc/priv/shim/payload.go3
-rw-r--r--internal/proc/priv/shim/seccomp.go81
5 files changed, 6 insertions, 474 deletions
diff --git a/internal/proc/priv/shim/export.c b/internal/proc/priv/shim/export.c
deleted file mode 100644
index 404dd135..00000000
--- a/internal/proc/priv/shim/export.c
+++ /dev/null
@@ -1,254 +0,0 @@
-#ifndef _GNU_SOURCE
-#define _GNU_SOURCE // CLONE_NEWUSER
-#endif
-
-#include "export.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 { \
- 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 = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ruleset[i].m_errno), ruleset[i].syscall, 1, *ruleset[i].arg); \
- else \
- ret = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ruleset[i].m_errno), ruleset[i].syscall, 0); \
- \
- if (ret == -EFAULT) { \
- res = 4; \
- goto out; \
- } else if (ret < 0) { \
- res = 5; \
- errno = -ret; \
- goto out; \
- } \
- } \
-} while (0)
-
-
-int f_tmpfile_fd() {
- FILE *f = tmpfile();
- if (f == NULL)
- return -1;
- return fileno(f);
-}
-
-int32_t f_export_bpf(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},
- };
-
- 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},
- };
-
- 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}
- };
-
- // 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;
-
- int ret;
-
- // 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 = seccomp_arch_add(ctx, arch);
- if (ret < 0 && ret != -EEXIST) {
- res = 2;
- errno = -ret;
- goto out;
- }
-
- if (allow_multiarch && multiarch != 0) {
- ret = seccomp_arch_add(ctx, multiarch);
- if (ret < 0 && ret != -EEXIST) {
- res = 3;
- errno = -ret;
- 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) {
- F_println("disabling modify_ldt");
-
- // 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.
- ret = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(modify_ldt), 0);
-
- // See above for the meaning of EFAULT.
- if (ret == -EFAULT) {
- // call fmsg here?
- res = 4;
- goto out;
- } else if (ret < 0) {
- res = 5;
- errno = -ret;
- goto out;
- }
- }
-
- // 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));
-
- ret = seccomp_export_bpf(ctx, fd);
- if (ret != 0) {
- res = 6;
- errno = -ret;
- goto out;
- }
-
-out:
- if (ctx)
- seccomp_release(ctx);
-
- return res;
-}
diff --git a/internal/proc/priv/shim/export.h b/internal/proc/priv/shim/export.h
deleted file mode 100644
index 5df0dc65..00000000
--- a/internal/proc/priv/shim/export.h
+++ /dev/null
@@ -1,22 +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_DENY_NS = 1 << 0,
- F_DENY_TTY = 1 << 1,
- F_DENY_DEVEL = 1 << 2,
- F_MULTIARCH = 1 << 3,
- F_LINUX32 = 1 << 4,
- F_CAN = 1 << 5,
- F_BLUETOOTH = 1 << 6,
-} f_syscall_opts;
-
-extern void F_println(char *v);
-int f_tmpfile_fd();
-int32_t f_export_bpf(int fd, uint32_t arch, uint32_t multiarch, f_syscall_opts opts); \ No newline at end of file
diff --git a/internal/proc/priv/shim/main.go b/internal/proc/priv/shim/main.go
index a92a3cfa..85c5bafe 100644
--- a/internal/proc/priv/shim/main.go
+++ b/internal/proc/priv/shim/main.go
@@ -2,8 +2,6 @@ package shim
import (
"errors"
- "flag"
- "io"
"os"
"path"
"strconv"
@@ -20,7 +18,7 @@ import (
// everything beyond this point runs as unconstrained target user
// proceed with caution!
-func Main(args []string) {
+func Main() {
// sharing stdout with fortify
// USE WITH CAUTION
fmsg.SetPrefix("shim")
@@ -31,46 +29,6 @@ func Main(args []string) {
panic("unreachable")
}
- set := flag.NewFlagSet("shim", flag.ExitOnError)
-
- // debug: export seccomp filter
- debugExportSeccomp := set.String("export-seccomp", "", "export the seccomp filter to file")
- debugExportSeccompFlags := [...]struct {
- o syscallOpts
- v *bool
- }{
- {flagDenyNS, set.Bool("deny-ns", false, "deny namespace-related syscalls")},
- {flagDenyTTY, set.Bool("deny-tty", false, "deny faking input ioctls")},
- {flagDenyDevel, set.Bool("deny-devel", false, "deny development syscalls")},
- {flagMultiarch, set.Bool("multiarch", false, "allow multiarch")},
- {flagLinux32, set.Bool("linux32", false, "allow PER_LINUX32")},
- {flagCan, set.Bool("can", false, "allow AF_CAN")},
- {flagBluetooth, set.Bool("bluetooth", false, "AF_BLUETOOTH")},
- }
-
- // Ignore errors; set is set for ExitOnError.
- _ = set.Parse(args[1:])
-
- // debug: export seccomp filter
- if *debugExportSeccomp != "" {
- var opts syscallOpts
- for _, opt := range debugExportSeccompFlags {
- if *opt.v {
- opts |= opt.o
- }
- }
-
- if f, err := os.Create(*debugExportSeccomp); err != nil {
- fmsg.Fatalf("cannot create %q: %v", *debugExportSeccomp, err)
- } else {
- mustExportFilter(f, opts)
- if err = f.Close(); err != nil {
- fmsg.Fatalf("cannot close %q: %v", *debugExportSeccomp, err)
- }
- }
- fmsg.Exit(0)
- }
-
// receive setup payload
var (
payload Payload
@@ -169,23 +127,19 @@ func Main(args []string) {
conf.Symlink("fortify", innerInit)
helper.BubblewrapName = payload.Exec[0] // resolved bwrap path by parent
+ if fmsg.Verbose() {
+ bwrap.CPrintln = fmsg.Println
+ }
if b, err := helper.NewBwrap(
conf, innerInit,
nil, func(int, int) []string { return make([]string, 0) },
- []helper.BwrapExtraFile{
- // keep this fd open while sandbox is running
- // (--sync-fd FD)
- {"--sync-fd", syncFd},
- // load and use seccomp rules from FD (not repeatable)
- // (--seccomp FD)
- {"--seccomp", mustResolveSeccomp(payload.Bwrap, payload.Syscall)},
- },
+ extraFiles,
+ syncFd,
); err != nil {
fmsg.Fatalf("malformed sandbox config: %v", err)
} else {
cmd := b.Unwrap()
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
- cmd.ExtraFiles = extraFiles
// run and pass through exit code
if err = b.Start(); err != nil {
@@ -200,65 +154,3 @@ func Main(args []string) {
}
}
}
-
-func mustResolveSeccomp(bwrap *bwrap.Config, syscall *fst.SyscallConfig) (seccompFd *os.File) {
- if syscall == nil {
- fmsg.VPrintln("syscall filter not configured, PROCEED WITH CAUTION")
- return
- }
-
- // resolve seccomp filter opts
- var (
- opts syscallOpts
- optd []string
- optCond = [...]struct {
- v bool
- o syscallOpts
- d string
- }{
- {!bwrap.UserNS, flagDenyNS, "denyns"},
- {bwrap.NewSession, flagDenyTTY, "denytty"},
- {syscall.DenyDevel, flagDenyDevel, "denydevel"},
- {syscall.Multiarch, flagMultiarch, "multiarch"},
- {syscall.Linux32, flagLinux32, "linux32"},
- {syscall.Can, flagCan, "can"},
- {syscall.Bluetooth, flagBluetooth, "bluetooth"},
- }
- )
- if fmsg.Verbose() {
- optd = make([]string, 1, len(optCond)+1)
- optd[0] = "fortify"
- }
- for _, opt := range optCond {
- if opt.v {
- opts |= opt.o
- if fmsg.Verbose() {
- optd = append(optd, opt.d)
- }
- }
- }
- if fmsg.Verbose() {
- fmsg.VPrintf("seccomp flags: %s", optd)
- }
-
- // export seccomp filter to tmpfile
- if f, err := tmpfile(); err != nil {
- fmsg.Fatalf("cannot create tmpfile: %v", err)
- panic("unreachable")
- } else {
- mustExportFilter(f, opts)
- seccompFd = f
- return
- }
-}
-
-func mustExportFilter(f *os.File, opts syscallOpts) {
- if err := exportFilter(f.Fd(), opts); err != nil {
- fmsg.Fatalf("cannot export seccomp filter: %v", err)
- panic("unreachable")
- }
- if _, err := f.Seek(0, io.SeekStart); err != nil {
- fmsg.Fatalf("cannot lseek seccomp file: %v", err)
- panic("unreachable")
- }
-}
diff --git a/internal/proc/priv/shim/payload.go b/internal/proc/priv/shim/payload.go
index 2d6854f9..e659e3fd 100644
--- a/internal/proc/priv/shim/payload.go
+++ b/internal/proc/priv/shim/payload.go
@@ -1,7 +1,6 @@
package shim
import (
- "git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/helper/bwrap"
)
@@ -18,8 +17,6 @@ type Payload struct {
Home string
// sync fd
Sync *uintptr
- // seccomp opts pass through
- Syscall *fst.SyscallConfig
// verbosity pass through
Verbose bool
diff --git a/internal/proc/priv/shim/seccomp.go b/internal/proc/priv/shim/seccomp.go
deleted file mode 100644
index bf458742..00000000
--- a/internal/proc/priv/shim/seccomp.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package shim
-
-/*
-#cgo linux pkg-config: --static libseccomp
-
-#include "export.h"
-*/
-import "C"
-import (
- "errors"
- "fmt"
- "os"
- "runtime"
-
- "git.gensokyo.uk/security/fortify/internal/fmsg"
-)
-
-var resErr = [...]error{
- 0: nil,
- 1: errors.New("seccomp_init failed"),
- 2: errors.New("seccomp_arch_add failed"),
- 3: errors.New("seccomp_arch_add failed (multiarch)"),
- 4: errors.New("internal libseccomp failure"),
- 5: errors.New("seccomp_rule_add failed"),
- 6: errors.New("seccomp_export_bpf failed"),
-}
-
-type (
- syscallOpts = C.f_syscall_opts
-)
-
-const (
- flagDenyNS syscallOpts = C.F_DENY_NS
- flagDenyTTY syscallOpts = C.F_DENY_TTY
- flagDenyDevel syscallOpts = C.F_DENY_DEVEL
- flagMultiarch syscallOpts = C.F_MULTIARCH
- flagLinux32 syscallOpts = C.F_LINUX32
- flagCan syscallOpts = C.F_CAN
- flagBluetooth syscallOpts = C.F_BLUETOOTH
-)
-
-func tmpfile() (*os.File, error) {
- fd, err := C.f_tmpfile_fd()
- if err != nil {
- return nil, err
- }
- return os.NewFile(uintptr(fd), "tmpfile"), err
-}
-
-func exportFilter(fd uintptr, 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
- }
-
- res, err := C.f_export_bpf(C.int(fd), arch, multiarch, opts)
- if re := resErr[res]; re != nil {
- if err == nil {
- return re
- }
- return fmt.Errorf("%s: %v", re.Error(), err)
- }
- return err
-}
-
-//export F_println
-func F_println(v *C.char) {
- fmsg.VPrintln(C.GoString(v))
-}