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>
This commit is contained in:
parent
82029948e6
commit
9a239fa1a5
@ -66,7 +66,7 @@ func (p *Proxy) String() string {
|
||||
return "(unsealed dbus proxy)"
|
||||
}
|
||||
|
||||
func (p *Proxy) Bwrap() []string {
|
||||
func (p *Proxy) BwrapStatic() []string {
|
||||
return p.bwrap.Args()
|
||||
}
|
||||
|
||||
|
@ -110,7 +110,7 @@ func (p *Proxy) Start(ready chan error, output io.Writer, sandbox bool) error {
|
||||
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()
|
||||
p.bwrap = bc
|
||||
}
|
||||
|
@ -31,8 +31,6 @@ type ConfinementConfig struct {
|
||||
Outer string `json:"home"`
|
||||
// bwrap sandbox confinement configuration
|
||||
Sandbox *SandboxConfig `json:"sandbox"`
|
||||
// seccomp syscall filter configuration
|
||||
Syscall *SyscallConfig `json:"syscall"`
|
||||
// extra acl entries to append
|
||||
ExtraPerms []*ExtraPermConfig `json:"extra_perms,omitempty"`
|
||||
|
||||
@ -47,14 +45,6 @@ type ConfinementConfig struct {
|
||||
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 {
|
||||
Ensure bool `json:"ensure,omitempty"`
|
||||
Path string `json:"path"`
|
||||
|
@ -22,6 +22,8 @@ type SandboxConfig struct {
|
||||
Net bool `json:"net,omitempty"`
|
||||
// share all devices
|
||||
Dev bool `json:"dev,omitempty"`
|
||||
// seccomp syscall filter policy
|
||||
Syscall *bwrap.SyscallPolicy `json:"syscall"`
|
||||
// do not run in new session
|
||||
NoNewSession bool `json:"no_new_session,omitempty"`
|
||||
// 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")
|
||||
}
|
||||
|
||||
if s.Syscall == nil {
|
||||
fmsg.VPrintln("syscall filter not configured, PROCEED WITH CAUTION")
|
||||
}
|
||||
|
||||
var uid int
|
||||
if !s.MapRealUID {
|
||||
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 */
|
||||
Filesystem: make([]bwrap.FSBuilder, 0, 256),
|
||||
|
||||
Syscall: s.Syscall,
|
||||
NewSession: !s.NoNewSession,
|
||||
DieWithParent: true,
|
||||
AsInit: true,
|
||||
|
@ -9,25 +9,17 @@ import (
|
||||
"sync"
|
||||
|
||||
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||
"git.gensokyo.uk/security/fortify/internal/proc"
|
||||
)
|
||||
|
||||
// BubblewrapName is the file name or path to bubblewrap.
|
||||
var BubblewrapName = "bwrap"
|
||||
|
||||
type BwrapExtraFile struct {
|
||||
Name string
|
||||
File *os.File
|
||||
}
|
||||
|
||||
type bubblewrap struct {
|
||||
// bwrap child file name
|
||||
name string
|
||||
|
||||
// bwrap pipes
|
||||
control *pipes
|
||||
// extra files with fd passed as argument
|
||||
extra []BwrapExtraFile
|
||||
// returns an array of arguments passed directly
|
||||
// to the child process spawned by bwrap
|
||||
argF func(argsFD, statFD int) []string
|
||||
@ -54,14 +46,6 @@ func (b *bubblewrap) StartNotify(ready chan error) error {
|
||||
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
|
||||
if argsFD, _, err := b.control.prepareCmd(b.Cmd); err != nil {
|
||||
return err
|
||||
@ -130,9 +114,10 @@ func (b *bubblewrap) Unwrap() *exec.Cmd {
|
||||
func MustNewBwrap(
|
||||
conf *bwrap.Config, name string,
|
||||
wt io.WriterTo, argF func(argsFD, statFD int) []string,
|
||||
extra []BwrapExtraFile,
|
||||
extraFiles []*os.File,
|
||||
syncFd *os.File,
|
||||
) Helper {
|
||||
b, err := NewBwrap(conf, name, wt, argF, extra)
|
||||
b, err := NewBwrap(conf, name, wt, argF, extraFiles, syncFd)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
} else {
|
||||
@ -146,23 +131,27 @@ func MustNewBwrap(
|
||||
func NewBwrap(
|
||||
conf *bwrap.Config, name string,
|
||||
wt io.WriterTo, argF func(argsFD, statFD int) []string,
|
||||
extra []BwrapExtraFile,
|
||||
extraFiles []*os.File,
|
||||
syncFd *os.File,
|
||||
) (Helper, error) {
|
||||
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.name = name
|
||||
if wt != nil {
|
||||
b.controlPt = &pipes{args: wt}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -1,6 +1,13 @@
|
||||
package bwrap
|
||||
|
||||
import "encoding/gob"
|
||||
import (
|
||||
"encoding/gob"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
|
||||
"git.gensokyo.uk/security/fortify/internal/proc"
|
||||
)
|
||||
|
||||
type Builder interface {
|
||||
Len() int
|
||||
@ -12,6 +19,11 @@ type FSBuilder interface {
|
||||
Builder
|
||||
}
|
||||
|
||||
type FDBuilder interface {
|
||||
Len() int
|
||||
Append(args *[]string, extraFiles *[]*os.File) error
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register(new(pairF))
|
||||
gob.Register(new(stringF))
|
||||
@ -45,6 +57,33 @@ func (s stringF) Append(args *[]string) {
|
||||
*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.
|
||||
func (c *Config) Args() (args []string) {
|
||||
builders := []Builder{
|
||||
@ -75,3 +114,25 @@ func (c *Config) Args() (args []string) {
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -47,6 +47,10 @@ type Config struct {
|
||||
// (--chmod OCTAL PATH)
|
||||
Chmod ChmodConfig `json:"chmod,omitempty"`
|
||||
|
||||
// load and use seccomp rules from FD (not repeatable)
|
||||
// (--seccomp FD)
|
||||
Syscall *SyscallPolicy
|
||||
|
||||
// create a new terminal session
|
||||
// (--new-session)
|
||||
NewSession bool `json:"new_session"`
|
||||
@ -70,7 +74,6 @@ type Config struct {
|
||||
--file FD DEST Copy from FD to destination 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
|
||||
--seccomp FD Load and use seccomp rules from FD (not 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
|
||||
--userns-block-fd FD Block on FD until the user namespace is ready
|
||||
|
@ -2,7 +2,7 @@
|
||||
#define _GNU_SOURCE // CLONE_NEWUSER
|
||||
#endif
|
||||
|
||||
#include "export.h"
|
||||
#include "seccomp-export.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
95
helper/bwrap/seccomp-resolve.go
Normal file
95
helper/bwrap/seccomp-resolve.go
Normal 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
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
package shim
|
||||
package bwrap
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: --static libseccomp
|
||||
|
||||
#include "export.h"
|
||||
#include "seccomp-export.h"
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
@ -11,10 +11,10 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||
)
|
||||
|
||||
var CPrintln func(v ...any)
|
||||
|
||||
var resErr = [...]error{
|
||||
0: nil,
|
||||
1: errors.New("seccomp_init failed"),
|
||||
@ -77,5 +77,7 @@ func exportFilter(fd uintptr, opts syscallOpts) error {
|
||||
|
||||
//export F_println
|
||||
func F_println(v *C.char) {
|
||||
fmsg.VPrintln(C.GoString(v))
|
||||
if CPrintln != nil {
|
||||
CPrintln(C.GoString(v))
|
||||
}
|
||||
}
|
@ -43,6 +43,9 @@ const (
|
||||
Overlay
|
||||
TmpOverlay
|
||||
ROOverlay
|
||||
|
||||
SyncFd
|
||||
Seccomp
|
||||
)
|
||||
|
||||
var positionalArgs = [...]string{
|
||||
@ -70,6 +73,9 @@ var positionalArgs = [...]string{
|
||||
Overlay: "--overlay",
|
||||
TmpOverlay: "--tmp-overlay",
|
||||
ROOverlay: "--ro-overlay",
|
||||
|
||||
SyncFd: "--sync-fd",
|
||||
Seccomp: "--seccomp",
|
||||
}
|
||||
|
||||
type PermConfig[T FSBuilder] struct {
|
||||
|
@ -34,7 +34,7 @@ func TestBwrap(t *testing.T) {
|
||||
h := helper.MustNewBwrap(
|
||||
sc, "fortify",
|
||||
argsWt, argF,
|
||||
nil,
|
||||
nil, nil,
|
||||
)
|
||||
|
||||
if err := h.Start(); !errors.Is(err, os.ErrNotExist) {
|
||||
@ -47,7 +47,7 @@ func TestBwrap(t *testing.T) {
|
||||
if got := helper.MustNewBwrap(
|
||||
sc, "fortify",
|
||||
argsWt, argF,
|
||||
nil,
|
||||
nil, nil,
|
||||
); got == nil {
|
||||
t.Errorf("MustNewBwrap(%#v, %#v, %#v) got nil",
|
||||
sc, argsWt, "fortify")
|
||||
@ -67,7 +67,7 @@ func TestBwrap(t *testing.T) {
|
||||
helper.MustNewBwrap(
|
||||
&bwrap.Config{Hostname: "\x00"}, "fortify",
|
||||
nil, argF,
|
||||
nil,
|
||||
nil, nil,
|
||||
)
|
||||
})
|
||||
|
||||
@ -84,7 +84,7 @@ func TestBwrap(t *testing.T) {
|
||||
helper.MustNewBwrap(
|
||||
sc, "fortify",
|
||||
nil, argF,
|
||||
nil,
|
||||
nil, nil,
|
||||
).StartNotify(make(chan error))))
|
||||
})
|
||||
|
||||
@ -94,7 +94,7 @@ func TestBwrap(t *testing.T) {
|
||||
h := helper.MustNewBwrap(
|
||||
sc, "crash-test-dummy",
|
||||
nil, argFChecked,
|
||||
nil,
|
||||
nil, nil,
|
||||
)
|
||||
cmd := h.Unwrap()
|
||||
|
||||
@ -127,6 +127,6 @@ func TestBwrap(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) })
|
||||
})
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ var testCasesPd = []sealTestCase{
|
||||
Net: true,
|
||||
UserNS: true,
|
||||
Clearenv: true,
|
||||
Syscall: new(bwrap.SyscallPolicy),
|
||||
Chdir: "/home/chronos",
|
||||
SetEnv: map[string]string{
|
||||
"HOME": "/home/chronos",
|
||||
@ -258,6 +259,7 @@ var testCasesPd = []sealTestCase{
|
||||
UserNS: true,
|
||||
Chdir: "/home/chronos",
|
||||
Clearenv: true,
|
||||
Syscall: new(bwrap.SyscallPolicy),
|
||||
SetEnv: map[string]string{
|
||||
"DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/65534/bus",
|
||||
"DBUS_SYSTEM_BUS_ADDRESS": "unix:path=/run/dbus/system_bus_socket",
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"git.gensokyo.uk/security/fortify/acl"
|
||||
"git.gensokyo.uk/security/fortify/dbus"
|
||||
"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/linux"
|
||||
"git.gensokyo.uk/security/fortify/internal/state"
|
||||
@ -52,8 +53,6 @@ type appSeal struct {
|
||||
et system.Enablements
|
||||
// initial config gob encoding buffer
|
||||
ct io.WriterTo
|
||||
// pass-through seccomp config from config
|
||||
scmp *fst.SyscallConfig
|
||||
// wayland socket direct access
|
||||
directWayland bool
|
||||
// extra UpdatePerm ops
|
||||
@ -196,6 +195,7 @@ func (a *app) Seal(config *fst.Config) error {
|
||||
conf := &fst.SandboxConfig{
|
||||
UserNS: true,
|
||||
Net: true,
|
||||
Syscall: new(bwrap.SyscallPolicy),
|
||||
NoNewSession: 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})
|
||||
|
||||
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
|
||||
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
|
||||
seal.sys.I = system.New(seal.sys.user.uid)
|
||||
|
||||
// pass through enablements and seccomp
|
||||
// pass through enablements
|
||||
seal.et = config.Confinement.Enablements
|
||||
seal.scmp = config.Confinement.Syscall
|
||||
|
||||
// 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 {
|
||||
|
@ -76,11 +76,10 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
|
||||
|
||||
// send payload
|
||||
if err = a.shim.Serve(shimSetupCtx, &shim.Payload{
|
||||
Argv: a.seal.command,
|
||||
Exec: shimExec,
|
||||
Bwrap: a.seal.sys.bwrap,
|
||||
Home: a.seal.sys.user.data,
|
||||
Syscall: a.seal.scmp,
|
||||
Argv: a.seal.command,
|
||||
Exec: shimExec,
|
||||
Bwrap: a.seal.sys.bwrap,
|
||||
Home: a.seal.sys.user.data,
|
||||
|
||||
Verbose: fmsg.Verbose(),
|
||||
}); err != nil {
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -99,7 +99,7 @@ func (d *DBus) apply(_ *I) error {
|
||||
}
|
||||
fmsg.VPrintln("starting message bus proxy:", d.proxy)
|
||||
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
|
||||
|
@ -23,7 +23,8 @@ func Exec(p string) ([]*Entry, error) {
|
||||
NewSession: true,
|
||||
DieWithParent: true,
|
||||
}).Bind("/", "/").DevTmpfs("/dev"), "ldd",
|
||||
nil, func(_, _ int) []string { return []string{p} }, nil,
|
||||
nil, func(_, _ int) []string { return []string{p} },
|
||||
nil, nil,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user