Compare commits
	
		
			No commits in common. "f955b15b84354c2f83ab542512f198404c1ec52f" and "a0062d827515c092cae7dfcf1af0033b87b3e90e" have entirely different histories.
		
	
	
		
			f955b15b84
			...
			a0062d8275
		
	
		
@ -1,8 +1,6 @@
 | 
			
		||||
package bwrap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
)
 | 
			
		||||
import "os"
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
Bind binds mount src on host to dest in sandbox.
 | 
			
		||||
@ -63,29 +61,6 @@ 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 {
 | 
			
		||||
 | 
			
		||||
@ -71,6 +71,9 @@ 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
 | 
			
		||||
 | 
			
		||||
@ -21,14 +21,15 @@ func TestConfig_Args(t *testing.T) {
 | 
			
		||||
		want []string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			"bind", (new(bwrap.Config)).
 | 
			
		||||
			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),
 | 
			
		||||
			[]string{
 | 
			
		||||
			want: []string{
 | 
			
		||||
				"--unshare-all", "--unshare-user",
 | 
			
		||||
				"--disable-userns", "--assert-userns-disabled",
 | 
			
		||||
				// Bind("/etc", "/.fortify/etc")
 | 
			
		||||
@ -46,13 +47,14 @@ func TestConfig_Args(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"dir remount-ro proc dev mqueue", (new(bwrap.Config)).
 | 
			
		||||
			name: "dir remount-ro proc dev mqueue",
 | 
			
		||||
			conf: (new(bwrap.Config)).
 | 
			
		||||
				Dir("/.fortify").
 | 
			
		||||
				RemountRO("/home").
 | 
			
		||||
				Procfs("/proc").
 | 
			
		||||
				DevTmpfs("/dev").
 | 
			
		||||
				Mqueue("/dev/mqueue"),
 | 
			
		||||
			[]string{
 | 
			
		||||
			want: []string{
 | 
			
		||||
				"--unshare-all", "--unshare-user",
 | 
			
		||||
				"--disable-userns", "--assert-userns-disabled",
 | 
			
		||||
				// Dir("/.fortify")
 | 
			
		||||
@ -68,10 +70,11 @@ func TestConfig_Args(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"tmpfs", (new(bwrap.Config)).
 | 
			
		||||
			name: "tmpfs",
 | 
			
		||||
			conf: (new(bwrap.Config)).
 | 
			
		||||
				Tmpfs("/run/user", 8192).
 | 
			
		||||
				Tmpfs("/run/dbus", 8192, 0755),
 | 
			
		||||
			[]string{
 | 
			
		||||
			want: []string{
 | 
			
		||||
				"--unshare-all", "--unshare-user",
 | 
			
		||||
				"--disable-userns", "--assert-userns-disabled",
 | 
			
		||||
				// Tmpfs("/run/user", 8192)
 | 
			
		||||
@ -81,10 +84,11 @@ func TestConfig_Args(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"symlink", (new(bwrap.Config)).
 | 
			
		||||
			name: "symlink",
 | 
			
		||||
			conf: (new(bwrap.Config)).
 | 
			
		||||
				Symlink("/.fortify/sbin/init", "/sbin/init").
 | 
			
		||||
				Symlink("/.fortify/sbin/init", "/sbin/init", 0755),
 | 
			
		||||
			[]string{
 | 
			
		||||
			want: []string{
 | 
			
		||||
				"--unshare-all", "--unshare-user",
 | 
			
		||||
				"--disable-userns", "--assert-userns-disabled",
 | 
			
		||||
				// Symlink("/.fortify/sbin/init", "/sbin/init")
 | 
			
		||||
@ -94,11 +98,12 @@ func TestConfig_Args(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"overlayfs", (new(bwrap.Config)).
 | 
			
		||||
			name: "overlayfs",
 | 
			
		||||
			conf: (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"),
 | 
			
		||||
			[]string{
 | 
			
		||||
			want: []string{
 | 
			
		||||
				"--unshare-all", "--unshare-user",
 | 
			
		||||
				"--disable-userns", "--assert-userns-disabled",
 | 
			
		||||
				// Overlay("/etc", "/etc")
 | 
			
		||||
@ -112,23 +117,8 @@ func TestConfig_Args(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"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{
 | 
			
		||||
			name: "unshare",
 | 
			
		||||
			conf: &bwrap.Config{Unshare: &bwrap.UnshareConfig{
 | 
			
		||||
				User:   false,
 | 
			
		||||
				IPC:    false,
 | 
			
		||||
				PID:    false,
 | 
			
		||||
@ -136,13 +126,14 @@ func TestConfig_Args(t *testing.T) {
 | 
			
		||||
				UTS:    false,
 | 
			
		||||
				CGroup: false,
 | 
			
		||||
			}},
 | 
			
		||||
			[]string{"--disable-userns", "--assert-userns-disabled"},
 | 
			
		||||
			want: []string{"--disable-userns", "--assert-userns-disabled"},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"uid gid sync", (new(bwrap.Config)).
 | 
			
		||||
			name: "uid gid sync",
 | 
			
		||||
			conf: (new(bwrap.Config)).
 | 
			
		||||
				SetUID(1971).
 | 
			
		||||
				SetGID(100),
 | 
			
		||||
			[]string{
 | 
			
		||||
			want: []string{
 | 
			
		||||
				"--unshare-all", "--unshare-user",
 | 
			
		||||
				"--disable-userns", "--assert-userns-disabled",
 | 
			
		||||
				// SetUID(1971)
 | 
			
		||||
@ -152,7 +143,8 @@ func TestConfig_Args(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"hostname chdir setenv unsetenv lockfile chmod syscall", &bwrap.Config{
 | 
			
		||||
			name: "hostname chdir setenv unsetenv lockfile chmod syscall",
 | 
			
		||||
			conf: &bwrap.Config{
 | 
			
		||||
				Hostname: "fortify",
 | 
			
		||||
				Chdir:    "/.fortify",
 | 
			
		||||
				SetEnv:   map[string]string{"FORTIFY_INIT": "/.fortify/sbin/init"},
 | 
			
		||||
@ -161,7 +153,7 @@ func TestConfig_Args(t *testing.T) {
 | 
			
		||||
				Syscall:  new(bwrap.SyscallPolicy),
 | 
			
		||||
				Chmod:    map[string]os.FileMode{"/.fortify/sbin/init": 0755},
 | 
			
		||||
			},
 | 
			
		||||
			[]string{
 | 
			
		||||
			want: []string{
 | 
			
		||||
				"--unshare-all", "--unshare-user",
 | 
			
		||||
				"--disable-userns", "--assert-userns-disabled",
 | 
			
		||||
				// Hostname: "fortify"
 | 
			
		||||
@ -183,7 +175,8 @@ func TestConfig_Args(t *testing.T) {
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			"xdg-dbus-proxy constraint sample", (&bwrap.Config{Clearenv: true, DieWithParent: true}).
 | 
			
		||||
			name: "xdg-dbus-proxy constraint sample",
 | 
			
		||||
			conf: (&bwrap.Config{Clearenv: true, DieWithParent: true}).
 | 
			
		||||
				Symlink("usr/bin", "/bin").
 | 
			
		||||
				Symlink("var/home", "/home").
 | 
			
		||||
				Symlink("usr/lib", "/lib").
 | 
			
		||||
@ -206,7 +199,7 @@ func TestConfig_Args(t *testing.T) {
 | 
			
		||||
				Bind("/sysroot", "/sysroot").
 | 
			
		||||
				Bind("/usr", "/usr").
 | 
			
		||||
				Bind("/etc", "/etc"),
 | 
			
		||||
			[]string{
 | 
			
		||||
			want: []string{
 | 
			
		||||
				"--unshare-all", "--unshare-user",
 | 
			
		||||
				"--disable-userns", "--assert-userns-disabled",
 | 
			
		||||
				"--clearenv", "--die-with-parent",
 | 
			
		||||
 | 
			
		||||
@ -2,19 +2,14 @@ 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
 | 
			
		||||
@ -49,10 +44,6 @@ const (
 | 
			
		||||
 | 
			
		||||
	SyncFd
 | 
			
		||||
	Seccomp
 | 
			
		||||
 | 
			
		||||
	File
 | 
			
		||||
	BindData
 | 
			
		||||
	ROBindData
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var positionalArgs = [...]string{
 | 
			
		||||
@ -83,10 +74,6 @@ var positionalArgs = [...]string{
 | 
			
		||||
 | 
			
		||||
	SyncFd:  "--sync-fd",
 | 
			
		||||
	Seccomp: "--seccomp",
 | 
			
		||||
 | 
			
		||||
	File:       "--file",
 | 
			
		||||
	BindData:   "--bind-data",
 | 
			
		||||
	ROBindData: "--ro-bind-data",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PermConfig[T FSBuilder] struct {
 | 
			
		||||
@ -215,59 +202,12 @@ 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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -62,6 +62,8 @@ var testCasesNixos = []sealTestCase{
 | 
			
		||||
			Ensure("/run/user/1971/fortify", 0700).UpdatePermType(system.User, "/run/user/1971/fortify", acl.Execute).
 | 
			
		||||
			Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
 | 
			
		||||
			Ephemeral(system.Process, "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1", acl.Execute).
 | 
			
		||||
			WriteType(system.Process, "/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/passwd", "u0_a1:x:1971:1971:Fortify:/var/lib/persist/module/fortify/0/1:/run/current-system/sw/bin/zsh\n").
 | 
			
		||||
			WriteType(system.Process, "/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/group", "fortify:x:1971:\n").
 | 
			
		||||
			Link("/run/user/1971/wayland-0", "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/wayland").
 | 
			
		||||
			UpdatePermType(system.EWayland, "/run/user/1971/wayland-0", acl.Read, acl.Write, acl.Execute).
 | 
			
		||||
			Link("/run/user/1971/pulse/native", "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/pulse").
 | 
			
		||||
@ -210,15 +212,13 @@ var testCasesNixos = []sealTestCase{
 | 
			
		||||
			Tmpfs("/run/user", 1048576).
 | 
			
		||||
			Tmpfs("/run/user/1971", 8388608).
 | 
			
		||||
			Bind("/var/lib/persist/module/fortify/0/1", "/var/lib/persist/module/fortify/0/1", false, true).
 | 
			
		||||
			CopyBind("/etc/passwd", []byte("u0_a1:x:1971:1971:Fortify:/var/lib/persist/module/fortify/0/1:/run/current-system/sw/bin/zsh\n")).
 | 
			
		||||
			CopyBind("/etc/group", []byte("fortify:x:1971:\n")).
 | 
			
		||||
			Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/passwd", "/etc/passwd").
 | 
			
		||||
			Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/group", "/etc/group").
 | 
			
		||||
			Bind("/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/wayland", "/run/user/1971/wayland-0").
 | 
			
		||||
			Bind("/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/pulse", "/run/user/1971/pulse/native").
 | 
			
		||||
			Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/pulse-cookie", fst.Tmp+"/pulse-cookie").
 | 
			
		||||
			Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", "/run/user/1971/bus").
 | 
			
		||||
			Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", "/run/dbus/system_bus_socket").
 | 
			
		||||
			Tmpfs("/var/run/nscd", 8192).
 | 
			
		||||
			Bind("/run/wrappers/bin/fortify", "/.fortify/sbin/fortify").
 | 
			
		||||
			Symlink("fortify", "/.fortify/sbin/init"),
 | 
			
		||||
			Tmpfs("/var/run/nscd", 8192),
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,9 @@ var testCasesPd = []sealTestCase{
 | 
			
		||||
			Ensure("/tmp/fortify.1971/tmpdir/0", 01700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir/0", acl.Read, acl.Write, acl.Execute).
 | 
			
		||||
			Ensure("/run/user/1971/fortify", 0700).UpdatePermType(system.User, "/run/user/1971/fortify", acl.Execute).
 | 
			
		||||
			Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
 | 
			
		||||
			Ephemeral(system.Process, "/run/user/1971/fortify/4a450b6596d7bc15bd01780eb9a607ac", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/4a450b6596d7bc15bd01780eb9a607ac", acl.Execute),
 | 
			
		||||
			Ephemeral(system.Process, "/run/user/1971/fortify/4a450b6596d7bc15bd01780eb9a607ac", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/4a450b6596d7bc15bd01780eb9a607ac", acl.Execute).
 | 
			
		||||
			WriteType(system.Process, "/tmp/fortify.1971/4a450b6596d7bc15bd01780eb9a607ac/passwd", "chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n").
 | 
			
		||||
			WriteType(system.Process, "/tmp/fortify.1971/4a450b6596d7bc15bd01780eb9a607ac/group", "fortify:x:65534:\n"),
 | 
			
		||||
		(&bwrap.Config{
 | 
			
		||||
			Net:      true,
 | 
			
		||||
			UserNS:   true,
 | 
			
		||||
@ -152,11 +154,9 @@ var testCasesPd = []sealTestCase{
 | 
			
		||||
			Tmpfs("/run/user", 1048576).
 | 
			
		||||
			Tmpfs("/run/user/65534", 8388608).
 | 
			
		||||
			Bind("/home/chronos", "/home/chronos", false, true).
 | 
			
		||||
			CopyBind("/etc/passwd", []byte("chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n")).
 | 
			
		||||
			CopyBind("/etc/group", []byte("fortify:x:65534:\n")).
 | 
			
		||||
			Tmpfs("/var/run/nscd", 8192).
 | 
			
		||||
			Bind("/run/wrappers/bin/fortify", "/.fortify/sbin/fortify").
 | 
			
		||||
			Symlink("fortify", "/.fortify/sbin/init"),
 | 
			
		||||
			Bind("/tmp/fortify.1971/4a450b6596d7bc15bd01780eb9a607ac/passwd", "/etc/passwd").
 | 
			
		||||
			Bind("/tmp/fortify.1971/4a450b6596d7bc15bd01780eb9a607ac/group", "/etc/group").
 | 
			
		||||
			Tmpfs("/var/run/nscd", 8192),
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		"nixos permissive defaults chromium", new(stubNixOS),
 | 
			
		||||
@ -216,6 +216,8 @@ var testCasesPd = []sealTestCase{
 | 
			
		||||
			Ensure("/run/user/1971/fortify", 0700).UpdatePermType(system.User, "/run/user/1971/fortify", acl.Execute).
 | 
			
		||||
			Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
 | 
			
		||||
			Ephemeral(system.Process, "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c", acl.Execute).
 | 
			
		||||
			WriteType(system.Process, "/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/passwd", "chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n").
 | 
			
		||||
			WriteType(system.Process, "/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/group", "fortify:x:65534:\n").
 | 
			
		||||
			Ensure("/tmp/fortify.1971/wayland", 0711).
 | 
			
		||||
			Wayland("/tmp/fortify.1971/wayland/ebf083d1b175911782d413369b64ce7c", "/run/user/1971/wayland-0", "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c").
 | 
			
		||||
			Link("/run/user/1971/pulse/native", "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/pulse").
 | 
			
		||||
@ -378,15 +380,13 @@ var testCasesPd = []sealTestCase{
 | 
			
		||||
			Tmpfs("/run/user", 1048576).
 | 
			
		||||
			Tmpfs("/run/user/65534", 8388608).
 | 
			
		||||
			Bind("/home/chronos", "/home/chronos", false, true).
 | 
			
		||||
			CopyBind("/etc/passwd", []byte("chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n")).
 | 
			
		||||
			CopyBind("/etc/group", []byte("fortify:x:65534:\n")).
 | 
			
		||||
			Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/passwd", "/etc/passwd").
 | 
			
		||||
			Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/group", "/etc/group").
 | 
			
		||||
			Bind("/tmp/fortify.1971/wayland/ebf083d1b175911782d413369b64ce7c", "/run/user/65534/wayland-0").
 | 
			
		||||
			Bind("/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/pulse", "/run/user/65534/pulse/native").
 | 
			
		||||
			Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/pulse-cookie", fst.Tmp+"/pulse-cookie").
 | 
			
		||||
			Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/bus", "/run/user/65534/bus").
 | 
			
		||||
			Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", "/run/dbus/system_bus_socket").
 | 
			
		||||
			Tmpfs("/var/run/nscd", 8192).
 | 
			
		||||
			Bind("/run/wrappers/bin/fortify", "/.fortify/sbin/fortify").
 | 
			
		||||
			Symlink("fortify", "/.fortify/sbin/init"),
 | 
			
		||||
			Tmpfs("/var/run/nscd", 8192),
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -16,12 +16,9 @@ type stubNixOS struct {
 | 
			
		||||
	usernameErr map[string]error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *stubNixOS) Geteuid() int                             { return 1971 }
 | 
			
		||||
func (s *stubNixOS) TempDir() string                          { return "/tmp" }
 | 
			
		||||
func (s *stubNixOS) MustExecutable() string                   { return "/run/wrappers/bin/fortify" }
 | 
			
		||||
func (s *stubNixOS) Exit(code int)                            { panic("called exit on stub with code " + strconv.Itoa(code)) }
 | 
			
		||||
func (s *stubNixOS) EvalSymlinks(path string) (string, error) { return path, nil }
 | 
			
		||||
func (s *stubNixOS) Uid(aid int) (int, error)                 { return 1000000 + 0*10000 + aid, nil }
 | 
			
		||||
func (s *stubNixOS) Geteuid() int {
 | 
			
		||||
	return 1971
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *stubNixOS) LookupEnv(key string) (string, bool) {
 | 
			
		||||
	switch key {
 | 
			
		||||
@ -42,6 +39,10 @@ func (s *stubNixOS) LookupEnv(key string) (string, bool) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *stubNixOS) TempDir() string {
 | 
			
		||||
	return "/tmp"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *stubNixOS) LookPath(file string) (string, error) {
 | 
			
		||||
	if s.lookPathErr != nil {
 | 
			
		||||
		if err, ok := s.lookPathErr[file]; ok {
 | 
			
		||||
@ -59,6 +60,10 @@ func (s *stubNixOS) LookPath(file string) (string, error) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *stubNixOS) Executable() (string, error) {
 | 
			
		||||
	return "/home/ophestra/.nix-profile/bin/fortify", nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *stubNixOS) LookupGroup(name string) (*user.Group, error) {
 | 
			
		||||
	switch name {
 | 
			
		||||
	case "video":
 | 
			
		||||
@ -122,6 +127,14 @@ func (s *stubNixOS) Open(name string) (fs.File, error) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *stubNixOS) EvalSymlinks(path string) (string, error) {
 | 
			
		||||
	return path, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *stubNixOS) Exit(code int) {
 | 
			
		||||
	panic("called exit on stub with code " + strconv.Itoa(code))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *stubNixOS) Paths() linux.Paths {
 | 
			
		||||
	return linux.Paths{
 | 
			
		||||
		SharePath:   "/tmp/fortify.1971",
 | 
			
		||||
@ -129,3 +142,11 @@ func (s *stubNixOS) Paths() linux.Paths {
 | 
			
		||||
		RunDirPath:  "/run/user/1971/fortify",
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *stubNixOS) Uid(aid int) (int, error) {
 | 
			
		||||
	return 1000000 + 0*10000 + aid, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *stubNixOS) SdBooted() bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -113,25 +113,34 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error {
 | 
			
		||||
		sh = s
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// bind home directory
 | 
			
		||||
	homeDir := "/var/empty"
 | 
			
		||||
	if seal.sys.user.home != "" {
 | 
			
		||||
		homeDir = seal.sys.user.home
 | 
			
		||||
	}
 | 
			
		||||
	// generate /etc/passwd
 | 
			
		||||
	passwdPath := path.Join(seal.share, "passwd")
 | 
			
		||||
	username := "chronos"
 | 
			
		||||
	if seal.sys.user.username != "" {
 | 
			
		||||
		username = seal.sys.user.username
 | 
			
		||||
	}
 | 
			
		||||
	homeDir := "/var/empty"
 | 
			
		||||
	if seal.sys.user.home != "" {
 | 
			
		||||
		homeDir = seal.sys.user.home
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// bind home directory
 | 
			
		||||
	seal.sys.bwrap.Bind(seal.sys.user.data, homeDir, false, true)
 | 
			
		||||
	seal.sys.bwrap.Chdir = homeDir
 | 
			
		||||
	seal.sys.bwrap.SetEnv["HOME"] = homeDir
 | 
			
		||||
	seal.sys.bwrap.SetEnv["USER"] = username
 | 
			
		||||
 | 
			
		||||
	// generate /etc/passwd and /etc/group
 | 
			
		||||
	seal.sys.bwrap.CopyBind("/etc/passwd",
 | 
			
		||||
		[]byte(username+":x:"+seal.sys.mappedIDString+":"+seal.sys.mappedIDString+":Fortify:"+homeDir+":"+sh+"\n"))
 | 
			
		||||
	seal.sys.bwrap.CopyBind("/etc/group",
 | 
			
		||||
		[]byte("fortify:x:"+seal.sys.mappedIDString+":\n"))
 | 
			
		||||
	seal.sys.bwrap.SetEnv["USER"] = username
 | 
			
		||||
	seal.sys.bwrap.SetEnv["HOME"] = homeDir
 | 
			
		||||
 | 
			
		||||
	passwd := username + ":x:" + seal.sys.mappedIDString + ":" + seal.sys.mappedIDString + ":Fortify:" + homeDir + ":" + sh + "\n"
 | 
			
		||||
	seal.sys.Write(passwdPath, passwd)
 | 
			
		||||
 | 
			
		||||
	// write /etc/group
 | 
			
		||||
	groupPath := path.Join(seal.share, "group")
 | 
			
		||||
	seal.sys.Write(groupPath, "fortify:x:"+seal.sys.mappedIDString+":\n")
 | 
			
		||||
 | 
			
		||||
	// bind /etc/passwd and /etc/group
 | 
			
		||||
	seal.sys.bwrap.Bind(passwdPath, "/etc/passwd")
 | 
			
		||||
	seal.sys.bwrap.Bind(groupPath, "/etc/group")
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
		Display servers
 | 
			
		||||
@ -284,10 +293,6 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error {
 | 
			
		||||
		seal.sys.bwrap.Tmpfs(dest, 8*1024)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// mount fortify in sandbox for init
 | 
			
		||||
	seal.sys.bwrap.Bind(os.MustExecutable(), path.Join(fst.Tmp, "sbin/fortify"))
 | 
			
		||||
	seal.sys.bwrap.Symlink("fortify", path.Join(fst.Tmp, "sbin/init"))
 | 
			
		||||
 | 
			
		||||
	// append extra perms
 | 
			
		||||
	for _, p := range seal.extraPerms {
 | 
			
		||||
		if p == nil {
 | 
			
		||||
 | 
			
		||||
@ -19,8 +19,8 @@ type System interface {
 | 
			
		||||
	TempDir() string
 | 
			
		||||
	// LookPath provides [exec.LookPath].
 | 
			
		||||
	LookPath(file string) (string, error)
 | 
			
		||||
	// MustExecutable provides [proc.MustExecutable].
 | 
			
		||||
	MustExecutable() string
 | 
			
		||||
	// Executable provides [os.Executable].
 | 
			
		||||
	Executable() (string, error)
 | 
			
		||||
	// LookupGroup provides [user.LookupGroup].
 | 
			
		||||
	LookupGroup(name string) (*user.Group, error)
 | 
			
		||||
	// ReadDir provides [os.ReadDir].
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,6 @@ import (
 | 
			
		||||
	"sync"
 | 
			
		||||
	"syscall"
 | 
			
		||||
 | 
			
		||||
	"git.gensokyo.uk/security/fortify/helper/proc"
 | 
			
		||||
	"git.gensokyo.uk/security/fortify/internal"
 | 
			
		||||
	"git.gensokyo.uk/security/fortify/internal/fmsg"
 | 
			
		||||
)
 | 
			
		||||
@ -33,7 +32,7 @@ func (s *Std) Geteuid() int                                 { return os.Geteuid(
 | 
			
		||||
func (s *Std) LookupEnv(key string) (string, bool)          { return os.LookupEnv(key) }
 | 
			
		||||
func (s *Std) TempDir() string                              { return os.TempDir() }
 | 
			
		||||
func (s *Std) LookPath(file string) (string, error)         { return exec.LookPath(file) }
 | 
			
		||||
func (s *Std) MustExecutable() string                       { return proc.MustExecutable() }
 | 
			
		||||
func (s *Std) Executable() (string, error)                  { return os.Executable() }
 | 
			
		||||
func (s *Std) LookupGroup(name string) (*user.Group, error) { return user.LookupGroup(name) }
 | 
			
		||||
func (s *Std) ReadDir(name string) ([]os.DirEntry, error)   { return os.ReadDir(name) }
 | 
			
		||||
func (s *Std) Stat(name string) (fs.FileInfo, error)        { return os.Stat(name) }
 | 
			
		||||
 | 
			
		||||
@ -121,12 +121,21 @@ func Main() {
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// bind fortify inside sandbox
 | 
			
		||||
	var (
 | 
			
		||||
		innerSbin    = path.Join(fst.Tmp, "sbin")
 | 
			
		||||
		innerFortify = path.Join(innerSbin, "fortify")
 | 
			
		||||
		innerInit    = path.Join(innerSbin, "init")
 | 
			
		||||
	)
 | 
			
		||||
	conf.Bind(proc.MustExecutable(), innerFortify)
 | 
			
		||||
	conf.Symlink("fortify", innerInit)
 | 
			
		||||
 | 
			
		||||
	helper.BubblewrapName = payload.Exec[0] // resolved bwrap path by parent
 | 
			
		||||
	if fmsg.Verbose() {
 | 
			
		||||
		seccomp.CPrintln = fmsg.Println
 | 
			
		||||
	}
 | 
			
		||||
	if b, err := helper.NewBwrap(
 | 
			
		||||
		conf, path.Join(fst.Tmp, "sbin/init"),
 | 
			
		||||
		conf, innerInit,
 | 
			
		||||
		nil, func(int, int) []string { return make([]string, 0) },
 | 
			
		||||
		extraFiles,
 | 
			
		||||
		syncFd,
 | 
			
		||||
 | 
			
		||||
@ -42,9 +42,26 @@ func (sys *I) LinkFileType(et Enablement, oldname, newname string) *I {
 | 
			
		||||
	return sys
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Write registers an Op that writes dst with the contents of src.
 | 
			
		||||
func (sys *I) Write(dst, src string) *I {
 | 
			
		||||
	return sys.WriteType(Process, dst, src)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WriteType registers a file writing Op labelled with type et.
 | 
			
		||||
func (sys *I) WriteType(et Enablement, dst, src string) *I {
 | 
			
		||||
	sys.lock.Lock()
 | 
			
		||||
	sys.ops = append(sys.ops, &Tmpfile{et, tmpfileWrite, dst, src})
 | 
			
		||||
	sys.lock.Unlock()
 | 
			
		||||
 | 
			
		||||
	sys.UpdatePermType(et, dst, acl.Read)
 | 
			
		||||
 | 
			
		||||
	return sys
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	tmpfileCopy uint8 = iota
 | 
			
		||||
	tmpfileLink
 | 
			
		||||
	tmpfileWrite
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Tmpfile struct {
 | 
			
		||||
@ -67,6 +84,10 @@ func (t *Tmpfile) apply(_ *I) error {
 | 
			
		||||
		fmsg.VPrintln("linking tmpfile", t)
 | 
			
		||||
		return fmsg.WrapErrorSuffix(os.Link(t.src, t.dst),
 | 
			
		||||
			fmt.Sprintf("cannot link tmpfile %q:", t.dst))
 | 
			
		||||
	case tmpfileWrite:
 | 
			
		||||
		fmsg.VPrintln("writing", t)
 | 
			
		||||
		return fmsg.WrapErrorSuffix(os.WriteFile(t.dst, []byte(t.src), 0600),
 | 
			
		||||
			fmt.Sprintf("cannot write tmpfile %q:", t.dst))
 | 
			
		||||
	default:
 | 
			
		||||
		panic("invalid tmpfile method " + strconv.Itoa(int(t.method)))
 | 
			
		||||
	}
 | 
			
		||||
@ -88,7 +109,12 @@ func (t *Tmpfile) Is(o Op) bool {
 | 
			
		||||
	return ok && t0 != nil && *t == *t0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *Tmpfile) Path() string { return t.src }
 | 
			
		||||
func (t *Tmpfile) Path() string {
 | 
			
		||||
	if t.method == tmpfileWrite {
 | 
			
		||||
		return fmt.Sprintf("(%d bytes of data)", len(t.src))
 | 
			
		||||
	}
 | 
			
		||||
	return t.src
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *Tmpfile) String() string {
 | 
			
		||||
	switch t.method {
 | 
			
		||||
@ -96,6 +122,8 @@ func (t *Tmpfile) String() string {
 | 
			
		||||
		return fmt.Sprintf("%q from %q", t.dst, t.src)
 | 
			
		||||
	case tmpfileLink:
 | 
			
		||||
		return fmt.Sprintf("%q from %q", t.dst, t.src)
 | 
			
		||||
	case tmpfileWrite:
 | 
			
		||||
		return fmt.Sprintf("%d bytes of data to %q", len(t.src), t.dst)
 | 
			
		||||
	default:
 | 
			
		||||
		panic("invalid tmpfile method " + strconv.Itoa(int(t.method)))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
package system
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"git.gensokyo.uk/security/fortify/acl"
 | 
			
		||||
@ -82,6 +83,47 @@ func TestLinkFileType(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWrite(t *testing.T) {
 | 
			
		||||
	testCases := []struct {
 | 
			
		||||
		dst, src string
 | 
			
		||||
	}{
 | 
			
		||||
		{"/etc/passwd", "chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n"},
 | 
			
		||||
		{"/etc/group", "fortify:x:65534:\n"},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tc := range testCases {
 | 
			
		||||
		t.Run("write "+strconv.Itoa(len(tc.src))+" bytes to "+tc.dst, func(t *testing.T) {
 | 
			
		||||
			sys := New(150)
 | 
			
		||||
			sys.Write(tc.dst, tc.src)
 | 
			
		||||
			(&tcOp{Process, "(" + strconv.Itoa(len(tc.src)) + " bytes of data)"}).test(t, sys.ops, []Op{
 | 
			
		||||
				&Tmpfile{Process, tmpfileWrite, tc.dst, tc.src},
 | 
			
		||||
				&ACL{Process, tc.dst, []acl.Perm{acl.Read}},
 | 
			
		||||
			}, "Write")
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWriteType(t *testing.T) {
 | 
			
		||||
	testCases := []struct {
 | 
			
		||||
		et       Enablement
 | 
			
		||||
		dst, src string
 | 
			
		||||
	}{
 | 
			
		||||
		{Process, "/etc/passwd", "chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n"},
 | 
			
		||||
		{Process, "/etc/group", "fortify:x:65534:\n"},
 | 
			
		||||
		{User, "/etc/passwd", "chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n"},
 | 
			
		||||
		{User, "/etc/group", "fortify:x:65534:\n"},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tc := range testCases {
 | 
			
		||||
		t.Run("write "+strconv.Itoa(len(tc.src))+" bytes to "+tc.dst+" with type "+TypeString(tc.et), func(t *testing.T) {
 | 
			
		||||
			sys := New(150)
 | 
			
		||||
			sys.WriteType(tc.et, tc.dst, tc.src)
 | 
			
		||||
			(&tcOp{tc.et, "(" + strconv.Itoa(len(tc.src)) + " bytes of data)"}).test(t, sys.ops, []Op{
 | 
			
		||||
				&Tmpfile{tc.et, tmpfileWrite, tc.dst, tc.src},
 | 
			
		||||
				&ACL{tc.et, tc.dst, []acl.Perm{acl.Read}},
 | 
			
		||||
			}, "WriteType")
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTmpfile_String(t *testing.T) {
 | 
			
		||||
	t.Run("invalid method panic", func(t *testing.T) {
 | 
			
		||||
		defer func() {
 | 
			
		||||
@ -105,6 +147,10 @@ func TestTmpfile_String(t *testing.T) {
 | 
			
		||||
			`"/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/wayland" from "/run/user/1971/wayland-0"`},
 | 
			
		||||
		{tmpfileLink, "/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse", "/run/user/1971/pulse/native",
 | 
			
		||||
			`"/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse" from "/run/user/1971/pulse/native"`},
 | 
			
		||||
		{tmpfileWrite, "/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/passwd", "chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n",
 | 
			
		||||
			`75 bytes of data to "/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/passwd"`},
 | 
			
		||||
		{tmpfileWrite, "/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/group", "fortify:x:65534:\n",
 | 
			
		||||
			`17 bytes of data to "/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/group"`},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tc := range testCases {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user