diff --git a/helper/bwrap/builder.go b/helper/bwrap/builder.go index 312295d..d196bc0 100644 --- a/helper/bwrap/builder.go +++ b/helper/bwrap/builder.go @@ -1,6 +1,8 @@ package bwrap -import "os" +import ( + "os" +) /* Bind binds mount src on host to dest in sandbox. @@ -61,6 +63,29 @@ func (c *Config) Bind(src, dest string, opts ...bool) *Config { } } +// Write copy from FD to destination DEST +// (--file FD DEST) +func (c *Config) Write(dest string, payload []byte) *Config { + c.Filesystem = append(c.Filesystem, &DataConfig{Dest: dest, Data: payload, Type: DataWrite}) + return c +} + +/* +CopyBind copy from FD to file which is readonly bind-mounted on DEST +(--ro-bind-data FD DEST) + +CopyBind(dest, payload, true) copy from FD to file which is bind-mounted on DEST +(--bind-data FD DEST) +*/ +func (c *Config) CopyBind(dest string, payload []byte, opts ...bool) *Config { + t := DataROBind + if len(opts) > 0 && opts[0] { + t = DataBind + } + c.Filesystem = append(c.Filesystem, &DataConfig{Dest: dest, Data: payload, Type: t}) + return c +} + // Dir create dir in sandbox // (--dir DEST) func (c *Config) Dir(dest string) *Config { diff --git a/helper/bwrap/config.go b/helper/bwrap/config.go index b9fa0c1..fdda14a 100644 --- a/helper/bwrap/config.go +++ b/helper/bwrap/config.go @@ -71,9 +71,6 @@ type Config struct { --ro-bind-fd FD DEST Bind open directory or path fd read-only on DEST --exec-label LABEL Exec label for the sandbox --file-label LABEL File label for temporary sandbox content - --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 --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 diff --git a/helper/bwrap/config_test.go b/helper/bwrap/config_test.go index 64d783c..ca6e963 100644 --- a/helper/bwrap/config_test.go +++ b/helper/bwrap/config_test.go @@ -21,15 +21,14 @@ func TestConfig_Args(t *testing.T) { want []string }{ { - name: "bind", - conf: (new(bwrap.Config)). + "bind", (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{ + []string{ "--unshare-all", "--unshare-user", "--disable-userns", "--assert-userns-disabled", // Bind("/etc", "/.fortify/etc") @@ -47,14 +46,13 @@ func TestConfig_Args(t *testing.T) { }, }, { - name: "dir remount-ro proc dev mqueue", - conf: (new(bwrap.Config)). + "dir remount-ro proc dev mqueue", (new(bwrap.Config)). Dir("/.fortify"). RemountRO("/home"). Procfs("/proc"). DevTmpfs("/dev"). Mqueue("/dev/mqueue"), - want: []string{ + []string{ "--unshare-all", "--unshare-user", "--disable-userns", "--assert-userns-disabled", // Dir("/.fortify") @@ -70,11 +68,10 @@ func TestConfig_Args(t *testing.T) { }, }, { - name: "tmpfs", - conf: (new(bwrap.Config)). + "tmpfs", (new(bwrap.Config)). Tmpfs("/run/user", 8192). Tmpfs("/run/dbus", 8192, 0755), - want: []string{ + []string{ "--unshare-all", "--unshare-user", "--disable-userns", "--assert-userns-disabled", // Tmpfs("/run/user", 8192) @@ -84,11 +81,10 @@ func TestConfig_Args(t *testing.T) { }, }, { - name: "symlink", - conf: (new(bwrap.Config)). + "symlink", (new(bwrap.Config)). Symlink("/.fortify/sbin/init", "/sbin/init"). Symlink("/.fortify/sbin/init", "/sbin/init", 0755), - want: []string{ + []string{ "--unshare-all", "--unshare-user", "--disable-userns", "--assert-userns-disabled", // Symlink("/.fortify/sbin/init", "/sbin/init") @@ -98,12 +94,11 @@ func TestConfig_Args(t *testing.T) { }, }, { - name: "overlayfs", - conf: (new(bwrap.Config)). + "overlayfs", (new(bwrap.Config)). Overlay("/etc", "/etc"). Join("/.fortify/bin", "/bin", "/usr/bin", "/usr/local/bin"). Persist("/nix", "/data/data/org.chromium.Chromium/overlay/rwsrc", "/data/data/org.chromium.Chromium/workdir", "/data/app/org.chromium.Chromium/nix"), - want: []string{ + []string{ "--unshare-all", "--unshare-user", "--disable-userns", "--assert-userns-disabled", // Overlay("/etc", "/etc") @@ -117,8 +112,23 @@ func TestConfig_Args(t *testing.T) { }, }, { - name: "unshare", - conf: &bwrap.Config{Unshare: &bwrap.UnshareConfig{ + "copy", (new(bwrap.Config)). + Write("/.fortify/version", make([]byte, 8)). + CopyBind("/etc/group", make([]byte, 8)). + CopyBind("/etc/passwd", make([]byte, 8), true), + []string{ + "--unshare-all", "--unshare-user", + "--disable-userns", "--assert-userns-disabled", + // Write("/.fortify/version", make([]byte, 8)) + "--file", "3", "/.fortify/version", + // CopyBind("/etc/group", make([]byte, 8)) + "--ro-bind-data", "4", "/etc/group", + // CopyBind("/etc/passwd", make([]byte, 8), true) + "--bind-data", "5", "/etc/passwd", + }, + }, + { + "unshare", &bwrap.Config{Unshare: &bwrap.UnshareConfig{ User: false, IPC: false, PID: false, @@ -126,14 +136,13 @@ func TestConfig_Args(t *testing.T) { UTS: false, CGroup: false, }}, - want: []string{"--disable-userns", "--assert-userns-disabled"}, + []string{"--disable-userns", "--assert-userns-disabled"}, }, { - name: "uid gid sync", - conf: (new(bwrap.Config)). + "uid gid sync", (new(bwrap.Config)). SetUID(1971). SetGID(100), - want: []string{ + []string{ "--unshare-all", "--unshare-user", "--disable-userns", "--assert-userns-disabled", // SetUID(1971) @@ -143,8 +152,7 @@ func TestConfig_Args(t *testing.T) { }, }, { - name: "hostname chdir setenv unsetenv lockfile chmod syscall", - conf: &bwrap.Config{ + "hostname chdir setenv unsetenv lockfile chmod syscall", &bwrap.Config{ Hostname: "fortify", Chdir: "/.fortify", SetEnv: map[string]string{"FORTIFY_INIT": "/.fortify/sbin/init"}, @@ -153,7 +161,7 @@ func TestConfig_Args(t *testing.T) { Syscall: new(bwrap.SyscallPolicy), Chmod: map[string]os.FileMode{"/.fortify/sbin/init": 0755}, }, - want: []string{ + []string{ "--unshare-all", "--unshare-user", "--disable-userns", "--assert-userns-disabled", // Hostname: "fortify" @@ -175,8 +183,7 @@ func TestConfig_Args(t *testing.T) { }, { - name: "xdg-dbus-proxy constraint sample", - conf: (&bwrap.Config{Clearenv: true, DieWithParent: true}). + "xdg-dbus-proxy constraint sample", (&bwrap.Config{Clearenv: true, DieWithParent: true}). Symlink("usr/bin", "/bin"). Symlink("var/home", "/home"). Symlink("usr/lib", "/lib"). @@ -199,7 +206,7 @@ func TestConfig_Args(t *testing.T) { Bind("/sysroot", "/sysroot"). Bind("/usr", "/usr"). Bind("/etc", "/etc"), - want: []string{ + []string{ "--unshare-all", "--unshare-user", "--disable-userns", "--assert-userns-disabled", "--clearenv", "--die-with-parent", diff --git a/helper/bwrap/sequential.go b/helper/bwrap/sequential.go index fb97e65..53ab8aa 100644 --- a/helper/bwrap/sequential.go +++ b/helper/bwrap/sequential.go @@ -2,14 +2,19 @@ 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 @@ -44,6 +49,10 @@ const ( SyncFd Seccomp + + File + BindData + ROBindData ) var positionalArgs = [...]string{ @@ -74,6 +83,10 @@ var positionalArgs = [...]string{ SyncFd: "--sync-fd", Seccomp: "--seccomp", + + File: "--file", + BindData: "--bind-data", + ROBindData: "--ro-bind-data", } type PermConfig[T FSBuilder] struct { @@ -202,12 +215,59 @@ func (s SymlinkConfig) Append(args *[]string) { *args = append(*args, Symlink.St type ChmodConfig map[string]os.FileMode -func (c ChmodConfig) Len() int { - return len(c) -} - +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) +}