aboutsummaryrefslogtreecommitdiffhomepage
path: root/helper
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-02-15 03:12:28 +0900
committerOphestra <cat@gensokyo.uk>2025-02-15 03:13:15 +0900
commit72b0160aadac590bc479b9ec86e0378a95d2bdc0 (patch)
tree6dc823776b40ef448e1ea6c073493cb426a84796 /helper
parentea8d1c07df18ceb94932fcc94ad00eb57c99da33 (diff)
helper/bwrap: implement file copy flags
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>
Diffstat (limited to 'helper')
-rw-r--r--helper/bwrap/builder.go27
-rw-r--r--helper/bwrap/config.go3
-rw-r--r--helper/bwrap/config_test.go61
-rw-r--r--helper/bwrap/sequential.go68
4 files changed, 124 insertions, 35 deletions
diff --git a/helper/bwrap/builder.go b/helper/bwrap/builder.go
index 312295d6..d196bc06 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 b9fa0c12..fdda14a4 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 64d783cf..ca6e9636 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 fb97e651..53ab8aae 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)
+}