diff --git a/dbus/run.go b/dbus/run.go index 2aad777..a9b31e5 100644 --- a/dbus/run.go +++ b/dbus/run.go @@ -79,11 +79,9 @@ func (p *Proxy) Start(ready chan error, output io.Writer, sandbox bool) error { } } } - bindTargetDedup := make([][2]string, 0, len(bindTarget)) for k := range bindTarget { - bindTargetDedup = append(bindTargetDedup, [2]string{k, k}) + bc.Bind(k, k, false, true) } - bc.Bind = append(bc.Bind, bindTargetDedup...) roBindTarget := make(map[string]struct{}, 2+1+len(proxyDeps)) @@ -103,11 +101,9 @@ func (p *Proxy) Start(ready chan error, output io.Writer, sandbox bool) error { } } - roBindTargetDedup := make([][2]string, 0, len(roBindTarget)) for k := range roBindTarget { - roBindTargetDedup = append(roBindTargetDedup, [2]string{k, k}) + bc.Bind(k, k) } - bc.ROBind = append(bc.ROBind, roBindTargetDedup...) h = helper.MustNewBwrap(bc, p.seal, toolPath, argF) cmd = h.Unwrap() diff --git a/helper/bwrap/arg.go b/helper/bwrap/arg.go new file mode 100644 index 0000000..efdab82 --- /dev/null +++ b/helper/bwrap/arg.go @@ -0,0 +1,77 @@ +package bwrap + +import "encoding/gob" + +type Builder interface { + Len() int + Append(args *[]string) +} + +type FSBuilder interface { + Path() string + Builder +} + +func init() { + gob.Register(new(pairF)) + gob.Register(new(stringF)) +} + +type pairF [3]string + +func (p *pairF) Path() string { + return p[2] +} + +func (p *pairF) Len() int { + return len(p) // compiler replaces this with 3 +} + +func (p *pairF) Append(args *[]string) { + *args = append(*args, p[0], p[1], p[2]) +} + +type stringF [2]string + +func (s stringF) Path() string { + return s[1] +} + +func (s stringF) Len() int { + return len(s) // compiler replaces this with 2 +} + +func (s stringF) Append(args *[]string) { + *args = append(*args, s[0], s[1]) +} + +// Args returns a slice of bwrap args corresponding to c. +func (c *Config) Args() (args []string) { + builders := []Builder{ + c.boolArgs(), + c.intArgs(), + c.stringArgs(), + c.pairArgs(), + } + + // copy FSBuilder slice to builder slice + fb := make([]Builder, len(c.Filesystem)+1) + for i, f := range c.Filesystem { + fb[i] = f + } + fb[len(fb)-1] = c.Chmod + builders = append(builders, fb...) + + // accumulate arg count + argc := 0 + for _, b := range builders { + argc += b.Len() + } + + args = make([]string, 0, argc) + for _, b := range builders { + b.Append(&args) + } + + return +} diff --git a/helper/bwrap/arg.static.awkward.go b/helper/bwrap/arg.static.awkward.go new file mode 100644 index 0000000..0848d1f --- /dev/null +++ b/helper/bwrap/arg.static.awkward.go @@ -0,0 +1,13 @@ +package bwrap + +const ( + Tmpfs = iota + Dir + Symlink +) + +var awkwardArgs = [...]string{ + Tmpfs: "--tmpfs", + Dir: "--dir", + Symlink: "--symlink", +} diff --git a/helper/bwrap/arg.static.bool.go b/helper/bwrap/arg.static.bool.go new file mode 100644 index 0000000..6c3ca0c --- /dev/null +++ b/helper/bwrap/arg.static.bool.go @@ -0,0 +1,81 @@ +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 new file mode 100644 index 0000000..cba3b1f --- /dev/null +++ b/helper/bwrap/arg.static.int.go @@ -0,0 +1,47 @@ +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 new file mode 100644 index 0000000..cc1765a --- /dev/null +++ b/helper/bwrap/arg.static.pair.go @@ -0,0 +1,64 @@ +package bwrap + +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, 0, len(c.SetEnv)) + for k, v := range c.SetEnv { + n[SetEnv] = append(n[SetEnv], [2]string{k, v}) + } + + // 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 new file mode 100644 index 0000000..e7f5c33 --- /dev/null +++ b/helper/bwrap/arg.static.string.go @@ -0,0 +1,65 @@ +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.bool.go b/helper/bwrap/config.bool.go deleted file mode 100644 index c30f4df..0000000 --- a/helper/bwrap/config.bool.go +++ /dev/null @@ -1,64 +0,0 @@ -package bwrap - -const ( - UnshareAll = iota - UnshareUser - UnshareIPC - UnsharePID - UnshareNet - UnshareUTS - UnshareCGroup - ShareNet - - UserNS - Clearenv - - NewSession - DieWithParent - AsInit - - boolC -) - -var boolArgs = func() (b [boolC][]string) { - b[UnshareAll] = []string{"--unshare-all", "--unshare-user"} - b[UnshareUser] = []string{"--unshare-user"} - b[UnshareIPC] = []string{"--unshare-ipc"} - b[UnsharePID] = []string{"--unshare-pid"} - b[UnshareNet] = []string{"--unshare-net"} - b[UnshareUTS] = []string{"--unshare-uts"} - b[UnshareCGroup] = []string{"--unshare-cgroup"} - b[ShareNet] = []string{"--share-net"} - - b[UserNS] = []string{"--disable-userns", "--assert-userns-disabled"} - b[Clearenv] = []string{"--clearenv"} - - b[NewSession] = []string{"--new-session"} - b[DieWithParent] = []string{"--die-with-parent"} - b[AsInit] = []string{"--as-pid-1"} - - return -}() - -func (c *Config) boolArgs() (b [boolC]bool) { - 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 - } - - b[UserNS] = !c.UserNS - b[Clearenv] = c.Clearenv - - b[NewSession] = c.NewSession - b[DieWithParent] = c.DieWithParent - b[AsInit] = c.AsInit - - return -} diff --git a/helper/bwrap/config.go b/helper/bwrap/config.go index 3ddb80b..f62cc24 100644 --- a/helper/bwrap/config.go +++ b/helper/bwrap/config.go @@ -5,75 +5,6 @@ import ( "strconv" ) -func (c *Config) Args() (args []string) { - b := c.boolArgs() - n := c.intArgs() - g := c.interfaceArgs() - s := c.stringArgs() - p := c.pairArgs() - - argc := 0 - for i, arg := range b { - if arg { - argc += len(boolArgs[i]) - } - } - for _, arg := range n { - if arg != nil { - argc += 2 - } - } - for _, arg := range g { - argc += len(arg) * 3 - } - for _, arg := range s { - argc += len(arg) * 2 - } - for _, arg := range p { - argc += len(arg) * 3 - } - - args = make([]string, 0, argc) - for i, arg := range b { - if arg { - args = append(args, boolArgs[i]...) - } - } - for i, arg := range n { - if arg != nil { - args = append(args, intArgs[i], strconv.Itoa(*arg)) - } - } - for i, arg := range g { - for _, v := range arg { - if v.Later() { - continue - } - args = append(args, v.Value(interfaceArgs[i])...) - } - } - for i, arg := range s { - for _, v := range arg { - args = append(args, stringArgs[i], v) - } - } - for i, arg := range p { - for _, v := range arg { - args = append(args, pairArgs[i], v[0], v[1]) - } - } - for i, arg := range g { - for _, v := range arg { - if !v.Later() { - continue - } - args = append(args, v.Value(interfaceArgs[i])...) - } - } - - return -} - type Config struct { // unshare every namespace we support by default if nil // (--unshare-all) @@ -114,53 +45,12 @@ type Config struct { // (--lock-file DEST) LockFile []string `json:"lock_file,omitempty"` - // bind mount host path on sandbox - // (--bind SRC DEST) - Bind [][2]string `json:"bind,omitempty"` - // equal to Bind but ignores non-existent host path - // (--bind-try SRC DEST) - BindTry [][2]string `json:"bind_try,omitempty"` - - // bind mount host path on sandbox, allowing device access - // (--dev-bind SRC DEST) - DevBind [][2]string `json:"dev_bind,omitempty"` - // equal to DevBind but ignores non-existent host path - // (--dev-bind-try SRC DEST) - DevBindTry [][2]string `json:"dev_bind_try,omitempty"` - - // bind mount host path readonly on sandbox - // (--ro-bind SRC DEST) - ROBind [][2]string `json:"ro_bind,omitempty"` - // equal to ROBind but ignores non-existent host path - // (--ro-bind-try SRC DEST) - ROBindTry [][2]string `json:"ro_bind_try,omitempty"` - - // remount path as readonly; does not recursively remount - // (--remount-ro DEST) - RemountRO []string `json:"remount_ro,omitempty"` - - // mount new procfs in sandbox - // (--proc DEST) - Procfs []string `json:"proc,omitempty"` - // mount new dev in sandbox - // (--dev DEST) - DevTmpfs []string `json:"dev,omitempty"` - // mount new tmpfs in sandbox - // (--tmpfs DEST) - Tmpfs []PermConfig[TmpfsConfig] `json:"tmpfs,omitempty"` - // mount new mqueue in sandbox - // (--mqueue DEST) - Mqueue []string `json:"mqueue,omitempty"` - // create dir in sandbox - // (--dir DEST) - Dir []PermConfig[string] `json:"dir,omitempty"` - // create symlink within sandbox - // (--symlink SRC DEST) - Symlink []PermConfig[[2]string] `json:"symlink,omitempty"` + // ordered filesystem args + Filesystem []FSBuilder // change permissions (must already exist) // (--chmod OCTAL PATH) - Chmod map[string]os.FileMode `json:"chmod,omitempty"` + Chmod ChmodConfig `json:"chmod,omitempty"` // create a new terminal session // (--new-session) @@ -217,6 +107,34 @@ type UnshareConfig struct { 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) @@ -226,62 +144,47 @@ type TmpfsConfig struct { Dir string `json:"dir"` } -type argOf interface { - Value(arg string) (args []string) - Later() bool +func (t *TmpfsConfig) Path() string { + return t.Dir } -func copyToArgOfSlice[T [2]string | string | TmpfsConfig](src []PermConfig[T]) (dst []argOf) { - dst = make([]argOf, len(src)) - for i, arg := range src { - dst[i] = arg - } - return -} - -type PermConfig[T [2]string | string | TmpfsConfig] struct { - // append this at the end of the argument stream - Last bool - - // set permissions of next argument - // (--perms OCTAL) - Mode *os.FileMode `json:"mode,omitempty"` - // path to get the new permission - // (--bind-data, --file, etc.) - Path T -} - -func (p PermConfig[T]) Later() bool { - return p.Last -} - -func (p PermConfig[T]) Value(arg string) (args []string) { - // max possible size - if p.Mode != nil { - args = make([]string, 0, 6) - args = append(args, "--perms", strconv.FormatInt(int64(*p.Mode), 8)) +func (t *TmpfsConfig) Len() int { + if t.Size > 0 { + return 4 } else { - args = make([]string, 0, 4) - } - - switch v := any(p.Path).(type) { - case string: - args = append(args, arg, v) - return - case [2]string: - args = append(args, arg, v[0], v[1]) - return - case TmpfsConfig: - if arg != "--tmpfs" { - panic("unreachable") - } - - if v.Size > 0 { - args = append(args, "--size", strconv.Itoa(v.Size)) - } - args = append(args, arg, v.Dir) - return - default: - panic("unreachable") + 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 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.int.go b/helper/bwrap/config.int.go deleted file mode 100644 index 27c88c3..0000000 --- a/helper/bwrap/config.int.go +++ /dev/null @@ -1,22 +0,0 @@ -package bwrap - -const ( - UID = iota - GID - - intC -) - -var intArgs = func() (n [intC]string) { - n[UID] = "--uid" - n[GID] = "--gid" - - return -}() - -func (c *Config) intArgs() (n [intC]*int) { - n[UID] = c.UID - n[GID] = c.GID - - return -} diff --git a/helper/bwrap/config.interface.go b/helper/bwrap/config.interface.go deleted file mode 100644 index f68df83..0000000 --- a/helper/bwrap/config.interface.go +++ /dev/null @@ -1,25 +0,0 @@ -package bwrap - -const ( - Tmpfs = iota - Dir - Symlink - - interfaceC -) - -var interfaceArgs = func() (g [interfaceC]string) { - g[Tmpfs] = "--tmpfs" - g[Dir] = "--dir" - g[Symlink] = "--symlink" - - return -}() - -func (c *Config) interfaceArgs() (g [interfaceC][]argOf) { - g[Tmpfs] = copyToArgOfSlice(c.Tmpfs) - g[Dir] = copyToArgOfSlice(c.Dir) - g[Symlink] = copyToArgOfSlice(c.Symlink) - - return -} diff --git a/helper/bwrap/config.pair.go b/helper/bwrap/config.pair.go deleted file mode 100644 index 049edaf..0000000 --- a/helper/bwrap/config.pair.go +++ /dev/null @@ -1,54 +0,0 @@ -package bwrap - -import "strconv" - -const ( - SetEnv = iota - - Bind - BindTry - DevBind - DevBindTry - ROBind - ROBindTry - - Chmod - - pairC -) - -var pairArgs = func() (n [pairC]string) { - n[SetEnv] = "--setenv" - - n[Bind] = "--bind" - n[BindTry] = "--bind-try" - n[DevBind] = "--dev-bind" - n[DevBindTry] = "--dev-bind-try" - n[ROBind] = "--ro-bind" - n[ROBindTry] = "--ro-bind-try" - - n[Chmod] = "--chmod" - - return -}() - -func (c *Config) pairArgs() (n [pairC][][2]string) { - n[SetEnv] = make([][2]string, 0, len(c.SetEnv)) - for k, v := range c.SetEnv { - n[SetEnv] = append(n[SetEnv], [2]string{k, v}) - } - - n[Bind] = c.Bind - n[BindTry] = c.BindTry - n[DevBind] = c.DevBind - n[DevBindTry] = c.DevBindTry - n[ROBind] = c.ROBind - n[ROBindTry] = c.ROBindTry - - n[Chmod] = make([][2]string, 0, len(c.Chmod)) - for path, octal := range c.Chmod { - n[Chmod] = append(n[Chmod], [2]string{strconv.FormatInt(int64(octal), 8), path}) - } - - return -} diff --git a/helper/bwrap/config.set.go b/helper/bwrap/config.set.go new file mode 100644 index 0000000..318ad7f --- /dev/null +++ b/helper/bwrap/config.set.go @@ -0,0 +1,138 @@ +package bwrap + +import "os" + +/* +Bind binds mount src on host to dest in sandbox. + +Bind(src, dest) bind mount host path readonly on sandbox +(--ro-bind SRC DEST). +Bind(src, dest, true) equal to ROBind but ignores non-existent host path +(--ro-bind-try SRC DEST). + +Bind(src, dest, false, true) bind mount host path on sandbox. +(--bind SRC DEST). +Bind(src, dest, true, true) equal to Bind but ignores non-existent host path +(--bind-try SRC DEST). + +Bind(src, dest, false, true, true) bind mount host path on sandbox, allowing device access +(--dev-bind SRC DEST). +Bind(src, dest, true, true, true) equal to DevBind but ignores non-existent host path +(--dev-bind-try SRC DEST). +*/ +func (c *Config) Bind(src, dest string, opts ...bool) *Config { + var ( + try bool + write bool + dev bool + ) + + if len(opts) > 0 { + try = opts[0] + } + if len(opts) > 1 { + write = opts[1] + } + if len(opts) > 2 { + dev = opts[2] + } + + if dev { + if try { + c.Filesystem = append(c.Filesystem, &pairF{pairArgs[DevBindTry], src, dest}) + } else { + c.Filesystem = append(c.Filesystem, &pairF{pairArgs[DevBind], src, dest}) + } + return c + } else if write { + if try { + c.Filesystem = append(c.Filesystem, &pairF{pairArgs[BindTry], src, dest}) + } else { + c.Filesystem = append(c.Filesystem, &pairF{pairArgs[Bind], src, dest}) + } + return c + } else { + if try { + c.Filesystem = append(c.Filesystem, &pairF{pairArgs[ROBindTry], src, dest}) + } else { + c.Filesystem = append(c.Filesystem, &pairF{pairArgs[ROBind], src, 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}) + 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}) + 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}) + return c +} + +// Tmpfs mount new tmpfs in sandbox +// (--tmpfs DEST) +func (c *Config) Tmpfs(dest string, size int, perm ...os.FileMode) *Config { + tmpfs := &PermConfig[*TmpfsConfig]{Inner: &TmpfsConfig{Dir: dest}} + if size >= 0 { + tmpfs.Inner.Size = size + } + if len(perm) == 1 { + tmpfs.Mode = &perm[0] + } + c.Filesystem = append(c.Filesystem, tmpfs) + 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{stringArgs[Dir], dest}) + return c +} + +// Symlink create symlink within sandbox +// (--symlink SRC DEST) +func (c *Config) Symlink(src, dest string, perm ...os.FileMode) *Config { + symlink := &PermConfig[SymlinkConfig]{Inner: SymlinkConfig{src, dest}} + if len(perm) == 1 { + symlink.Mode = &perm[0] + } + c.Filesystem = append(c.Filesystem, symlink) + return c +} + +// SetUID sets custom uid in the sandbox, requires new user namespace (--uid UID). +func (c *Config) SetUID(uid int) *Config { + if uid >= 0 { + c.UID = &uid + } + return c +} + +// SetGID sets custom gid in the sandbox, requires new user namespace (--gid GID). +func (c *Config) SetGID(gid int) *Config { + if gid >= 0 { + c.GID = &gid + } + return c +} diff --git a/helper/bwrap/config.string.go b/helper/bwrap/config.string.go deleted file mode 100644 index 2aa05be..0000000 --- a/helper/bwrap/config.string.go +++ /dev/null @@ -1,44 +0,0 @@ -package bwrap - -const ( - Hostname = iota - Chdir - UnsetEnv - LockFile - RemountRO - Procfs - DevTmpfs - Mqueue - - stringC -) - -var stringArgs = func() (n [stringC]string) { - n[Hostname] = "--hostname" - n[Chdir] = "--chdir" - n[UnsetEnv] = "--unsetenv" - n[LockFile] = "--lock-file" - n[RemountRO] = "--remount-ro" - n[Procfs] = "--proc" - n[DevTmpfs] = "--dev" - n[Mqueue] = "--mqueue" - - return -}() - -func (c *Config) stringArgs() (n [stringC][]string) { - if c.Hostname != "" { - n[Hostname] = []string{c.Hostname} - } - if c.Chdir != "" { - n[Chdir] = []string{c.Chdir} - } - n[UnsetEnv] = c.UnsetEnv - n[LockFile] = c.LockFile - n[RemountRO] = c.RemountRO - n[Procfs] = c.Procfs - n[DevTmpfs] = c.DevTmpfs - n[Mqueue] = c.Mqueue - - return -} diff --git a/helper/bwrap/config_test.go b/helper/bwrap/config_test.go index a14e310..30b9ab0 100644 --- a/helper/bwrap/config_test.go +++ b/helper/bwrap/config_test.go @@ -13,40 +13,34 @@ func TestConfig_Args(t *testing.T) { }{ { name: "xdg-dbus-proxy constraint sample", - conf: &Config{ - Unshare: nil, - UserNS: false, - Clearenv: true, - Symlink: []PermConfig[[2]string]{ - {Path: [2]string{"usr/bin", "/bin"}}, - {Path: [2]string{"var/home", "/home"}}, - {Path: [2]string{"usr/lib", "/lib"}}, - {Path: [2]string{"usr/lib64", "/lib64"}}, - {Path: [2]string{"run/media", "/media"}}, - {Path: [2]string{"var/mnt", "/mnt"}}, - {Path: [2]string{"var/opt", "/opt"}}, - {Path: [2]string{"sysroot/ostree", "/ostree"}}, - {Path: [2]string{"var/roothome", "/root"}}, - {Path: [2]string{"usr/sbin", "/sbin"}}, - {Path: [2]string{"var/srv", "/srv"}}, - }, - Bind: [][2]string{ - {"/run", "/run"}, - {"/tmp", "/tmp"}, - {"/var", "/var"}, - {"/run/user/1971/.dbus-proxy/", "/run/user/1971/.dbus-proxy/"}, - }, - ROBind: [][2]string{ - {"/boot", "/boot"}, - {"/dev", "/dev"}, - {"/proc", "/proc"}, - {"/sys", "/sys"}, - {"/sysroot", "/sysroot"}, - {"/usr", "/usr"}, - {"/etc", "/etc"}, - }, + conf: (&Config{ + Unshare: nil, + UserNS: false, + Clearenv: true, DieWithParent: true, - }, + }). + Symlink("usr/bin", "/bin"). + Symlink("var/home", "/home"). + Symlink("usr/lib", "/lib"). + Symlink("usr/lib64", "/lib64"). + Symlink("run/media", "/media"). + Symlink("var/mnt", "/mnt"). + Symlink("var/opt", "/opt"). + Symlink("sysroot/ostree", "/ostree"). + Symlink("var/roothome", "/root"). + Symlink("usr/sbin", "/sbin"). + Symlink("var/srv", "/srv"). + Bind("/run", "/run", false, true). + Bind("/tmp", "/tmp", false, true). + Bind("/var", "/var", false, true). + Bind("/run/user/1971/.dbus-proxy/", "/run/user/1971/.dbus-proxy/", false, true). + Bind("/boot", "/boot"). + Bind("/dev", "/dev"). + Bind("/proc", "/proc"). + Bind("/sys", "/sys"). + Bind("/sysroot", "/sysroot"). + Bind("/usr", "/usr"). + Bind("/etc", "/etc"), want: []string{ "--unshare-all", "--unshare-user", diff --git a/internal/app/config.go b/internal/app/config.go index 7950c59..3f6d1c0 100644 --- a/internal/app/config.go +++ b/internal/app/config.go @@ -1,11 +1,18 @@ package app import ( + "encoding/gob" + "os" + "git.ophivana.moe/cat/fortify/dbus" "git.ophivana.moe/cat/fortify/helper/bwrap" "git.ophivana.moe/cat/fortify/internal/state" ) +func init() { + gob.Register(new(bwrap.PermConfig[*bwrap.TmpfsConfig])) +} + // Config is used to seal an *App type Config struct { // D-Bus application ID @@ -55,7 +62,7 @@ type SandboxConfig struct { // sandbox host filesystem access Filesystem []*FilesystemConfig `json:"filesystem"` // tmpfs mount points to mount last - Tmpfs []bwrap.TmpfsConfig `json:"tmpfs"` + Tmpfs []string `json:"tmpfs"` } type FilesystemConfig struct { @@ -71,61 +78,39 @@ type FilesystemConfig struct { Must bool `json:"require,omitempty"` } +// Bwrap returns the address of the corresponding bwrap.Config to s. +// Note that remaining tmpfs entries must be queued by the caller prior to launch. func (s *SandboxConfig) Bwrap() *bwrap.Config { if s == nil { return nil } - nobody := 65534 - conf := &bwrap.Config{ + conf := (&bwrap.Config{ Net: s.Net, UserNS: s.UserNS, - UID: &nobody, - GID: &nobody, Hostname: s.Hostname, Clearenv: true, SetEnv: s.Env, - Procfs: []string{"/proc"}, - DevTmpfs: []string{"/dev"}, - Mqueue: []string{"/dev/mqueue"}, NewSession: !s.NoNewSession, DieWithParent: true, AsInit: true, - } + + // initialise map + Chmod: make(map[string]os.FileMode), + }). + SetUID(65534).SetGID(65534). + Procfs("/proc").DevTmpfs("/dev").Mqueue("/dev/mqueue") for _, c := range s.Filesystem { if c == nil { continue } - p := [2]string{c.Src, c.Dst} + src := c.Src + dest := c.Dst if c.Dst == "" { - p[1] = c.Src + dest = c.Src } - - switch { - case c.Device: - if c.Must { - conf.DevBind = append(conf.DevBind, p) - } else { - conf.DevBindTry = append(conf.DevBindTry, p) - } - case c.Write: - if c.Must { - conf.Bind = append(conf.Bind, p) - } else { - conf.BindTry = append(conf.BindTry, p) - } - default: - if c.Must { - conf.ROBind = append(conf.ROBind, p) - } else { - conf.ROBindTry = append(conf.ROBindTry, p) - } - } - } - - for _, tmpfs := range s.Tmpfs { - conf.Tmpfs = append(conf.Tmpfs, bwrap.PermConfig[bwrap.TmpfsConfig]{Path: tmpfs, Last: true}) + conf.Bind(src, dest, !c.Must, c.Write, c.Device) } return conf @@ -164,9 +149,7 @@ func Template() *Config { {Src: "/data/user/0", Dst: "/data/data", Write: true, Must: true}, {Src: "/var/tmp", Write: true}, }, - Tmpfs: []bwrap.TmpfsConfig{ - {Size: 8 * 1024, Dir: "/var/run/nscd"}, - }, + Tmpfs: []string{"/var/run/nscd"}, }, SystemBus: &dbus.Config{ See: nil, diff --git a/internal/app/seal.go b/internal/app/seal.go index 9199f5b..146bcc5 100644 --- a/internal/app/seal.go +++ b/internal/app/seal.go @@ -9,7 +9,6 @@ import ( "strconv" "git.ophivana.moe/cat/fortify/dbus" - "git.ophivana.moe/cat/fortify/helper/bwrap" "git.ophivana.moe/cat/fortify/internal" "git.ophivana.moe/cat/fortify/internal/state" "git.ophivana.moe/cat/fortify/internal/verbose" @@ -163,7 +162,7 @@ func (a *app) Seal(config *Config) error { // hide nscd from sandbox if present nscd := "/var/run/nscd" if _, err := os.Stat(nscd); !errors.Is(err, os.ErrNotExist) { - conf.Tmpfs = append(conf.Tmpfs, bwrap.TmpfsConfig{Size: 8 * 1024, Dir: nscd}) + conf.Tmpfs = append(conf.Tmpfs, nscd) } // bind GPU stuff if config.Confinement.Enablements.Has(state.EnableX) || config.Confinement.Enablements.Has(state.EnableWayland) { @@ -172,6 +171,7 @@ func (a *app) Seal(config *Config) error { config.Confinement.Sandbox = conf } seal.sys.bwrap = config.Confinement.Sandbox.Bwrap() + seal.sys.tmpfs = config.Confinement.Sandbox.Tmpfs if seal.sys.bwrap.SetEnv == nil { seal.sys.bwrap.SetEnv = make(map[string]string) } diff --git a/internal/app/share.dbus.go b/internal/app/share.dbus.go index 804dbd3..912cd97 100644 --- a/internal/app/share.dbus.go +++ b/internal/app/share.dbus.go @@ -65,12 +65,12 @@ func (seal *appSeal) shareDBus(config [2]*dbus.Config) error { // share proxy sockets sessionInner := path.Join(seal.sys.runtime, "bus") seal.sys.setEnv(dbusSessionBusAddress, "unix:path="+sessionInner) - seal.sys.bind(sessionBus[1], sessionInner, true) + seal.sys.bwrap.Bind(sessionBus[1], sessionInner) seal.sys.updatePerm(sessionBus[1], acl.Read, acl.Write) if seal.sys.dbusSystem { systemInner := "/run/dbus/system_bus_socket" seal.sys.setEnv(dbusSystemBusAddress, "unix:path="+systemInner) - seal.sys.bind(systemBus[1], systemInner, true) + seal.sys.bwrap.Bind(systemBus[1], systemInner) seal.sys.updatePerm(systemBus[1], acl.Read, acl.Write) } diff --git a/internal/app/share.display.go b/internal/app/share.display.go index da9f6ef..86343d8 100644 --- a/internal/app/share.display.go +++ b/internal/app/share.display.go @@ -41,7 +41,7 @@ func (seal *appSeal) shareDisplay() error { w := path.Join(seal.sys.runtime, "wayland-0") seal.sys.link(wp, wpi) seal.sys.setEnv(waylandDisplay, w) - seal.sys.bind(wpi, w, true) + seal.sys.bwrap.Bind(wpi, w) // ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`) seal.sys.updatePermTag(state.EnableWayland, wp, acl.Read, acl.Write, acl.Execute) @@ -59,7 +59,7 @@ func (seal *appSeal) shareDisplay() error { } else { seal.sys.changeHosts(seal.sys.Username) seal.sys.setEnv(display, d) - seal.sys.bind("/tmp/.X11-unix", "/tmp/.X11-unix", true) + seal.sys.bwrap.Bind("/tmp/.X11-unix", "/tmp/.X11-unix") } } diff --git a/internal/app/share.pulse.go b/internal/app/share.pulse.go index 39e6fc8..df26b16 100644 --- a/internal/app/share.pulse.go +++ b/internal/app/share.pulse.go @@ -65,7 +65,7 @@ func (seal *appSeal) sharePulse() error { psi := path.Join(seal.shareLocal, "pulse") p := path.Join(seal.sys.runtime, "pulse", "native") seal.sys.link(ps, psi) - seal.sys.bind(psi, p, true) + seal.sys.bwrap.Bind(psi, p) seal.sys.setEnv(pulseServer, "unix:"+p) // publish current user's pulse cookie for target user diff --git a/internal/app/share.runtime.go b/internal/app/share.runtime.go index ac4135f..ebb13aa 100644 --- a/internal/app/share.runtime.go +++ b/internal/app/share.runtime.go @@ -4,7 +4,6 @@ import ( "path" "git.ophivana.moe/cat/fortify/acl" - "git.ophivana.moe/cat/fortify/helper/bwrap" "git.ophivana.moe/cat/fortify/internal/state" ) @@ -17,20 +16,13 @@ const ( // shareRuntime queues actions for sharing/ensuring the runtime and share directories func (seal *appSeal) shareRuntime() { // mount tmpfs on inner runtime (e.g. `/run/user/%d`) - seal.sys.bwrap.Tmpfs = append(seal.sys.bwrap.Tmpfs, - bwrap.PermConfig[bwrap.TmpfsConfig]{ - Path: bwrap.TmpfsConfig{ - Size: 1 * 1024 * 1024, - Dir: "/run/user", - }, - }, - bwrap.PermConfig[bwrap.TmpfsConfig]{ - Path: bwrap.TmpfsConfig{ - Size: 8 * 1024 * 1024, - Dir: seal.sys.runtime, - }, - }, - ) + seal.sys.bwrap.Tmpfs("/run/user", 1*1024*1024) + seal.sys.bwrap.Tmpfs(seal.sys.runtime, 8*1024*1024) + + // point to inner runtime path `/run/user/%d` + seal.sys.setEnv(xdgRuntimeDir, seal.sys.runtime) + seal.sys.setEnv(xdgSessionClass, "user") + seal.sys.setEnv(xdgSessionType, "tty") // ensure RunDir (e.g. `/run/user/%d/fortify`) seal.sys.ensure(seal.RunDirPath, 0700) @@ -53,22 +45,3 @@ func (seal *appSeal) shareRuntime() { seal.sys.ensureEphemeral(seal.shareLocal, 0700) seal.sys.updatePerm(seal.shareLocal, acl.Execute) } - -func (seal *appSeal) shareRuntimeChild() string { - // ensure child runtime parent directory (e.g. `/tmp/fortify.%d/runtime`) - targetRuntimeParent := path.Join(seal.SharePath, "runtime") - seal.sys.ensure(targetRuntimeParent, 0700) - seal.sys.updatePermTag(state.EnableLength, targetRuntimeParent, acl.Execute) - - // ensure child runtime directory (e.g. `/tmp/fortify.%d/runtime/%d`) - targetRuntime := path.Join(targetRuntimeParent, seal.sys.Uid) - seal.sys.ensure(targetRuntime, 0700) - seal.sys.updatePermTag(state.EnableLength, targetRuntime, acl.Read, acl.Write, acl.Execute) - - // point to ensured runtime path - seal.sys.setEnv(xdgRuntimeDir, targetRuntime) - seal.sys.setEnv(xdgSessionClass, "user") - seal.sys.setEnv(xdgSessionType, "tty") - - return targetRuntime -} diff --git a/internal/app/share.system.go b/internal/app/share.system.go index 1b17ca4..e1a3e1d 100644 --- a/internal/app/share.system.go +++ b/internal/app/share.system.go @@ -3,6 +3,9 @@ package app import ( "os" "path" + + "git.ophivana.moe/cat/fortify/acl" + "git.ophivana.moe/cat/fortify/internal/state" ) const ( @@ -38,6 +41,24 @@ func (seal *appSeal) shareSystem() { seal.sys.writeFile(groupPath, []byte("fortify:x:65534:\n")) // bind /etc/passwd and /etc/group - seal.sys.bind(passwdPath, "/etc/passwd", true) - seal.sys.bind(groupPath, "/etc/group", true) + seal.sys.bwrap.Bind(passwdPath, "/etc/passwd") + seal.sys.bwrap.Bind(groupPath, "/etc/group") +} + +func (seal *appSeal) shareTmpdirChild() string { + // ensure child tmpdir parent directory (e.g. `/tmp/fortify.%d/tmpdir`) + targetTmpdirParent := path.Join(seal.SharePath, "tmpdir") + seal.sys.ensure(targetTmpdirParent, 0700) + seal.sys.updatePermTag(state.EnableLength, targetTmpdirParent, acl.Execute) + + // ensure child tmpdir (e.g. `/tmp/fortify.%d/tmpdir/%d`) + targetTmpdir := path.Join(targetTmpdirParent, seal.sys.Uid) + seal.sys.ensure(targetTmpdir, 01700) + seal.sys.updatePermTag(state.EnableLength, targetTmpdir, acl.Read, acl.Write, acl.Execute) + seal.sys.bwrap.Bind(targetTmpdir, "/tmp", false, true) + + // mount tmpfs on inner shared directory (e.g. `/tmp/fortify.%d`) + seal.sys.bwrap.Tmpfs(seal.SharePath, 1*1024*1024) + + return targetTmpdir } diff --git a/internal/app/start.go b/internal/app/start.go index 725d69d..c496561 100644 --- a/internal/app/start.go +++ b/internal/app/start.go @@ -70,7 +70,7 @@ func (a *app) Start() error { a.cmd.Stderr = os.Stderr a.cmd.Dir = a.seal.RunDirPath - if wls, err := shim.ServeConfig(confSockPath, &shim.Payload{ + if wls, err := shim.ServeConfig(confSockPath, a.seal.sys.uid, &shim.Payload{ Argv: a.seal.command, Exec: shimExec, Bwrap: a.seal.sys.bwrap, diff --git a/internal/app/system.go b/internal/app/system.go index 4f3b673..a016293 100644 --- a/internal/app/system.go +++ b/internal/app/system.go @@ -59,6 +59,7 @@ type appSeal struct { // appSealTx contains the system-level component of the app seal type appSealTx struct { bwrap *bwrap.Config + tmpfs []string // reference to D-Bus proxy instance, nil if disabled dbus *dbus.Proxy @@ -110,15 +111,6 @@ func (tx *appSealTx) setEnv(k, v string) { tx.bwrap.SetEnv[k] = v } -// bind mounts a directory within the sandbox -func (tx *appSealTx) bind(src, dest string, ro bool) { - if !ro { - tx.bwrap.Bind = append(tx.bwrap.Bind, [2]string{src, dest}) - } else { - tx.bwrap.ROBind = append(tx.bwrap.ROBind, [2]string{src, dest}) - } -} - // ensure appends a directory ensure action func (tx *appSealTx) ensure(path string, perm os.FileMode) { tx.mkdir = append(tx.mkdir, appEnsureEntry{path, perm, false}) @@ -183,14 +175,14 @@ func (tx *appSealTx) changeHosts(username string) { func (tx *appSealTx) writeFile(dst string, data []byte) { tx.files = append(tx.files, [2]string{dst, string(data)}) tx.updatePerm(dst, acl.Read) - tx.bind(dst, dst, true) + tx.bwrap.Bind(dst, dst) } // copyFile appends a tmpfiles action func (tx *appSealTx) copyFile(dst, src string) { tx.tmpfiles = append(tx.tmpfiles, [2]string{dst, src}) tx.updatePerm(dst, acl.Read) - tx.bind(dst, dst, true) + tx.bwrap.Bind(dst, dst) } // link appends a hardlink action @@ -324,6 +316,12 @@ func (tx *appSealTx) commit() error { // disarm partial commit rollback txp = nil + + // queue tmpfs at the end of tx.bwrap.Filesystem + for _, dest := range tx.tmpfs { + tx.bwrap.Tmpfs(dest, 8*1024) + } + return nil } @@ -416,10 +414,10 @@ func (seal *appSeal) shareAll(bus [2]*dbus.Config) error { } seal.shared = true + targetTmpdir := seal.shareTmpdirChild() + verbose.Printf("child tmpdir %q configured\n", targetTmpdir) seal.shareRuntime() seal.shareSystem() - targetRuntime := seal.shareRuntimeChild() - verbose.Printf("child runtime data dir '%s' configured\n", targetRuntime) if err := seal.shareDisplay(); err != nil { return err } diff --git a/internal/shim/parent.go b/internal/shim/parent.go index 9885655..59be588 100644 --- a/internal/shim/parent.go +++ b/internal/shim/parent.go @@ -8,12 +8,13 @@ import ( "os" "syscall" + "git.ophivana.moe/cat/fortify/acl" "git.ophivana.moe/cat/fortify/internal/verbose" ) // called in the parent process -func ServeConfig(socket string, payload *Payload, wl string, done chan struct{}) (*net.UnixConn, error) { +func ServeConfig(socket string, uid int, payload *Payload, wl string, done chan struct{}) (*net.UnixConn, error) { var ws *net.UnixConn if payload.WL { if f, err := net.DialUnix("unix", nil, &net.UnixAddr{Name: wl, Net: "unix"}); err != nil { @@ -28,7 +29,7 @@ func ServeConfig(socket string, payload *Payload, wl string, done chan struct{}) return nil, err } else { verbose.Println("configuring shim on socket", socket) - if err = os.Chmod(socket, 0777); err != nil { + if err = acl.UpdatePerm(socket, uid, acl.Read, acl.Write, acl.Execute); err != nil { fmt.Println("fortify: cannot change permissions of shim setup socket:", err) } @@ -39,6 +40,7 @@ func ServeConfig(socket string, payload *Payload, wl string, done chan struct{}) } else { if err = gob.NewEncoder(conn).Encode(*payload); err != nil { fmt.Println("fortify: cannot stream shim payload:", err) + _ = os.Remove(socket) return }