These are significantly more efficient and less error-prone than mounting an external tmpfile. This should also reduce attack surface as the resulting files are private to its specific sandbox. Signed-off-by: Ophestra <cat@gensokyo.uk>
274 lines
5.2 KiB
Go
274 lines
5.2 KiB
Go
package bwrap
|
|
|
|
import (
|
|
"encoding/gob"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strconv"
|
|
|
|
"git.gensokyo.uk/security/fortify/helper/proc"
|
|
)
|
|
|
|
func init() {
|
|
gob.Register(new(PermConfig[SymlinkConfig]))
|
|
gob.Register(new(PermConfig[*TmpfsConfig]))
|
|
gob.Register(new(OverlayConfig))
|
|
gob.Register(new(DataConfig))
|
|
}
|
|
|
|
type PositionalArg int
|
|
|
|
func (p PositionalArg) String() 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
|
|
|
|
SyncFd
|
|
Seccomp
|
|
|
|
File
|
|
BindData
|
|
ROBindData
|
|
)
|
|
|
|
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",
|
|
|
|
SyncFd: "--sync-fd",
|
|
Seccomp: "--seccomp",
|
|
|
|
File: "--file",
|
|
BindData: "--bind-data",
|
|
ROBindData: "--ro-bind-data",
|
|
}
|
|
|
|
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.String(), 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.String(), strconv.Itoa(t.Size))
|
|
}
|
|
*args = append(*args, Tmpfs.String(), 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.String(), src)
|
|
}
|
|
|
|
if o.Persist != nil {
|
|
if o.Persist[0] != "" && o.Persist[1] != "" {
|
|
// --overlay RWSRC WORKDIR
|
|
*args = append(*args, Overlay.String(), o.Persist[0], o.Persist[1])
|
|
} else {
|
|
// --ro-overlay
|
|
*args = append(*args, ROOverlay.String())
|
|
}
|
|
} else {
|
|
// --tmp-overlay
|
|
*args = append(*args, TmpOverlay.String())
|
|
}
|
|
|
|
// 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.String(), 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.String(), strconv.FormatInt(int64(mode), 8), path)
|
|
}
|
|
}
|
|
|
|
const (
|
|
DataWrite = iota
|
|
DataBind
|
|
DataROBind
|
|
)
|
|
|
|
type DataConfig struct {
|
|
Dest string `json:"dest"`
|
|
Data []byte `json:"data,omitempty"`
|
|
Type int `json:"type"`
|
|
proc.File
|
|
}
|
|
|
|
func (d *DataConfig) Path() string { return d.Dest }
|
|
func (d *DataConfig) Len() int {
|
|
if d == nil || d.Data == nil {
|
|
return 0
|
|
}
|
|
return 3
|
|
}
|
|
func (d *DataConfig) Init(fd uintptr, v **os.File) uintptr {
|
|
if d.File != nil {
|
|
panic("file initialised twice")
|
|
}
|
|
d.File = proc.NewWriterTo(d)
|
|
return d.File.Init(fd, v)
|
|
}
|
|
func (d *DataConfig) WriteTo(w io.Writer) (int64, error) {
|
|
n, err := w.Write(d.Data)
|
|
return int64(n), err
|
|
}
|
|
func (d *DataConfig) Append(args *[]string) {
|
|
if d == nil || d.Data == nil {
|
|
return
|
|
}
|
|
var a PositionalArg
|
|
switch d.Type {
|
|
case DataWrite:
|
|
a = File
|
|
case DataBind:
|
|
a = BindData
|
|
case DataROBind:
|
|
a = ROBindData
|
|
default:
|
|
panic(fmt.Sprintf("invalid type %d", a))
|
|
}
|
|
|
|
*args = append(*args, a.String(), strconv.Itoa(int(d.Fd())), d.Dest)
|
|
}
|