diff --git a/helper/bwrap.go b/helper/bwrap.go index ec5c8ca..7197d42 100644 --- a/helper/bwrap.go +++ b/helper/bwrap.go @@ -20,7 +20,7 @@ type bubblewrap struct { name string // bwrap pipes - p *pipes + control *pipes // sync pipe sync *os.File // returns an array of arguments passed directly @@ -29,7 +29,7 @@ type bubblewrap struct { // pipes received by the child // nil if no pipes are required - cp *pipes + controlPt *pipes lock sync.RWMutex *exec.Cmd @@ -39,7 +39,7 @@ func (b *bubblewrap) StartNotify(ready chan error) error { b.lock.Lock() defer b.lock.Unlock() - if ready != nil && b.cp == nil { + if ready != nil && b.controlPt == nil { panic("attempted to start with status monitoring on a bwrap child initialised without pipes") } @@ -50,16 +50,16 @@ func (b *bubblewrap) StartNotify(ready chan error) error { } // prepare bwrap pipe and args - if argsFD, _, err := b.p.prepareCmd(b.Cmd); err != nil { + if argsFD, _, err := b.control.prepareCmd(b.Cmd); err != nil { return err } else { b.Cmd.Args = append(b.Cmd.Args, "--args", strconv.Itoa(argsFD), "--", b.name) } // prepare child args and pipes if enabled - if b.cp != nil { - b.cp.ready = ready - if argsFD, statFD, err := b.cp.prepareCmd(b.Cmd); err != nil { + if b.controlPt != nil { + b.controlPt.ready = ready + if argsFD, statFD, err := b.controlPt.prepareCmd(b.Cmd); err != nil { return err } else { b.Cmd.Args = append(b.Cmd.Args, b.argF(argsFD, statFD)...) @@ -70,7 +70,7 @@ func (b *bubblewrap) StartNotify(ready chan error) error { if ready != nil { b.Cmd.Env = append(b.Cmd.Env, FortifyHelper+"=1", FortifyStatus+"=1") - } else if b.cp != nil { + } else if b.controlPt != nil { b.Cmd.Env = append(b.Cmd.Env, FortifyHelper+"=1", FortifyStatus+"=0") } else { b.Cmd.Env = append(b.Cmd.Env, FortifyHelper+"=1", FortifyStatus+"=-1") @@ -85,13 +85,13 @@ func (b *bubblewrap) StartNotify(ready chan error) error { } // write bwrap args first - if err := b.p.readyWriteArgs(); err != nil { + if err := b.control.readyWriteArgs(); err != nil { return err } // write child args if enabled - if b.cp != nil { - if err := b.cp.readyWriteArgs(); err != nil { + if b.controlPt != nil { + if err := b.controlPt.readyWriteArgs(); err != nil { return err } } @@ -100,11 +100,11 @@ func (b *bubblewrap) StartNotify(ready chan error) error { } func (b *bubblewrap) Close() error { - if b.cp == nil { + if b.controlPt == nil { panic("attempted to close bwrap child initialised without pipes") } - return b.cp.closeStatus() + return b.controlPt.closeStatus() } func (b *bubblewrap) Start() error { @@ -136,14 +136,14 @@ func NewBwrap(conf *bwrap.Config, wt io.WriterTo, name string, argF func(argsFD, if args, err := NewCheckedArgs(conf.Args()); err != nil { return nil, err } else { - b.p = &pipes{args: args} + b.control = &pipes{args: args} } b.sync = conf.Sync() b.argF = argF b.name = name if wt != nil { - b.cp = &pipes{args: wt} + b.controlPt = &pipes{args: wt} } b.Cmd = execCommand(BubblewrapName) diff --git a/helper/bwrap/arg.static.awkward.go b/helper/bwrap/arg.static.awkward.go deleted file mode 100644 index 91fde87..0000000 --- a/helper/bwrap/arg.static.awkward.go +++ /dev/null @@ -1,23 +0,0 @@ -package bwrap - -const ( - Tmpfs = iota - Dir - Symlink - - OverlaySrc - Overlay - TmpOverlay - ROOverlay -) - -var awkwardArgs = [...]string{ - Tmpfs: "--tmpfs", - Dir: "--dir", - Symlink: "--symlink", - - OverlaySrc: "--overlay-src", - Overlay: "--overlay", - TmpOverlay: "--tmp-overlay", - ROOverlay: "--ro-overlay", -} diff --git a/helper/bwrap/arg.static.bool.go b/helper/bwrap/arg.static.bool.go deleted file mode 100644 index 6c3ca0c..0000000 --- a/helper/bwrap/arg.static.bool.go +++ /dev/null @@ -1,81 +0,0 @@ -package bwrap - -const ( - UnshareAll = iota - UnshareUser - UnshareIPC - UnsharePID - UnshareNet - UnshareUTS - UnshareCGroup - ShareNet - - UserNS - Clearenv - - NewSession - DieWithParent - AsInit -) - -var boolArgs = [...][]string{ - UnshareAll: {"--unshare-all", "--unshare-user"}, - UnshareUser: {"--unshare-user"}, - UnshareIPC: {"--unshare-ipc"}, - UnsharePID: {"--unshare-pid"}, - UnshareNet: {"--unshare-net"}, - UnshareUTS: {"--unshare-uts"}, - UnshareCGroup: {"--unshare-cgroup"}, - ShareNet: {"--share-net"}, - - UserNS: {"--disable-userns", "--assert-userns-disabled"}, - Clearenv: {"--clearenv"}, - - NewSession: {"--new-session"}, - DieWithParent: {"--die-with-parent"}, - AsInit: {"--as-pid-1"}, -} - -func (c *Config) boolArgs() Builder { - b := boolArg{ - UserNS: !c.UserNS, - Clearenv: c.Clearenv, - - NewSession: c.NewSession, - DieWithParent: c.DieWithParent, - AsInit: c.AsInit, - } - - if c.Unshare == nil { - b[UnshareAll] = true - b[ShareNet] = c.Net - } else { - b[UnshareUser] = c.Unshare.User - b[UnshareIPC] = c.Unshare.IPC - b[UnsharePID] = c.Unshare.PID - b[UnshareNet] = c.Unshare.Net - b[UnshareUTS] = c.Unshare.UTS - b[UnshareCGroup] = c.Unshare.CGroup - } - - return &b -} - -type boolArg [len(boolArgs)]bool - -func (b *boolArg) Len() (l int) { - for i, v := range b { - if v { - l += len(boolArgs[i]) - } - } - return -} - -func (b *boolArg) Append(args *[]string) { - for i, v := range b { - if v { - *args = append(*args, boolArgs[i]...) - } - } -} diff --git a/helper/bwrap/arg.static.int.go b/helper/bwrap/arg.static.int.go deleted file mode 100644 index cba3b1f..0000000 --- a/helper/bwrap/arg.static.int.go +++ /dev/null @@ -1,47 +0,0 @@ -package bwrap - -import "strconv" - -const ( - UID = iota - GID - Perms - Size -) - -var intArgs = [...]string{ - UID: "--uid", - GID: "--gid", - Perms: "--perms", - Size: "--size", -} - -func (c *Config) intArgs() Builder { - // Arg types: - // Perms - // are handled by the sequential builder - - return &intArg{ - UID: c.UID, - GID: c.GID, - } -} - -type intArg [len(intArgs)]*int - -func (n *intArg) Len() (l int) { - for _, v := range n { - if v != nil { - l += 2 - } - } - return -} - -func (n *intArg) Append(args *[]string) { - for i, v := range n { - if v != nil { - *args = append(*args, intArgs[i], strconv.Itoa(*v)) - } - } -} diff --git a/helper/bwrap/arg.static.pair.go b/helper/bwrap/arg.static.pair.go deleted file mode 100644 index 18d3415..0000000 --- a/helper/bwrap/arg.static.pair.go +++ /dev/null @@ -1,73 +0,0 @@ -package bwrap - -import ( - "slices" -) - -const ( - SetEnv = iota - - Bind - BindTry - DevBind - DevBindTry - ROBind - ROBindTry - - Chmod -) - -var pairArgs = [...]string{ - SetEnv: "--setenv", - - Bind: "--bind", - BindTry: "--bind-try", - DevBind: "--dev-bind", - DevBindTry: "--dev-bind-try", - ROBind: "--ro-bind", - ROBindTry: "--ro-bind-try", - - Chmod: "--chmod", -} - -func (c *Config) pairArgs() Builder { - var n pairArg - n[SetEnv] = make([][2]string, len(c.SetEnv)) - keys := make([]string, 0, len(c.SetEnv)) - for k := range c.SetEnv { - keys = append(keys, k) - } - slices.Sort(keys) - for i, k := range keys { - n[SetEnv][i] = [2]string{k, c.SetEnv[k]} - } - - // Arg types: - // Bind - // BindTry - // DevBind - // DevBindTry - // ROBind - // ROBindTry - // Chmod - // are handled by the sequential builder - - return &n -} - -type pairArg [len(pairArgs)][][2]string - -func (p *pairArg) Len() (l int) { - for _, v := range p { - l += len(v) * 3 - } - return -} - -func (p *pairArg) Append(args *[]string) { - for i, arg := range p { - for _, v := range arg { - *args = append(*args, pairArgs[i], v[0], v[1]) - } - } -} diff --git a/helper/bwrap/arg.static.string.go b/helper/bwrap/arg.static.string.go deleted file mode 100644 index e7f5c33..0000000 --- a/helper/bwrap/arg.static.string.go +++ /dev/null @@ -1,65 +0,0 @@ -package bwrap - -const ( - Hostname = iota - Chdir - UnsetEnv - LockFile - - RemountRO - Procfs - DevTmpfs - Mqueue -) - -var stringArgs = [...]string{ - Hostname: "--hostname", - Chdir: "--chdir", - UnsetEnv: "--unsetenv", - LockFile: "--lock-file", - - RemountRO: "--remount-ro", - Procfs: "--proc", - DevTmpfs: "--dev", - Mqueue: "--mqueue", -} - -func (c *Config) stringArgs() Builder { - n := stringArg{ - UnsetEnv: c.UnsetEnv, - LockFile: c.LockFile, - } - - if c.Hostname != "" { - n[Hostname] = []string{c.Hostname} - } - if c.Chdir != "" { - n[Chdir] = []string{c.Chdir} - } - - // Arg types: - // RemountRO - // Procfs - // DevTmpfs - // Mqueue - // are handled by the sequential builder - - return &n -} - -type stringArg [len(stringArgs)][]string - -func (s *stringArg) Len() (l int) { - for _, arg := range s { - l += len(arg) * 2 - } - return -} - -func (s *stringArg) Append(args *[]string) { - for i, arg := range s { - for _, v := range arg { - *args = append(*args, stringArgs[i], v) - } - } -} diff --git a/helper/bwrap/config.set.go b/helper/bwrap/builder.go similarity index 82% rename from helper/bwrap/config.set.go rename to helper/bwrap/builder.go index 8cbafda..bd67da1 100644 --- a/helper/bwrap/config.set.go +++ b/helper/bwrap/builder.go @@ -39,46 +39,60 @@ func (c *Config) Bind(src, dest string, opts ...bool) *Config { if dev { if try { - c.Filesystem = append(c.Filesystem, &pairF{pairArgs[DevBindTry], src, dest}) + c.Filesystem = append(c.Filesystem, &pairF{DevBindTry.Unwrap(), src, dest}) } else { - c.Filesystem = append(c.Filesystem, &pairF{pairArgs[DevBind], src, dest}) + c.Filesystem = append(c.Filesystem, &pairF{DevBind.Unwrap(), src, dest}) } return c } else if write { if try { - c.Filesystem = append(c.Filesystem, &pairF{pairArgs[BindTry], src, dest}) + c.Filesystem = append(c.Filesystem, &pairF{BindTry.Unwrap(), src, dest}) } else { - c.Filesystem = append(c.Filesystem, &pairF{pairArgs[Bind], src, dest}) + c.Filesystem = append(c.Filesystem, &pairF{Bind.Unwrap(), src, dest}) } return c } else { if try { - c.Filesystem = append(c.Filesystem, &pairF{pairArgs[ROBindTry], src, dest}) + c.Filesystem = append(c.Filesystem, &pairF{ROBindTry.Unwrap(), src, dest}) } else { - c.Filesystem = append(c.Filesystem, &pairF{pairArgs[ROBind], src, dest}) + c.Filesystem = append(c.Filesystem, &pairF{ROBind.Unwrap(), src, dest}) } return c } } +// Dir create dir in sandbox +// (--dir DEST) +func (c *Config) Dir(dest string) *Config { + c.Filesystem = append(c.Filesystem, &stringF{Dir.Unwrap(), dest}) + return c +} + // RemountRO remount path as readonly; does not recursively remount // (--remount-ro DEST) func (c *Config) RemountRO(dest string) *Config { - c.Filesystem = append(c.Filesystem, &stringF{stringArgs[RemountRO], dest}) + c.Filesystem = append(c.Filesystem, &stringF{RemountRO.Unwrap(), dest}) return c } // Procfs mount new procfs in sandbox // (--proc DEST) func (c *Config) Procfs(dest string) *Config { - c.Filesystem = append(c.Filesystem, &stringF{stringArgs[Procfs], dest}) + c.Filesystem = append(c.Filesystem, &stringF{Procfs.Unwrap(), dest}) return c } // DevTmpfs mount new dev in sandbox // (--dev DEST) func (c *Config) DevTmpfs(dest string) *Config { - c.Filesystem = append(c.Filesystem, &stringF{stringArgs[DevTmpfs], dest}) + c.Filesystem = append(c.Filesystem, &stringF{DevTmpfs.Unwrap(), dest}) + return c +} + +// Mqueue mount new mqueue in sandbox +// (--mqueue DEST) +func (c *Config) Mqueue(dest string) *Config { + c.Filesystem = append(c.Filesystem, &stringF{Mqueue.Unwrap(), dest}) return c } @@ -121,20 +135,6 @@ func (c *Config) Persist(dest, rwsrc, workdir string, src ...string) *Config { return c } -// Mqueue mount new mqueue in sandbox -// (--mqueue DEST) -func (c *Config) Mqueue(dest string) *Config { - c.Filesystem = append(c.Filesystem, &stringF{stringArgs[Mqueue], dest}) - return c -} - -// Dir create dir in sandbox -// (--dir DEST) -func (c *Config) Dir(dest string) *Config { - c.Filesystem = append(c.Filesystem, &stringF{awkwardArgs[Dir], dest}) - return c -} - // Symlink create symlink within sandbox // (--symlink SRC DEST) func (c *Config) Symlink(src, dest string, perm ...os.FileMode) *Config { diff --git a/helper/bwrap/config.go b/helper/bwrap/config.go index c1ab37c..6ca6779 100644 --- a/helper/bwrap/config.go +++ b/helper/bwrap/config.go @@ -1,17 +1,9 @@ package bwrap import ( - "encoding/gob" "os" - "strconv" ) -func init() { - gob.Register(new(PermConfig[SymlinkConfig])) - gob.Register(new(PermConfig[*TmpfsConfig])) - gob.Register(new(OverlayConfig)) -} - type Config struct { // unshare every namespace we support by default if nil // (--unshare-all) @@ -124,153 +116,3 @@ type UnshareConfig struct { // create new cgroup namespace CGroup bool `json:"cgroup"` } - -type PermConfig[T FSBuilder] struct { - // set permissions of next argument - // (--perms OCTAL) - Mode *os.FileMode `json:"mode,omitempty"` - // path to get the new permission - // (--bind-data, --file, etc.) - Inner T `json:"path"` -} - -func (p *PermConfig[T]) Path() string { - return p.Inner.Path() -} - -func (p *PermConfig[T]) Len() int { - if p.Mode != nil { - return p.Inner.Len() + 2 - } else { - return p.Inner.Len() - } -} - -func (p *PermConfig[T]) Append(args *[]string) { - if p.Mode != nil { - *args = append(*args, intArgs[Perms], strconv.FormatInt(int64(*p.Mode), 8)) - } - p.Inner.Append(args) -} - -type TmpfsConfig struct { - // set size of tmpfs - // (--size BYTES) - Size int `json:"size,omitempty"` - // mount point of new tmpfs - // (--tmpfs DEST) - Dir string `json:"dir"` -} - -func (t *TmpfsConfig) Path() string { - return t.Dir -} - -func (t *TmpfsConfig) Len() int { - if t.Size > 0 { - return 4 - } else { - return 2 - } -} - -func (t *TmpfsConfig) Append(args *[]string) { - if t.Size > 0 { - *args = append(*args, intArgs[Size], strconv.Itoa(t.Size)) - } - *args = append(*args, awkwardArgs[Tmpfs], t.Dir) -} - -type OverlayConfig struct { - /* - read files from SRC in the following overlay - (--overlay-src SRC) - */ - Src []string `json:"src,omitempty"` - - /* - mount overlayfs on DEST, with RWSRC as the host path for writes and - WORKDIR an empty directory on the same filesystem as RWSRC - (--overlay RWSRC WORKDIR DEST) - - if nil, mount overlayfs on DEST, with writes going to an invisible tmpfs - (--tmp-overlay DEST) - - if either strings are empty, mount overlayfs read-only on DEST - (--ro-overlay DEST) - */ - Persist *[2]string `json:"persist,omitempty"` - - /* - --overlay RWSRC WORKDIR DEST - - --tmp-overlay DEST - - --ro-overlay DEST - */ - Dest string `json:"dest"` -} - -func (o *OverlayConfig) Path() string { - return o.Dest -} - -func (o *OverlayConfig) Len() int { - // (--tmp-overlay DEST) or (--ro-overlay DEST) - p := 2 - // (--overlay RWSRC WORKDIR DEST) - if o.Persist != nil && o.Persist[0] != "" && o.Persist[1] != "" { - p = 4 - } - - return p + len(o.Src)*2 -} - -func (o *OverlayConfig) Append(args *[]string) { - // --overlay-src SRC - for _, src := range o.Src { - *args = append(*args, awkwardArgs[OverlaySrc], src) - } - - if o.Persist != nil { - if o.Persist[0] != "" && o.Persist[1] != "" { - // --overlay RWSRC WORKDIR - *args = append(*args, awkwardArgs[Overlay], o.Persist[0], o.Persist[1]) - } else { - // --ro-overlay - *args = append(*args, awkwardArgs[ROOverlay]) - } - } else { - // --tmp-overlay - *args = append(*args, awkwardArgs[TmpOverlay]) - } - - // DEST - *args = append(*args, o.Dest) -} - -type SymlinkConfig [2]string - -func (s SymlinkConfig) Path() string { - return s[1] -} - -func (s SymlinkConfig) Len() int { - return 3 -} - -func (s SymlinkConfig) Append(args *[]string) { - *args = append(*args, awkwardArgs[Symlink], s[0], s[1]) -} - -type ChmodConfig map[string]os.FileMode - -func (c ChmodConfig) Len() int { - return len(c) -} - -func (c ChmodConfig) Append(args *[]string) { - for path, mode := range c { - *args = append(*args, pairArgs[Chmod], strconv.FormatInt(int64(mode), 8), path) - } -} diff --git a/helper/bwrap/config_test.go b/helper/bwrap/config_test.go index afbcede..95749f0 100644 --- a/helper/bwrap/config_test.go +++ b/helper/bwrap/config_test.go @@ -1,6 +1,7 @@ package bwrap_test import ( + "os" "slices" "testing" @@ -13,6 +14,83 @@ func TestConfig_Args(t *testing.T) { conf *bwrap.Config want []string }{ + { + name: "bind", + conf: (new(bwrap.Config)). + Bind("/etc", "/.fortify/etc"). + Bind("/etc", "/.fortify/etc", true). + Bind("/run", "/.fortify/run", false, true). + Bind("/sys/devices", "/.fortify/sys/devices", true, true). + Bind("/dev/dri", "/.fortify/dev/dri", false, true, true). + Bind("/dev/dri", "/.fortify/dev/dri", true, true, true), + want: []string{ + "--unshare-all", "--unshare-user", + "--disable-userns", "--assert-userns-disabled", + // Bind("/etc", "/.fortify/etc") + "--ro-bind", "/etc", "/.fortify/etc", + // Bind("/etc", "/.fortify/etc", true) + "--ro-bind-try", "/etc", "/.fortify/etc", + // Bind("/run", "/.fortify/run", false, true) + "--bind", "/run", "/.fortify/run", + // Bind("/sys/devices", "/.fortify/sys/devices", true, true) + "--bind-try", "/sys/devices", "/.fortify/sys/devices", + // Bind("/dev/dri", "/.fortify/dev/dri", false, true, true) + "--dev-bind", "/dev/dri", "/.fortify/dev/dri", + // Bind("/dev/dri", "/.fortify/dev/dri", true, true, true) + "--dev-bind-try", "/dev/dri", "/.fortify/dev/dri", + }, + }, + { + name: "dir remount-ro proc dev mqueue", + conf: (new(bwrap.Config)). + Dir("/.fortify"). + RemountRO("/home"). + Procfs("/proc"). + DevTmpfs("/dev"). + Mqueue("/dev/mqueue"), + want: []string{ + "--unshare-all", "--unshare-user", + "--disable-userns", "--assert-userns-disabled", + // Dir("/.fortify") + "--dir", "/.fortify", + // RemountRO("/home") + "--remount-ro", "/home", + // Procfs("/proc") + "--proc", "/proc", + // DevTmpfs("/dev") + "--dev", "/dev", + // Mqueue("/dev/mqueue") + "--mqueue", "/dev/mqueue", + }, + }, + { + name: "tmpfs", + conf: (new(bwrap.Config)). + Tmpfs("/run/user", 8192). + Tmpfs("/run/dbus", 8192, 0755), + want: []string{ + "--unshare-all", "--unshare-user", + "--disable-userns", "--assert-userns-disabled", + // Tmpfs("/run/user", 8192) + "--size", "8192", "--tmpfs", "/run/user", + // Tmpfs("/run/dbus", 8192, 0755) + "--perms", "755", "--size", "8192", "--tmpfs", "/run/dbus", + }, + }, + { + name: "symlink", + conf: (new(bwrap.Config)). + Symlink("/.fortify/sbin/init", "/sbin/init"). + Symlink("/.fortify/sbin/init", "/sbin/init", 0755), + want: []string{ + "--unshare-all", "--unshare-user", + "--disable-userns", "--assert-userns-disabled", + // Symlink("/.fortify/sbin/init", "/sbin/init") + "--symlink", "/.fortify/sbin/init", "/sbin/init", + // Symlink("/.fortify/sbin/init", "/sbin/init", 0755) + "--perms", "755", "--symlink", "/.fortify/sbin/init", "/sbin/init", + }, + }, { name: "overlayfs", conf: (new(bwrap.Config)). @@ -32,6 +110,64 @@ func TestConfig_Args(t *testing.T) { "--overlay", "/data/data/org.chromium.Chromium/overlay/rwsrc", "/data/data/org.chromium.Chromium/workdir", "/nix", }, }, + { + name: "unshare", + conf: &bwrap.Config{Unshare: &bwrap.UnshareConfig{ + User: false, + IPC: false, + PID: false, + Net: false, + UTS: false, + CGroup: false, + }}, + want: []string{"--disable-userns", "--assert-userns-disabled"}, + }, + { + name: "uid gid sync", + conf: (new(bwrap.Config)). + SetUID(1971). + SetGID(100). + SetSync(os.Stdin), + want: []string{ + "--unshare-all", "--unshare-user", + "--disable-userns", "--assert-userns-disabled", + // SetUID(1971) + "--uid", "1971", + // SetGID(100) + "--gid", "100", + // SetSync(os.Stdin) + // this is set when the process is created + }, + }, + { + name: "hostname chdir setenv unsetenv lockfile chmod", + conf: &bwrap.Config{ + Hostname: "fortify", + Chdir: "/.fortify", + SetEnv: map[string]string{"FORTIFY_INIT": "/.fortify/sbin/init"}, + UnsetEnv: []string{"HOME", "HOST"}, + LockFile: []string{"/.fortify/lock"}, + Chmod: map[string]os.FileMode{"/.fortify/sbin/init": 0755}, + }, + want: []string{ + "--unshare-all", "--unshare-user", + "--disable-userns", "--assert-userns-disabled", + // Hostname: "fortify" + "--hostname", "fortify", + // Chdir: "/.fortify" + "--chdir", "/.fortify", + // UnsetEnv: []string{"HOME", "HOST"} + "--unsetenv", "HOME", + "--unsetenv", "HOST", + // LockFile: []string{"/.fortify/lock"}, + "--lock-file", "/.fortify/lock", + // SetEnv: map[string]string{"FORTIFY_INIT": "/.fortify/sbin/init"} + "--setenv", "FORTIFY_INIT", "/.fortify/sbin/init", + // Chmod: map[string]os.FileMode{"/.fortify/sbin/init": 0755} + "--chmod", "755", "/.fortify/sbin/init", + }, + }, + { name: "xdg-dbus-proxy constraint sample", conf: (&bwrap.Config{ @@ -90,148 +226,6 @@ func TestConfig_Args(t *testing.T) { "--ro-bind", "/etc", "/etc", }, }, - { - name: "fortify permissive default nixos", - conf: (&bwrap.Config{ - Unshare: nil, - Net: true, - UserNS: true, - Clearenv: true, - SetEnv: map[string]string{ - "HOME": "/home/chronos", - "TERM": "xterm-256color", - "FORTIFY_INIT": "3", - "XDG_RUNTIME_DIR": "/run/user/150", - "XDG_SESSION_CLASS": "user", - "XDG_SESSION_TYPE": "tty", - "SHELL": "/run/current-system/sw/bin/zsh", - "USER": "chronos", - }, - DieWithParent: true, - AsInit: true, - }).SetUID(65534).SetGID(65534). - Procfs("/proc").DevTmpfs("/dev").Mqueue("/dev/mqueue"). - Bind("/bin", "/bin", false, true). - Bind("/boot", "/boot", false, true). - Bind("/etc", "/etc", false, true). - Bind("/home", "/home", false, true). - Bind("/lib", "/lib", false, true). - Bind("/lib64", "/lib64", false, true). - Bind("/nix", "/nix", false, true). - Bind("/root", "/root", false, true). - Bind("/srv", "/srv", false, true). - Bind("/sys", "/sys", false, true). - Bind("/usr", "/usr", false, true). - Bind("/var", "/var", false, true). - Bind("/run/NetworkManager", "/run/NetworkManager", false, true). - Bind("/run/agetty.reload", "/run/agetty.reload", false, true). - Bind("/run/binfmt", "/run/binfmt", false, true). - Bind("/run/booted-system", "/run/booted-system", false, true). - Bind("/run/credentials", "/run/credentials", false, true). - Bind("/run/cryptsetup", "/run/cryptsetup", false, true). - Bind("/run/current-system", "/run/current-system", false, true). - Bind("/run/host", "/run/host", false, true). - Bind("/run/keys", "/run/keys", false, true). - Bind("/run/libvirt", "/run/libvirt", false, true). - Bind("/run/libvirtd.pid", "/run/libvirtd.pid", false, true). - Bind("/run/lock", "/run/lock", false, true). - Bind("/run/log", "/run/log", false, true). - Bind("/run/lvm", "/run/lvm", false, true). - Bind("/run/mount", "/run/mount", false, true). - Bind("/run/nginx", "/run/nginx", false, true). - Bind("/run/nscd", "/run/nscd", false, true). - Bind("/run/opengl-driver", "/run/opengl-driver", false, true). - Bind("/run/pppd", "/run/pppd", false, true). - Bind("/run/resolvconf", "/run/resolvconf", false, true). - Bind("/run/sddm", "/run/sddm", false, true). - Bind("/run/syncoid", "/run/syncoid", false, true). - Bind("/run/systemd", "/run/systemd", false, true). - Bind("/run/tmpfiles.d", "/run/tmpfiles.d", false, true). - Bind("/run/udev", "/run/udev", false, true). - Bind("/run/udisks2", "/run/udisks2", false, true). - Bind("/run/utmp", "/run/utmp", false, true). - Bind("/run/virtlogd.pid", "/run/virtlogd.pid", false, true). - Bind("/run/wrappers", "/run/wrappers", false, true). - Bind("/run/zed.pid", "/run/zed.pid", false, true). - Bind("/run/zed.state", "/run/zed.state", false, true). - Bind("/tmp/fortify.1971/tmpdir/150", "/tmp", false, true). - Tmpfs("/tmp/fortify.1971", 1048576). - Tmpfs("/run/user", 1048576). - Tmpfs("/run/user/150", 8388608). - Bind("/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/passwd", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/passwd"). - Bind("/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/group", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/group"). - Bind("/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/passwd", "/etc/passwd"). - Bind("/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/group", "/etc/group"). - Tmpfs("/var/run/nscd", 8192), - want: []string{ - "--unshare-all", "--unshare-user", "--share-net", - "--clearenv", "--die-with-parent", "--as-pid-1", - "--uid", "65534", - "--gid", "65534", - "--setenv", "FORTIFY_INIT", "3", - "--setenv", "HOME", "/home/chronos", - "--setenv", "SHELL", "/run/current-system/sw/bin/zsh", - "--setenv", "TERM", "xterm-256color", - "--setenv", "USER", "chronos", - "--setenv", "XDG_RUNTIME_DIR", "/run/user/150", - "--setenv", "XDG_SESSION_CLASS", "user", - "--setenv", "XDG_SESSION_TYPE", "tty", - "--proc", "/proc", "--dev", "/dev", - "--mqueue", "/dev/mqueue", - "--bind", "/bin", "/bin", - "--bind", "/boot", "/boot", - "--bind", "/etc", "/etc", - "--bind", "/home", "/home", - "--bind", "/lib", "/lib", - "--bind", "/lib64", "/lib64", - "--bind", "/nix", "/nix", - "--bind", "/root", "/root", - "--bind", "/srv", "/srv", - "--bind", "/sys", "/sys", - "--bind", "/usr", "/usr", - "--bind", "/var", "/var", - "--bind", "/run/NetworkManager", "/run/NetworkManager", - "--bind", "/run/agetty.reload", "/run/agetty.reload", - "--bind", "/run/binfmt", "/run/binfmt", - "--bind", "/run/booted-system", "/run/booted-system", - "--bind", "/run/credentials", "/run/credentials", - "--bind", "/run/cryptsetup", "/run/cryptsetup", - "--bind", "/run/current-system", "/run/current-system", - "--bind", "/run/host", "/run/host", - "--bind", "/run/keys", "/run/keys", - "--bind", "/run/libvirt", "/run/libvirt", - "--bind", "/run/libvirtd.pid", "/run/libvirtd.pid", - "--bind", "/run/lock", "/run/lock", - "--bind", "/run/log", "/run/log", - "--bind", "/run/lvm", "/run/lvm", - "--bind", "/run/mount", "/run/mount", - "--bind", "/run/nginx", "/run/nginx", - "--bind", "/run/nscd", "/run/nscd", - "--bind", "/run/opengl-driver", "/run/opengl-driver", - "--bind", "/run/pppd", "/run/pppd", - "--bind", "/run/resolvconf", "/run/resolvconf", - "--bind", "/run/sddm", "/run/sddm", - "--bind", "/run/syncoid", "/run/syncoid", - "--bind", "/run/systemd", "/run/systemd", - "--bind", "/run/tmpfiles.d", "/run/tmpfiles.d", - "--bind", "/run/udev", "/run/udev", - "--bind", "/run/udisks2", "/run/udisks2", - "--bind", "/run/utmp", "/run/utmp", - "--bind", "/run/virtlogd.pid", "/run/virtlogd.pid", - "--bind", "/run/wrappers", "/run/wrappers", - "--bind", "/run/zed.pid", "/run/zed.pid", - "--bind", "/run/zed.state", "/run/zed.state", - "--bind", "/tmp/fortify.1971/tmpdir/150", "/tmp", - "--size", "1048576", "--tmpfs", "/tmp/fortify.1971", - "--size", "1048576", "--tmpfs", "/run/user", - "--size", "8388608", "--tmpfs", "/run/user/150", - "--ro-bind", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/passwd", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/passwd", - "--ro-bind", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/group", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/group", - "--ro-bind", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/passwd", "/etc/passwd", - "--ro-bind", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/group", "/etc/group", - "--size", "8192", "--tmpfs", "/var/run/nscd", - }, - }, } for _, tc := range testCases { @@ -241,4 +235,21 @@ func TestConfig_Args(t *testing.T) { } }) } + + // test persist validation + t.Run("invalid persist", func(t *testing.T) { + defer func() { + wantPanic := "persist called without required paths" + if r := recover(); r != wantPanic { + t.Errorf("Persist() panic = %v; wantPanic %v", r, wantPanic) + } + }() + (new(bwrap.Config)).Persist("/run", "", "") + }) + + t.Run("sync file", func(t *testing.T) { + if s := (new(bwrap.Config)).SetSync(os.Stdout).Sync(); s != os.Stdout { + t.Errorf("Sync() = %v", s) + } + }) } diff --git a/helper/bwrap/sequential.go b/helper/bwrap/sequential.go new file mode 100644 index 0000000..42a4b2b --- /dev/null +++ b/helper/bwrap/sequential.go @@ -0,0 +1,223 @@ +package bwrap + +import ( + "encoding/gob" + "os" + "strconv" +) + +func init() { + gob.Register(new(PermConfig[SymlinkConfig])) + gob.Register(new(PermConfig[*TmpfsConfig])) + gob.Register(new(OverlayConfig)) +} + +type PositionalArg int + +func (p PositionalArg) Unwrap() string { + return positionalArgs[p] +} + +const ( + Tmpfs PositionalArg = iota + Symlink + + Bind + BindTry + DevBind + DevBindTry + ROBind + ROBindTry + + Chmod + Dir + RemountRO + Procfs + DevTmpfs + Mqueue + + Perms + Size + + OverlaySrc + Overlay + TmpOverlay + ROOverlay +) + +var positionalArgs = [...]string{ + Tmpfs: "--tmpfs", + Symlink: "--symlink", + + Bind: "--bind", + BindTry: "--bind-try", + DevBind: "--dev-bind", + DevBindTry: "--dev-bind-try", + ROBind: "--ro-bind", + ROBindTry: "--ro-bind-try", + + Chmod: "--chmod", + Dir: "--dir", + RemountRO: "--remount-ro", + Procfs: "--proc", + DevTmpfs: "--dev", + Mqueue: "--mqueue", + + Perms: "--perms", + Size: "--size", + + OverlaySrc: "--overlay-src", + Overlay: "--overlay", + TmpOverlay: "--tmp-overlay", + ROOverlay: "--ro-overlay", +} + +type PermConfig[T FSBuilder] struct { + // set permissions of next argument + // (--perms OCTAL) + Mode *os.FileMode `json:"mode,omitempty"` + // path to get the new permission + // (--bind-data, --file, etc.) + Inner T `json:"path"` +} + +func (p *PermConfig[T]) Path() string { + return p.Inner.Path() +} + +func (p *PermConfig[T]) Len() int { + if p.Mode != nil { + return p.Inner.Len() + 2 + } else { + return p.Inner.Len() + } +} + +func (p *PermConfig[T]) Append(args *[]string) { + if p.Mode != nil { + *args = append(*args, Perms.Unwrap(), strconv.FormatInt(int64(*p.Mode), 8)) + } + p.Inner.Append(args) +} + +type TmpfsConfig struct { + // set size of tmpfs + // (--size BYTES) + Size int `json:"size,omitempty"` + // mount point of new tmpfs + // (--tmpfs DEST) + Dir string `json:"dir"` +} + +func (t *TmpfsConfig) Path() string { + return t.Dir +} + +func (t *TmpfsConfig) Len() int { + if t.Size > 0 { + return 4 + } else { + return 2 + } +} + +func (t *TmpfsConfig) Append(args *[]string) { + if t.Size > 0 { + *args = append(*args, Size.Unwrap(), strconv.Itoa(t.Size)) + } + *args = append(*args, Tmpfs.Unwrap(), t.Dir) +} + +type OverlayConfig struct { + /* + read files from SRC in the following overlay + (--overlay-src SRC) + */ + Src []string `json:"src,omitempty"` + + /* + mount overlayfs on DEST, with RWSRC as the host path for writes and + WORKDIR an empty directory on the same filesystem as RWSRC + (--overlay RWSRC WORKDIR DEST) + + if nil, mount overlayfs on DEST, with writes going to an invisible tmpfs + (--tmp-overlay DEST) + + if either strings are empty, mount overlayfs read-only on DEST + (--ro-overlay DEST) + */ + Persist *[2]string `json:"persist,omitempty"` + + /* + --overlay RWSRC WORKDIR DEST + + --tmp-overlay DEST + + --ro-overlay DEST + */ + Dest string `json:"dest"` +} + +func (o *OverlayConfig) Path() string { + return o.Dest +} + +func (o *OverlayConfig) Len() int { + // (--tmp-overlay DEST) or (--ro-overlay DEST) + p := 2 + // (--overlay RWSRC WORKDIR DEST) + if o.Persist != nil && o.Persist[0] != "" && o.Persist[1] != "" { + p = 4 + } + + return p + len(o.Src)*2 +} + +func (o *OverlayConfig) Append(args *[]string) { + // --overlay-src SRC + for _, src := range o.Src { + *args = append(*args, OverlaySrc.Unwrap(), src) + } + + if o.Persist != nil { + if o.Persist[0] != "" && o.Persist[1] != "" { + // --overlay RWSRC WORKDIR + *args = append(*args, Overlay.Unwrap(), o.Persist[0], o.Persist[1]) + } else { + // --ro-overlay + *args = append(*args, ROOverlay.Unwrap()) + } + } else { + // --tmp-overlay + *args = append(*args, TmpOverlay.Unwrap()) + } + + // DEST + *args = append(*args, o.Dest) +} + +type SymlinkConfig [2]string + +func (s SymlinkConfig) Path() string { + return s[1] +} + +func (s SymlinkConfig) Len() int { + return 3 +} + +func (s SymlinkConfig) Append(args *[]string) { + *args = append(*args, Symlink.Unwrap(), s[0], s[1]) +} + +type ChmodConfig map[string]os.FileMode + +func (c ChmodConfig) Len() int { + return len(c) +} + +func (c ChmodConfig) Append(args *[]string) { + for path, mode := range c { + *args = append(*args, Chmod.Unwrap(), strconv.FormatInt(int64(mode), 8), path) + } +} diff --git a/helper/bwrap/static.go b/helper/bwrap/static.go new file mode 100644 index 0000000..7a8535c --- /dev/null +++ b/helper/bwrap/static.go @@ -0,0 +1,249 @@ +package bwrap + +import ( + "slices" + "strconv" +) + +/* + static boolean args +*/ + +type BoolArg int + +func (b BoolArg) Unwrap() []string { + return boolArgs[b] +} + +const ( + UnshareAll BoolArg = iota + UnshareUser + UnshareIPC + UnsharePID + UnshareNet + UnshareUTS + UnshareCGroup + ShareNet + + UserNS + Clearenv + + NewSession + DieWithParent + AsInit +) + +var boolArgs = [...][]string{ + UnshareAll: {"--unshare-all", "--unshare-user"}, + UnshareUser: {"--unshare-user"}, + UnshareIPC: {"--unshare-ipc"}, + UnsharePID: {"--unshare-pid"}, + UnshareNet: {"--unshare-net"}, + UnshareUTS: {"--unshare-uts"}, + UnshareCGroup: {"--unshare-cgroup"}, + ShareNet: {"--share-net"}, + + UserNS: {"--disable-userns", "--assert-userns-disabled"}, + Clearenv: {"--clearenv"}, + + NewSession: {"--new-session"}, + DieWithParent: {"--die-with-parent"}, + AsInit: {"--as-pid-1"}, +} + +func (c *Config) boolArgs() Builder { + b := boolArg{ + UserNS: !c.UserNS, + Clearenv: c.Clearenv, + + NewSession: c.NewSession, + DieWithParent: c.DieWithParent, + AsInit: c.AsInit, + } + + if c.Unshare == nil { + b[UnshareAll] = true + b[ShareNet] = c.Net + } else { + b[UnshareUser] = c.Unshare.User + b[UnshareIPC] = c.Unshare.IPC + b[UnsharePID] = c.Unshare.PID + b[UnshareNet] = c.Unshare.Net + b[UnshareUTS] = c.Unshare.UTS + b[UnshareCGroup] = c.Unshare.CGroup + } + + return &b +} + +type boolArg [len(boolArgs)]bool + +func (b *boolArg) Len() (l int) { + for i, v := range b { + if v { + l += len(boolArgs[i]) + } + } + return +} + +func (b *boolArg) Append(args *[]string) { + for i, v := range b { + if v { + *args = append(*args, BoolArg(i).Unwrap()...) + } + } +} + +/* + static integer args +*/ + +type IntArg int + +func (i IntArg) Unwrap() string { + return intArgs[i] +} + +const ( + UID IntArg = iota + GID +) + +var intArgs = [...]string{ + UID: "--uid", + GID: "--gid", +} + +func (c *Config) intArgs() Builder { + return &intArg{ + UID: c.UID, + GID: c.GID, + } +} + +type intArg [len(intArgs)]*int + +func (n *intArg) Len() (l int) { + for _, v := range n { + if v != nil { + l += 2 + } + } + return +} + +func (n *intArg) Append(args *[]string) { + for i, v := range n { + if v != nil { + *args = append(*args, IntArg(i).Unwrap(), strconv.Itoa(*v)) + } + } +} + +/* + static string args +*/ + +type StringArg int + +func (s StringArg) Unwrap() string { + return stringArgs[s] +} + +const ( + Hostname StringArg = iota + Chdir + UnsetEnv + LockFile +) + +var stringArgs = [...]string{ + Hostname: "--hostname", + Chdir: "--chdir", + UnsetEnv: "--unsetenv", + LockFile: "--lock-file", +} + +func (c *Config) stringArgs() Builder { + n := stringArg{ + UnsetEnv: c.UnsetEnv, + LockFile: c.LockFile, + } + + if c.Hostname != "" { + n[Hostname] = []string{c.Hostname} + } + if c.Chdir != "" { + n[Chdir] = []string{c.Chdir} + } + + return &n +} + +type stringArg [len(stringArgs)][]string + +func (s *stringArg) Len() (l int) { + for _, arg := range s { + l += len(arg) * 2 + } + return +} + +func (s *stringArg) Append(args *[]string) { + for i, arg := range s { + for _, v := range arg { + *args = append(*args, StringArg(i).Unwrap(), v) + } + } +} + +/* + static pair args +*/ + +type PairArg int + +func (p PairArg) Unwrap() string { + return pairArgs[p] +} + +const ( + SetEnv PairArg = iota +) + +var pairArgs = [...]string{ + SetEnv: "--setenv", +} + +func (c *Config) pairArgs() Builder { + var n pairArg + n[SetEnv] = make([][2]string, len(c.SetEnv)) + keys := make([]string, 0, len(c.SetEnv)) + for k := range c.SetEnv { + keys = append(keys, k) + } + slices.Sort(keys) + for i, k := range keys { + n[SetEnv][i] = [2]string{k, c.SetEnv[k]} + } + + return &n +} + +type pairArg [len(pairArgs)][][2]string + +func (p *pairArg) Len() (l int) { + for _, v := range p { + l += len(v) * 3 + } + return +} + +func (p *pairArg) Append(args *[]string) { + for i, arg := range p { + for _, v := range arg { + *args = append(*args, PairArg(i).Unwrap(), v[0], v[1]) + } + } +}