app/instance: wrap internal implementation
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Test / Create distribution (push) Successful in 26s
				
			
		
			
				
	
				Test / Sandbox (push) Successful in 1m44s
				
			
		
			
				
	
				Test / Fortify (push) Successful in 2m37s
				
			
		
			
				
	
				Test / Sandbox (race detector) (push) Successful in 2m59s
				
			
		
			
				
	
				Test / Fpkg (push) Successful in 3m34s
				
			
		
			
				
	
				Test / Fortify (race detector) (push) Successful in 4m6s
				
			
		
			
				
	
				Test / Flake checks (push) Successful in 59s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Test / Create distribution (push) Successful in 26s
				
			Test / Sandbox (push) Successful in 1m44s
				
			Test / Fortify (push) Successful in 2m37s
				
			Test / Sandbox (race detector) (push) Successful in 2m59s
				
			Test / Fpkg (push) Successful in 3m34s
				
			Test / Fortify (race detector) (push) Successful in 4m6s
				
			Test / Flake checks (push) Successful in 59s
				
			This reduces the scope of the fst package, which was growing questionably large. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
		
							parent
							
								
									0d7c1a9a43
								
							
						
					
					
						commit
						6309469e93
					
				| @ -13,7 +13,7 @@ import ( | ||||
| 	"git.gensokyo.uk/security/fortify/command" | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	"git.gensokyo.uk/security/fortify/internal" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app/setuid" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app/instance" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/sys" | ||||
| 	"git.gensokyo.uk/security/fortify/sandbox" | ||||
| @ -62,7 +62,7 @@ func main() { | ||||
| 		Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console"). | ||||
| 		Flag(&flagDropShell, "s", command.BoolFlag(false), "Drop to a shell in place of next fortify action") | ||||
| 
 | ||||
| 	c.Command("shim", command.UsageInternal, func([]string) error { setuid.ShimMain(); return errSuccess }) | ||||
| 	c.Command("shim", command.UsageInternal, func([]string) error { instance.ShimMain(); return errSuccess }) | ||||
| 
 | ||||
| 	{ | ||||
| 		var ( | ||||
|  | ||||
| @ -5,20 +5,21 @@ import ( | ||||
| 	"os" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app/setuid" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app/instance" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||
| ) | ||||
| 
 | ||||
| func mustRunApp(ctx context.Context, config *fst.Config, beforeFail func()) { | ||||
| 	rs := new(fst.RunState) | ||||
| 	a := setuid.MustNew(ctx, std) | ||||
| 	rs := new(app.RunState) | ||||
| 	a := instance.MustNew(instance.ISetuid, ctx, std) | ||||
| 
 | ||||
| 	var code int | ||||
| 	if sa, err := a.Seal(config); err != nil { | ||||
| 		fmsg.PrintBaseError(err, "cannot seal app:") | ||||
| 		code = 1 | ||||
| 	} else { | ||||
| 		code = setuid.PrintRunStateErr(rs, sa.Run(rs)) | ||||
| 		code = instance.PrintRunStateErr(instance.ISetuid, rs, sa.Run(rs)) | ||||
| 	} | ||||
| 
 | ||||
| 	if code != 0 { | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| // Package fst exports shared fortify types. | ||||
| package fst | ||||
| 
 | ||||
| import ( | ||||
|  | ||||
							
								
								
									
										192
									
								
								fst/sandbox.go
									
									
									
									
									
								
							
							
						
						
									
										192
									
								
								fst/sandbox.go
									
									
									
									
									
								
							| @ -1,16 +1,6 @@ | ||||
| package fst | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/fs" | ||||
| 	"maps" | ||||
| 	"path" | ||||
| 	"slices" | ||||
| 	"syscall" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/dbus" | ||||
| 	"git.gensokyo.uk/security/fortify/sandbox" | ||||
| 	"git.gensokyo.uk/security/fortify/sandbox/seccomp" | ||||
| ) | ||||
| 
 | ||||
| @ -57,18 +47,6 @@ type ( | ||||
| 		Cover []string `json:"cover"` | ||||
| 	} | ||||
| 
 | ||||
| 	// SandboxSys encapsulates system functions used during [sandbox.Container] initialisation. | ||||
| 	SandboxSys interface { | ||||
| 		Getuid() int | ||||
| 		Getgid() int | ||||
| 		Paths() Paths | ||||
| 		ReadDir(name string) ([]fs.DirEntry, error) | ||||
| 		EvalSymlinks(path string) (string, error) | ||||
| 
 | ||||
| 		Println(v ...any) | ||||
| 		Printf(format string, v ...any) | ||||
| 	} | ||||
| 
 | ||||
| 	// FilesystemConfig is a representation of [sandbox.BindMount]. | ||||
| 	FilesystemConfig struct { | ||||
| 		// mount point in container, same as src if empty | ||||
| @ -83,173 +61,3 @@ type ( | ||||
| 		Must bool `json:"require,omitempty"` | ||||
| 	} | ||||
| ) | ||||
| 
 | ||||
| // ToContainer initialises [sandbox.Params] via [SandboxConfig]. | ||||
| // Note that remaining container setup must be queued by the [App] implementation. | ||||
| func (s *SandboxConfig) ToContainer(sys SandboxSys, uid, gid *int) (*sandbox.Params, map[string]string, error) { | ||||
| 	if s == nil { | ||||
| 		return nil, nil, syscall.EBADE | ||||
| 	} | ||||
| 
 | ||||
| 	container := &sandbox.Params{ | ||||
| 		Hostname: s.Hostname, | ||||
| 		Ops:      new(sandbox.Ops), | ||||
| 		Seccomp:  s.Seccomp, | ||||
| 	} | ||||
| 
 | ||||
| 	if s.Multiarch { | ||||
| 		container.Seccomp |= seccomp.FilterMultiarch | ||||
| 	} | ||||
| 
 | ||||
| 	/* this is only 4 KiB of memory on a 64-bit system, | ||||
| 	permissive defaults on NixOS results in around 100 entries | ||||
| 	so this capacity should eliminate copies for most setups */ | ||||
| 	*container.Ops = slices.Grow(*container.Ops, 1<<8) | ||||
| 
 | ||||
| 	if s.Devel { | ||||
| 		container.Flags |= sandbox.FAllowDevel | ||||
| 	} | ||||
| 	if s.Userns { | ||||
| 		container.Flags |= sandbox.FAllowUserns | ||||
| 	} | ||||
| 	if s.Net { | ||||
| 		container.Flags |= sandbox.FAllowNet | ||||
| 	} | ||||
| 	if s.Tty { | ||||
| 		container.Flags |= sandbox.FAllowTTY | ||||
| 	} | ||||
| 
 | ||||
| 	if s.MapRealUID { | ||||
| 		/* some programs fail to connect to dbus session running as a different uid | ||||
| 		so this workaround is introduced to map priv-side caller uid in container */ | ||||
| 		container.Uid = sys.Getuid() | ||||
| 		*uid = container.Uid | ||||
| 		container.Gid = sys.Getgid() | ||||
| 		*gid = container.Gid | ||||
| 	} else { | ||||
| 		*uid = sandbox.OverflowUid() | ||||
| 		*gid = sandbox.OverflowGid() | ||||
| 	} | ||||
| 
 | ||||
| 	container. | ||||
| 		Proc("/proc"). | ||||
| 		Tmpfs(Tmp, 1<<12, 0755) | ||||
| 
 | ||||
| 	if !s.Device { | ||||
| 		container.Dev("/dev").Mqueue("/dev/mqueue") | ||||
| 	} else { | ||||
| 		container.Bind("/dev", "/dev", sandbox.BindWritable|sandbox.BindDevice) | ||||
| 	} | ||||
| 
 | ||||
| 	/* retrieve paths and hide them if they're made available in the sandbox; | ||||
| 	this feature tries to improve user experience of permissive defaults, and | ||||
| 	to warn about issues in custom configuration; it is NOT a security feature | ||||
| 	and should not be treated as such, ALWAYS be careful with what you bind */ | ||||
| 	var hidePaths []string | ||||
| 	sc := sys.Paths() | ||||
| 	hidePaths = append(hidePaths, sc.RuntimePath, sc.SharePath) | ||||
| 	_, systemBusAddr := dbus.Address() | ||||
| 	if entries, err := dbus.Parse([]byte(systemBusAddr)); err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} else { | ||||
| 		// there is usually only one, do not preallocate | ||||
| 		for _, entry := range entries { | ||||
| 			if entry.Method != "unix" { | ||||
| 				continue | ||||
| 			} | ||||
| 			for _, pair := range entry.Values { | ||||
| 				if pair[0] == "path" { | ||||
| 					if path.IsAbs(pair[1]) { | ||||
| 						// get parent dir of socket | ||||
| 						dir := path.Dir(pair[1]) | ||||
| 						if dir == "." || dir == "/" { | ||||
| 							sys.Printf("dbus socket %q is in an unusual location", pair[1]) | ||||
| 						} | ||||
| 						hidePaths = append(hidePaths, dir) | ||||
| 					} else { | ||||
| 						sys.Printf("dbus socket %q is not absolute", pair[1]) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	hidePathMatch := make([]bool, len(hidePaths)) | ||||
| 	for i := range hidePaths { | ||||
| 		if err := evalSymlinks(sys, &hidePaths[i]); err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for _, c := range s.Filesystem { | ||||
| 		if c == nil { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		if !path.IsAbs(c.Src) { | ||||
| 			return nil, nil, fmt.Errorf("src path %q is not absolute", c.Src) | ||||
| 		} | ||||
| 
 | ||||
| 		dest := c.Dst | ||||
| 		if c.Dst == "" { | ||||
| 			dest = c.Src | ||||
| 		} else if !path.IsAbs(dest) { | ||||
| 			return nil, nil, fmt.Errorf("dst path %q is not absolute", dest) | ||||
| 		} | ||||
| 
 | ||||
| 		srcH := c.Src | ||||
| 		if err := evalSymlinks(sys, &srcH); err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		for i := range hidePaths { | ||||
| 			// skip matched entries | ||||
| 			if hidePathMatch[i] { | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			if ok, err := deepContainsH(srcH, hidePaths[i]); err != nil { | ||||
| 				return nil, nil, err | ||||
| 			} else if ok { | ||||
| 				hidePathMatch[i] = true | ||||
| 				sys.Printf("hiding paths from %q", c.Src) | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		var flags int | ||||
| 		if c.Write { | ||||
| 			flags |= sandbox.BindWritable | ||||
| 		} | ||||
| 		if c.Device { | ||||
| 			flags |= sandbox.BindDevice | sandbox.BindWritable | ||||
| 		} | ||||
| 		if !c.Must { | ||||
| 			flags |= sandbox.BindOptional | ||||
| 		} | ||||
| 		container.Bind(c.Src, dest, flags) | ||||
| 	} | ||||
| 
 | ||||
| 	// cover matched paths | ||||
| 	for i, ok := range hidePathMatch { | ||||
| 		if ok { | ||||
| 			container.Tmpfs(hidePaths[i], 1<<13, 0755) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for _, l := range s.Link { | ||||
| 		container.Link(l[0], l[1]) | ||||
| 	} | ||||
| 
 | ||||
| 	return container, maps.Clone(s.Env), nil | ||||
| } | ||||
| 
 | ||||
| func evalSymlinks(sys SandboxSys, v *string) error { | ||||
| 	if p, err := sys.EvalSymlinks(*v); err != nil { | ||||
| 		if !errors.Is(err, fs.ErrNotExist) { | ||||
| 			return err | ||||
| 		} | ||||
| 		sys.Printf("path %q does not yet exist", *v) | ||||
| 	} else { | ||||
| 		*v = p | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @ -1,18 +1,20 @@ | ||||
| // Package fst exports shared fortify types. | ||||
| package fst | ||||
| // Package app defines the generic [App] interface. | ||||
| package app | ||||
| 
 | ||||
| import ( | ||||
| 	"syscall" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| ) | ||||
| 
 | ||||
| type App interface { | ||||
| 	// ID returns a copy of [fst.ID] held by App. | ||||
| 	// ID returns a copy of [ID] held by App. | ||||
| 	ID() ID | ||||
| 
 | ||||
| 	// Seal determines the outcome of config as a [SealedApp]. | ||||
| 	// The value of config might be overwritten and must not be used again. | ||||
| 	Seal(config *Config) (SealedApp, error) | ||||
| 	Seal(config *fst.Config) (SealedApp, error) | ||||
| 
 | ||||
| 	String() string | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| package fst | ||||
| package app | ||||
| 
 | ||||
| import ( | ||||
| 	"crypto/rand" | ||||
| @ -1,22 +1,22 @@ | ||||
| package fst_test | ||||
| package app_test | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	. "git.gensokyo.uk/security/fortify/internal/app" | ||||
| ) | ||||
| 
 | ||||
| func TestParseAppID(t *testing.T) { | ||||
| 	t.Run("bad length", func(t *testing.T) { | ||||
| 		if err := fst.ParseAppID(new(fst.ID), "meow"); !errors.Is(err, fst.ErrInvalidLength) { | ||||
| 			t.Errorf("ParseAppID: error = %v, wantErr =  %v", err, fst.ErrInvalidLength) | ||||
| 		if err := ParseAppID(new(ID), "meow"); !errors.Is(err, ErrInvalidLength) { | ||||
| 			t.Errorf("ParseAppID: error = %v, wantErr =  %v", err, ErrInvalidLength) | ||||
| 		} | ||||
| 	}) | ||||
| 
 | ||||
| 	t.Run("bad byte", func(t *testing.T) { | ||||
| 		wantErr := "invalid char '\\n' at byte 15" | ||||
| 		if err := fst.ParseAppID(new(fst.ID), "02bc7f8936b2af6\n\ne2535cd71ef0bb7"); err == nil || err.Error() != wantErr { | ||||
| 		if err := ParseAppID(new(ID), "02bc7f8936b2af6\n\ne2535cd71ef0bb7"); err == nil || err.Error() != wantErr { | ||||
| 			t.Errorf("ParseAppID: error = %v, wantErr =  %v", err, wantErr) | ||||
| 		} | ||||
| 	}) | ||||
| @ -30,30 +30,30 @@ func TestParseAppID(t *testing.T) { | ||||
| 
 | ||||
| func FuzzParseAppID(f *testing.F) { | ||||
| 	for i := 0; i < 16; i++ { | ||||
| 		id := new(fst.ID) | ||||
| 		if err := fst.NewAppID(id); err != nil { | ||||
| 		id := new(ID) | ||||
| 		if err := NewAppID(id); err != nil { | ||||
| 			panic(err.Error()) | ||||
| 		} | ||||
| 		f.Add(id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15]) | ||||
| 	} | ||||
| 
 | ||||
| 	f.Fuzz(func(t *testing.T, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15 byte) { | ||||
| 		testParseAppID(t, &fst.ID{b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15}) | ||||
| 		testParseAppID(t, &ID{b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15}) | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| func testParseAppIDWithRandom(t *testing.T) { | ||||
| 	id := new(fst.ID) | ||||
| 	if err := fst.NewAppID(id); err != nil { | ||||
| 	id := new(ID) | ||||
| 	if err := NewAppID(id); err != nil { | ||||
| 		t.Fatalf("cannot generate app ID: %v", err) | ||||
| 	} | ||||
| 	testParseAppID(t, id) | ||||
| } | ||||
| 
 | ||||
| func testParseAppID(t *testing.T, id *fst.ID) { | ||||
| func testParseAppID(t *testing.T, id *ID) { | ||||
| 	s := id.String() | ||||
| 	got := new(fst.ID) | ||||
| 	if err := fst.ParseAppID(got, s); err != nil { | ||||
| 	got := new(ID) | ||||
| 	if err := ParseAppID(got, s); err != nil { | ||||
| 		t.Fatalf("cannot parse app ID: %v", err) | ||||
| 	} | ||||
| 
 | ||||
							
								
								
									
										187
									
								
								internal/app/instance/common/container.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								internal/app/instance/common/container.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,187 @@ | ||||
| package common | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/fs" | ||||
| 	"maps" | ||||
| 	"path" | ||||
| 	"slices" | ||||
| 	"syscall" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/dbus" | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/sys" | ||||
| 	"git.gensokyo.uk/security/fortify/sandbox" | ||||
| 	"git.gensokyo.uk/security/fortify/sandbox/seccomp" | ||||
| ) | ||||
| 
 | ||||
| // NewContainer initialises [sandbox.Params] via [fst.SandboxConfig]. | ||||
| // Note that remaining container setup must be queued by the caller. | ||||
| func NewContainer(s *fst.SandboxConfig, os sys.State, uid, gid *int) (*sandbox.Params, map[string]string, error) { | ||||
| 	if s == nil { | ||||
| 		return nil, nil, syscall.EBADE | ||||
| 	} | ||||
| 
 | ||||
| 	container := &sandbox.Params{ | ||||
| 		Hostname: s.Hostname, | ||||
| 		Ops:      new(sandbox.Ops), | ||||
| 		Seccomp:  s.Seccomp, | ||||
| 	} | ||||
| 
 | ||||
| 	if s.Multiarch { | ||||
| 		container.Seccomp |= seccomp.FilterMultiarch | ||||
| 	} | ||||
| 
 | ||||
| 	/* this is only 4 KiB of memory on a 64-bit system, | ||||
| 	permissive defaults on NixOS results in around 100 entries | ||||
| 	so this capacity should eliminate copies for most setups */ | ||||
| 	*container.Ops = slices.Grow(*container.Ops, 1<<8) | ||||
| 
 | ||||
| 	if s.Devel { | ||||
| 		container.Flags |= sandbox.FAllowDevel | ||||
| 	} | ||||
| 	if s.Userns { | ||||
| 		container.Flags |= sandbox.FAllowUserns | ||||
| 	} | ||||
| 	if s.Net { | ||||
| 		container.Flags |= sandbox.FAllowNet | ||||
| 	} | ||||
| 	if s.Tty { | ||||
| 		container.Flags |= sandbox.FAllowTTY | ||||
| 	} | ||||
| 
 | ||||
| 	if s.MapRealUID { | ||||
| 		/* some programs fail to connect to dbus session running as a different uid | ||||
| 		so this workaround is introduced to map priv-side caller uid in container */ | ||||
| 		container.Uid = os.Getuid() | ||||
| 		*uid = container.Uid | ||||
| 		container.Gid = os.Getgid() | ||||
| 		*gid = container.Gid | ||||
| 	} else { | ||||
| 		*uid = sandbox.OverflowUid() | ||||
| 		*gid = sandbox.OverflowGid() | ||||
| 	} | ||||
| 
 | ||||
| 	container. | ||||
| 		Proc("/proc"). | ||||
| 		Tmpfs(fst.Tmp, 1<<12, 0755) | ||||
| 
 | ||||
| 	if !s.Device { | ||||
| 		container.Dev("/dev").Mqueue("/dev/mqueue") | ||||
| 	} else { | ||||
| 		container.Bind("/dev", "/dev", sandbox.BindWritable|sandbox.BindDevice) | ||||
| 	} | ||||
| 
 | ||||
| 	/* retrieve paths and hide them if they're made available in the sandbox; | ||||
| 	this feature tries to improve user experience of permissive defaults, and | ||||
| 	to warn about issues in custom configuration; it is NOT a security feature | ||||
| 	and should not be treated as such, ALWAYS be careful with what you bind */ | ||||
| 	var hidePaths []string | ||||
| 	sc := os.Paths() | ||||
| 	hidePaths = append(hidePaths, sc.RuntimePath, sc.SharePath) | ||||
| 	_, systemBusAddr := dbus.Address() | ||||
| 	if entries, err := dbus.Parse([]byte(systemBusAddr)); err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} else { | ||||
| 		// there is usually only one, do not preallocate | ||||
| 		for _, entry := range entries { | ||||
| 			if entry.Method != "unix" { | ||||
| 				continue | ||||
| 			} | ||||
| 			for _, pair := range entry.Values { | ||||
| 				if pair[0] == "path" { | ||||
| 					if path.IsAbs(pair[1]) { | ||||
| 						// get parent dir of socket | ||||
| 						dir := path.Dir(pair[1]) | ||||
| 						if dir == "." || dir == "/" { | ||||
| 							os.Printf("dbus socket %q is in an unusual location", pair[1]) | ||||
| 						} | ||||
| 						hidePaths = append(hidePaths, dir) | ||||
| 					} else { | ||||
| 						os.Printf("dbus socket %q is not absolute", pair[1]) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	hidePathMatch := make([]bool, len(hidePaths)) | ||||
| 	for i := range hidePaths { | ||||
| 		if err := evalSymlinks(os, &hidePaths[i]); err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for _, c := range s.Filesystem { | ||||
| 		if c == nil { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		if !path.IsAbs(c.Src) { | ||||
| 			return nil, nil, fmt.Errorf("src path %q is not absolute", c.Src) | ||||
| 		} | ||||
| 
 | ||||
| 		dest := c.Dst | ||||
| 		if c.Dst == "" { | ||||
| 			dest = c.Src | ||||
| 		} else if !path.IsAbs(dest) { | ||||
| 			return nil, nil, fmt.Errorf("dst path %q is not absolute", dest) | ||||
| 		} | ||||
| 
 | ||||
| 		srcH := c.Src | ||||
| 		if err := evalSymlinks(os, &srcH); err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		for i := range hidePaths { | ||||
| 			// skip matched entries | ||||
| 			if hidePathMatch[i] { | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			if ok, err := deepContainsH(srcH, hidePaths[i]); err != nil { | ||||
| 				return nil, nil, err | ||||
| 			} else if ok { | ||||
| 				hidePathMatch[i] = true | ||||
| 				os.Printf("hiding paths from %q", c.Src) | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		var flags int | ||||
| 		if c.Write { | ||||
| 			flags |= sandbox.BindWritable | ||||
| 		} | ||||
| 		if c.Device { | ||||
| 			flags |= sandbox.BindDevice | sandbox.BindWritable | ||||
| 		} | ||||
| 		if !c.Must { | ||||
| 			flags |= sandbox.BindOptional | ||||
| 		} | ||||
| 		container.Bind(c.Src, dest, flags) | ||||
| 	} | ||||
| 
 | ||||
| 	// cover matched paths | ||||
| 	for i, ok := range hidePathMatch { | ||||
| 		if ok { | ||||
| 			container.Tmpfs(hidePaths[i], 1<<13, 0755) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for _, l := range s.Link { | ||||
| 		container.Link(l[0], l[1]) | ||||
| 	} | ||||
| 
 | ||||
| 	return container, maps.Clone(s.Env), nil | ||||
| } | ||||
| 
 | ||||
| func evalSymlinks(os sys.State, v *string) error { | ||||
| 	if p, err := os.EvalSymlinks(*v); err != nil { | ||||
| 		if !errors.Is(err, fs.ErrNotExist) { | ||||
| 			return err | ||||
| 		} | ||||
| 		os.Printf("path %q does not yet exist", *v) | ||||
| 	} else { | ||||
| 		*v = p | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| package fst | ||||
| package common | ||||
| 
 | ||||
| import ( | ||||
| 	"path/filepath" | ||||
| @ -1,4 +1,4 @@ | ||||
| package fst | ||||
| package common | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
							
								
								
									
										17
									
								
								internal/app/instance/errors.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								internal/app/instance/errors.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| package instance | ||||
| 
 | ||||
| import ( | ||||
| 	"syscall" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app/internal/setuid" | ||||
| ) | ||||
| 
 | ||||
| func PrintRunStateErr(whence int, rs *app.RunState, runErr error) (code int) { | ||||
| 	switch whence { | ||||
| 	case ISetuid: | ||||
| 		return setuid.PrintRunStateErr(rs, runErr) | ||||
| 	default: | ||||
| 		panic(syscall.EINVAL) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										33
									
								
								internal/app/instance/new.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								internal/app/instance/new.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| // Package instance exposes cross-package implementation details and provides constructors for builtin implementations. | ||||
| package instance | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"log" | ||||
| 	"syscall" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app/internal/setuid" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/sys" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	ISetuid = iota | ||||
| ) | ||||
| 
 | ||||
| func New(whence int, ctx context.Context, os sys.State) (app.App, error) { | ||||
| 	switch whence { | ||||
| 	case ISetuid: | ||||
| 		return setuid.New(ctx, os) | ||||
| 	default: | ||||
| 		return nil, syscall.EINVAL | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func MustNew(whence int, ctx context.Context, os sys.State) app.App { | ||||
| 	a, err := New(whence, ctx, os) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("cannot create app: %v", err) | ||||
| 	} | ||||
| 	return a | ||||
| } | ||||
							
								
								
									
										6
									
								
								internal/app/instance/shim.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								internal/app/instance/shim.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| package instance | ||||
| 
 | ||||
| import "git.gensokyo.uk/security/fortify/internal/app/internal/setuid" | ||||
| 
 | ||||
| // ShimMain is the main function of the shim process and runs as the unconstrained target user. | ||||
| func ShimMain() { setuid.ShimMain() } | ||||
| @ -3,36 +3,28 @@ package setuid | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"sync" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	. "git.gensokyo.uk/security/fortify/internal/app" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/sys" | ||||
| ) | ||||
| 
 | ||||
| func New(ctx context.Context, os sys.State) (fst.App, error) { | ||||
| func New(ctx context.Context, os sys.State) (App, error) { | ||||
| 	a := new(app) | ||||
| 	a.sys = os | ||||
| 	a.ctx = ctx | ||||
| 
 | ||||
| 	id := new(fst.ID) | ||||
| 	err := fst.NewAppID(id) | ||||
| 	id := new(ID) | ||||
| 	err := NewAppID(id) | ||||
| 	a.id = newID(id) | ||||
| 
 | ||||
| 	return a, err | ||||
| } | ||||
| 
 | ||||
| func MustNew(ctx context.Context, os sys.State) fst.App { | ||||
| 	a, err := New(ctx, os) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("cannot create app: %v", err) | ||||
| 	} | ||||
| 	return a | ||||
| } | ||||
| 
 | ||||
| type app struct { | ||||
| 	id  *stringPair[fst.ID] | ||||
| 	id  *stringPair[ID] | ||||
| 	sys sys.State | ||||
| 	ctx context.Context | ||||
| 
 | ||||
| @ -40,7 +32,7 @@ type app struct { | ||||
| 	mu sync.RWMutex | ||||
| } | ||||
| 
 | ||||
| func (a *app) ID() fst.ID { a.mu.RLock(); defer a.mu.RUnlock(); return a.id.unwrap() } | ||||
| func (a *app) ID() ID { a.mu.RLock(); defer a.mu.RUnlock(); return a.id.unwrap() } | ||||
| 
 | ||||
| func (a *app) String() string { | ||||
| 	if a == nil { | ||||
| @ -60,7 +52,7 @@ func (a *app) String() string { | ||||
| 	return fmt.Sprintf("(unsealed app %s)", a.id) | ||||
| } | ||||
| 
 | ||||
| func (a *app) Seal(config *fst.Config) (fst.SealedApp, error) { | ||||
| func (a *app) Seal(config *fst.Config) (SealedApp, error) { | ||||
| 	a.mu.Lock() | ||||
| 	defer a.mu.Unlock() | ||||
| 
 | ||||
| @ -4,6 +4,7 @@ import ( | ||||
| 	"git.gensokyo.uk/security/fortify/acl" | ||||
| 	"git.gensokyo.uk/security/fortify/dbus" | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app" | ||||
| 	"git.gensokyo.uk/security/fortify/sandbox" | ||||
| 	"git.gensokyo.uk/security/fortify/system" | ||||
| ) | ||||
| @ -48,7 +49,7 @@ var testCasesNixos = []sealTestCase{ | ||||
| 				Enablements: system.EWayland | system.EDBus | system.EPulse, | ||||
| 			}, | ||||
| 		}, | ||||
| 		fst.ID{ | ||||
| 		app.ID{ | ||||
| 			0x8e, 0x2c, 0x76, 0xb0, | ||||
| 			0x66, 0xda, 0xbe, 0x57, | ||||
| 			0x4c, 0xf0, 0x73, 0xbd, | ||||
| @ -6,6 +6,7 @@ import ( | ||||
| 	"git.gensokyo.uk/security/fortify/acl" | ||||
| 	"git.gensokyo.uk/security/fortify/dbus" | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app" | ||||
| 	"git.gensokyo.uk/security/fortify/sandbox" | ||||
| 	"git.gensokyo.uk/security/fortify/system" | ||||
| ) | ||||
| @ -20,7 +21,7 @@ var testCasesPd = []sealTestCase{ | ||||
| 				Outer:    "/home/chronos", | ||||
| 			}, | ||||
| 		}, | ||||
| 		fst.ID{ | ||||
| 		app.ID{ | ||||
| 			0x4a, 0x45, 0x0b, 0x65, | ||||
| 			0x96, 0xd7, 0xbc, 0x15, | ||||
| 			0xbd, 0x01, 0x78, 0x0e, | ||||
| @ -117,7 +118,7 @@ var testCasesPd = []sealTestCase{ | ||||
| 				Enablements: system.EWayland | system.EDBus | system.EPulse, | ||||
| 			}, | ||||
| 		}, | ||||
| 		fst.ID{ | ||||
| 		app.ID{ | ||||
| 			0xeb, 0xf0, 0x83, 0xd1, | ||||
| 			0xb1, 0x75, 0x91, 0x17, | ||||
| 			0x82, 0xd4, 0x13, 0x36, | ||||
| @ -7,7 +7,7 @@ import ( | ||||
| 	"os/user" | ||||
| 	"strconv" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app" | ||||
| ) | ||||
| 
 | ||||
| // fs methods are not implemented using a real FS | ||||
| @ -125,8 +125,8 @@ func (s *stubNixOS) Open(name string) (fs.File, error) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (s *stubNixOS) Paths() fst.Paths { | ||||
| 	return fst.Paths{ | ||||
| func (s *stubNixOS) Paths() app.Paths { | ||||
| 	return app.Paths{ | ||||
| 		SharePath:   "/tmp/fortify.1971", | ||||
| 		RuntimePath: "/run/user/1971", | ||||
| 		RunDirPath:  "/run/user/1971/fortify", | ||||
| @ -8,7 +8,8 @@ import ( | ||||
| 	"time" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app/setuid" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app/internal/setuid" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/sys" | ||||
| 	"git.gensokyo.uk/security/fortify/sandbox" | ||||
| 	"git.gensokyo.uk/security/fortify/system" | ||||
| @ -18,7 +19,7 @@ type sealTestCase struct { | ||||
| 	name          string | ||||
| 	os            sys.State | ||||
| 	config        *fst.Config | ||||
| 	id            fst.ID | ||||
| 	id            app.ID | ||||
| 	wantSys       *system.I | ||||
| 	wantContainer *sandbox.Params | ||||
| } | ||||
| @ -4,11 +4,11 @@ import ( | ||||
| 	"errors" | ||||
| 	"log" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	. "git.gensokyo.uk/security/fortify/internal/app" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||
| ) | ||||
| 
 | ||||
| func PrintRunStateErr(rs *fst.RunState, runErr error) (code int) { | ||||
| func PrintRunStateErr(rs *RunState, runErr error) (code int) { | ||||
| 	code = rs.ExitStatus() | ||||
| 
 | ||||
| 	if runErr != nil { | ||||
| @ -1,20 +1,20 @@ | ||||
| package setuid | ||||
| 
 | ||||
| import ( | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	. "git.gensokyo.uk/security/fortify/internal/app" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/sys" | ||||
| 	"git.gensokyo.uk/security/fortify/sandbox" | ||||
| 	"git.gensokyo.uk/security/fortify/system" | ||||
| ) | ||||
| 
 | ||||
| func NewWithID(id fst.ID, os sys.State) fst.App { | ||||
| func NewWithID(id ID, os sys.State) App { | ||||
| 	a := new(app) | ||||
| 	a.id = newID(&id) | ||||
| 	a.sys = os | ||||
| 	return a | ||||
| } | ||||
| 
 | ||||
| func AppIParams(a fst.App, sa fst.SealedApp) (*system.I, *sandbox.Params) { | ||||
| func AppIParams(a App, sa SealedApp) (*system.I, *sandbox.Params) { | ||||
| 	v := a.(*app) | ||||
| 	seal := sa.(*outcome) | ||||
| 	if v.outcome != seal || v.id != seal.id { | ||||
| @ -12,8 +12,8 @@ import ( | ||||
| 	"syscall" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	"git.gensokyo.uk/security/fortify/internal" | ||||
| 	. "git.gensokyo.uk/security/fortify/internal/app" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/state" | ||||
| 	"git.gensokyo.uk/security/fortify/sandbox" | ||||
| @ -22,7 +22,7 @@ import ( | ||||
| 
 | ||||
| const shimWaitTimeout = 5 * time.Second | ||||
| 
 | ||||
| func (seal *outcome) Run(rs *fst.RunState) error { | ||||
| func (seal *outcome) Run(rs *RunState) error { | ||||
| 	if !seal.f.CompareAndSwap(false, true) { | ||||
| 		// run does much more than just starting a process; calling it twice, even if the first call fails, will result | ||||
| 		// in inconsistent state that is impossible to clean up; return here to limit damage and hopefully give the | ||||
| @ -20,6 +20,8 @@ import ( | ||||
| 	"git.gensokyo.uk/security/fortify/dbus" | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	"git.gensokyo.uk/security/fortify/internal" | ||||
| 	. "git.gensokyo.uk/security/fortify/internal/app" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app/instance/common" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/sys" | ||||
| 	"git.gensokyo.uk/security/fortify/sandbox" | ||||
| @ -64,7 +66,7 @@ var posixUsername = regexp.MustCompilePOSIX("^[a-z_]([A-Za-z0-9_-]{0,31}|[A-Za-z | ||||
| // outcome stores copies of various parts of [fst.Config] | ||||
| type outcome struct { | ||||
| 	// copied from initialising [app] | ||||
| 	id *stringPair[fst.ID] | ||||
| 	id *stringPair[ID] | ||||
| 	// copied from [sys.State] response | ||||
| 	runDirPath string | ||||
| 
 | ||||
| @ -95,7 +97,7 @@ type shareHost struct { | ||||
| 	runtimeSharePath string | ||||
| 
 | ||||
| 	seal *outcome | ||||
| 	sc   fst.Paths | ||||
| 	sc   Paths | ||||
| } | ||||
| 
 | ||||
| // ensureRuntimeDir must be called if direct access to paths within XDG_RUNTIME_DIR is required | ||||
| @ -279,7 +281,7 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co | ||||
| 	{ | ||||
| 		var uid, gid int | ||||
| 		var err error | ||||
| 		seal.container, seal.env, err = config.Confinement.Sandbox.ToContainer(sys, &uid, &gid) | ||||
| 		seal.container, seal.env, err = common.NewContainer(config.Confinement.Sandbox, sys, &uid, &gid) | ||||
| 		if err != nil { | ||||
| 			return fmsg.WrapErrorSuffix(err, | ||||
| 				"cannot initialise container configuration:") | ||||
| @ -3,11 +3,11 @@ package setuid | ||||
| import ( | ||||
| 	"strconv" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	. "git.gensokyo.uk/security/fortify/internal/app" | ||||
| ) | ||||
| 
 | ||||
| func newInt(v int) *stringPair[int]        { return &stringPair[int]{v, strconv.Itoa(v)} } | ||||
| func newID(id *fst.ID) *stringPair[fst.ID] { return &stringPair[fst.ID]{*id, id.String()} } | ||||
| func newInt(v int) *stringPair[int] { return &stringPair[int]{v, strconv.Itoa(v)} } | ||||
| func newID(id *ID) *stringPair[ID]  { return &stringPair[ID]{*id, id.String()} } | ||||
| 
 | ||||
| // stringPair stores a value and its string representation. | ||||
| type stringPair[T comparable] struct { | ||||
| @ -14,6 +14,7 @@ import ( | ||||
| 	"syscall" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||
| ) | ||||
| 
 | ||||
| @ -129,7 +130,7 @@ type multiBackend struct { | ||||
| 	lock sync.RWMutex | ||||
| } | ||||
| 
 | ||||
| func (b *multiBackend) filename(id *fst.ID) string { | ||||
| func (b *multiBackend) filename(id *app.ID) string { | ||||
| 	return path.Join(b.path, id.String()) | ||||
| } | ||||
| 
 | ||||
| @ -189,8 +190,8 @@ func (b *multiBackend) load(decode bool) (Entries, error) { | ||||
| 			return nil, fmt.Errorf("unexpected directory %q in store", e.Name()) | ||||
| 		} | ||||
| 
 | ||||
| 		id := new(fst.ID) | ||||
| 		if err := fst.ParseAppID(id, e.Name()); err != nil { | ||||
| 		id := new(app.ID) | ||||
| 		if err := app.ParseAppID(id, e.Name()); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| @ -335,7 +336,7 @@ func (b *multiBackend) encodeState(w io.WriteSeeker, state *State, configWriter | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (b *multiBackend) Destroy(id fst.ID) error { | ||||
| func (b *multiBackend) Destroy(id app.ID) error { | ||||
| 	b.lock.Lock() | ||||
| 	defer b.lock.Unlock() | ||||
| 
 | ||||
|  | ||||
| @ -6,11 +6,12 @@ import ( | ||||
| 	"time" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app" | ||||
| ) | ||||
| 
 | ||||
| var ErrNoConfig = errors.New("state does not contain config") | ||||
| 
 | ||||
| type Entries map[fst.ID]*State | ||||
| type Entries map[app.ID]*State | ||||
| 
 | ||||
| type Store interface { | ||||
| 	// Do calls f exactly once and ensures store exclusivity until f returns. | ||||
| @ -29,7 +30,7 @@ type Store interface { | ||||
| // Cursor provides access to the store | ||||
| type Cursor interface { | ||||
| 	Save(state *State, configWriter io.WriterTo) error | ||||
| 	Destroy(id fst.ID) error | ||||
| 	Destroy(id app.ID) error | ||||
| 	Load() (Entries, error) | ||||
| 	Len() (int, error) | ||||
| } | ||||
| @ -37,7 +38,7 @@ type Cursor interface { | ||||
| // State is a fortify process's state | ||||
| type State struct { | ||||
| 	// fortify instance id | ||||
| 	ID fst.ID `json:"instance"` | ||||
| 	ID app.ID `json:"instance"` | ||||
| 	// child process PID value | ||||
| 	PID int `json:"pid"` | ||||
| 	// sealed app configuration | ||||
|  | ||||
| @ -11,6 +11,7 @@ import ( | ||||
| 	"time" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/state" | ||||
| ) | ||||
| 
 | ||||
| @ -133,7 +134,7 @@ func testStore(t *testing.T, s state.Store) { | ||||
| } | ||||
| 
 | ||||
| func makeState(t *testing.T, s *state.State, ct io.Writer) { | ||||
| 	if err := fst.NewAppID(&s.ID); err != nil { | ||||
| 	if err := app.NewAppID(&s.ID); err != nil { | ||||
| 		t.Fatalf("cannot create dummy state: %v", err) | ||||
| 	} | ||||
| 	if err := gob.NewEncoder(ct).Encode(fst.Template()); err != nil { | ||||
|  | ||||
| @ -6,7 +6,7 @@ import ( | ||||
| 	"path" | ||||
| 	"strconv" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||
| ) | ||||
| 
 | ||||
| @ -41,14 +41,14 @@ type State interface { | ||||
| 	Printf(format string, v ...any) | ||||
| 
 | ||||
| 	// Paths returns a populated [Paths] struct. | ||||
| 	Paths() fst.Paths | ||||
| 	Paths() app.Paths | ||||
| 	// Uid invokes fsu and returns target uid. | ||||
| 	// Any errors returned by Uid is already wrapped [fmsg.BaseError]. | ||||
| 	Uid(aid int) (int, error) | ||||
| } | ||||
| 
 | ||||
| // CopyPaths is a generic implementation of [fst.Paths]. | ||||
| func CopyPaths(os State, v *fst.Paths) { | ||||
| func CopyPaths(os State, v *app.Paths) { | ||||
| 	v.SharePath = path.Join(os.TempDir(), "fortify."+strconv.Itoa(os.Getuid())) | ||||
| 
 | ||||
| 	fmsg.Verbosef("process share directory at %q", v.SharePath) | ||||
|  | ||||
| @ -12,15 +12,15 @@ import ( | ||||
| 	"sync" | ||||
| 	"syscall" | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	"git.gensokyo.uk/security/fortify/internal" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||
| 	"git.gensokyo.uk/security/fortify/sandbox" | ||||
| ) | ||||
| 
 | ||||
| // Std implements System using the standard library. | ||||
| type Std struct { | ||||
| 	paths     fst.Paths | ||||
| 	paths     app.Paths | ||||
| 	pathsOnce sync.Once | ||||
| 
 | ||||
| 	uidOnce sync.Once | ||||
| @ -48,7 +48,7 @@ func (s *Std) Printf(format string, v ...any)               { fmsg.Verbosef(form | ||||
| 
 | ||||
| const xdgRuntimeDir = "XDG_RUNTIME_DIR" | ||||
| 
 | ||||
| func (s *Std) Paths() fst.Paths { | ||||
| func (s *Std) Paths() app.Paths { | ||||
| 	s.pathsOnce.Do(func() { CopyPaths(s, &s.paths) }) | ||||
| 	return s.paths | ||||
| } | ||||
|  | ||||
							
								
								
									
										15
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								main.go
									
									
									
									
									
								
							| @ -19,7 +19,8 @@ import ( | ||||
| 	"git.gensokyo.uk/security/fortify/dbus" | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	"git.gensokyo.uk/security/fortify/internal" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app/setuid" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app/instance" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/state" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/sys" | ||||
| @ -73,7 +74,7 @@ func buildCommand(out io.Writer) command.Command { | ||||
| 		Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console"). | ||||
| 		Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output as JSON when applicable") | ||||
| 
 | ||||
| 	c.Command("shim", command.UsageInternal, func([]string) error { setuid.ShimMain(); return errSuccess }) | ||||
| 	c.Command("shim", command.UsageInternal, func([]string) error { instance.ShimMain(); return errSuccess }) | ||||
| 
 | ||||
| 	c.Command("app", "Launch app defined by the specified config file", func(args []string) error { | ||||
| 		if len(args) < 1 { | ||||
| @ -239,11 +240,11 @@ func buildCommand(out io.Writer) command.Command { | ||||
| 
 | ||||
| 		case 1: // instance | ||||
| 			name := args[0] | ||||
| 			config, instance := tryShort(name) | ||||
| 			config, entry := tryShort(name) | ||||
| 			if config == nil { | ||||
| 				config = tryPath(name) | ||||
| 			} | ||||
| 			printShowInstance(os.Stdout, time.Now().UTC(), instance, config, showFlagShort, flagJSON) | ||||
| 			printShowInstance(os.Stdout, time.Now().UTC(), entry, config, showFlagShort, flagJSON) | ||||
| 
 | ||||
| 		default: | ||||
| 			log.Fatal("show requires 1 argument") | ||||
| @ -284,14 +285,14 @@ func runApp(config *fst.Config) { | ||||
| 	ctx, stop := signal.NotifyContext(context.Background(), | ||||
| 		syscall.SIGINT, syscall.SIGTERM) | ||||
| 	defer stop() // unreachable | ||||
| 	a := setuid.MustNew(ctx, std) | ||||
| 	a := instance.MustNew(instance.ISetuid, ctx, std) | ||||
| 
 | ||||
| 	rs := new(fst.RunState) | ||||
| 	rs := new(app.RunState) | ||||
| 	if sa, err := a.Seal(config); err != nil { | ||||
| 		fmsg.PrintBaseError(err, "cannot seal app:") | ||||
| 		internal.Exit(1) | ||||
| 	} else { | ||||
| 		internal.Exit(setuid.PrintRunStateErr(rs, sa.Run(rs))) | ||||
| 		internal.Exit(instance.PrintRunStateErr(instance.ISetuid, rs, sa.Run(rs))) | ||||
| 	} | ||||
| 
 | ||||
| 	*(*int)(nil) = 0 // not reached | ||||
|  | ||||
							
								
								
									
										6
									
								
								parse.go
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								parse.go
									
									
									
									
									
								
							| @ -67,7 +67,7 @@ func tryFd(name string) io.ReadCloser { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func tryShort(name string) (config *fst.Config, instance *state.State) { | ||||
| func tryShort(name string) (config *fst.Config, entry *state.State) { | ||||
| 	likePrefix := false | ||||
| 	if len(name) <= 32 { | ||||
| 		likePrefix = true | ||||
| @ -96,8 +96,8 @@ func tryShort(name string) (config *fst.Config, instance *state.State) { | ||||
| 				v := id.String() | ||||
| 				if strings.HasPrefix(v, name) { | ||||
| 					// match, use config from this state entry | ||||
| 					instance = entries[id] | ||||
| 					config = instance.Config | ||||
| 					entry = entries[id] | ||||
| 					config = entry.Config | ||||
| 					break | ||||
| 				} | ||||
| 
 | ||||
|  | ||||
| @ -7,11 +7,12 @@ import ( | ||||
| 
 | ||||
| 	"git.gensokyo.uk/security/fortify/dbus" | ||||
| 	"git.gensokyo.uk/security/fortify/fst" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/app" | ||||
| 	"git.gensokyo.uk/security/fortify/internal/state" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	testID = fst.ID{ | ||||
| 	testID = app.ID{ | ||||
| 		0x8e, 0x2c, 0x76, 0xb0, | ||||
| 		0x66, 0xda, 0xbe, 0x57, | ||||
| 		0x4c, 0xf0, 0x73, 0xbd, | ||||
| @ -457,7 +458,7 @@ func Test_printPs(t *testing.T) { | ||||
| 		{"no entries", make(state.Entries), false, false, "    Instance    PID    Application    Uptime\n"}, | ||||
| 		{"no entries short", make(state.Entries), true, false, ""}, | ||||
| 		{"nil instance", state.Entries{testID: nil}, false, false, "    Instance    PID    Application    Uptime\n"}, | ||||
| 		{"state corruption", state.Entries{fst.ID{}: testState}, false, false, "    Instance    PID    Application    Uptime\n"}, | ||||
| 		{"state corruption", state.Entries{app.ID{}: testState}, false, false, "    Instance    PID    Application    Uptime\n"}, | ||||
| 
 | ||||
| 		{"valid pd", state.Entries{testID: &state.State{ID: testID, PID: 1 << 8, Config: new(fst.Config), Time: testAppTime}}, false, false, `    Instance    PID    Application                         Uptime | ||||
|     8e2c76b0    256    0 (uk.gensokyo.fortify.8e2c76b0)    1h2m32s | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user