helper/bwrap: integrate seccomp into helper interface
All checks were successful
Build / Create distribution (push) Successful in 1m36s
Test / Run NixOS test (push) Successful in 3m40s

This makes API usage much cleaner, and encapsulates all bwrap arguments in argsWt.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
Ophestra 2025-01-22 01:51:10 +09:00
parent 82029948e6
commit 9a239fa1a5
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
21 changed files with 224 additions and 187 deletions

View File

@ -66,7 +66,7 @@ func (p *Proxy) String() string {
return "(unsealed dbus proxy)" return "(unsealed dbus proxy)"
} }
func (p *Proxy) Bwrap() []string { func (p *Proxy) BwrapStatic() []string {
return p.bwrap.Args() return p.bwrap.Args()
} }

View File

@ -110,7 +110,7 @@ func (p *Proxy) Start(ready chan error, output io.Writer, sandbox bool) error {
bc.Bind(k, k) bc.Bind(k, k)
} }
h = helper.MustNewBwrap(bc, toolPath, p.seal, argF, nil) h = helper.MustNewBwrap(bc, toolPath, p.seal, argF, nil, nil)
cmd = h.Unwrap() cmd = h.Unwrap()
p.bwrap = bc p.bwrap = bc
} }

View File

@ -31,8 +31,6 @@ type ConfinementConfig struct {
Outer string `json:"home"` Outer string `json:"home"`
// bwrap sandbox confinement configuration // bwrap sandbox confinement configuration
Sandbox *SandboxConfig `json:"sandbox"` Sandbox *SandboxConfig `json:"sandbox"`
// seccomp syscall filter configuration
Syscall *SyscallConfig `json:"syscall"`
// extra acl entries to append // extra acl entries to append
ExtraPerms []*ExtraPermConfig `json:"extra_perms,omitempty"` ExtraPerms []*ExtraPermConfig `json:"extra_perms,omitempty"`
@ -47,14 +45,6 @@ type ConfinementConfig struct {
Enablements system.Enablements `json:"enablements"` Enablements system.Enablements `json:"enablements"`
} }
type SyscallConfig struct {
DenyDevel bool `json:"deny_devel"`
Multiarch bool `json:"multiarch"`
Linux32 bool `json:"linux32"`
Can bool `json:"can"`
Bluetooth bool `json:"bluetooth"`
}
type ExtraPermConfig struct { type ExtraPermConfig struct {
Ensure bool `json:"ensure,omitempty"` Ensure bool `json:"ensure,omitempty"`
Path string `json:"path"` Path string `json:"path"`

View File

@ -22,6 +22,8 @@ type SandboxConfig struct {
Net bool `json:"net,omitempty"` Net bool `json:"net,omitempty"`
// share all devices // share all devices
Dev bool `json:"dev,omitempty"` Dev bool `json:"dev,omitempty"`
// seccomp syscall filter policy
Syscall *bwrap.SyscallPolicy `json:"syscall"`
// do not run in new session // do not run in new session
NoNewSession bool `json:"no_new_session,omitempty"` NoNewSession bool `json:"no_new_session,omitempty"`
// map target user uid to privileged user uid in the user namespace // map target user uid to privileged user uid in the user namespace
@ -50,6 +52,10 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) {
return nil, errors.New("nil sandbox config") return nil, errors.New("nil sandbox config")
} }
if s.Syscall == nil {
fmsg.VPrintln("syscall filter not configured, PROCEED WITH CAUTION")
}
var uid int var uid int
if !s.MapRealUID { if !s.MapRealUID {
uid = 65534 uid = 65534
@ -69,6 +75,7 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) {
so this capacity should eliminate copies for most setups */ so this capacity should eliminate copies for most setups */
Filesystem: make([]bwrap.FSBuilder, 0, 256), Filesystem: make([]bwrap.FSBuilder, 0, 256),
Syscall: s.Syscall,
NewSession: !s.NoNewSession, NewSession: !s.NoNewSession,
DieWithParent: true, DieWithParent: true,
AsInit: true, AsInit: true,

View File

@ -9,25 +9,17 @@ import (
"sync" "sync"
"git.gensokyo.uk/security/fortify/helper/bwrap" "git.gensokyo.uk/security/fortify/helper/bwrap"
"git.gensokyo.uk/security/fortify/internal/proc"
) )
// BubblewrapName is the file name or path to bubblewrap. // BubblewrapName is the file name or path to bubblewrap.
var BubblewrapName = "bwrap" var BubblewrapName = "bwrap"
type BwrapExtraFile struct {
Name string
File *os.File
}
type bubblewrap struct { type bubblewrap struct {
// bwrap child file name // bwrap child file name
name string name string
// bwrap pipes // bwrap pipes
control *pipes control *pipes
// extra files with fd passed as argument
extra []BwrapExtraFile
// returns an array of arguments passed directly // returns an array of arguments passed directly
// to the child process spawned by bwrap // to the child process spawned by bwrap
argF func(argsFD, statFD int) []string argF func(argsFD, statFD int) []string
@ -54,14 +46,6 @@ func (b *bubblewrap) StartNotify(ready chan error) error {
return errors.New("exec: already started") return errors.New("exec: already started")
} }
// pass extra fd to bwrap
for _, e := range b.extra {
if e.File == nil {
continue
}
b.Cmd.Args = append(b.Cmd.Args, e.Name, strconv.Itoa(int(proc.ExtraFile(b.Cmd, e.File))))
}
// prepare bwrap pipe and args // prepare bwrap pipe and args
if argsFD, _, err := b.control.prepareCmd(b.Cmd); err != nil { if argsFD, _, err := b.control.prepareCmd(b.Cmd); err != nil {
return err return err
@ -130,9 +114,10 @@ func (b *bubblewrap) Unwrap() *exec.Cmd {
func MustNewBwrap( func MustNewBwrap(
conf *bwrap.Config, name string, conf *bwrap.Config, name string,
wt io.WriterTo, argF func(argsFD, statFD int) []string, wt io.WriterTo, argF func(argsFD, statFD int) []string,
extra []BwrapExtraFile, extraFiles []*os.File,
syncFd *os.File,
) Helper { ) Helper {
b, err := NewBwrap(conf, name, wt, argF, extra) b, err := NewBwrap(conf, name, wt, argF, extraFiles, syncFd)
if err != nil { if err != nil {
panic(err.Error()) panic(err.Error())
} else { } else {
@ -146,23 +131,27 @@ func MustNewBwrap(
func NewBwrap( func NewBwrap(
conf *bwrap.Config, name string, conf *bwrap.Config, name string,
wt io.WriterTo, argF func(argsFD, statFD int) []string, wt io.WriterTo, argF func(argsFD, statFD int) []string,
extra []BwrapExtraFile, extraFiles []*os.File,
syncFd *os.File,
) (Helper, error) { ) (Helper, error) {
b := new(bubblewrap) b := new(bubblewrap)
if args, err := NewCheckedArgs(conf.Args()); err != nil {
return nil, err
} else {
b.control = &pipes{args: args}
}
b.extra = extra
b.argF = argF b.argF = argF
b.name = name b.name = name
if wt != nil { if wt != nil {
b.controlPt = &pipes{args: wt} b.controlPt = &pipes{args: wt}
} }
b.Cmd = execCommand(BubblewrapName) b.Cmd = execCommand(BubblewrapName)
b.control = new(pipes)
args := conf.Args()
if fdArgs, err := conf.FDArgs(syncFd, &extraFiles); err != nil {
return nil, err
} else if b.control.args, err = NewCheckedArgs(append(args, fdArgs...)); err != nil {
return nil, err
} else {
b.Cmd.ExtraFiles = extraFiles
}
return b, nil return b, nil
} }

View File

@ -1,6 +1,13 @@
package bwrap package bwrap
import "encoding/gob" import (
"encoding/gob"
"os"
"slices"
"strconv"
"git.gensokyo.uk/security/fortify/internal/proc"
)
type Builder interface { type Builder interface {
Len() int Len() int
@ -12,6 +19,11 @@ type FSBuilder interface {
Builder Builder
} }
type FDBuilder interface {
Len() int
Append(args *[]string, extraFiles *[]*os.File) error
}
func init() { func init() {
gob.Register(new(pairF)) gob.Register(new(pairF))
gob.Register(new(stringF)) gob.Register(new(stringF))
@ -45,6 +57,33 @@ func (s stringF) Append(args *[]string) {
*args = append(*args, s[0], s[1]) *args = append(*args, s[0], s[1])
} }
type fileF struct {
name string
file *os.File
}
func (f *fileF) Len() int {
if f.file == nil {
return 0
}
return 2
}
func (f *fileF) Append(args *[]string, extraFiles *[]*os.File) error {
if f.file == nil {
return nil
}
extraFile(args, extraFiles, f.name, f.file)
return nil
}
func extraFile(args *[]string, extraFiles *[]*os.File, name string, f *os.File) {
if f == nil {
return
}
*args = append(*args, name, strconv.Itoa(int(proc.ExtraFileSlice(extraFiles, f))))
}
// Args returns a slice of bwrap args corresponding to c. // Args returns a slice of bwrap args corresponding to c.
func (c *Config) Args() (args []string) { func (c *Config) Args() (args []string) {
builders := []Builder{ builders := []Builder{
@ -75,3 +114,25 @@ func (c *Config) Args() (args []string) {
return return
} }
func (c *Config) FDArgs(syncFd *os.File, extraFiles *[]*os.File) (args []string, err error) {
builders := []FDBuilder{
&seccompBuilder{c},
&fileF{positionalArgs[SyncFd], syncFd},
}
argc := 0
for _, b := range builders {
argc += b.Len()
}
args = make([]string, 0, argc)
*extraFiles = slices.Grow(*extraFiles, len(builders))
for _, b := range builders {
if err = b.Append(&args, extraFiles); err != nil {
break
}
}
return
}

View File

@ -47,6 +47,10 @@ type Config struct {
// (--chmod OCTAL PATH) // (--chmod OCTAL PATH)
Chmod ChmodConfig `json:"chmod,omitempty"` Chmod ChmodConfig `json:"chmod,omitempty"`
// load and use seccomp rules from FD (not repeatable)
// (--seccomp FD)
Syscall *SyscallPolicy
// create a new terminal session // create a new terminal session
// (--new-session) // (--new-session)
NewSession bool `json:"new_session"` NewSession bool `json:"new_session"`
@ -70,7 +74,6 @@ type Config struct {
--file FD DEST Copy from FD to destination DEST --file FD DEST Copy from FD to destination DEST
--bind-data FD DEST Copy from FD to file which is bind-mounted on DEST --bind-data FD DEST Copy from FD to file which is bind-mounted on DEST
--ro-bind-data FD DEST Copy from FD to file which is readonly bind-mounted on DEST --ro-bind-data FD DEST Copy from FD to file which is readonly bind-mounted on DEST
--seccomp FD Load and use seccomp rules from FD (not repeatable)
--add-seccomp-fd FD Load and use seccomp rules from FD (repeatable) --add-seccomp-fd FD Load and use seccomp rules from FD (repeatable)
--block-fd FD Block on FD until some data to read is available --block-fd FD Block on FD until some data to read is available
--userns-block-fd FD Block on FD until the user namespace is ready --userns-block-fd FD Block on FD until the user namespace is ready

View File

@ -2,7 +2,7 @@
#define _GNU_SOURCE // CLONE_NEWUSER #define _GNU_SOURCE // CLONE_NEWUSER
#endif #endif
#include "export.h" #include "seccomp-export.h"
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <assert.h> #include <assert.h>

View File

@ -0,0 +1,95 @@
package bwrap
import (
"fmt"
"io"
"os"
"git.gensokyo.uk/security/fortify/internal/fmsg"
)
type SyscallPolicy struct {
DenyDevel bool `json:"deny_devel"`
Multiarch bool `json:"multiarch"`
Linux32 bool `json:"linux32"`
Can bool `json:"can"`
Bluetooth bool `json:"bluetooth"`
}
type seccompBuilder struct {
config *Config
}
func (s *seccompBuilder) Len() int {
if s == nil {
return 0
}
return 2
}
func (s *seccompBuilder) Append(args *[]string, extraFiles *[]*os.File) error {
if s == nil {
return nil
}
if f, err := s.config.resolveSeccomp(); err != nil {
return err
} else {
extraFile(args, extraFiles, positionalArgs[Seccomp], f)
return nil
}
}
func (c *Config) resolveSeccomp() (*os.File, error) {
if c.Syscall == nil {
return nil, nil
}
// resolve seccomp filter opts
var (
opts syscallOpts
optd []string
optCond = [...]struct {
v bool
o syscallOpts
d string
}{
{!c.UserNS, flagDenyNS, "denyns"},
{c.NewSession, flagDenyTTY, "denytty"},
{c.Syscall.DenyDevel, flagDenyDevel, "denydevel"},
{c.Syscall.Multiarch, flagMultiarch, "multiarch"},
{c.Syscall.Linux32, flagLinux32, "linux32"},
{c.Syscall.Can, flagCan, "can"},
{c.Syscall.Bluetooth, flagBluetooth, "bluetooth"},
}
)
if CPrintln != nil {
optd = make([]string, 1, len(optCond)+1)
optd[0] = "common"
}
for _, opt := range optCond {
if opt.v {
opts |= opt.o
if fmsg.Verbose() {
optd = append(optd, opt.d)
}
}
}
if CPrintln != nil {
CPrintln(fmt.Sprintf("seccomp flags: %s", optd))
}
// export seccomp filter to tmpfile
if f, err := tmpfile(); err != nil {
return nil, err
} else {
return f, exportAndSeek(f, opts)
}
}
func exportAndSeek(f *os.File, opts syscallOpts) error {
if err := exportFilter(f.Fd(), opts); err != nil {
return err
}
_, err := f.Seek(0, io.SeekStart)
return err
}

View File

@ -1,9 +1,9 @@
package shim package bwrap
/* /*
#cgo linux pkg-config: --static libseccomp #cgo linux pkg-config: --static libseccomp
#include "export.h" #include "seccomp-export.h"
*/ */
import "C" import "C"
import ( import (
@ -11,10 +11,10 @@ import (
"fmt" "fmt"
"os" "os"
"runtime" "runtime"
"git.gensokyo.uk/security/fortify/internal/fmsg"
) )
var CPrintln func(v ...any)
var resErr = [...]error{ var resErr = [...]error{
0: nil, 0: nil,
1: errors.New("seccomp_init failed"), 1: errors.New("seccomp_init failed"),
@ -77,5 +77,7 @@ func exportFilter(fd uintptr, opts syscallOpts) error {
//export F_println //export F_println
func F_println(v *C.char) { func F_println(v *C.char) {
fmsg.VPrintln(C.GoString(v)) if CPrintln != nil {
CPrintln(C.GoString(v))
}
} }

View File

@ -43,6 +43,9 @@ const (
Overlay Overlay
TmpOverlay TmpOverlay
ROOverlay ROOverlay
SyncFd
Seccomp
) )
var positionalArgs = [...]string{ var positionalArgs = [...]string{
@ -70,6 +73,9 @@ var positionalArgs = [...]string{
Overlay: "--overlay", Overlay: "--overlay",
TmpOverlay: "--tmp-overlay", TmpOverlay: "--tmp-overlay",
ROOverlay: "--ro-overlay", ROOverlay: "--ro-overlay",
SyncFd: "--sync-fd",
Seccomp: "--seccomp",
} }
type PermConfig[T FSBuilder] struct { type PermConfig[T FSBuilder] struct {

View File

@ -34,7 +34,7 @@ func TestBwrap(t *testing.T) {
h := helper.MustNewBwrap( h := helper.MustNewBwrap(
sc, "fortify", sc, "fortify",
argsWt, argF, argsWt, argF,
nil, nil, nil,
) )
if err := h.Start(); !errors.Is(err, os.ErrNotExist) { if err := h.Start(); !errors.Is(err, os.ErrNotExist) {
@ -47,7 +47,7 @@ func TestBwrap(t *testing.T) {
if got := helper.MustNewBwrap( if got := helper.MustNewBwrap(
sc, "fortify", sc, "fortify",
argsWt, argF, argsWt, argF,
nil, nil, nil,
); got == nil { ); got == nil {
t.Errorf("MustNewBwrap(%#v, %#v, %#v) got nil", t.Errorf("MustNewBwrap(%#v, %#v, %#v) got nil",
sc, argsWt, "fortify") sc, argsWt, "fortify")
@ -67,7 +67,7 @@ func TestBwrap(t *testing.T) {
helper.MustNewBwrap( helper.MustNewBwrap(
&bwrap.Config{Hostname: "\x00"}, "fortify", &bwrap.Config{Hostname: "\x00"}, "fortify",
nil, argF, nil, argF,
nil, nil, nil,
) )
}) })
@ -84,7 +84,7 @@ func TestBwrap(t *testing.T) {
helper.MustNewBwrap( helper.MustNewBwrap(
sc, "fortify", sc, "fortify",
nil, argF, nil, argF,
nil, nil, nil,
).StartNotify(make(chan error)))) ).StartNotify(make(chan error))))
}) })
@ -94,7 +94,7 @@ func TestBwrap(t *testing.T) {
h := helper.MustNewBwrap( h := helper.MustNewBwrap(
sc, "crash-test-dummy", sc, "crash-test-dummy",
nil, argFChecked, nil, argFChecked,
nil, nil, nil,
) )
cmd := h.Unwrap() cmd := h.Unwrap()
@ -127,6 +127,6 @@ func TestBwrap(t *testing.T) {
}) })
t.Run("implementation compliance", func(t *testing.T) { t.Run("implementation compliance", func(t *testing.T) {
testHelper(t, func() helper.Helper { return helper.MustNewBwrap(sc, "crash-test-dummy", argsWt, argF, nil) }) testHelper(t, func() helper.Helper { return helper.MustNewBwrap(sc, "crash-test-dummy", argsWt, argF, nil, nil) })
}) })
} }

View File

@ -39,6 +39,7 @@ var testCasesPd = []sealTestCase{
Net: true, Net: true,
UserNS: true, UserNS: true,
Clearenv: true, Clearenv: true,
Syscall: new(bwrap.SyscallPolicy),
Chdir: "/home/chronos", Chdir: "/home/chronos",
SetEnv: map[string]string{ SetEnv: map[string]string{
"HOME": "/home/chronos", "HOME": "/home/chronos",
@ -258,6 +259,7 @@ var testCasesPd = []sealTestCase{
UserNS: true, UserNS: true,
Chdir: "/home/chronos", Chdir: "/home/chronos",
Clearenv: true, Clearenv: true,
Syscall: new(bwrap.SyscallPolicy),
SetEnv: map[string]string{ SetEnv: map[string]string{
"DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/65534/bus", "DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/65534/bus",
"DBUS_SYSTEM_BUS_ADDRESS": "unix:path=/run/dbus/system_bus_socket", "DBUS_SYSTEM_BUS_ADDRESS": "unix:path=/run/dbus/system_bus_socket",

View File

@ -14,6 +14,7 @@ import (
"git.gensokyo.uk/security/fortify/acl" "git.gensokyo.uk/security/fortify/acl"
"git.gensokyo.uk/security/fortify/dbus" "git.gensokyo.uk/security/fortify/dbus"
"git.gensokyo.uk/security/fortify/fst" "git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/helper/bwrap"
"git.gensokyo.uk/security/fortify/internal/fmsg" "git.gensokyo.uk/security/fortify/internal/fmsg"
"git.gensokyo.uk/security/fortify/internal/linux" "git.gensokyo.uk/security/fortify/internal/linux"
"git.gensokyo.uk/security/fortify/internal/state" "git.gensokyo.uk/security/fortify/internal/state"
@ -52,8 +53,6 @@ type appSeal struct {
et system.Enablements et system.Enablements
// initial config gob encoding buffer // initial config gob encoding buffer
ct io.WriterTo ct io.WriterTo
// pass-through seccomp config from config
scmp *fst.SyscallConfig
// wayland socket direct access // wayland socket direct access
directWayland bool directWayland bool
// extra UpdatePerm ops // extra UpdatePerm ops
@ -196,6 +195,7 @@ func (a *app) Seal(config *fst.Config) error {
conf := &fst.SandboxConfig{ conf := &fst.SandboxConfig{
UserNS: true, UserNS: true,
Net: true, Net: true,
Syscall: new(bwrap.SyscallPolicy),
NoNewSession: true, NoNewSession: true,
AutoEtc: true, AutoEtc: true,
} }
@ -233,12 +233,6 @@ func (a *app) Seal(config *fst.Config) error {
conf.Filesystem = append(conf.Filesystem, &fst.FilesystemConfig{Src: "/dev/kvm", Device: true}) conf.Filesystem = append(conf.Filesystem, &fst.FilesystemConfig{Src: "/dev/kvm", Device: true})
config.Confinement.Sandbox = conf config.Confinement.Sandbox = conf
// ensure syscall filter
if config.Confinement.Syscall == nil {
config.Confinement.Syscall = new(fst.SyscallConfig)
config.Confinement.Syscall.Multiarch = true
}
} }
seal.directWayland = config.Confinement.Sandbox.DirectWayland seal.directWayland = config.Confinement.Sandbox.DirectWayland
if b, err := config.Confinement.Sandbox.Bwrap(a.os); err != nil { if b, err := config.Confinement.Sandbox.Bwrap(a.os); err != nil {
@ -259,9 +253,8 @@ func (a *app) Seal(config *fst.Config) error {
// initialise system interface with full uid // initialise system interface with full uid
seal.sys.I = system.New(seal.sys.user.uid) seal.sys.I = system.New(seal.sys.user.uid)
// pass through enablements and seccomp // pass through enablements
seal.et = config.Confinement.Enablements seal.et = config.Confinement.Enablements
seal.scmp = config.Confinement.Syscall
// this method calls all share methods in sequence // this method calls all share methods in sequence
if err := seal.setupShares([2]*dbus.Config{config.Confinement.SessionBus, config.Confinement.SystemBus}, a.os); err != nil { if err := seal.setupShares([2]*dbus.Config{config.Confinement.SessionBus, config.Confinement.SystemBus}, a.os); err != nil {

View File

@ -80,7 +80,6 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
Exec: shimExec, Exec: shimExec,
Bwrap: a.seal.sys.bwrap, Bwrap: a.seal.sys.bwrap,
Home: a.seal.sys.user.data, Home: a.seal.sys.user.data,
Syscall: a.seal.scmp,
Verbose: fmsg.Verbose(), Verbose: fmsg.Verbose(),
}); err != nil { }); err != nil {

View File

@ -2,8 +2,6 @@ package shim
import ( import (
"errors" "errors"
"flag"
"io"
"os" "os"
"path" "path"
"strconv" "strconv"
@ -20,7 +18,7 @@ import (
// everything beyond this point runs as unconstrained target user // everything beyond this point runs as unconstrained target user
// proceed with caution! // proceed with caution!
func Main(args []string) { func Main() {
// sharing stdout with fortify // sharing stdout with fortify
// USE WITH CAUTION // USE WITH CAUTION
fmsg.SetPrefix("shim") fmsg.SetPrefix("shim")
@ -31,46 +29,6 @@ func Main(args []string) {
panic("unreachable") 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 // receive setup payload
var ( var (
payload Payload payload Payload
@ -169,23 +127,19 @@ func Main(args []string) {
conf.Symlink("fortify", innerInit) conf.Symlink("fortify", innerInit)
helper.BubblewrapName = payload.Exec[0] // resolved bwrap path by parent helper.BubblewrapName = payload.Exec[0] // resolved bwrap path by parent
if fmsg.Verbose() {
bwrap.CPrintln = fmsg.Println
}
if b, err := helper.NewBwrap( if b, err := helper.NewBwrap(
conf, innerInit, conf, innerInit,
nil, func(int, int) []string { return make([]string, 0) }, nil, func(int, int) []string { return make([]string, 0) },
[]helper.BwrapExtraFile{ extraFiles,
// keep this fd open while sandbox is running syncFd,
// (--sync-fd FD)
{"--sync-fd", syncFd},
// load and use seccomp rules from FD (not repeatable)
// (--seccomp FD)
{"--seccomp", mustResolveSeccomp(payload.Bwrap, payload.Syscall)},
},
); err != nil { ); err != nil {
fmsg.Fatalf("malformed sandbox config: %v", err) fmsg.Fatalf("malformed sandbox config: %v", err)
} else { } else {
cmd := b.Unwrap() cmd := b.Unwrap()
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
cmd.ExtraFiles = extraFiles
// run and pass through exit code // run and pass through exit code
if err = b.Start(); err != nil { 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")
}
}

View File

@ -1,7 +1,6 @@
package shim package shim
import ( import (
"git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/helper/bwrap" "git.gensokyo.uk/security/fortify/helper/bwrap"
) )
@ -18,8 +17,6 @@ type Payload struct {
Home string Home string
// sync fd // sync fd
Sync *uintptr Sync *uintptr
// seccomp opts pass through
Syscall *fst.SyscallConfig
// verbosity pass through // verbosity pass through
Verbose bool Verbose bool

View File

@ -99,7 +99,7 @@ func (d *DBus) apply(_ *I) error {
} }
fmsg.VPrintln("starting message bus proxy:", d.proxy) fmsg.VPrintln("starting message bus proxy:", d.proxy)
if fmsg.Verbose() { // save the extra bwrap arg build when verbose logging is off if fmsg.Verbose() { // save the extra bwrap arg build when verbose logging is off
fmsg.VPrintln("message bus proxy bwrap args:", d.proxy.Bwrap()) fmsg.VPrintln("message bus proxy bwrap args:", d.proxy.BwrapStatic())
} }
// background wait for proxy instance and notify completion // background wait for proxy instance and notify completion

View File

@ -23,7 +23,8 @@ func Exec(p string) ([]*Entry, error) {
NewSession: true, NewSession: true,
DieWithParent: true, DieWithParent: true,
}).Bind("/", "/").DevTmpfs("/dev"), "ldd", }).Bind("/", "/").DevTmpfs("/dev"), "ldd",
nil, func(_, _ int) []string { return []string{p} }, nil, nil, func(_, _ int) []string { return []string{p} },
nil, nil,
); err != nil { ); err != nil {
return nil, err return nil, err
} else { } else {

View File

@ -291,7 +291,7 @@ func main() {
// internal commands // internal commands
case "shim": case "shim":
shim.Main(args) shim.Main()
fmsg.Exit(0) fmsg.Exit(0)
case "init": case "init":
init0.Main() init0.Main()