Compare commits
	
		
			No commits in common. "9b206072faa0511eea65f243d36226248c0a098f" and "c109ac26530ba38d3713bedcb404c1ddd7452d13" have entirely different histories.
		
	
	
		
			9b206072fa
			...
			c109ac2653
		
	
		
| @ -13,8 +13,6 @@ type Payload struct { | ||||
| 	Exec [2]string | ||||
| 	// bwrap config | ||||
| 	Bwrap *bwrap.Config | ||||
| 	// path to outer home directory | ||||
| 	Home string | ||||
| 	// sync fd | ||||
| 	Sync *uintptr | ||||
| 
 | ||||
|  | ||||
| @ -9,7 +9,6 @@ import ( | ||||
| 
 | ||||
| 	init0 "git.gensokyo.uk/security/fortify/cmd/finit/ipc" | ||||
| 	shim "git.gensokyo.uk/security/fortify/cmd/fshim/ipc" | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	"git.gensokyo.uk/security/fortify/helper" | ||||
| 	"git.gensokyo.uk/security/fortify/internal" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||
| @ -81,21 +80,6 @@ func main() { | ||||
| 		// not fatal | ||||
| 	} | ||||
| 
 | ||||
| 	// ensure home directory as target user | ||||
| 	if s, err := os.Stat(payload.Home); err != nil { | ||||
| 		if os.IsNotExist(err) { | ||||
| 			if err = os.Mkdir(payload.Home, 0700); err != nil { | ||||
| 				fmsg.Fatalf("cannot create home directory: %v", err) | ||||
| 			} | ||||
| 		} else { | ||||
| 			fmsg.Fatalf("cannot access home directory: %v", err) | ||||
| 		} | ||||
| 
 | ||||
| 		// home directory is created, proceed | ||||
| 	} else if !s.IsDir() { | ||||
| 		fmsg.Fatalf("data path %q is not a directory", payload.Home) | ||||
| 	} | ||||
| 
 | ||||
| 	var ic init0.Payload | ||||
| 
 | ||||
| 	// resolve argv0 | ||||
| @ -133,12 +117,8 @@ func main() { | ||||
| 		}() | ||||
| 	} | ||||
| 
 | ||||
| 	// bind finit inside sandbox | ||||
| 	finitInnerPath := path.Join(fst.Tmp, "sbin", "init") | ||||
| 	conf.Bind(finitPath, finitInnerPath) | ||||
| 
 | ||||
| 	helper.BubblewrapName = payload.Exec[0] // resolved bwrap path by parent | ||||
| 	if b, err := helper.NewBwrap(conf, nil, finitInnerPath, | ||||
| 	if b, err := helper.NewBwrap(conf, nil, finitPath, | ||||
| 		func(int, int) []string { return make([]string, 0) }); err != nil { | ||||
| 		fmsg.Fatalf("malformed sandbox config: %v", err) | ||||
| 	} else { | ||||
|  | ||||
| @ -124,8 +124,6 @@ func testProxyStartWaitCloseString(t *testing.T, sandbox bool) { | ||||
| 
 | ||||
| 		t.Run("proxy for "+id, func(t *testing.T) { | ||||
| 			helper.InternalReplaceExecCommand(t) | ||||
| 			overridePath(t) | ||||
| 
 | ||||
| 			p := dbus.New(tc[0].bus, tc[1].bus) | ||||
| 			output := new(strings.Builder) | ||||
| 
 | ||||
| @ -176,7 +174,7 @@ func testProxyStartWaitCloseString(t *testing.T, sandbox bool) { | ||||
| 
 | ||||
| 				t.Run("sealed start of "+id, func(t *testing.T) { | ||||
| 					if err := p.Start(nil, output, sandbox); err != nil { | ||||
| 						t.Fatalf("Start(nil, nil) error = %v", | ||||
| 						t.Errorf("Start(nil, nil) error = %v", | ||||
| 							err) | ||||
| 					} | ||||
| 
 | ||||
| @ -215,11 +213,3 @@ func testProxyStartWaitCloseString(t *testing.T, sandbox bool) { | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func overridePath(t *testing.T) { | ||||
| 	proxyName := dbus.ProxyName | ||||
| 	dbus.ProxyName = "/nonexistent-xdg-dbus-proxy" | ||||
| 	t.Cleanup(func() { | ||||
| 		dbus.ProxyName = proxyName | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| @ -46,16 +46,14 @@ func (p *Proxy) Start(ready chan error, output io.Writer, sandbox bool) error { | ||||
| 		// look up absolute path if name is just a file name | ||||
| 		toolPath := p.name | ||||
| 		if filepath.Base(p.name) == p.name { | ||||
| 			if s, err := exec.LookPath(p.name); err != nil { | ||||
| 				return err | ||||
| 			} else { | ||||
| 			if s, err := exec.LookPath(p.name); err == nil { | ||||
| 				toolPath = s | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// resolve libraries by parsing ldd output | ||||
| 		var proxyDeps []*ldd.Entry | ||||
| 		if toolPath != "/nonexistent-xdg-dbus-proxy" { | ||||
| 		if path.IsAbs(toolPath) { | ||||
| 			if l, err := ldd.Exec(toolPath); err != nil { | ||||
| 				return err | ||||
| 			} else { | ||||
| @ -93,9 +91,6 @@ func (p *Proxy) Start(ready chan error, output io.Writer, sandbox bool) error { | ||||
| 			if path.IsAbs(ent.Path) { | ||||
| 				roBindTarget[path.Dir(ent.Path)] = struct{}{} | ||||
| 			} | ||||
| 			if path.IsAbs(ent.Name) { | ||||
| 				roBindTarget[path.Dir(ent.Name)] = struct{}{} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// resolve upstream bus directories | ||||
|  | ||||
							
								
								
									
										4
									
								
								dist/install.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								dist/install.sh
									
									
									
									
										vendored
									
									
								
							| @ -7,8 +7,4 @@ install -vDm0755 "bin/finit" "${FORTIFY_INSTALL_PREFIX}/usr/libexec/fortify/fini | ||||
| install -vDm0755 "bin/fuserdb" "${FORTIFY_INSTALL_PREFIX}/usr/libexec/fortify/fuserdb" | ||||
| 
 | ||||
| install -vDm6511 "bin/fsu" "${FORTIFY_INSTALL_PREFIX}/usr/bin/fsu" | ||||
| if [ ! -f "${FORTIFY_INSTALL_PREFIX}/etc/fsurc" ]; then | ||||
| install -vDm0400 "fsurc.default" "${FORTIFY_INSTALL_PREFIX}/etc/fsurc" | ||||
| fi | ||||
| 
 | ||||
| install -vDm0644 "comp/_fortify" "${FORTIFY_INSTALL_PREFIX}/usr/share/zsh/site-functions/_fortify" | ||||
							
								
								
									
										7
									
								
								dist/release.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								dist/release.sh
									
									
									
									
										vendored
									
									
								
							| @ -5,10 +5,9 @@ pname="fortify-${VERSION}" | ||||
| out="dist/${pname}" | ||||
| 
 | ||||
| mkdir -p "${out}" | ||||
| cp -v "README.md" "dist/fsurc.default" "dist/install.sh" "${out}" | ||||
| cp -rv "comp" "${out}" | ||||
| cp "README.md" "dist/fsurc.default" "dist/install.sh" "${out}" | ||||
| 
 | ||||
| go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w | ||||
| go build -v -o "${out}/bin/" -ldflags "-s -w | ||||
|   -X git.gensokyo.uk/security/fortify/internal.Version=${VERSION} | ||||
|   -X git.gensokyo.uk/security/fortify/internal.Fsu=/usr/bin/fsu | ||||
|   -X git.gensokyo.uk/security/fortify/internal.Finit=/usr/libexec/fortify/finit | ||||
| @ -17,4 +16,4 @@ go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w | ||||
| 
 | ||||
| rm -f "./${out}.tar.gz" && tar -C dist -czf "${out}.tar.gz" "${pname}" | ||||
| rm -rf "./${out}" | ||||
| (cd dist && sha512sum "${pname}.tar.gz" > "${pname}.tar.gz.sha512") | ||||
| sha512sum "${out}.tar.gz" > "${out}.tar.gz.sha512" | ||||
| @ -13,11 +13,12 @@ const Tmp = "/.fortify" | ||||
| 
 | ||||
| // Config is used to seal an *App | ||||
| type Config struct { | ||||
| 	// application ID | ||||
| 	// D-Bus application ID | ||||
| 	ID string `json:"id"` | ||||
| 	// value passed through to the child process as its argv | ||||
| 	Command []string `json:"command"` | ||||
| 
 | ||||
| 	// child confinement configuration | ||||
| 	Confinement ConfinementConfig `json:"confinement"` | ||||
| } | ||||
| 
 | ||||
| @ -27,7 +28,7 @@ type ConfinementConfig struct { | ||||
| 	AppID int `json:"app_id"` | ||||
| 	// list of supplementary groups to inherit | ||||
| 	Groups []string `json:"groups"` | ||||
| 	// passwd username in the sandbox, defaults to passwd name of target uid or chronos | ||||
| 	// passwd username in the sandbox, defaults to chronos | ||||
| 	Username string `json:"username,omitempty"` | ||||
| 	// home directory in sandbox, empty for outer | ||||
| 	Inner string `json:"home_inner"` | ||||
| @ -35,8 +36,6 @@ type ConfinementConfig struct { | ||||
| 	Outer string `json:"home"` | ||||
| 	// bwrap sandbox confinement configuration | ||||
| 	Sandbox *SandboxConfig `json:"sandbox"` | ||||
| 	// extra acl entries to append | ||||
| 	ExtraPerms []*ExtraPermConfig `json:"extra_perms,omitempty"` | ||||
| 
 | ||||
| 	// reference to a system D-Bus proxy configuration, | ||||
| 	// nil value disables system bus proxy | ||||
| @ -45,7 +44,7 @@ type ConfinementConfig struct { | ||||
| 	// nil value makes session bus proxy assume built-in defaults | ||||
| 	SessionBus *dbus.Config `json:"session_bus,omitempty"` | ||||
| 
 | ||||
| 	// system resources to expose to the sandbox | ||||
| 	// child capability enablements | ||||
| 	Enablements system.Enablements `json:"enablements"` | ||||
| } | ||||
| 
 | ||||
| @ -53,7 +52,7 @@ type ConfinementConfig struct { | ||||
| type SandboxConfig struct { | ||||
| 	// unix hostname within sandbox | ||||
| 	Hostname string `json:"hostname,omitempty"` | ||||
| 	// allow userns within sandbox | ||||
| 	// userns availability within sandbox | ||||
| 	UserNS bool `json:"userns,omitempty"` | ||||
| 	// share net namespace | ||||
| 	Net bool `json:"net,omitempty"` | ||||
| @ -72,42 +71,12 @@ type SandboxConfig struct { | ||||
| 	Filesystem []*FilesystemConfig `json:"filesystem"` | ||||
| 	// symlinks created inside the sandbox | ||||
| 	Link [][2]string `json:"symlink"` | ||||
| 	// read-only /etc directory | ||||
| 	Etc string `json:"etc,omitempty"` | ||||
| 	// automatically set up /etc symlinks | ||||
| 	AutoEtc bool `json:"auto_etc"` | ||||
| 	// paths to override by mounting tmpfs over them | ||||
| 	Override []string `json:"override"` | ||||
| } | ||||
| 
 | ||||
| type ExtraPermConfig struct { | ||||
| 	Ensure  bool   `json:"ensure,omitempty"` | ||||
| 	Path    string `json:"path"` | ||||
| 	Read    bool   `json:"r,omitempty"` | ||||
| 	Write   bool   `json:"w,omitempty"` | ||||
| 	Execute bool   `json:"x,omitempty"` | ||||
| } | ||||
| 
 | ||||
| func (e *ExtraPermConfig) String() string { | ||||
| 	buf := make([]byte, 0, 5+len(e.Path)) | ||||
| 	buf = append(buf, '-', '-', '-') | ||||
| 	if e.Ensure { | ||||
| 		buf = append(buf, '+') | ||||
| 	} | ||||
| 	buf = append(buf, ':') | ||||
| 	buf = append(buf, []byte(e.Path)...) | ||||
| 	if e.Read { | ||||
| 		buf[0] = 'r' | ||||
| 	} | ||||
| 	if e.Write { | ||||
| 		buf[1] = 'w' | ||||
| 	} | ||||
| 	if e.Execute { | ||||
| 		buf[2] = 'x' | ||||
| 	} | ||||
| 	return string(buf) | ||||
| } | ||||
| 
 | ||||
| type FilesystemConfig struct { | ||||
| 	// mount point in sandbox, same as src if empty | ||||
| 	Dst string `json:"dst,omitempty"` | ||||
| @ -117,7 +86,7 @@ type FilesystemConfig struct { | ||||
| 	Write bool `json:"write,omitempty"` | ||||
| 	// device access | ||||
| 	Device bool `json:"dev,omitempty"` | ||||
| 	// fail if mount fails | ||||
| 	// exit if unable to share | ||||
| 	Must bool `json:"require,omitempty"` | ||||
| } | ||||
| 
 | ||||
| @ -159,11 +128,7 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) { | ||||
| 	} | ||||
| 
 | ||||
| 	if !s.AutoEtc { | ||||
| 		if s.Etc == "" { | ||||
| 		conf.Dir("/etc") | ||||
| 		} else { | ||||
| 			conf.Bind(s.Etc, "/etc") | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for _, c := range s.Filesystem { | ||||
| @ -183,14 +148,10 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) { | ||||
| 	} | ||||
| 
 | ||||
| 	if s.AutoEtc { | ||||
| 		etc := s.Etc | ||||
| 		if etc == "" { | ||||
| 			etc = "/etc" | ||||
| 		} | ||||
| 		conf.Bind(etc, Tmp+"/etc") | ||||
| 		conf.Bind("/etc", Tmp+"/etc") | ||||
| 
 | ||||
| 		// link host /etc contents to prevent passwd/group from being overwritten | ||||
| 		if d, err := os.ReadDir(etc); err != nil { | ||||
| 		if d, err := os.ReadDir("/etc"); err != nil { | ||||
| 			return nil, err | ||||
| 		} else { | ||||
| 			for _, ent := range d { | ||||
| @ -252,7 +213,6 @@ func Template() *Config { | ||||
| 					{Src: "/dev/dri", Device: true}, | ||||
| 				}, | ||||
| 				Link:     [][2]string{{"/run/user/65534", "/run/user/150"}}, | ||||
| 				Etc:      "/etc", | ||||
| 				AutoEtc:  true, | ||||
| 				Override: []string{"/var/run/nscd"}, | ||||
| 			}, | ||||
|  | ||||
| @ -106,7 +106,7 @@ func (c *Config) Mqueue(dest string) *Config { | ||||
| // Dir create dir in sandbox | ||||
| // (--dir DEST) | ||||
| func (c *Config) Dir(dest string) *Config { | ||||
| 	c.Filesystem = append(c.Filesystem, &stringF{awkwardArgs[Dir], dest}) | ||||
| 	c.Filesystem = append(c.Filesystem, &stringF{stringArgs[Dir], dest}) | ||||
| 	return c | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -8,7 +8,6 @@ import ( | ||||
| 	"regexp" | ||||
| 	"strconv" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/acl" | ||||
| 	"git.gensokyo.uk/security/fortify/dbus" | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||
| @ -49,8 +48,6 @@ type appSeal struct { | ||||
| 	et system.Enablements | ||||
| 	// wayland socket direct access | ||||
| 	directWayland bool | ||||
| 	// extra UpdatePerm ops | ||||
| 	extraPerms []*sealedExtraPerm | ||||
| 
 | ||||
| 	// prevents sharing from happening twice | ||||
| 	shared bool | ||||
| @ -62,12 +59,6 @@ type appSeal struct { | ||||
| 	// protected by upstream mutex | ||||
| } | ||||
| 
 | ||||
| type sealedExtraPerm struct { | ||||
| 	name   string | ||||
| 	perms  acl.Perms | ||||
| 	ensure bool | ||||
| } | ||||
| 
 | ||||
| // Seal seals the app launch context | ||||
| func (a *app) Seal(config *fst.Config) error { | ||||
| 	a.lock.Lock() | ||||
| @ -109,7 +100,7 @@ func (a *app) Seal(config *fst.Config) error { | ||||
| 	if config.Confinement.AppID < 0 || config.Confinement.AppID > 9999 { | ||||
| 		return fmsg.WrapError(ErrUser, | ||||
| 			fmt.Sprintf("aid %d out of range", config.Confinement.AppID)) | ||||
| 	} | ||||
| 	} else { | ||||
| 		seal.sys.user = appUser{ | ||||
| 			aid:      config.Confinement.AppID, | ||||
| 			as:       strconv.Itoa(config.Confinement.AppID), | ||||
| @ -150,27 +141,6 @@ func (a *app) Seal(config *fst.Config) error { | ||||
| 				seal.sys.user.supp[i] = g.Gid | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 	// build extra perms | ||||
| 	seal.extraPerms = make([]*sealedExtraPerm, len(config.Confinement.ExtraPerms)) | ||||
| 	for i, p := range config.Confinement.ExtraPerms { | ||||
| 		if p == nil { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		seal.extraPerms[i] = new(sealedExtraPerm) | ||||
| 		seal.extraPerms[i].name = p.Path | ||||
| 		seal.extraPerms[i].perms = make(acl.Perms, 0, 3) | ||||
| 		if p.Read { | ||||
| 			seal.extraPerms[i].perms = append(seal.extraPerms[i].perms, acl.Read) | ||||
| 		} | ||||
| 		if p.Write { | ||||
| 			seal.extraPerms[i].perms = append(seal.extraPerms[i].perms, acl.Write) | ||||
| 		} | ||||
| 		if p.Execute { | ||||
| 			seal.extraPerms[i].perms = append(seal.extraPerms[i].perms, acl.Execute) | ||||
| 		} | ||||
| 		seal.extraPerms[i].ensure = p.Ensure | ||||
| 	} | ||||
| 
 | ||||
| 	// map sandbox config to bwrap | ||||
| @ -259,7 +229,7 @@ func (a *app) Seal(config *fst.Config) error { | ||||
| 	seal.et = config.Confinement.Enablements | ||||
| 
 | ||||
| 	// this method calls all share methods in sequence | ||||
| 	if err := seal.setupShares([2]*dbus.Config{config.Confinement.SessionBus, config.Confinement.SystemBus}, a.os); err != nil { | ||||
| 	if err := seal.shareAll([2]*dbus.Config{config.Confinement.SessionBus, config.Confinement.SystemBus}, a.os); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										44
									
								
								internal/app/share.dbus.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								internal/app/share.dbus.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,44 @@ | ||||
| package app | ||||
| 
 | ||||
| import ( | ||||
| 	"path" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/acl" | ||||
| 	"git.gensokyo.uk/security/fortify/dbus" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/system" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	dbusSessionBusAddress = "DBUS_SESSION_BUS_ADDRESS" | ||||
| 	dbusSystemBusAddress  = "DBUS_SYSTEM_BUS_ADDRESS" | ||||
| ) | ||||
| 
 | ||||
| func (seal *appSeal) shareDBus(config [2]*dbus.Config) error { | ||||
| 	if !seal.et.Has(system.EDBus) { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	// downstream socket paths | ||||
| 	sessionPath, systemPath := path.Join(seal.share, "bus"), path.Join(seal.share, "system_bus_socket") | ||||
| 
 | ||||
| 	// configure dbus proxy | ||||
| 	if f, err := seal.sys.ProxyDBus(config[0], config[1], sessionPath, systemPath); err != nil { | ||||
| 		return err | ||||
| 	} else { | ||||
| 		seal.dbusMsg = f | ||||
| 	} | ||||
| 
 | ||||
| 	// share proxy sockets | ||||
| 	sessionInner := path.Join(seal.sys.runtime, "bus") | ||||
| 	seal.sys.bwrap.SetEnv[dbusSessionBusAddress] = "unix:path=" + sessionInner | ||||
| 	seal.sys.bwrap.Bind(sessionPath, sessionInner) | ||||
| 	seal.sys.UpdatePerm(sessionPath, acl.Read, acl.Write) | ||||
| 	if config[1] != nil { | ||||
| 		systemInner := "/run/dbus/system_bus_socket" | ||||
| 		seal.sys.bwrap.SetEnv[dbusSystemBusAddress] = "unix:path=" + systemInner | ||||
| 		seal.sys.bwrap.Bind(systemPath, systemInner) | ||||
| 		seal.sys.UpdatePerm(systemPath, acl.Read, acl.Write) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										81
									
								
								internal/app/share.display.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								internal/app/share.display.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,81 @@ | ||||
| package app | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"path" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/acl" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/linux" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/system" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	term    = "TERM" | ||||
| 	display = "DISPLAY" | ||||
| 
 | ||||
| 	// https://manpages.debian.org/experimental/libwayland-doc/wl_display_connect.3.en.html | ||||
| 	waylandDisplay = "WAYLAND_DISPLAY" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	ErrWayland  = errors.New(waylandDisplay + " unset") | ||||
| 	ErrXDisplay = errors.New(display + " unset") | ||||
| ) | ||||
| 
 | ||||
| func (seal *appSeal) shareDisplay(os linux.System) error { | ||||
| 	// pass $TERM to launcher | ||||
| 	if t, ok := os.LookupEnv(term); ok { | ||||
| 		seal.sys.bwrap.SetEnv[term] = t | ||||
| 	} | ||||
| 
 | ||||
| 	// set up wayland | ||||
| 	if seal.et.Has(system.EWayland) { | ||||
| 		var wp string | ||||
| 		if wd, ok := os.LookupEnv(waylandDisplay); !ok { | ||||
| 			return fmsg.WrapError(ErrWayland, | ||||
| 				"WAYLAND_DISPLAY is not set") | ||||
| 		} else { | ||||
| 			wp = path.Join(seal.RuntimePath, wd) | ||||
| 		} | ||||
| 
 | ||||
| 		w := path.Join(seal.sys.runtime, "wayland-0") | ||||
| 		seal.sys.bwrap.SetEnv[waylandDisplay] = w | ||||
| 
 | ||||
| 		if seal.directWayland { | ||||
| 			// hardlink wayland socket | ||||
| 			wpi := path.Join(seal.shareLocal, "wayland") | ||||
| 			seal.sys.Link(wp, wpi) | ||||
| 			seal.sys.bwrap.Bind(wpi, w) | ||||
| 
 | ||||
| 			// ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`) | ||||
| 			seal.sys.UpdatePermType(system.EWayland, wp, acl.Read, acl.Write, acl.Execute) | ||||
| 		} else { | ||||
| 			wc := path.Join(seal.SharePath, "wayland") | ||||
| 			wt := path.Join(wc, seal.id) | ||||
| 			seal.sys.Ensure(wc, 0711) | ||||
| 			appID := seal.fid | ||||
| 			if appID == "" { | ||||
| 				// use instance ID in case app id is not set | ||||
| 				appID = "moe.ophivana.fortify." + seal.id | ||||
| 			} | ||||
| 			seal.sys.Wayland(wt, wp, appID, seal.id) | ||||
| 			seal.sys.bwrap.Bind(wt, w) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// set up X11 | ||||
| 	if seal.et.Has(system.EX11) { | ||||
| 		// discover X11 and grant user permission via the `ChangeHosts` command | ||||
| 		if d, ok := os.LookupEnv(display); !ok { | ||||
| 			return fmsg.WrapError(ErrXDisplay, | ||||
| 				"DISPLAY is not set") | ||||
| 		} else { | ||||
| 			seal.sys.ChangeHosts("#" + seal.sys.user.us) | ||||
| 			seal.sys.bwrap.SetEnv[display] = d | ||||
| 			seal.sys.bwrap.Bind("/tmp/.X11-unix", "/tmp/.X11-unix") | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| @ -1,346 +0,0 @@ | ||||
| package app | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/fs" | ||||
| 	"path" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/acl" | ||||
| 	"git.gensokyo.uk/security/fortify/dbus" | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/linux" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/system" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	home  = "HOME" | ||||
| 	shell = "SHELL" | ||||
| 
 | ||||
| 	xdgConfigHome   = "XDG_CONFIG_HOME" | ||||
| 	xdgRuntimeDir   = "XDG_RUNTIME_DIR" | ||||
| 	xdgSessionClass = "XDG_SESSION_CLASS" | ||||
| 	xdgSessionType  = "XDG_SESSION_TYPE" | ||||
| 
 | ||||
| 	term    = "TERM" | ||||
| 	display = "DISPLAY" | ||||
| 
 | ||||
| 	// https://manpages.debian.org/experimental/libwayland-doc/wl_display_connect.3.en.html | ||||
| 	waylandDisplay = "WAYLAND_DISPLAY" | ||||
| 
 | ||||
| 	pulseServer = "PULSE_SERVER" | ||||
| 	pulseCookie = "PULSE_COOKIE" | ||||
| 
 | ||||
| 	dbusSessionBusAddress = "DBUS_SESSION_BUS_ADDRESS" | ||||
| 	dbusSystemBusAddress  = "DBUS_SYSTEM_BUS_ADDRESS" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	ErrWayland  = errors.New(waylandDisplay + " unset") | ||||
| 	ErrXDisplay = errors.New(display + " unset") | ||||
| 
 | ||||
| 	ErrPulseCookie = errors.New("pulse cookie not present") | ||||
| 	ErrPulseSocket = errors.New("pulse socket not present") | ||||
| 	ErrPulseMode   = errors.New("unexpected pulse socket mode") | ||||
| ) | ||||
| 
 | ||||
| func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error { | ||||
| 	if seal.shared { | ||||
| 		panic("seal shared twice") | ||||
| 	} | ||||
| 	seal.shared = true | ||||
| 
 | ||||
| 	/* | ||||
| 		Tmpdir-based share directory | ||||
| 	*/ | ||||
| 
 | ||||
| 	// ensure Share (e.g. `/tmp/fortify.%d`) | ||||
| 	// acl is unnecessary as this directory is world executable | ||||
| 	seal.sys.Ensure(seal.SharePath, 0711) | ||||
| 
 | ||||
| 	// ensure process-specific share (e.g. `/tmp/fortify.%d/%s`) | ||||
| 	// acl is unnecessary as this directory is world executable | ||||
| 	seal.share = path.Join(seal.SharePath, seal.id) | ||||
| 	seal.sys.Ephemeral(system.Process, seal.share, 0711) | ||||
| 
 | ||||
| 	// ensure child tmpdir parent directory (e.g. `/tmp/fortify.%d/tmpdir`) | ||||
| 	targetTmpdirParent := path.Join(seal.SharePath, "tmpdir") | ||||
| 	seal.sys.Ensure(targetTmpdirParent, 0700) | ||||
| 	seal.sys.UpdatePermType(system.User, targetTmpdirParent, acl.Execute) | ||||
| 
 | ||||
| 	// ensure child tmpdir (e.g. `/tmp/fortify.%d/tmpdir/%d`) | ||||
| 	targetTmpdir := path.Join(targetTmpdirParent, seal.sys.user.as) | ||||
| 	seal.sys.Ensure(targetTmpdir, 01700) | ||||
| 	seal.sys.UpdatePermType(system.User, targetTmpdir, acl.Read, acl.Write, acl.Execute) | ||||
| 	seal.sys.bwrap.Bind(targetTmpdir, "/tmp", false, true) | ||||
| 
 | ||||
| 	/* | ||||
| 		XDG runtime directory | ||||
| 	*/ | ||||
| 
 | ||||
| 	// mount tmpfs on inner runtime (e.g. `/run/user/%d`) | ||||
| 	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.bwrap.SetEnv[xdgRuntimeDir] = seal.sys.runtime | ||||
| 	seal.sys.bwrap.SetEnv[xdgSessionClass] = "user" | ||||
| 	seal.sys.bwrap.SetEnv[xdgSessionType] = "tty" | ||||
| 
 | ||||
| 	// ensure RunDir (e.g. `/run/user/%d/fortify`) | ||||
| 	seal.sys.Ensure(seal.RunDirPath, 0700) | ||||
| 	seal.sys.UpdatePermType(system.User, seal.RunDirPath, acl.Execute) | ||||
| 
 | ||||
| 	// ensure runtime directory ACL (e.g. `/run/user/%d`) | ||||
| 	seal.sys.Ensure(seal.RuntimePath, 0700) // ensure this dir in case XDG_RUNTIME_DIR is unset | ||||
| 	seal.sys.UpdatePermType(system.User, seal.RuntimePath, acl.Execute) | ||||
| 
 | ||||
| 	// ensure process-specific share local to XDG_RUNTIME_DIR (e.g. `/run/user/%d/fortify/%s`) | ||||
| 	seal.shareLocal = path.Join(seal.RunDirPath, seal.id) | ||||
| 	seal.sys.Ephemeral(system.Process, seal.shareLocal, 0700) | ||||
| 	seal.sys.UpdatePerm(seal.shareLocal, acl.Execute) | ||||
| 
 | ||||
| 	/* | ||||
| 		Inner passwd database | ||||
| 	*/ | ||||
| 
 | ||||
| 	// look up shell | ||||
| 	sh := "/bin/sh" | ||||
| 	if s, ok := os.LookupEnv(shell); ok { | ||||
| 		seal.sys.bwrap.SetEnv[shell] = s | ||||
| 		sh = s | ||||
| 	} | ||||
| 
 | ||||
| 	// 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["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 | ||||
| 	*/ | ||||
| 
 | ||||
| 	// pass $TERM to launcher | ||||
| 	if t, ok := os.LookupEnv(term); ok { | ||||
| 		seal.sys.bwrap.SetEnv[term] = t | ||||
| 	} | ||||
| 
 | ||||
| 	// set up wayland | ||||
| 	if seal.et.Has(system.EWayland) { | ||||
| 		var wp string | ||||
| 		if wd, ok := os.LookupEnv(waylandDisplay); !ok { | ||||
| 			return fmsg.WrapError(ErrWayland, | ||||
| 				"WAYLAND_DISPLAY is not set") | ||||
| 		} else { | ||||
| 			wp = path.Join(seal.RuntimePath, wd) | ||||
| 		} | ||||
| 
 | ||||
| 		w := path.Join(seal.sys.runtime, "wayland-0") | ||||
| 		seal.sys.bwrap.SetEnv[waylandDisplay] = w | ||||
| 
 | ||||
| 		if !seal.directWayland { // set up security-context-v1 | ||||
| 			wc := path.Join(seal.SharePath, "wayland") | ||||
| 			wt := path.Join(wc, seal.id) | ||||
| 			seal.sys.Ensure(wc, 0711) | ||||
| 			appID := seal.fid | ||||
| 			if appID == "" { | ||||
| 				// use instance ID in case app id is not set | ||||
| 				appID = "moe.ophivana.fortify." + seal.id | ||||
| 			} | ||||
| 			seal.sys.Wayland(wt, wp, appID, seal.id) | ||||
| 			seal.sys.bwrap.Bind(wt, w) | ||||
| 		} else { // bind mount wayland socket (insecure) | ||||
| 			// hardlink wayland socket | ||||
| 			wpi := path.Join(seal.shareLocal, "wayland") | ||||
| 			seal.sys.Link(wp, wpi) | ||||
| 			seal.sys.bwrap.Bind(wpi, w) | ||||
| 
 | ||||
| 			// ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`) | ||||
| 			seal.sys.UpdatePermType(system.EWayland, wp, acl.Read, acl.Write, acl.Execute) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// set up X11 | ||||
| 	if seal.et.Has(system.EX11) { | ||||
| 		// discover X11 and grant user permission via the `ChangeHosts` command | ||||
| 		if d, ok := os.LookupEnv(display); !ok { | ||||
| 			return fmsg.WrapError(ErrXDisplay, | ||||
| 				"DISPLAY is not set") | ||||
| 		} else { | ||||
| 			seal.sys.ChangeHosts("#" + seal.sys.user.us) | ||||
| 			seal.sys.bwrap.SetEnv[display] = d | ||||
| 			seal.sys.bwrap.Bind("/tmp/.X11-unix", "/tmp/.X11-unix") | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/* | ||||
| 		PulseAudio server and authentication | ||||
| 	*/ | ||||
| 
 | ||||
| 	if seal.et.Has(system.EPulse) { | ||||
| 		// check PulseAudio directory presence (e.g. `/run/user/%d/pulse`) | ||||
| 		pd := path.Join(seal.RuntimePath, "pulse") | ||||
| 		ps := path.Join(pd, "native") | ||||
| 		if _, err := os.Stat(pd); err != nil { | ||||
| 			if !errors.Is(err, fs.ErrNotExist) { | ||||
| 				return fmsg.WrapErrorSuffix(err, | ||||
| 					fmt.Sprintf("cannot access PulseAudio directory %q:", pd)) | ||||
| 			} | ||||
| 			return fmsg.WrapError(ErrPulseSocket, | ||||
| 				fmt.Sprintf("PulseAudio directory %q not found", pd)) | ||||
| 		} | ||||
| 
 | ||||
| 		// check PulseAudio socket permission (e.g. `/run/user/%d/pulse/native`) | ||||
| 		if s, err := os.Stat(ps); err != nil { | ||||
| 			if !errors.Is(err, fs.ErrNotExist) { | ||||
| 				return fmsg.WrapErrorSuffix(err, | ||||
| 					fmt.Sprintf("cannot access PulseAudio socket %q:", ps)) | ||||
| 			} | ||||
| 			return fmsg.WrapError(ErrPulseSocket, | ||||
| 				fmt.Sprintf("PulseAudio directory %q found but socket does not exist", pd)) | ||||
| 		} else { | ||||
| 			if m := s.Mode(); m&0o006 != 0o006 { | ||||
| 				return fmsg.WrapError(ErrPulseMode, | ||||
| 					fmt.Sprintf("unexpected permissions on %q:", ps), m) | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// hard link pulse socket into target-executable share | ||||
| 		psi := path.Join(seal.shareLocal, "pulse") | ||||
| 		p := path.Join(seal.sys.runtime, "pulse", "native") | ||||
| 		seal.sys.Link(ps, psi) | ||||
| 		seal.sys.bwrap.Bind(psi, p) | ||||
| 		seal.sys.bwrap.SetEnv[pulseServer] = "unix:" + p | ||||
| 
 | ||||
| 		// publish current user's pulse cookie for target user | ||||
| 		if src, err := discoverPulseCookie(os); err != nil { | ||||
| 			// not fatal | ||||
| 			fmsg.VPrintln(err.(*fmsg.BaseError).Message()) | ||||
| 		} else { | ||||
| 			dst := path.Join(seal.share, "pulse-cookie") | ||||
| 			innerDst := fst.Tmp + "/pulse-cookie" | ||||
| 			seal.sys.bwrap.SetEnv[pulseCookie] = innerDst | ||||
| 			seal.sys.CopyFile(dst, src) | ||||
| 			seal.sys.bwrap.Bind(dst, innerDst) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/* | ||||
| 		D-Bus proxy | ||||
| 	*/ | ||||
| 
 | ||||
| 	if seal.et.Has(system.EDBus) { | ||||
| 		// ensure dbus session bus defaults | ||||
| 		if bus[0] == nil { | ||||
| 			bus[0] = dbus.NewConfig(seal.fid, true, true) | ||||
| 		} | ||||
| 
 | ||||
| 		// downstream socket paths | ||||
| 		sessionPath, systemPath := path.Join(seal.share, "bus"), path.Join(seal.share, "system_bus_socket") | ||||
| 
 | ||||
| 		// configure dbus proxy | ||||
| 		if f, err := seal.sys.ProxyDBus(bus[0], bus[1], sessionPath, systemPath); err != nil { | ||||
| 			return err | ||||
| 		} else { | ||||
| 			seal.dbusMsg = f | ||||
| 		} | ||||
| 
 | ||||
| 		// share proxy sockets | ||||
| 		sessionInner := path.Join(seal.sys.runtime, "bus") | ||||
| 		seal.sys.bwrap.SetEnv[dbusSessionBusAddress] = "unix:path=" + sessionInner | ||||
| 		seal.sys.bwrap.Bind(sessionPath, sessionInner) | ||||
| 		seal.sys.UpdatePerm(sessionPath, acl.Read, acl.Write) | ||||
| 		if bus[1] != nil { | ||||
| 			systemInner := "/run/dbus/system_bus_socket" | ||||
| 			seal.sys.bwrap.SetEnv[dbusSystemBusAddress] = "unix:path=" + systemInner | ||||
| 			seal.sys.bwrap.Bind(systemPath, systemInner) | ||||
| 			seal.sys.UpdatePerm(systemPath, acl.Read, acl.Write) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/* | ||||
| 		Miscellaneous | ||||
| 	*/ | ||||
| 
 | ||||
| 	// queue overriding tmpfs at the end of seal.sys.bwrap.Filesystem | ||||
| 	for _, dest := range seal.sys.override { | ||||
| 		seal.sys.bwrap.Tmpfs(dest, 8*1024) | ||||
| 	} | ||||
| 
 | ||||
| 	// append extra perms | ||||
| 	for _, p := range seal.extraPerms { | ||||
| 		if p == nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		if p.ensure { | ||||
| 			seal.sys.Ensure(p.name, 0700) | ||||
| 		} | ||||
| 		seal.sys.UpdatePermType(system.User, p.name, p.perms...) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // discoverPulseCookie attempts various standard methods to discover the current user's PulseAudio authentication cookie | ||||
| func discoverPulseCookie(os linux.System) (string, error) { | ||||
| 	if p, ok := os.LookupEnv(pulseCookie); ok { | ||||
| 		return p, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// dotfile $HOME/.pulse-cookie | ||||
| 	if p, ok := os.LookupEnv(home); ok { | ||||
| 		p = path.Join(p, ".pulse-cookie") | ||||
| 		if s, err := os.Stat(p); err != nil { | ||||
| 			if !errors.Is(err, fs.ErrNotExist) { | ||||
| 				return p, fmsg.WrapErrorSuffix(err, | ||||
| 					fmt.Sprintf("cannot access PulseAudio cookie %q:", p)) | ||||
| 			} | ||||
| 			// not found, try next method | ||||
| 		} else if !s.IsDir() { | ||||
| 			return p, nil | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// $XDG_CONFIG_HOME/pulse/cookie | ||||
| 	if p, ok := os.LookupEnv(xdgConfigHome); ok { | ||||
| 		p = path.Join(p, "pulse", "cookie") | ||||
| 		if s, err := os.Stat(p); err != nil { | ||||
| 			if !errors.Is(err, fs.ErrNotExist) { | ||||
| 				return p, fmsg.WrapErrorSuffix(err, | ||||
| 					fmt.Sprintf("cannot access PulseAudio cookie %q:", p)) | ||||
| 			} | ||||
| 			// not found, try next method | ||||
| 		} else if !s.IsDir() { | ||||
| 			return p, nil | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return "", fmsg.WrapError(ErrPulseCookie, | ||||
| 		fmt.Sprintf("cannot locate PulseAudio cookie (tried $%s, $%s/pulse/cookie, $%s/.pulse-cookie)", | ||||
| 			pulseCookie, xdgConfigHome, home)) | ||||
| } | ||||
							
								
								
									
										119
									
								
								internal/app/share.pulse.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								internal/app/share.pulse.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,119 @@ | ||||
| package app | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/fs" | ||||
| 	"path" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/linux" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/system" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	pulseServer = "PULSE_SERVER" | ||||
| 	pulseCookie = "PULSE_COOKIE" | ||||
| 
 | ||||
| 	home          = "HOME" | ||||
| 	xdgConfigHome = "XDG_CONFIG_HOME" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	ErrPulseCookie = errors.New("pulse cookie not present") | ||||
| 	ErrPulseSocket = errors.New("pulse socket not present") | ||||
| 	ErrPulseMode   = errors.New("unexpected pulse socket mode") | ||||
| ) | ||||
| 
 | ||||
| func (seal *appSeal) sharePulse(os linux.System) error { | ||||
| 	if !seal.et.Has(system.EPulse) { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	// check PulseAudio directory presence (e.g. `/run/user/%d/pulse`) | ||||
| 	pd := path.Join(seal.RuntimePath, "pulse") | ||||
| 	ps := path.Join(pd, "native") | ||||
| 	if _, err := os.Stat(pd); err != nil { | ||||
| 		if !errors.Is(err, fs.ErrNotExist) { | ||||
| 			return fmsg.WrapErrorSuffix(err, | ||||
| 				fmt.Sprintf("cannot access PulseAudio directory %q:", pd)) | ||||
| 		} | ||||
| 		return fmsg.WrapError(ErrPulseSocket, | ||||
| 			fmt.Sprintf("PulseAudio directory %q not found", pd)) | ||||
| 	} | ||||
| 
 | ||||
| 	// check PulseAudio socket permission (e.g. `/run/user/%d/pulse/native`) | ||||
| 	if s, err := os.Stat(ps); err != nil { | ||||
| 		if !errors.Is(err, fs.ErrNotExist) { | ||||
| 			return fmsg.WrapErrorSuffix(err, | ||||
| 				fmt.Sprintf("cannot access PulseAudio socket %q:", ps)) | ||||
| 		} | ||||
| 		return fmsg.WrapError(ErrPulseSocket, | ||||
| 			fmt.Sprintf("PulseAudio directory %q found but socket does not exist", pd)) | ||||
| 	} else { | ||||
| 		if m := s.Mode(); m&0o006 != 0o006 { | ||||
| 			return fmsg.WrapError(ErrPulseMode, | ||||
| 				fmt.Sprintf("unexpected permissions on %q:", ps), m) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// hard link pulse socket into target-executable share | ||||
| 	psi := path.Join(seal.shareLocal, "pulse") | ||||
| 	p := path.Join(seal.sys.runtime, "pulse", "native") | ||||
| 	seal.sys.Link(ps, psi) | ||||
| 	seal.sys.bwrap.Bind(psi, p) | ||||
| 	seal.sys.bwrap.SetEnv[pulseServer] = "unix:" + p | ||||
| 
 | ||||
| 	// publish current user's pulse cookie for target user | ||||
| 	if src, err := discoverPulseCookie(os); err != nil { | ||||
| 		fmsg.VPrintln(err.(*fmsg.BaseError).Message()) | ||||
| 	} else { | ||||
| 		dst := path.Join(seal.share, "pulse-cookie") | ||||
| 		innerDst := fst.Tmp + "/pulse-cookie" | ||||
| 		seal.sys.bwrap.SetEnv[pulseCookie] = innerDst | ||||
| 		seal.sys.CopyFile(dst, src) | ||||
| 		seal.sys.bwrap.Bind(dst, innerDst) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // discoverPulseCookie attempts various standard methods to discover the current user's PulseAudio authentication cookie | ||||
| func discoverPulseCookie(os linux.System) (string, error) { | ||||
| 	if p, ok := os.LookupEnv(pulseCookie); ok { | ||||
| 		return p, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// dotfile $HOME/.pulse-cookie | ||||
| 	if p, ok := os.LookupEnv(home); ok { | ||||
| 		p = path.Join(p, ".pulse-cookie") | ||||
| 		if s, err := os.Stat(p); err != nil { | ||||
| 			if !errors.Is(err, fs.ErrNotExist) { | ||||
| 				return p, fmsg.WrapErrorSuffix(err, | ||||
| 					fmt.Sprintf("cannot access PulseAudio cookie %q:", p)) | ||||
| 			} | ||||
| 			// not found, try next method | ||||
| 		} else if !s.IsDir() { | ||||
| 			return p, nil | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// $XDG_CONFIG_HOME/pulse/cookie | ||||
| 	if p, ok := os.LookupEnv(xdgConfigHome); ok { | ||||
| 		p = path.Join(p, "pulse", "cookie") | ||||
| 		if s, err := os.Stat(p); err != nil { | ||||
| 			if !errors.Is(err, fs.ErrNotExist) { | ||||
| 				return p, fmsg.WrapErrorSuffix(err, | ||||
| 					fmt.Sprintf("cannot access PulseAudio cookie %q:", p)) | ||||
| 			} | ||||
| 			// not found, try next method | ||||
| 		} else if !s.IsDir() { | ||||
| 			return p, nil | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return "", fmsg.WrapError(ErrPulseCookie, | ||||
| 		fmt.Sprintf("cannot locate PulseAudio cookie (tried $%s, $%s/pulse/cookie, $%s/.pulse-cookie)", | ||||
| 			pulseCookie, xdgConfigHome, home)) | ||||
| } | ||||
							
								
								
									
										39
									
								
								internal/app/share.runtime.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								internal/app/share.runtime.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | ||||
| package app | ||||
| 
 | ||||
| import ( | ||||
| 	"path" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/acl" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/system" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	xdgRuntimeDir   = "XDG_RUNTIME_DIR" | ||||
| 	xdgSessionClass = "XDG_SESSION_CLASS" | ||||
| 	xdgSessionType  = "XDG_SESSION_TYPE" | ||||
| ) | ||||
| 
 | ||||
| // 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("/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.bwrap.SetEnv[xdgRuntimeDir] = seal.sys.runtime | ||||
| 	seal.sys.bwrap.SetEnv[xdgSessionClass] = "user" | ||||
| 	seal.sys.bwrap.SetEnv[xdgSessionType] = "tty" | ||||
| 
 | ||||
| 	// ensure RunDir (e.g. `/run/user/%d/fortify`) | ||||
| 	seal.sys.Ensure(seal.RunDirPath, 0700) | ||||
| 	seal.sys.UpdatePermType(system.User, seal.RunDirPath, acl.Execute) | ||||
| 
 | ||||
| 	// ensure runtime directory ACL (e.g. `/run/user/%d`) | ||||
| 	seal.sys.Ensure(seal.RuntimePath, 0700) // ensure this dir in case XDG_RUNTIME_DIR is unset | ||||
| 	seal.sys.UpdatePermType(system.User, seal.RuntimePath, acl.Execute) | ||||
| 
 | ||||
| 	// ensure process-specific share local to XDG_RUNTIME_DIR (e.g. `/run/user/%d/fortify/%s`) | ||||
| 	seal.shareLocal = path.Join(seal.RunDirPath, seal.id) | ||||
| 	seal.sys.Ephemeral(system.Process, seal.shareLocal, 0700) | ||||
| 	seal.sys.UpdatePerm(seal.shareLocal, acl.Execute) | ||||
| } | ||||
							
								
								
									
										74
									
								
								internal/app/share.system.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								internal/app/share.system.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,74 @@ | ||||
| package app | ||||
| 
 | ||||
| import ( | ||||
| 	"path" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/acl" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/linux" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/system" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	shell = "SHELL" | ||||
| ) | ||||
| 
 | ||||
| // shareSystem queues various system-related actions | ||||
| func (seal *appSeal) shareSystem() { | ||||
| 	// ensure Share (e.g. `/tmp/fortify.%d`) | ||||
| 	// acl is unnecessary as this directory is world executable | ||||
| 	seal.sys.Ensure(seal.SharePath, 0711) | ||||
| 
 | ||||
| 	// ensure process-specific share (e.g. `/tmp/fortify.%d/%s`) | ||||
| 	// acl is unnecessary as this directory is world executable | ||||
| 	seal.share = path.Join(seal.SharePath, seal.id) | ||||
| 	seal.sys.Ephemeral(system.Process, seal.share, 0711) | ||||
| 
 | ||||
| 	// ensure child tmpdir parent directory (e.g. `/tmp/fortify.%d/tmpdir`) | ||||
| 	targetTmpdirParent := path.Join(seal.SharePath, "tmpdir") | ||||
| 	seal.sys.Ensure(targetTmpdirParent, 0700) | ||||
| 	seal.sys.UpdatePermType(system.User, targetTmpdirParent, acl.Execute) | ||||
| 
 | ||||
| 	// ensure child tmpdir (e.g. `/tmp/fortify.%d/tmpdir/%d`) | ||||
| 	targetTmpdir := path.Join(targetTmpdirParent, seal.sys.user.as) | ||||
| 	seal.sys.Ensure(targetTmpdir, 01700) | ||||
| 	seal.sys.UpdatePermType(system.User, targetTmpdir, acl.Read, acl.Write, acl.Execute) | ||||
| 	seal.sys.bwrap.Bind(targetTmpdir, "/tmp", false, true) | ||||
| } | ||||
| 
 | ||||
| func (seal *appSeal) sharePasswd(os linux.System) { | ||||
| 	// look up shell | ||||
| 	sh := "/bin/sh" | ||||
| 	if s, ok := os.LookupEnv(shell); ok { | ||||
| 		seal.sys.bwrap.SetEnv[shell] = s | ||||
| 		sh = s | ||||
| 	} | ||||
| 
 | ||||
| 	// 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["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") | ||||
| } | ||||
| @ -49,7 +49,6 @@ func (a *app) Start() error { | ||||
| 			Argv:  a.seal.command, | ||||
| 			Exec:  shimExec, | ||||
| 			Bwrap: a.seal.sys.bwrap, | ||||
| 			Home:  a.seal.sys.user.data, | ||||
| 
 | ||||
| 			Verbose: fmsg.Verbose(), | ||||
| 		}, | ||||
|  | ||||
| @ -1,7 +1,9 @@ | ||||
| package app | ||||
| 
 | ||||
| import ( | ||||
| 	"git.gensokyo.uk/security/fortify/dbus" | ||||
| 	"git.gensokyo.uk/security/fortify/helper/bwrap" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/linux" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/system" | ||||
| ) | ||||
| 
 | ||||
| @ -49,3 +51,37 @@ type appUser struct { | ||||
| 	// passwd database username | ||||
| 	username string | ||||
| } | ||||
| 
 | ||||
| // shareAll calls all share methods in sequence | ||||
| func (seal *appSeal) shareAll(bus [2]*dbus.Config, os linux.System) error { | ||||
| 	if seal.shared { | ||||
| 		panic("seal shared twice") | ||||
| 	} | ||||
| 	seal.shared = true | ||||
| 
 | ||||
| 	seal.shareSystem() | ||||
| 	seal.shareRuntime() | ||||
| 	seal.sharePasswd(os) | ||||
| 	if err := seal.shareDisplay(os); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := seal.sharePulse(os); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	// ensure dbus session bus defaults | ||||
| 	if bus[0] == nil { | ||||
| 		bus[0] = dbus.NewConfig(seal.fid, true, true) | ||||
| 	} | ||||
| 
 | ||||
| 	if err := seal.shareDBus(bus); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	// queue overriding tmpfs at the end of seal.sys.bwrap.Filesystem | ||||
| 	for _, dest := range seal.sys.override { | ||||
| 		seal.sys.bwrap.Tmpfs(dest, 8*1024) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @ -36,7 +36,7 @@ func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath st | ||||
| 	} | ||||
| 
 | ||||
| 	// system bus is optional | ||||
| 	d.system = system != nil | ||||
| 	d.system = system == nil | ||||
| 
 | ||||
| 	// upstream address, downstream socket path | ||||
| 	var sessionBus, systemBus [2]string | ||||
|  | ||||
| @ -32,7 +32,7 @@ func Parse(stdout fmt.Stringer) ([]*Entry, error) { | ||||
| 		switch len(segment) { | ||||
| 		case 2: // /lib/ld-musl-x86_64.so.1 (0x7f04d14ef000) | ||||
| 			iL = 1 | ||||
| 			result[i] = &Entry{Name: strings.TrimSpace(segment[0])} | ||||
| 			result[i] = &Entry{Name: segment[0]} | ||||
| 		case 4: // libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7f04d14ef000) | ||||
| 			iL = 3 | ||||
| 			if segment[1] != "=>" { | ||||
| @ -42,7 +42,7 @@ func Parse(stdout fmt.Stringer) ([]*Entry, error) { | ||||
| 				return nil, ErrPathNotAbsolute | ||||
| 			} | ||||
| 			result[i] = &Entry{ | ||||
| 				Name: strings.TrimSpace(segment[0]), | ||||
| 				Name: segment[0], | ||||
| 				Path: segment[2], | ||||
| 			} | ||||
| 		default: | ||||
|  | ||||
| @ -79,35 +79,6 @@ libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)`, | ||||
| 				{"libpthread.so.0", "/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libpthread.so.0", 0x00007f3199ab0000}, | ||||
| 				{"/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/ld-linux-x86-64.so.2", "/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib64/ld-linux-x86-64.so.2", 0x00007f3199da5000}, | ||||
| 			}}, | ||||
| 		{"glibc /usr/bin/xdg-dbus-proxy", ` | ||||
| 	linux-vdso.so.1 (0x00007725f5772000) | ||||
| 	libglib-2.0.so.0 => /usr/lib/libglib-2.0.so.0 (0x00007725f55d5000) | ||||
| 	libgio-2.0.so.0 => /usr/lib/libgio-2.0.so.0 (0x00007725f5406000) | ||||
| 	libgobject-2.0.so.0 => /usr/lib/libgobject-2.0.so.0 (0x00007725f53a6000) | ||||
| 	libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007725f5378000) | ||||
| 	libc.so.6 => /usr/lib/libc.so.6 (0x00007725f5187000) | ||||
| 	libpcre2-8.so.0 => /usr/lib/libpcre2-8.so.0 (0x00007725f50e8000) | ||||
| 	libgmodule-2.0.so.0 => /usr/lib/libgmodule-2.0.so.0 (0x00007725f50df000) | ||||
| 	libz.so.1 => /usr/lib/libz.so.1 (0x00007725f50c6000) | ||||
| 	libmount.so.1 => /usr/lib/libmount.so.1 (0x00007725f5076000) | ||||
| 	libffi.so.8 => /usr/lib/libffi.so.8 (0x00007725f506b000) | ||||
| 	/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007725f5774000) | ||||
| 	libblkid.so.1 => /usr/lib/libblkid.so.1 (0x00007725f5032000)`, | ||||
| 			[]*ldd.Entry{ | ||||
| 				{"linux-vdso.so.1", "", 0x00007725f5772000}, | ||||
| 				{"libglib-2.0.so.0", "/usr/lib/libglib-2.0.so.0", 0x00007725f55d5000}, | ||||
| 				{"libgio-2.0.so.0", "/usr/lib/libgio-2.0.so.0", 0x00007725f5406000}, | ||||
| 				{"libgobject-2.0.so.0", "/usr/lib/libgobject-2.0.so.0", 0x00007725f53a6000}, | ||||
| 				{"libgcc_s.so.1", "/usr/lib/libgcc_s.so.1", 0x00007725f5378000}, | ||||
| 				{"libc.so.6", "/usr/lib/libc.so.6", 0x00007725f5187000}, | ||||
| 				{"libpcre2-8.so.0", "/usr/lib/libpcre2-8.so.0", 0x00007725f50e8000}, | ||||
| 				{"libgmodule-2.0.so.0", "/usr/lib/libgmodule-2.0.so.0", 0x00007725f50df000}, | ||||
| 				{"libz.so.1", "/usr/lib/libz.so.1", 0x00007725f50c6000}, | ||||
| 				{"libmount.so.1", "/usr/lib/libmount.so.1", 0x00007725f5076000}, | ||||
| 				{"libffi.so.8", "/usr/lib/libffi.so.8", 0x00007725f506b000}, | ||||
| 				{"/lib64/ld-linux-x86-64.so.2", "/usr/lib64/ld-linux-x86-64.so.2", 0x00007725f5774000}, | ||||
| 				{"libblkid.so.1", "/usr/lib/libblkid.so.1", 0x00007725f5032000}, | ||||
| 			}}, | ||||
| 	} | ||||
| 	for _, tc := range testCases { | ||||
| 		t.Run(tc.file, func(t *testing.T) { | ||||
|  | ||||
							
								
								
									
										69
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										69
									
								
								main.go
									
									
									
									
									
								
							| @ -2,6 +2,7 @@ package main | ||||
| 
 | ||||
| import ( | ||||
| 	_ "embed" | ||||
| 	"encoding/json" | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"os/user" | ||||
| @ -128,21 +129,64 @@ func main() { | ||||
| 		// Ignore errors; set is set for ExitOnError. | ||||
| 		_ = set.Parse(args[1:]) | ||||
| 
 | ||||
| 		if len(set.Args()) != 1 { | ||||
| 			fmsg.Fatal("show requires 1 argument") | ||||
| 		} | ||||
| 
 | ||||
| 		likePrefix := false | ||||
| 		if len(set.Args()[0]) <= 32 { | ||||
| 			likePrefix = true | ||||
| 			for _, c := range set.Args()[0] { | ||||
| 				if c >= '0' && c <= '9' { | ||||
| 					continue | ||||
| 				} | ||||
| 				if c >= 'a' && c <= 'f' { | ||||
| 					continue | ||||
| 				} | ||||
| 				likePrefix = false | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		var ( | ||||
| 			config   *fst.Config | ||||
| 			instance *state.State | ||||
| 			name     string | ||||
| 		) | ||||
| 
 | ||||
| 		if len(set.Args()) != 1 { | ||||
| 			fmsg.Fatal("show requires 1 argument") | ||||
| 		// try to match from state store | ||||
| 		if likePrefix && len(set.Args()[0]) >= 8 { | ||||
| 			fmsg.VPrintln("argument looks like prefix") | ||||
| 
 | ||||
| 			s := state.NewMulti(os.Paths().RunDirPath) | ||||
| 			if entries, err := state.Join(s); err != nil { | ||||
| 				fmsg.Printf("cannot join store: %v", err) | ||||
| 				// drop to fetch from file | ||||
| 			} else { | ||||
| 			name = set.Args()[0] | ||||
| 			config, instance = tryShort(name) | ||||
| 				for id := range entries { | ||||
| 					v := id.String() | ||||
| 					if strings.HasPrefix(v, set.Args()[0]) { | ||||
| 						// match, use config from this state entry | ||||
| 						instance = entries[id] | ||||
| 						config = instance.Config | ||||
| 						break | ||||
| 					} | ||||
| 
 | ||||
| 					fmsg.VPrintf("instance %s skipped", v) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if config == nil { | ||||
| 			config = tryPath(name) | ||||
| 			fmsg.VPrintf("reading from file") | ||||
| 
 | ||||
| 			config = new(fst.Config) | ||||
| 			if f, err := os.Open(set.Args()[0]); err != nil { | ||||
| 				fmsg.Fatalf("cannot access config file %q: %s", set.Args()[0], err) | ||||
| 				panic("unreachable") | ||||
| 			} else if err = json.NewDecoder(f).Decode(&config); err != nil { | ||||
| 				fmsg.Fatalf("cannot parse config file %q: %s", set.Args()[0], err) | ||||
| 				panic("unreachable") | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		printShow(instance, config, short) | ||||
| @ -152,13 +196,20 @@ func main() { | ||||
| 			fmsg.Fatal("app requires at least 1 argument") | ||||
| 		} | ||||
| 
 | ||||
| 		// config extraArgs... | ||||
| 		config := tryPath(args[1]) | ||||
| 		config := new(fst.Config) | ||||
| 		if f, err := os.Open(args[1]); err != nil { | ||||
| 			fmsg.Fatalf("cannot access config file %q: %s", args[1], err) | ||||
| 			panic("unreachable") | ||||
| 		} else if err = json.NewDecoder(f).Decode(&config); err != nil { | ||||
| 			fmsg.Fatalf("cannot parse config file %q: %s", args[1], err) | ||||
| 			panic("unreachable") | ||||
| 		} | ||||
| 
 | ||||
| 		// append extra args | ||||
| 		config.Command = append(config.Command, args[2:]...) | ||||
| 
 | ||||
| 		// invoke app | ||||
| 		runApp(config) | ||||
| 		panic("unreachable") | ||||
| 	case "run": // run app in permissive defaults usage pattern | ||||
| 		set := flag.NewFlagSet("run", flag.ExitOnError) | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										108
									
								
								parse.go
									
									
									
									
									
								
							
							
						
						
									
										108
									
								
								parse.go
									
									
									
									
									
								
							| @ -1,108 +0,0 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"io" | ||||
| 	direct "os" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"syscall" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/state" | ||||
| ) | ||||
| 
 | ||||
| func tryPath(name string) (config *fst.Config) { | ||||
| 	var r io.Reader | ||||
| 	config = new(fst.Config) | ||||
| 
 | ||||
| 	if name != "-" { | ||||
| 		r = tryFd(name) | ||||
| 		if r == nil { | ||||
| 			fmsg.VPrintln("load configuration from file") | ||||
| 
 | ||||
| 			if f, err := os.Open(name); err != nil { | ||||
| 				fmsg.Fatalf("cannot access configuration file %q: %s", name, err) | ||||
| 				panic("unreachable") | ||||
| 			} else { | ||||
| 				// finalizer closes f | ||||
| 				r = f | ||||
| 			} | ||||
| 		} else { | ||||
| 			defer func() { | ||||
| 				if err := r.(io.ReadCloser).Close(); err != nil { | ||||
| 					fmsg.Printf("cannot close config fd: %v", err) | ||||
| 				} | ||||
| 			}() | ||||
| 		} | ||||
| 	} else { | ||||
| 		r = direct.Stdin | ||||
| 	} | ||||
| 
 | ||||
| 	if err := json.NewDecoder(r).Decode(&config); err != nil { | ||||
| 		fmsg.Fatalf("cannot load configuration: %v", err) | ||||
| 		panic("unreachable") | ||||
| 	} | ||||
| 
 | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func tryFd(name string) io.ReadCloser { | ||||
| 	if v, err := strconv.Atoi(name); err != nil { | ||||
| 		fmsg.VPrintf("name cannot be interpreted as int64: %v", err) | ||||
| 		return nil | ||||
| 	} else { | ||||
| 		fd := uintptr(v) | ||||
| 		if _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_GETFD, 0); errno != 0 { | ||||
| 			if errors.Is(errno, syscall.EBADF) { | ||||
| 				return nil | ||||
| 			} | ||||
| 			fmsg.Fatalf("cannot get fd %d: %v", fd, errno) | ||||
| 		} | ||||
| 		return direct.NewFile(fd, strconv.Itoa(v)) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func tryShort(name string) (config *fst.Config, instance *state.State) { | ||||
| 	likePrefix := false | ||||
| 	if len(name) <= 32 { | ||||
| 		likePrefix = true | ||||
| 		for _, c := range name { | ||||
| 			if c >= '0' && c <= '9' { | ||||
| 				continue | ||||
| 			} | ||||
| 			if c >= 'a' && c <= 'f' { | ||||
| 				continue | ||||
| 			} | ||||
| 			likePrefix = false | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// try to match from state store | ||||
| 	if likePrefix && len(name) >= 8 { | ||||
| 		fmsg.VPrintln("argument looks like prefix") | ||||
| 
 | ||||
| 		s := state.NewMulti(os.Paths().RunDirPath) | ||||
| 		if entries, err := state.Join(s); err != nil { | ||||
| 			fmsg.Printf("cannot join store: %v", err) | ||||
| 			// drop to fetch from file | ||||
| 		} else { | ||||
| 			for id := range entries { | ||||
| 				v := id.String() | ||||
| 				if strings.HasPrefix(v, name) { | ||||
| 					// match, use config from this state entry | ||||
| 					instance = entries[id] | ||||
| 					config = instance.Config | ||||
| 					break | ||||
| 				} | ||||
| 
 | ||||
| 				fmsg.VPrintf("instance %s skipped", v) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										31
									
								
								print.go
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								print.go
									
									
									
									
									
								
							| @ -70,16 +70,7 @@ func printShow(instance *state.State, config *fst.Config, short bool) { | ||||
| 			flags = append(flags, "none") | ||||
| 		} | ||||
| 		fmt.Fprintf(w, " Flags:\t%s\n", strings.Join(flags, " ")) | ||||
| 
 | ||||
| 		etc := sandbox.Etc | ||||
| 		if etc == "" { | ||||
| 			etc = "/etc" | ||||
| 		} | ||||
| 		fmt.Fprintf(w, " Etc:\t%s\n", etc) | ||||
| 
 | ||||
| 		if len(sandbox.Override) > 0 { | ||||
| 		fmt.Fprintf(w, " Overrides:\t%s\n", strings.Join(sandbox.Override, " ")) | ||||
| 		} | ||||
| 
 | ||||
| 		// Env           map[string]string   `json:"env"` | ||||
| 		// Link          [][2]string         `json:"symlink"` | ||||
| @ -90,17 +81,10 @@ func printShow(instance *state.State, config *fst.Config, short bool) { | ||||
| 	fmt.Fprintf(w, " Command:\t%s\n", strings.Join(config.Command, " ")) | ||||
| 	fmt.Fprintf(w, "\n") | ||||
| 
 | ||||
| 	if !short { | ||||
| 		if config.Confinement.Sandbox != nil && len(config.Confinement.Sandbox.Filesystem) > 0 { | ||||
| 			fmt.Fprintf(w, "Filesystem\n") | ||||
| 	if !short && config.Confinement.Sandbox != nil && len(config.Confinement.Sandbox.Filesystem) > 0 { | ||||
| 		fmt.Fprintf(w, "Filesystem:\n") | ||||
| 		for _, f := range config.Confinement.Sandbox.Filesystem { | ||||
| 				if f == nil { | ||||
| 					continue | ||||
| 				} | ||||
| 
 | ||||
| 			expr := new(strings.Builder) | ||||
| 				expr.Grow(3 + len(f.Src) + 1 + len(f.Dst)) | ||||
| 
 | ||||
| 			if f.Device { | ||||
| 				expr.WriteString(" d") | ||||
| 			} else if f.Write { | ||||
| @ -121,17 +105,6 @@ func printShow(instance *state.State, config *fst.Config, short bool) { | ||||
| 		} | ||||
| 		fmt.Fprintf(w, "\n") | ||||
| 	} | ||||
| 		if len(config.Confinement.ExtraPerms) > 0 { | ||||
| 			fmt.Fprintf(w, "Extra ACL\n") | ||||
| 			for _, p := range config.Confinement.ExtraPerms { | ||||
| 				if p == nil { | ||||
| 					continue | ||||
| 				} | ||||
| 				fmt.Fprintf(w, " %s\n", p.String()) | ||||
| 			} | ||||
| 			fmt.Fprintf(w, "\n") | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	printDBus := func(c *dbus.Config) { | ||||
| 		fmt.Fprintf(w, " Filter:\t%v\n", c.Filter) | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user