Compare commits
	
		
			6 Commits
		
	
	
		
			90b86a5531
			...
			468696f611
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 468696f611 | |||
| 29c38caac8 | |||
| e599b5583d | |||
| 33a4ab11c2 | |||
| 1fa5e992e4 | |||
| c667b13a00 | 
| @ -2,10 +2,10 @@ package main | |||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
|  | 	"log" | ||||||
| 	"os" | 	"os" | ||||||
| 
 | 
 | ||||||
| 	"git.gensokyo.uk/security/fortify/dbus" | 	"git.gensokyo.uk/security/fortify/dbus" | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" |  | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/system" | 	"git.gensokyo.uk/security/fortify/internal/system" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| @ -63,18 +63,18 @@ func loadBundleInfo(name string, beforeFail func()) *bundleInfo { | |||||||
| 	bundle := new(bundleInfo) | 	bundle := new(bundleInfo) | ||||||
| 	if f, err := os.Open(name); err != nil { | 	if f, err := os.Open(name); err != nil { | ||||||
| 		beforeFail() | 		beforeFail() | ||||||
| 		fmsg.Fatalf("cannot open bundle: %v", err) | 		log.Fatalf("cannot open bundle: %v", err) | ||||||
| 	} else if err = json.NewDecoder(f).Decode(&bundle); err != nil { | 	} else if err = json.NewDecoder(f).Decode(&bundle); err != nil { | ||||||
| 		beforeFail() | 		beforeFail() | ||||||
| 		fmsg.Fatalf("cannot parse bundle metadata: %v", err) | 		log.Fatalf("cannot parse bundle metadata: %v", err) | ||||||
| 	} else if err = f.Close(); err != nil { | 	} else if err = f.Close(); err != nil { | ||||||
| 		fmsg.Printf("cannot close bundle metadata: %v", err) | 		log.Printf("cannot close bundle metadata: %v", err) | ||||||
| 		// not fatal | 		// not fatal | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if bundle.ID == "" { | 	if bundle.ID == "" { | ||||||
| 		beforeFail() | 		beforeFail() | ||||||
| 		fmsg.Fatal("application identifier must not be empty") | 		log.Fatal("application identifier must not be empty") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return bundle | 	return bundle | ||||||
| @ -82,7 +82,7 @@ func loadBundleInfo(name string, beforeFail func()) *bundleInfo { | |||||||
| 
 | 
 | ||||||
| func formatHostname(name string) string { | func formatHostname(name string) string { | ||||||
| 	if h, err := os.Hostname(); err != nil { | 	if h, err := os.Hostname(); err != nil { | ||||||
| 		fmsg.Printf("cannot get hostname: %v", err) | 		log.Printf("cannot get hostname: %v", err) | ||||||
| 		return "fortify-" + name | 		return "fortify-" + name | ||||||
| 	} else { | 	} else { | ||||||
| 		return h + "-" + name | 		return h + "-" + name | ||||||
|  | |||||||
| @ -3,10 +3,12 @@ package main | |||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"flag" | 	"flag" | ||||||
|  | 	"log" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path" | 	"path" | ||||||
| 
 | 
 | ||||||
| 	"git.gensokyo.uk/security/fortify/fst" | 	"git.gensokyo.uk/security/fortify/fst" | ||||||
|  | 	"git.gensokyo.uk/security/fortify/internal" | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| @ -25,12 +27,12 @@ func actionInstall(args []string) { | |||||||
| 	args = set.Args() | 	args = set.Args() | ||||||
| 
 | 
 | ||||||
| 	if len(args) != 1 { | 	if len(args) != 1 { | ||||||
| 		fmsg.Fatal("invalid argument") | 		log.Fatal("invalid argument") | ||||||
| 	} | 	} | ||||||
| 	pkgPath := args[0] | 	pkgPath := args[0] | ||||||
| 	if !path.IsAbs(pkgPath) { | 	if !path.IsAbs(pkgPath) { | ||||||
| 		if dir, err := os.Getwd(); err != nil { | 		if dir, err := os.Getwd(); err != nil { | ||||||
| 			fmsg.Fatalf("cannot get current directory: %v", err) | 			log.Fatalf("cannot get current directory: %v", err) | ||||||
| 		} else { | 		} else { | ||||||
| 			pkgPath = path.Join(dir, pkgPath) | 			pkgPath = path.Join(dir, pkgPath) | ||||||
| 		} | 		} | ||||||
| @ -54,7 +56,7 @@ func actionInstall(args []string) { | |||||||
| 
 | 
 | ||||||
| 	var workDir string | 	var workDir string | ||||||
| 	if p, err := os.MkdirTemp("", "fpkg.*"); err != nil { | 	if p, err := os.MkdirTemp("", "fpkg.*"); err != nil { | ||||||
| 		fmsg.Fatalf("cannot create temporary directory: %v", err) | 		log.Fatalf("cannot create temporary directory: %v", err) | ||||||
| 	} else { | 	} else { | ||||||
| 		workDir = p | 		workDir = p | ||||||
| 	} | 	} | ||||||
| @ -78,19 +80,17 @@ func actionInstall(args []string) { | |||||||
| 	if s, err := os.Stat(pathSet.metaPath); err != nil { | 	if s, err := os.Stat(pathSet.metaPath); err != nil { | ||||||
| 		if !os.IsNotExist(err) { | 		if !os.IsNotExist(err) { | ||||||
| 			cleanup() | 			cleanup() | ||||||
| 			fmsg.Fatalf("cannot access %q: %v", pathSet.metaPath, err) | 			log.Fatalf("cannot access %q: %v", pathSet.metaPath, err) | ||||||
| 			panic("unreachable") |  | ||||||
| 		} | 		} | ||||||
| 		// did not modify app, clean installation condition met later | 		// did not modify app, clean installation condition met later | ||||||
| 	} else if s.IsDir() { | 	} else if s.IsDir() { | ||||||
| 		cleanup() | 		cleanup() | ||||||
| 		fmsg.Fatalf("metadata path %q is not a file", pathSet.metaPath) | 		log.Fatalf("metadata path %q is not a file", pathSet.metaPath) | ||||||
| 		panic("unreachable") |  | ||||||
| 	} else { | 	} else { | ||||||
| 		app = loadBundleInfo(pathSet.metaPath, cleanup) | 		app = loadBundleInfo(pathSet.metaPath, cleanup) | ||||||
| 		if app.ID != bundle.ID { | 		if app.ID != bundle.ID { | ||||||
| 			cleanup() | 			cleanup() | ||||||
| 			fmsg.Fatalf("app %q claims to have identifier %q", bundle.ID, app.ID) | 			log.Fatalf("app %q claims to have identifier %q", bundle.ID, app.ID) | ||||||
| 		} | 		} | ||||||
| 		// sec: should verify credentials | 		// sec: should verify credentials | ||||||
| 	} | 	} | ||||||
| @ -102,21 +102,20 @@ func actionInstall(args []string) { | |||||||
| 			app.Launcher == bundle.Launcher && | 			app.Launcher == bundle.Launcher && | ||||||
| 			app.ActivationPackage == bundle.ActivationPackage { | 			app.ActivationPackage == bundle.ActivationPackage { | ||||||
| 			cleanup() | 			cleanup() | ||||||
| 			fmsg.Printf("package %q is identical to local application %q", pkgPath, app.ID) | 			log.Printf("package %q is identical to local application %q", pkgPath, app.ID) | ||||||
| 			fmsg.Exit(0) | 			internal.Exit(0) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// AppID determines uid | 		// AppID determines uid | ||||||
| 		if app.AppID != bundle.AppID { | 		if app.AppID != bundle.AppID { | ||||||
| 			cleanup() | 			cleanup() | ||||||
| 			fmsg.Fatalf("package %q app id %d differs from installed %d", pkgPath, bundle.AppID, app.AppID) | 			log.Fatalf("package %q app id %d differs from installed %d", pkgPath, bundle.AppID, app.AppID) | ||||||
| 			panic("unreachable") |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// sec: should compare version string | 		// sec: should compare version string | ||||||
| 		fmsg.VPrintf("installing application %q version %q over local %q", bundle.ID, bundle.Version, app.Version) | 		fmsg.Verbosef("installing application %q version %q over local %q", bundle.ID, bundle.Version, app.Version) | ||||||
| 	} else { | 	} else { | ||||||
| 		fmsg.VPrintf("application %q clean installation", bundle.ID) | 		fmsg.Verbosef("application %q clean installation", bundle.ID) | ||||||
| 		// sec: should install credentials | 		// sec: should install credentials | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @ -174,21 +173,18 @@ func actionInstall(args []string) { | |||||||
| 	// serialise metadata to ensure consistency | 	// serialise metadata to ensure consistency | ||||||
| 	if f, err := os.OpenFile(pathSet.metaPath+"~", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err != nil { | 	if f, err := os.OpenFile(pathSet.metaPath+"~", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err != nil { | ||||||
| 		cleanup() | 		cleanup() | ||||||
| 		fmsg.Fatalf("cannot create metadata file: %v", err) | 		log.Fatalf("cannot create metadata file: %v", err) | ||||||
| 		panic("unreachable") |  | ||||||
| 	} else if err = json.NewEncoder(f).Encode(bundle); err != nil { | 	} else if err = json.NewEncoder(f).Encode(bundle); err != nil { | ||||||
| 		cleanup() | 		cleanup() | ||||||
| 		fmsg.Fatalf("cannot write metadata: %v", err) | 		log.Fatalf("cannot write metadata: %v", err) | ||||||
| 		panic("unreachable") |  | ||||||
| 	} else if err = f.Close(); err != nil { | 	} else if err = f.Close(); err != nil { | ||||||
| 		fmsg.Printf("cannot close metadata file: %v", err) | 		log.Printf("cannot close metadata file: %v", err) | ||||||
| 		// not fatal | 		// not fatal | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if err := os.Rename(pathSet.metaPath+"~", pathSet.metaPath); err != nil { | 	if err := os.Rename(pathSet.metaPath+"~", pathSet.metaPath); err != nil { | ||||||
| 		cleanup() | 		cleanup() | ||||||
| 		fmsg.Fatalf("cannot rename metadata file: %v", err) | 		log.Fatalf("cannot rename metadata file: %v", err) | ||||||
| 		panic("unreachable") |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	cleanup() | 	cleanup() | ||||||
|  | |||||||
| @ -2,8 +2,10 @@ package main | |||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"flag" | 	"flag" | ||||||
|  | 	"log" | ||||||
| 	"os" | 	"os" | ||||||
| 
 | 
 | ||||||
|  | 	"git.gensokyo.uk/security/fortify/internal" | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| @ -11,7 +13,7 @@ const shell = "/run/current-system/sw/bin/bash" | |||||||
| 
 | 
 | ||||||
| func init() { | func init() { | ||||||
| 	if err := os.Setenv("SHELL", shell); err != nil { | 	if err := os.Setenv("SHELL", shell); err != nil { | ||||||
| 		fmsg.Fatalf("cannot set $SHELL: %v", err) | 		log.Fatalf("cannot set $SHELL: %v", err) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -24,14 +26,14 @@ func init() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func main() { | func main() { | ||||||
| 	fmsg.SetPrefix("fpkg") | 	fmsg.Prepare("fpkg") | ||||||
| 
 | 
 | ||||||
| 	flag.Parse() | 	flag.Parse() | ||||||
| 	fmsg.SetVerbose(flagVerbose) | 	fmsg.Store(flagVerbose) | ||||||
| 
 | 
 | ||||||
| 	args := flag.Args() | 	args := flag.Args() | ||||||
| 	if len(args) < 1 { | 	if len(args) < 1 { | ||||||
| 		fmsg.Fatal("invalid argument") | 		log.Fatal("invalid argument") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	switch args[0] { | 	switch args[0] { | ||||||
| @ -41,8 +43,8 @@ func main() { | |||||||
| 		actionStart(args[1:]) | 		actionStart(args[1:]) | ||||||
| 
 | 
 | ||||||
| 	default: | 	default: | ||||||
| 		fmsg.Fatal("invalid argument") | 		log.Fatal("invalid argument") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	fmsg.Exit(0) | 	internal.Exit(0) | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package main | package main | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"log" | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"path" | 	"path" | ||||||
| @ -25,8 +26,8 @@ func init() { | |||||||
| 
 | 
 | ||||||
| func lookPath(file string) string { | func lookPath(file string) string { | ||||||
| 	if p, err := exec.LookPath(file); err != nil { | 	if p, err := exec.LookPath(file); err != nil { | ||||||
| 		fmsg.Fatalf("%s: command not found", file) | 		log.Fatalf("%s: command not found", file) | ||||||
| 		panic("unreachable") | 		return "" | ||||||
| 	} else { | 	} else { | ||||||
| 		return p | 		return p | ||||||
| 	} | 	} | ||||||
| @ -35,15 +36,14 @@ func lookPath(file string) string { | |||||||
| var beforeRunFail = new(atomic.Pointer[func()]) | var beforeRunFail = new(atomic.Pointer[func()]) | ||||||
| 
 | 
 | ||||||
| func mustRun(name string, arg ...string) { | func mustRun(name string, arg ...string) { | ||||||
| 	fmsg.VPrintf("spawning process: %q %q", name, arg) | 	fmsg.Verbosef("spawning process: %q %q", name, arg) | ||||||
| 	cmd := exec.Command(name, arg...) | 	cmd := exec.Command(name, arg...) | ||||||
| 	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr | 	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr | ||||||
| 	if err := cmd.Run(); err != nil { | 	if err := cmd.Run(); err != nil { | ||||||
| 		if f := beforeRunFail.Swap(nil); f != nil { | 		if f := beforeRunFail.Swap(nil); f != nil { | ||||||
| 			(*f)() | 			(*f)() | ||||||
| 		} | 		} | ||||||
| 		fmsg.Fatalf("%s: %v", name, err) | 		log.Fatalf("%s: %v", name, err) | ||||||
| 		panic("unreachable") |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ import ( | |||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"io" | 	"io" | ||||||
|  | 	"log" | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 
 | 
 | ||||||
| @ -25,14 +26,12 @@ func fortifyApp(config *fst.Config, beforeFail func()) { | |||||||
| 	) | 	) | ||||||
| 	if p, ok := internal.Path(Fmain); !ok { | 	if p, ok := internal.Path(Fmain); !ok { | ||||||
| 		beforeFail() | 		beforeFail() | ||||||
| 		fmsg.Fatal("invalid fortify path, this copy of fpkg is not compiled correctly") | 		log.Fatal("invalid fortify path, this copy of fpkg is not compiled correctly") | ||||||
| 		panic("unreachable") |  | ||||||
| 	} else if r, w, err := os.Pipe(); err != nil { | 	} else if r, w, err := os.Pipe(); err != nil { | ||||||
| 		beforeFail() | 		beforeFail() | ||||||
| 		fmsg.Fatalf("cannot pipe: %v", err) | 		log.Fatalf("cannot pipe: %v", err) | ||||||
| 		panic("unreachable") |  | ||||||
| 	} else { | 	} else { | ||||||
| 		if fmsg.Verbose() { | 		if fmsg.Load() { | ||||||
| 			cmd = exec.Command(p, "-v", "app", "3") | 			cmd = exec.Command(p, "-v", "app", "3") | ||||||
| 		} else { | 		} else { | ||||||
| 			cmd = exec.Command(p, "app", "3") | 			cmd = exec.Command(p, "app", "3") | ||||||
| @ -45,26 +44,22 @@ func fortifyApp(config *fst.Config, beforeFail func()) { | |||||||
| 	go func() { | 	go func() { | ||||||
| 		if err := json.NewEncoder(st).Encode(config); err != nil { | 		if err := json.NewEncoder(st).Encode(config); err != nil { | ||||||
| 			beforeFail() | 			beforeFail() | ||||||
| 			fmsg.Fatalf("cannot send configuration: %v", err) | 			log.Fatalf("cannot send configuration: %v", err) | ||||||
| 			panic("unreachable") |  | ||||||
| 		} | 		} | ||||||
| 	}() | 	}() | ||||||
| 
 | 
 | ||||||
| 	if err := cmd.Start(); err != nil { | 	if err := cmd.Start(); err != nil { | ||||||
| 		beforeFail() | 		beforeFail() | ||||||
| 		fmsg.Fatalf("cannot start fortify: %v", err) | 		log.Fatalf("cannot start fortify: %v", err) | ||||||
| 		panic("unreachable") |  | ||||||
| 	} | 	} | ||||||
| 	if err := cmd.Wait(); err != nil { | 	if err := cmd.Wait(); err != nil { | ||||||
| 		var exitError *exec.ExitError | 		var exitError *exec.ExitError | ||||||
| 		if errors.As(err, &exitError) { | 		if errors.As(err, &exitError) { | ||||||
| 			beforeFail() | 			beforeFail() | ||||||
| 			fmsg.Exit(exitError.ExitCode()) | 			internal.Exit(exitError.ExitCode()) | ||||||
| 			panic("unreachable") |  | ||||||
| 		} else { | 		} else { | ||||||
| 			beforeFail() | 			beforeFail() | ||||||
| 			fmsg.Fatalf("cannot wait: %v", err) | 			log.Fatalf("cannot wait: %v", err) | ||||||
| 			panic("unreachable") |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,11 +2,12 @@ package main | |||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"flag" | 	"flag" | ||||||
|  | 	"log" | ||||||
| 	"path" | 	"path" | ||||||
| 
 | 
 | ||||||
| 	"git.gensokyo.uk/security/fortify/fst" | 	"git.gensokyo.uk/security/fortify/fst" | ||||||
| 	"git.gensokyo.uk/security/fortify/helper/bwrap" | 	"git.gensokyo.uk/security/fortify/helper/bwrap" | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | 	"git.gensokyo.uk/security/fortify/internal" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func actionStart(args []string) { | func actionStart(args []string) { | ||||||
| @ -26,7 +27,7 @@ func actionStart(args []string) { | |||||||
| 	args = set.Args() | 	args = set.Args() | ||||||
| 
 | 
 | ||||||
| 	if len(args) < 1 { | 	if len(args) < 1 { | ||||||
| 		fmsg.Fatal("invalid argument") | 		log.Fatal("invalid argument") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/* | 	/* | ||||||
| @ -37,7 +38,7 @@ func actionStart(args []string) { | |||||||
| 	pathSet := pathSetByApp(id) | 	pathSet := pathSetByApp(id) | ||||||
| 	app := loadBundleInfo(pathSet.metaPath, func() {}) | 	app := loadBundleInfo(pathSet.metaPath, func() {}) | ||||||
| 	if app.ID != id { | 	if app.ID != id { | ||||||
| 		fmsg.Fatalf("app %q claims to have identifier %q", id, app.ID) | 		log.Fatalf("app %q claims to have identifier %q", id, app.ID) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/* | 	/* | ||||||
| @ -144,7 +145,7 @@ func actionStart(args []string) { | |||||||
| 	*/ | 	*/ | ||||||
| 
 | 
 | ||||||
| 	fortifyApp(config, func() {}) | 	fortifyApp(config, func() {}) | ||||||
| 	fmsg.Exit(0) | 	internal.Exit(0) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func appendGPUFilesystem(config *fst.Config) { | func appendGPUFilesystem(config *fst.Config) { | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ import ( | |||||||
| 
 | 
 | ||||||
| 	"git.gensokyo.uk/security/fortify/fst" | 	"git.gensokyo.uk/security/fortify/fst" | ||||||
| 	"git.gensokyo.uk/security/fortify/helper/bwrap" | 	"git.gensokyo.uk/security/fortify/helper/bwrap" | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | 	"git.gensokyo.uk/security/fortify/internal" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func withNixDaemon( | func withNixDaemon( | ||||||
| @ -95,7 +95,7 @@ func fortifyAppDropShell(config *fst.Config, dropShell bool, beforeFail func()) | |||||||
| 		config.Command = []string{shell, "-l"} | 		config.Command = []string{shell, "-l"} | ||||||
| 		fortifyApp(config, beforeFail) | 		fortifyApp(config, beforeFail) | ||||||
| 		beforeFail() | 		beforeFail() | ||||||
| 		fmsg.Exit(0) | 		internal.Exit(0) | ||||||
| 	} | 	} | ||||||
| 	fortifyApp(config, beforeFail) | 	fortifyApp(config, beforeFail) | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										15
									
								
								error.go
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								error.go
									
									
									
									
									
								
							| @ -2,6 +2,7 @@ package main | |||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"log" | ||||||
| 
 | 
 | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/app" | 	"git.gensokyo.uk/security/fortify/internal/app" | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||||
| @ -10,13 +11,13 @@ import ( | |||||||
| func logWaitError(err error) { | func logWaitError(err error) { | ||||||
| 	var e *fmsg.BaseError | 	var e *fmsg.BaseError | ||||||
| 	if !fmsg.AsBaseError(err, &e) { | 	if !fmsg.AsBaseError(err, &e) { | ||||||
| 		fmsg.Println("wait failed:", err) | 		log.Println("wait failed:", err) | ||||||
| 	} else { | 	} else { | ||||||
| 		// Wait only returns either *app.ProcessError or *app.StateStoreError wrapped in a *app.BaseError | 		// Wait only returns either *app.ProcessError or *app.StateStoreError wrapped in a *app.BaseError | ||||||
| 		var se *app.StateStoreError | 		var se *app.StateStoreError | ||||||
| 		if !errors.As(err, &se) { | 		if !errors.As(err, &se) { | ||||||
| 			// does not need special handling | 			// does not need special handling | ||||||
| 			fmsg.Print(e.Message()) | 			log.Print(e.Message()) | ||||||
| 		} else { | 		} else { | ||||||
| 			// inner error are either unwrapped store errors | 			// inner error are either unwrapped store errors | ||||||
| 			// or joined errors returned by *appSealTx revert | 			// or joined errors returned by *appSealTx revert | ||||||
| @ -24,7 +25,7 @@ func logWaitError(err error) { | |||||||
| 			var ej app.RevertCompoundError | 			var ej app.RevertCompoundError | ||||||
| 			if !errors.As(se.InnerErr, &ej) { | 			if !errors.As(se.InnerErr, &ej) { | ||||||
| 				// does not require special handling | 				// does not require special handling | ||||||
| 				fmsg.Print(e.Message()) | 				log.Print(e.Message()) | ||||||
| 			} else { | 			} else { | ||||||
| 				errs := ej.Unwrap() | 				errs := ej.Unwrap() | ||||||
| 
 | 
 | ||||||
| @ -33,10 +34,10 @@ func logWaitError(err error) { | |||||||
| 					var eb *fmsg.BaseError | 					var eb *fmsg.BaseError | ||||||
| 					if !errors.As(ei, &eb) { | 					if !errors.As(ei, &eb) { | ||||||
| 						// unreachable | 						// unreachable | ||||||
| 						fmsg.Println("invalid error type returned by revert:", ei) | 						log.Println("invalid error type returned by revert:", ei) | ||||||
| 					} else { | 					} else { | ||||||
| 						// print inner *app.BaseError message | 						// print inner *app.BaseError message | ||||||
| 						fmsg.Print(eb.Message()) | 						log.Print(eb.Message()) | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| @ -48,8 +49,8 @@ func logBaseError(err error, message string) { | |||||||
| 	var e *fmsg.BaseError | 	var e *fmsg.BaseError | ||||||
| 
 | 
 | ||||||
| 	if fmsg.AsBaseError(err, &e) { | 	if fmsg.AsBaseError(err, &e) { | ||||||
| 		fmsg.Print(e.Message()) | 		log.Print(e.Message()) | ||||||
| 	} else { | 	} else { | ||||||
| 		fmsg.Println(message, err) | 		log.Println(message, err) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -53,7 +53,7 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if s.Syscall == nil { | 	if s.Syscall == nil { | ||||||
| 		fmsg.VPrintln("syscall filter not configured, PROCEED WITH CAUTION") | 		fmsg.Verbose("syscall filter not configured, PROCEED WITH CAUTION") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var uid int | 	var uid int | ||||||
| @ -121,11 +121,11 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) { | |||||||
| 						// get parent dir of socket | 						// get parent dir of socket | ||||||
| 						dir := path.Dir(pair[1]) | 						dir := path.Dir(pair[1]) | ||||||
| 						if dir == "." || dir == "/" { | 						if dir == "." || dir == "/" { | ||||||
| 							fmsg.VPrintf("dbus socket %q is in an unusual location", pair[1]) | 							fmsg.Verbosef("dbus socket %q is in an unusual location", pair[1]) | ||||||
| 						} | 						} | ||||||
| 						hidePaths = append(hidePaths, dir) | 						hidePaths = append(hidePaths, dir) | ||||||
| 					} else { | 					} else { | ||||||
| 						fmsg.VPrintf("dbus socket %q is not absolute", pair[1]) | 						fmsg.Verbosef("dbus socket %q is not absolute", pair[1]) | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| @ -169,7 +169,7 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) { | |||||||
| 				return nil, err | 				return nil, err | ||||||
| 			} else if ok { | 			} else if ok { | ||||||
| 				hidePathMatch[i] = true | 				hidePathMatch[i] = true | ||||||
| 				fmsg.VPrintf("hiding paths from %q", c.Src) | 				fmsg.Verbosef("hiding paths from %q", c.Src) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| @ -221,7 +221,7 @@ func evalSymlinks(os linux.System, v *string) error { | |||||||
| 		if !errors.Is(err, fs.ErrNotExist) { | 		if !errors.Is(err, fs.ErrNotExist) { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		fmsg.VPrintf("path %q does not yet exist", *v) | 		fmsg.Verbosef("path %q does not yet exist", *v) | ||||||
| 	} else { | 	} else { | ||||||
| 		*v = p | 		*v = p | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -78,11 +78,22 @@ CopyBind(dest, payload, true) copy from FD to file which is bind-mounted on DEST | |||||||
| (--bind-data FD DEST) | (--bind-data FD DEST) | ||||||
| */ | */ | ||||||
| func (c *Config) CopyBind(dest string, payload []byte, opts ...bool) *Config { | func (c *Config) CopyBind(dest string, payload []byte, opts ...bool) *Config { | ||||||
|  | 	var p *[]byte | ||||||
|  | 	c.CopyBindRef(dest, &p, opts...) | ||||||
|  | 	*p = payload | ||||||
|  | 	return c | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CopyBindRef is the same as CopyBind but writes the address of DataConfig.Data. | ||||||
|  | func (c *Config) CopyBindRef(dest string, payloadRef **[]byte, opts ...bool) *Config { | ||||||
| 	t := DataROBind | 	t := DataROBind | ||||||
| 	if len(opts) > 0 && opts[0] { | 	if len(opts) > 0 && opts[0] { | ||||||
| 		t = DataBind | 		t = DataBind | ||||||
| 	} | 	} | ||||||
| 	c.Filesystem = append(c.Filesystem, &DataConfig{Dest: dest, Data: payload, Type: t}) | 	d := &DataConfig{Dest: dest, Type: t} | ||||||
|  | 	*payloadRef = &d.Data | ||||||
|  | 
 | ||||||
|  | 	c.Filesystem = append(c.Filesystem, d) | ||||||
| 	return c | 	return c | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package bwrap_test | package bwrap_test | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"log" | ||||||
| 	"os" | 	"os" | ||||||
| 	"slices" | 	"slices" | ||||||
| 	"testing" | 	"testing" | ||||||
| @ -8,11 +9,10 @@ import ( | |||||||
| 	"git.gensokyo.uk/security/fortify/helper/bwrap" | 	"git.gensokyo.uk/security/fortify/helper/bwrap" | ||||||
| 	"git.gensokyo.uk/security/fortify/helper/proc" | 	"git.gensokyo.uk/security/fortify/helper/proc" | ||||||
| 	"git.gensokyo.uk/security/fortify/helper/seccomp" | 	"git.gensokyo.uk/security/fortify/helper/seccomp" | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestConfig_Args(t *testing.T) { | func TestConfig_Args(t *testing.T) { | ||||||
| 	seccomp.CPrintln = fmsg.Println | 	seccomp.CPrintln = log.Println | ||||||
| 	t.Cleanup(func() { seccomp.CPrintln = nil }) | 	t.Cleanup(func() { seccomp.CPrintln = nil }) | ||||||
| 
 | 
 | ||||||
| 	testCases := []struct { | 	testCases := []struct { | ||||||
|  | |||||||
| @ -4,12 +4,12 @@ import ( | |||||||
| 	"crypto/sha512" | 	"crypto/sha512" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"io" | 	"io" | ||||||
|  | 	"log" | ||||||
| 	"slices" | 	"slices" | ||||||
| 	"syscall" | 	"syscall" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
| 	"git.gensokyo.uk/security/fortify/helper/seccomp" | 	"git.gensokyo.uk/security/fortify/helper/seccomp" | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestExport(t *testing.T) { | func TestExport(t *testing.T) { | ||||||
| @ -79,7 +79,7 @@ func TestExport(t *testing.T) { | |||||||
| 	buf := make([]byte, 8) | 	buf := make([]byte, 8) | ||||||
| 	for _, tc := range testCases { | 	for _, tc := range testCases { | ||||||
| 		t.Run(tc.name, func(t *testing.T) { | 		t.Run(tc.name, func(t *testing.T) { | ||||||
| 			seccomp.CPrintln = fmsg.Println | 			seccomp.CPrintln = log.Println | ||||||
| 			t.Cleanup(func() { seccomp.CPrintln = nil }) | 			t.Cleanup(func() { seccomp.CPrintln = nil }) | ||||||
| 
 | 
 | ||||||
| 			e := seccomp.New(tc.opts) | 			e := seccomp.New(tc.opts) | ||||||
|  | |||||||
| @ -14,7 +14,7 @@ import ( | |||||||
| 
 | 
 | ||||||
| 	"git.gensokyo.uk/security/fortify/helper/bwrap" | 	"git.gensokyo.uk/security/fortify/helper/bwrap" | ||||||
| 	"git.gensokyo.uk/security/fortify/helper/proc" | 	"git.gensokyo.uk/security/fortify/helper/proc" | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | 	"git.gensokyo.uk/security/fortify/internal" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // InternalChildStub is an internal function but exported because it is cross-package; | // InternalChildStub is an internal function but exported because it is cross-package; | ||||||
| @ -40,7 +40,7 @@ func InternalChildStub() { | |||||||
| 		genericStub(flagRestoreFiles(4, ap, sp)) | 		genericStub(flagRestoreFiles(4, ap, sp)) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	fmsg.Exit(0) | 	internal.Exit(0) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // InternalReplaceExecCommand is an internal function but exported because it is cross-package; | // InternalReplaceExecCommand is an internal function but exported because it is cross-package; | ||||||
|  | |||||||
| @ -5,8 +5,8 @@ import ( | |||||||
| 	"sync" | 	"sync" | ||||||
| 
 | 
 | ||||||
| 	"git.gensokyo.uk/security/fortify/fst" | 	"git.gensokyo.uk/security/fortify/fst" | ||||||
|  | 	"git.gensokyo.uk/security/fortify/internal/app/shim" | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/linux" | 	"git.gensokyo.uk/security/fortify/internal/linux" | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/priv/shim" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type App interface { | type App interface { | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ import ( | |||||||
| 	"os" | 	"os" | ||||||
| 	"path" | 	"path" | ||||||
| 
 | 
 | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | 	"git.gensokyo.uk/security/fortify/internal" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // used by the parent process | // used by the parent process | ||||||
| @ -13,6 +13,6 @@ import ( | |||||||
| func TryArgv0() { | func TryArgv0() { | ||||||
| 	if len(os.Args) > 0 && path.Base(os.Args[0]) == "init" { | 	if len(os.Args) > 0 && path.Base(os.Args[0]) == "init" { | ||||||
| 		Main() | 		Main() | ||||||
| 		fmsg.Exit(0) | 		internal.Exit(0) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @ -2,6 +2,7 @@ package init0 | |||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"log" | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"os/signal" | 	"os/signal" | ||||||
| @ -24,17 +25,15 @@ const ( | |||||||
| func Main() { | func Main() { | ||||||
| 	// sharing stdout with shim | 	// sharing stdout with shim | ||||||
| 	// USE WITH CAUTION | 	// USE WITH CAUTION | ||||||
| 	fmsg.SetPrefix("init") | 	fmsg.Prepare("init") | ||||||
| 
 | 
 | ||||||
| 	// setting this prevents ptrace | 	// setting this prevents ptrace | ||||||
| 	if err := internal.PR_SET_DUMPABLE__SUID_DUMP_DISABLE(); err != nil { | 	if err := internal.PR_SET_DUMPABLE__SUID_DUMP_DISABLE(); err != nil { | ||||||
| 		fmsg.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err) | 		log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err) | ||||||
| 		panic("unreachable") |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if os.Getpid() != 1 { | 	if os.Getpid() != 1 { | ||||||
| 		fmsg.Fatal("this process must run as pid 1") | 		log.Fatal("this process must run as pid 1") | ||||||
| 		panic("unreachable") |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// receive setup payload | 	// receive setup payload | ||||||
| @ -44,30 +43,29 @@ func Main() { | |||||||
| 	) | 	) | ||||||
| 	if f, err := proc.Receive(Env, &payload); err != nil { | 	if f, err := proc.Receive(Env, &payload); err != nil { | ||||||
| 		if errors.Is(err, proc.ErrInvalid) { | 		if errors.Is(err, proc.ErrInvalid) { | ||||||
| 			fmsg.Fatal("invalid config descriptor") | 			log.Fatal("invalid config descriptor") | ||||||
| 		} | 		} | ||||||
| 		if errors.Is(err, proc.ErrNotSet) { | 		if errors.Is(err, proc.ErrNotSet) { | ||||||
| 			fmsg.Fatal("FORTIFY_INIT not set") | 			log.Fatal("FORTIFY_INIT not set") | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		fmsg.Fatalf("cannot decode init setup payload: %v", err) | 		log.Fatalf("cannot decode init setup payload: %v", err) | ||||||
| 		panic("unreachable") |  | ||||||
| 	} else { | 	} else { | ||||||
| 		fmsg.SetVerbose(payload.Verbose) | 		fmsg.Store(payload.Verbose) | ||||||
| 		closeSetup = f | 		closeSetup = f | ||||||
| 
 | 
 | ||||||
| 		// child does not need to see this | 		// child does not need to see this | ||||||
| 		if err = os.Unsetenv(Env); err != nil { | 		if err = os.Unsetenv(Env); err != nil { | ||||||
| 			fmsg.Printf("cannot unset %s: %v", Env, err) | 			log.Printf("cannot unset %s: %v", Env, err) | ||||||
| 			// not fatal | 			// not fatal | ||||||
| 		} else { | 		} else { | ||||||
| 			fmsg.VPrintln("received configuration") | 			fmsg.Verbose("received configuration") | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// die with parent | 	// die with parent | ||||||
| 	if err := internal.PR_SET_PDEATHSIG__SIGKILL(); err != nil { | 	if err := internal.PR_SET_PDEATHSIG__SIGKILL(); err != nil { | ||||||
| 		fmsg.Fatalf("prctl(PR_SET_PDEATHSIG, SIGKILL): %v", err) | 		log.Fatalf("prctl(PR_SET_PDEATHSIG, SIGKILL): %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	cmd := exec.Command(payload.Argv0) | 	cmd := exec.Command(payload.Argv0) | ||||||
| @ -76,13 +74,13 @@ func Main() { | |||||||
| 	cmd.Env = os.Environ() | 	cmd.Env = os.Environ() | ||||||
| 
 | 
 | ||||||
| 	if err := cmd.Start(); err != nil { | 	if err := cmd.Start(); err != nil { | ||||||
| 		fmsg.Fatalf("cannot start %q: %v", payload.Argv0, err) | 		log.Fatalf("cannot start %q: %v", payload.Argv0, err) | ||||||
| 	} | 	} | ||||||
| 	fmsg.Suspend() | 	fmsg.Suspend() | ||||||
| 
 | 
 | ||||||
| 	// close setup pipe as setup is now complete | 	// close setup pipe as setup is now complete | ||||||
| 	if err := closeSetup(); err != nil { | 	if err := closeSetup(); err != nil { | ||||||
| 		fmsg.Println("cannot close setup pipe:", err) | 		log.Println("cannot close setup pipe:", err) | ||||||
| 		// not fatal | 		// not fatal | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @ -119,7 +117,7 @@ func Main() { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		if !errors.Is(err, syscall.ECHILD) { | 		if !errors.Is(err, syscall.ECHILD) { | ||||||
| 			fmsg.Println("unexpected wait4 response:", err) | 			log.Println("unexpected wait4 response:", err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		close(done) | 		close(done) | ||||||
| @ -132,9 +130,12 @@ func Main() { | |||||||
| 	for { | 	for { | ||||||
| 		select { | 		select { | ||||||
| 		case s := <-sig: | 		case s := <-sig: | ||||||
| 			fmsg.VPrintln("received", s.String()) | 			if fmsg.Resume() { | ||||||
| 			fmsg.Resume() // output could still be withheld at this point, so resume is called | 				fmsg.Verbosef("terminating on %s after process start", s.String()) | ||||||
| 			fmsg.Exit(0) | 			} else { | ||||||
|  | 				fmsg.Verbosef("terminating on %s", s.String()) | ||||||
|  | 			} | ||||||
|  | 			internal.Exit(0) | ||||||
| 		case w := <-info: | 		case w := <-info: | ||||||
| 			if w.wpid == cmd.Process.Pid { | 			if w.wpid == cmd.Process.Pid { | ||||||
| 				// initial process exited, output is most likely available again | 				// initial process exited, output is most likely available again | ||||||
| @ -155,10 +156,10 @@ func Main() { | |||||||
| 				}() | 				}() | ||||||
| 			} | 			} | ||||||
| 		case <-done: | 		case <-done: | ||||||
| 			fmsg.Exit(r) | 			internal.Exit(r) | ||||||
| 		case <-timeout: | 		case <-timeout: | ||||||
| 			fmsg.Println("timeout exceeded waiting for lingering processes") | 			log.Println("timeout exceeded waiting for lingering processes") | ||||||
| 			fmsg.Exit(r) | 			internal.Exit(r) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @ -191,7 +191,7 @@ func (a *app) Seal(config *fst.Config) error { | |||||||
| 
 | 
 | ||||||
| 	// map sandbox config to bwrap | 	// map sandbox config to bwrap | ||||||
| 	if config.Confinement.Sandbox == nil { | 	if config.Confinement.Sandbox == nil { | ||||||
| 		fmsg.VPrintln("sandbox configuration not supplied, PROCEED WITH CAUTION") | 		fmsg.Verbose("sandbox configuration not supplied, PROCEED WITH CAUTION") | ||||||
| 
 | 
 | ||||||
| 		// permissive defaults | 		// permissive defaults | ||||||
| 		conf := &fst.SandboxConfig{ | 		conf := &fst.SandboxConfig{ | ||||||
| @ -264,7 +264,7 @@ func (a *app) Seal(config *fst.Config) error { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// verbose log seal information | 	// verbose log seal information | ||||||
| 	fmsg.VPrintf("created application seal for uid %s (%s) groups: %v, command: %s", | 	fmsg.Verbosef("created application seal for uid %s (%s) groups: %v, command: %s", | ||||||
| 		seal.sys.user.us, seal.sys.user.username, config.Confinement.Groups, config.Command) | 		seal.sys.user.us, seal.sys.user.username, config.Confinement.Groups, config.Command) | ||||||
| 
 | 
 | ||||||
| 	// seal app and release lock | 	// seal app and release lock | ||||||
|  | |||||||
| @ -143,7 +143,7 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error { | |||||||
| 	if seal.et.Has(system.EWayland) { | 	if seal.et.Has(system.EWayland) { | ||||||
| 		var socketPath string | 		var socketPath string | ||||||
| 		if name, ok := os.LookupEnv(wl.WaylandDisplay); !ok { | 		if name, ok := os.LookupEnv(wl.WaylandDisplay); !ok { | ||||||
| 			fmsg.VPrintln(wl.WaylandDisplay + " is not set, assuming " + wl.FallbackName) | 			fmsg.Verbose(wl.WaylandDisplay + " is not set, assuming " + wl.FallbackName) | ||||||
| 			socketPath = path.Join(seal.RuntimePath, wl.FallbackName) | 			socketPath = path.Join(seal.RuntimePath, wl.FallbackName) | ||||||
| 		} else if !path.IsAbs(name) { | 		} else if !path.IsAbs(name) { | ||||||
| 			socketPath = path.Join(seal.RuntimePath, name) | 			socketPath = path.Join(seal.RuntimePath, name) | ||||||
| @ -166,7 +166,7 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error { | |||||||
| 			seal.sys.Wayland(outerPath, socketPath, appID, seal.id) | 			seal.sys.Wayland(outerPath, socketPath, appID, seal.id) | ||||||
| 			seal.sys.bwrap.Bind(outerPath, innerPath) | 			seal.sys.bwrap.Bind(outerPath, innerPath) | ||||||
| 		} else { // bind mount wayland socket (insecure) | 		} else { // bind mount wayland socket (insecure) | ||||||
| 			fmsg.VPrintln("direct wayland access, PROCEED WITH CAUTION") | 			fmsg.Verbose("direct wayland access, PROCEED WITH CAUTION") | ||||||
| 			seal.sys.bwrap.Bind(socketPath, innerPath) | 			seal.sys.bwrap.Bind(socketPath, innerPath) | ||||||
| 
 | 
 | ||||||
| 			// ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`) | 			// ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`) | ||||||
| @ -229,7 +229,7 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error { | |||||||
| 		// publish current user's pulse cookie for target user | 		// publish current user's pulse cookie for target user | ||||||
| 		if src, err := discoverPulseCookie(os); err != nil { | 		if src, err := discoverPulseCookie(os); err != nil { | ||||||
| 			// not fatal | 			// not fatal | ||||||
| 			fmsg.VPrintln(strings.TrimSpace(err.(*fmsg.BaseError).Message())) | 			fmsg.Verbose(strings.TrimSpace(err.(*fmsg.BaseError).Message())) | ||||||
| 		} else { | 		} else { | ||||||
| 			dst := path.Join(seal.share, "pulse-cookie") | 			dst := path.Join(seal.share, "pulse-cookie") | ||||||
| 			innerDst := fst.Tmp + "/pulse-cookie" | 			innerDst := fst.Tmp + "/pulse-cookie" | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ package shim | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"log" | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"os/signal" | 	"os/signal" | ||||||
| @ -15,8 +16,8 @@ import ( | |||||||
| 	"git.gensokyo.uk/security/fortify/helper/proc" | 	"git.gensokyo.uk/security/fortify/helper/proc" | ||||||
| 	"git.gensokyo.uk/security/fortify/helper/seccomp" | 	"git.gensokyo.uk/security/fortify/helper/seccomp" | ||||||
| 	"git.gensokyo.uk/security/fortify/internal" | 	"git.gensokyo.uk/security/fortify/internal" | ||||||
|  | 	init0 "git.gensokyo.uk/security/fortify/internal/app/init" | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||||
| 	init0 "git.gensokyo.uk/security/fortify/internal/priv/init" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // everything beyond this point runs as unconstrained target user | // everything beyond this point runs as unconstrained target user | ||||||
| @ -25,12 +26,11 @@ import ( | |||||||
| func Main() { | func Main() { | ||||||
| 	// sharing stdout with fortify | 	// sharing stdout with fortify | ||||||
| 	// USE WITH CAUTION | 	// USE WITH CAUTION | ||||||
| 	fmsg.SetPrefix("shim") | 	fmsg.Prepare("shim") | ||||||
| 
 | 
 | ||||||
| 	// setting this prevents ptrace | 	// setting this prevents ptrace | ||||||
| 	if err := internal.PR_SET_DUMPABLE__SUID_DUMP_DISABLE(); err != nil { | 	if err := internal.PR_SET_DUMPABLE__SUID_DUMP_DISABLE(); err != nil { | ||||||
| 		fmsg.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err) | 		log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err) | ||||||
| 		panic("unreachable") |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// receive setup payload | 	// receive setup payload | ||||||
| @ -40,21 +40,20 @@ func Main() { | |||||||
| 	) | 	) | ||||||
| 	if f, err := proc.Receive(Env, &payload); err != nil { | 	if f, err := proc.Receive(Env, &payload); err != nil { | ||||||
| 		if errors.Is(err, proc.ErrInvalid) { | 		if errors.Is(err, proc.ErrInvalid) { | ||||||
| 			fmsg.Fatal("invalid config descriptor") | 			log.Fatal("invalid config descriptor") | ||||||
| 		} | 		} | ||||||
| 		if errors.Is(err, proc.ErrNotSet) { | 		if errors.Is(err, proc.ErrNotSet) { | ||||||
| 			fmsg.Fatal("FORTIFY_SHIM not set") | 			log.Fatal("FORTIFY_SHIM not set") | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		fmsg.Fatalf("cannot decode shim setup payload: %v", err) | 		log.Fatalf("cannot decode shim setup payload: %v", err) | ||||||
| 		panic("unreachable") |  | ||||||
| 	} else { | 	} else { | ||||||
| 		fmsg.SetVerbose(payload.Verbose) | 		fmsg.Store(payload.Verbose) | ||||||
| 		closeSetup = f | 		closeSetup = f | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if payload.Bwrap == nil { | 	if payload.Bwrap == nil { | ||||||
| 		fmsg.Fatal("bwrap config not supplied") | 		log.Fatal("bwrap config not supplied") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// restore bwrap sync fd | 	// restore bwrap sync fd | ||||||
| @ -65,7 +64,7 @@ func Main() { | |||||||
| 
 | 
 | ||||||
| 	// close setup socket | 	// close setup socket | ||||||
| 	if err := closeSetup(); err != nil { | 	if err := closeSetup(); err != nil { | ||||||
| 		fmsg.Println("cannot close setup pipe:", err) | 		log.Println("cannot close setup pipe:", err) | ||||||
| 		// not fatal | 		// not fatal | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @ -73,15 +72,15 @@ func Main() { | |||||||
| 	if s, err := os.Stat(payload.Home); err != nil { | 	if s, err := os.Stat(payload.Home); err != nil { | ||||||
| 		if os.IsNotExist(err) { | 		if os.IsNotExist(err) { | ||||||
| 			if err = os.Mkdir(payload.Home, 0700); err != nil { | 			if err = os.Mkdir(payload.Home, 0700); err != nil { | ||||||
| 				fmsg.Fatalf("cannot create home directory: %v", err) | 				log.Fatalf("cannot create home directory: %v", err) | ||||||
| 			} | 			} | ||||||
| 		} else { | 		} else { | ||||||
| 			fmsg.Fatalf("cannot access home directory: %v", err) | 			log.Fatalf("cannot access home directory: %v", err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// home directory is created, proceed | 		// home directory is created, proceed | ||||||
| 	} else if !s.IsDir() { | 	} else if !s.IsDir() { | ||||||
| 		fmsg.Fatalf("data path %q is not a directory", payload.Home) | 		log.Fatalf("data path %q is not a directory", payload.Home) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var ic init0.Payload | 	var ic init0.Payload | ||||||
| @ -95,10 +94,10 @@ func Main() { | |||||||
| 		// no argv, look up shell instead | 		// no argv, look up shell instead | ||||||
| 		var ok bool | 		var ok bool | ||||||
| 		if payload.Bwrap.SetEnv == nil { | 		if payload.Bwrap.SetEnv == nil { | ||||||
| 			fmsg.Fatal("no command was specified and environment is unset") | 			log.Fatal("no command was specified and environment is unset") | ||||||
| 		} | 		} | ||||||
| 		if ic.Argv0, ok = payload.Bwrap.SetEnv["SHELL"]; !ok { | 		if ic.Argv0, ok = payload.Bwrap.SetEnv["SHELL"]; !ok { | ||||||
| 			fmsg.Fatal("no command was specified and $SHELL was unset") | 			log.Fatal("no command was specified and $SHELL was unset") | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		ic.Argv = []string{ic.Argv0} | 		ic.Argv = []string{ic.Argv0} | ||||||
| @ -110,20 +109,20 @@ func Main() { | |||||||
| 
 | 
 | ||||||
| 	// serve setup payload | 	// serve setup payload | ||||||
| 	if fd, encoder, err := proc.Setup(&extraFiles); err != nil { | 	if fd, encoder, err := proc.Setup(&extraFiles); err != nil { | ||||||
| 		fmsg.Fatalf("cannot pipe: %v", err) | 		log.Fatalf("cannot pipe: %v", err) | ||||||
| 	} else { | 	} else { | ||||||
| 		conf.SetEnv[init0.Env] = strconv.Itoa(fd) | 		conf.SetEnv[init0.Env] = strconv.Itoa(fd) | ||||||
| 		go func() { | 		go func() { | ||||||
| 			fmsg.VPrintln("transmitting config to init") | 			fmsg.Verbose("transmitting config to init") | ||||||
| 			if err = encoder.Encode(&ic); err != nil { | 			if err = encoder.Encode(&ic); err != nil { | ||||||
| 				fmsg.Fatalf("cannot transmit init config: %v", err) | 				log.Fatalf("cannot transmit init config: %v", err) | ||||||
| 			} | 			} | ||||||
| 		}() | 		}() | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	helper.BubblewrapName = payload.Exec[0] // resolved bwrap path by parent | 	helper.BubblewrapName = payload.Exec[0] // resolved bwrap path by parent | ||||||
| 	if fmsg.Verbose() { | 	if fmsg.Load() { | ||||||
| 		seccomp.CPrintln = fmsg.Println | 		seccomp.CPrintln = log.Println | ||||||
| 	} | 	} | ||||||
| 	if b, err := helper.NewBwrap( | 	if b, err := helper.NewBwrap( | ||||||
| 		conf, path.Join(fst.Tmp, "sbin/init"), | 		conf, path.Join(fst.Tmp, "sbin/init"), | ||||||
| @ -131,7 +130,7 @@ func Main() { | |||||||
| 		extraFiles, | 		extraFiles, | ||||||
| 		syncFd, | 		syncFd, | ||||||
| 	); err != nil { | 	); err != nil { | ||||||
| 		fmsg.Fatalf("malformed sandbox config: %v", err) | 		log.Fatalf("malformed sandbox config: %v", err) | ||||||
| 	} else { | 	} else { | ||||||
| 		b.Stdin(os.Stdin).Stdout(os.Stdout).Stderr(os.Stderr) | 		b.Stdin(os.Stdin).Stdout(os.Stdout).Stderr(os.Stderr) | ||||||
| 		ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) | 		ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) | ||||||
| @ -139,15 +138,15 @@ func Main() { | |||||||
| 
 | 
 | ||||||
| 		// run and pass through exit code | 		// run and pass through exit code | ||||||
| 		if err = b.Start(ctx, false); err != nil { | 		if err = b.Start(ctx, false); err != nil { | ||||||
| 			fmsg.Fatalf("cannot start target process: %v", err) | 			log.Fatalf("cannot start target process: %v", err) | ||||||
| 		} else if err = b.Wait(); err != nil { | 		} else if err = b.Wait(); err != nil { | ||||||
| 			var exitError *exec.ExitError | 			var exitError *exec.ExitError | ||||||
| 			if !errors.As(err, &exitError) { | 			if !errors.As(err, &exitError) { | ||||||
| 				fmsg.Println("wait:", err) | 				log.Printf("wait: %v", err) | ||||||
| 				fmsg.Exit(127) | 				internal.Exit(127) | ||||||
| 				panic("unreachable") | 				panic("unreachable") | ||||||
| 			} | 			} | ||||||
| 			fmsg.Exit(exitError.ExitCode()) | 			internal.Exit(exitError.ExitCode()) | ||||||
| 			panic("unreachable") | 			panic("unreachable") | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @ -54,8 +54,8 @@ func (s *Shim) Start( | |||||||
| 	// prepare user switcher invocation | 	// prepare user switcher invocation | ||||||
| 	var fsu string | 	var fsu string | ||||||
| 	if p, ok := internal.Path(internal.Fsu); !ok { | 	if p, ok := internal.Path(internal.Fsu); !ok { | ||||||
| 		fmsg.Fatal("invalid fsu path, this copy of fortify is not compiled correctly") | 		return nil, fmsg.WrapError(errors.New("bad fsu path"), | ||||||
| 		panic("unreachable") | 			"invalid fsu path, this copy of fortify is not compiled correctly") | ||||||
| 	} else { | 	} else { | ||||||
| 		fsu = p | 		fsu = p | ||||||
| 	} | 	} | ||||||
| @ -75,7 +75,7 @@ func (s *Shim) Start( | |||||||
| 
 | 
 | ||||||
| 	// format fsu supplementary groups | 	// format fsu supplementary groups | ||||||
| 	if len(supp) > 0 { | 	if len(supp) > 0 { | ||||||
| 		fmsg.VPrintf("attaching supplementary group ids %s", supp) | 		fmsg.Verbosef("attaching supplementary group ids %s", supp) | ||||||
| 		s.cmd.Env = append(s.cmd.Env, "FORTIFY_GROUPS="+strings.Join(supp, " ")) | 		s.cmd.Env = append(s.cmd.Env, "FORTIFY_GROUPS="+strings.Join(supp, " ")) | ||||||
| 	} | 	} | ||||||
| 	s.cmd.Stdin, s.cmd.Stdout, s.cmd.Stderr = os.Stdin, os.Stdout, os.Stderr | 	s.cmd.Stdin, s.cmd.Stdout, s.cmd.Stderr = os.Stdin, os.Stdout, os.Stderr | ||||||
| @ -87,7 +87,7 @@ func (s *Shim) Start( | |||||||
| 		s.sync = &fd | 		s.sync = &fd | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	fmsg.VPrintln("starting shim via fsu:", s.cmd) | 	fmsg.Verbose("starting shim via fsu:", s.cmd) | ||||||
| 	// withhold messages to stderr | 	// withhold messages to stderr | ||||||
| 	fmsg.Suspend() | 	fmsg.Suspend() | ||||||
| 	if err := s.cmd.Start(); err != nil { | 	if err := s.cmd.Start(); err != nil { | ||||||
| @ -4,14 +4,15 @@ import ( | |||||||
| 	"context" | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"log" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"git.gensokyo.uk/security/fortify/helper" | 	"git.gensokyo.uk/security/fortify/helper" | ||||||
|  | 	"git.gensokyo.uk/security/fortify/internal/app/shim" | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/priv/shim" |  | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/state" | 	"git.gensokyo.uk/security/fortify/internal/state" | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/system" | 	"git.gensokyo.uk/security/fortify/internal/system" | ||||||
| ) | ) | ||||||
| @ -81,7 +82,7 @@ func (a *app) Run(ctx context.Context, rs *RunState) error { | |||||||
| 			Bwrap: a.seal.sys.bwrap, | 			Bwrap: a.seal.sys.bwrap, | ||||||
| 			Home:  a.seal.sys.user.data, | 			Home:  a.seal.sys.user.data, | ||||||
| 
 | 
 | ||||||
| 			Verbose: fmsg.Verbose(), | 			Verbose: fmsg.Load(), | ||||||
| 		}); err != nil { | 		}); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| @ -119,8 +120,8 @@ func (a *app) Run(ctx context.Context, rs *RunState) error { | |||||||
| 		} else { | 		} else { | ||||||
| 			rs.ExitCode = a.shim.Unwrap().ProcessState.ExitCode() | 			rs.ExitCode = a.shim.Unwrap().ProcessState.ExitCode() | ||||||
| 		} | 		} | ||||||
| 		if fmsg.Verbose() { | 		if fmsg.Load() { | ||||||
| 			fmsg.VPrintf("process %d exited with exit code %d", a.shim.Unwrap().Process.Pid, rs.ExitCode) | 			fmsg.Verbosef("process %d exited with exit code %d", a.shim.Unwrap().Process.Pid, rs.ExitCode) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	// this is reached when a fault makes an already running shim impossible to continue execution | 	// this is reached when a fault makes an already running shim impossible to continue execution | ||||||
| @ -128,11 +129,11 @@ func (a *app) Run(ctx context.Context, rs *RunState) error { | |||||||
| 	// the effects of this is similar to the alternative exit path and ensures shim death | 	// the effects of this is similar to the alternative exit path and ensures shim death | ||||||
| 	case err := <-a.shim.WaitFallback(): | 	case err := <-a.shim.WaitFallback(): | ||||||
| 		rs.ExitCode = 255 | 		rs.ExitCode = 255 | ||||||
| 		fmsg.Printf("cannot terminate shim on faulted setup: %v", err) | 		log.Printf("cannot terminate shim on faulted setup: %v", err) | ||||||
| 
 | 
 | ||||||
| 	// alternative exit path relying on shim behaviour on monitor process exit | 	// alternative exit path relying on shim behaviour on monitor process exit | ||||||
| 	case <-ctx.Done(): | 	case <-ctx.Done(): | ||||||
| 		fmsg.VPrintln("alternative exit path selected") | 		fmsg.Verbose("alternative exit path selected") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// child process exited, resume output | 	// child process exited, resume output | ||||||
| @ -163,10 +164,10 @@ func (a *app) Run(ctx context.Context, rs *RunState) error { | |||||||
| 			} else { | 			} else { | ||||||
| 				if l := len(states); l == 0 { | 				if l := len(states); l == 0 { | ||||||
| 					// cleanup globals as the final launcher | 					// cleanup globals as the final launcher | ||||||
| 					fmsg.VPrintln("no other launchers active, will clean up globals") | 					fmsg.Verbose("no other launchers active, will clean up globals") | ||||||
| 					ec.Set(system.User) | 					ec.Set(system.User) | ||||||
| 				} else { | 				} else { | ||||||
| 					fmsg.VPrintf("found %d active launchers, cleaning up without globals", l) | 					fmsg.Verbosef("found %d active launchers, cleaning up without globals", l) | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				// accumulate capabilities of other launchers | 				// accumulate capabilities of other launchers | ||||||
| @ -174,7 +175,7 @@ func (a *app) Run(ctx context.Context, rs *RunState) error { | |||||||
| 					if s.Config != nil { | 					if s.Config != nil { | ||||||
| 						*rt |= s.Config.Confinement.Enablements | 						*rt |= s.Config.Confinement.Enablements | ||||||
| 					} else { | 					} else { | ||||||
| 						fmsg.Printf("state entry %d does not contain config", i) | 						log.Printf("state entry %d does not contain config", i) | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| @ -184,7 +185,7 @@ func (a *app) Run(ctx context.Context, rs *RunState) error { | |||||||
| 					ec.Set(i) | 					ec.Set(i) | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			if fmsg.Verbose() { | 			if fmsg.Load() { | ||||||
| 				labels := make([]string, 0, system.ELen+1) | 				labels := make([]string, 0, system.ELen+1) | ||||||
| 				for i := system.Enablement(0); i < system.Enablement(system.ELen+2); i++ { | 				for i := system.Enablement(0); i < system.Enablement(system.ELen+2); i++ { | ||||||
| 					if ec.Has(i) { | 					if ec.Has(i) { | ||||||
| @ -192,7 +193,7 @@ func (a *app) Run(ctx context.Context, rs *RunState) error { | |||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 				if len(labels) > 0 { | 				if len(labels) > 0 { | ||||||
| 					fmsg.VPrintln("reverting operations labelled", strings.Join(labels, ", ")) | 					fmsg.Verbose("reverting operations labelled", strings.Join(labels, ", ")) | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package proc | package internal | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"log" | ||||||
| 	"os" | 	"os" | ||||||
| 	"sync" | 	"sync" | ||||||
| 
 | 
 | ||||||
| @ -14,7 +15,8 @@ var ( | |||||||
| 
 | 
 | ||||||
| func copyExecutable() { | func copyExecutable() { | ||||||
| 	if name, err := os.Executable(); err != nil { | 	if name, err := os.Executable(); err != nil { | ||||||
| 		fmsg.Fatalf("cannot read executable path: %v", err) | 		fmsg.BeforeExit() | ||||||
|  | 		log.Fatalf("cannot read executable path: %v", err) | ||||||
| 	} else { | 	} else { | ||||||
| 		executable = name | 		executable = name | ||||||
| 	} | 	} | ||||||
							
								
								
									
										9
									
								
								internal/exit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								internal/exit.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | |||||||
|  | package internal | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"os" | ||||||
|  | 
 | ||||||
|  | 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func Exit(code int) { fmsg.BeforeExit(); os.Exit(code) } | ||||||
| @ -1,98 +0,0 @@ | |||||||
| package fmsg |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"os" |  | ||||||
| 	"sync" |  | ||||||
| 	"sync/atomic" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var ( |  | ||||||
| 	wstate   atomic.Bool |  | ||||||
| 	dropped  atomic.Uint64 |  | ||||||
| 	withhold = make(chan struct{}, 1) |  | ||||||
| 	msgbuf   = make(chan dOp, 64) // these ops are tiny so a large buffer is allocated for withholding output |  | ||||||
| 
 |  | ||||||
| 	dequeueOnce sync.Once |  | ||||||
| 	queueSync   sync.WaitGroup |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func dequeue() { |  | ||||||
| 	go func() { |  | ||||||
| 		for { |  | ||||||
| 			select { |  | ||||||
| 			case op := <-msgbuf: |  | ||||||
| 				op.Do() |  | ||||||
| 				queueSync.Done() |  | ||||||
| 			case <-withhold: |  | ||||||
| 				<-withhold |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	}() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // queue submits ops to msgbuf but drops messages |  | ||||||
| // when the buffer is full and dequeue is withholding |  | ||||||
| func queue(op dOp) { |  | ||||||
| 	queueSync.Add(1) |  | ||||||
| 
 |  | ||||||
| 	select { |  | ||||||
| 	case msgbuf <- op: |  | ||||||
| 	default: |  | ||||||
| 		// send the op anyway if not withholding |  | ||||||
| 		// as dequeue will get to it eventually |  | ||||||
| 		if !wstate.Load() { |  | ||||||
| 			msgbuf <- op |  | ||||||
| 		} else { |  | ||||||
| 			queueSync.Done() |  | ||||||
| 			// increment dropped message count |  | ||||||
| 			dropped.Add(1) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type dOp interface{ Do() } |  | ||||||
| 
 |  | ||||||
| func Exit(code int) { |  | ||||||
| 	Resume() // resume here to avoid deadlock |  | ||||||
| 	queueSync.Wait() |  | ||||||
| 	os.Exit(code) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func Suspend() { |  | ||||||
| 	dequeueOnce.Do(dequeue) |  | ||||||
| 	if wstate.CompareAndSwap(false, true) { |  | ||||||
| 		queueSync.Wait() |  | ||||||
| 		withhold <- struct{}{} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func Resume() { |  | ||||||
| 	dequeueOnce.Do(dequeue) |  | ||||||
| 	if wstate.CompareAndSwap(true, false) { |  | ||||||
| 		withhold <- struct{}{} |  | ||||||
| 		if d := dropped.Swap(0); d != 0 { |  | ||||||
| 			Printf("dropped %d messages during withhold", d) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type dPrint []any |  | ||||||
| 
 |  | ||||||
| func (v dPrint) Do() { |  | ||||||
| 	std.Print(v...) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type dPrintf struct { |  | ||||||
| 	format string |  | ||||||
| 	v      []any |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (d *dPrintf) Do() { |  | ||||||
| 	std.Printf(d.format, d.v...) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type dPrintln []any |  | ||||||
| 
 |  | ||||||
| func (v dPrintln) Do() { |  | ||||||
| 	std.Println(v...) |  | ||||||
| } |  | ||||||
| @ -2,39 +2,85 @@ | |||||||
| package fmsg | package fmsg | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"io" | ||||||
| 	"log" | 	"log" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"sync" | ||||||
|  | 	"sync/atomic" | ||||||
|  | 	"syscall" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var std = log.New(os.Stderr, "fortify: ", 0) | const ( | ||||||
|  | 	bufSize    = 4 * 1024 | ||||||
|  | 	bufSizeMax = 16 * 1024 * 1024 | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| func SetPrefix(prefix string) { | var o = &suspendable{w: os.Stderr} | ||||||
| 	prefix += ": " | 
 | ||||||
| 	std.SetPrefix(prefix) | // Prepare configures the system logger for [Suspend] and [Resume] to take effect. | ||||||
| 	std.SetPrefix(prefix) | func Prepare(prefix string) { log.SetPrefix(prefix + ": "); log.SetFlags(0); log.SetOutput(o) } | ||||||
|  | 
 | ||||||
|  | type suspendable struct { | ||||||
|  | 	w io.Writer | ||||||
|  | 	s atomic.Bool | ||||||
|  | 
 | ||||||
|  | 	buf     bytes.Buffer | ||||||
|  | 	bufOnce sync.Once | ||||||
|  | 	bufMu   sync.Mutex | ||||||
|  | 	dropped int | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func Print(v ...any) { | func (s *suspendable) Write(p []byte) (n int, err error) { | ||||||
| 	dequeueOnce.Do(dequeue) | 	if !s.s.Load() { | ||||||
| 	queue(dPrint(v)) | 		return s.w.Write(p) | ||||||
|  | 	} | ||||||
|  | 	s.bufOnce.Do(func() { s.prepareBuf() }) | ||||||
|  | 
 | ||||||
|  | 	s.bufMu.Lock() | ||||||
|  | 	defer s.bufMu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	if l := len(p); s.buf.Len()+l > bufSizeMax { | ||||||
|  | 		s.dropped += l | ||||||
|  | 		return 0, syscall.ENOMEM | ||||||
|  | 	} | ||||||
|  | 	return s.buf.Write(p) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func Printf(format string, v ...any) { | func (s *suspendable) prepareBuf()   { s.buf.Grow(bufSize) } | ||||||
| 	dequeueOnce.Do(dequeue) | func (s *suspendable) Suspend() bool { return o.s.CompareAndSwap(false, true) } | ||||||
| 	queue(&dPrintf{format, v}) | func (s *suspendable) Resume() (resumed bool, dropped uintptr, n int64, err error) { | ||||||
|  | 	if o.s.CompareAndSwap(true, false) { | ||||||
|  | 		o.bufMu.Lock() | ||||||
|  | 		defer o.bufMu.Unlock() | ||||||
|  | 
 | ||||||
|  | 		resumed = true | ||||||
|  | 		dropped = uintptr(o.dropped) | ||||||
|  | 
 | ||||||
|  | 		o.dropped = 0 | ||||||
|  | 		n, err = io.Copy(s.w, &s.buf) | ||||||
|  | 		s.buf = bytes.Buffer{} | ||||||
|  | 		s.prepareBuf() | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func Println(v ...any) { | func Suspend() bool { return o.Suspend() } | ||||||
| 	dequeueOnce.Do(dequeue) | func Resume() bool { | ||||||
| 	queue(dPrintln(v)) | 	resumed, dropped, _, err := o.Resume() | ||||||
|  | 	if err != nil { | ||||||
|  | 		// probably going to result in an error as well, | ||||||
|  | 		// so this call is as good as unreachable | ||||||
|  | 		log.Printf("cannot dump buffer on resume: %v", err) | ||||||
|  | 	} | ||||||
|  | 	if resumed && dropped > 0 { | ||||||
|  | 		log.Fatalf("dropped %d bytes while output is suspended", dropped) | ||||||
|  | 	} | ||||||
|  | 	return resumed | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func Fatal(v ...any) { | func BeforeExit() { | ||||||
| 	Print(v...) | 	if Resume() { | ||||||
| 	Exit(1) | 		log.Printf("beforeExit reached on suspended output") | ||||||
| 	} | 	} | ||||||
| 
 |  | ||||||
| func Fatalf(format string, v ...any) { |  | ||||||
| 	Printf(format, v...) |  | ||||||
| 	Exit(1) |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,25 +1,23 @@ | |||||||
| package fmsg | package fmsg | ||||||
| 
 | 
 | ||||||
| import "sync/atomic" | import ( | ||||||
|  | 	"log" | ||||||
|  | 	"sync/atomic" | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| var verbose = new(atomic.Bool) | var verbose = new(atomic.Bool) | ||||||
| 
 | 
 | ||||||
| func Verbose() bool { | func Load() bool   { return verbose.Load() } | ||||||
| 	return verbose.Load() | func Store(v bool) { verbose.Store(v) } | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| func SetVerbose(v bool) { | func Verbosef(format string, v ...any) { | ||||||
| 	verbose.Store(v) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func VPrintf(format string, v ...any) { |  | ||||||
| 	if verbose.Load() { | 	if verbose.Load() { | ||||||
| 		Printf(format, v...) | 		log.Printf(format, v...) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func VPrintln(v ...any) { | func Verbose(v ...any) { | ||||||
| 	if verbose.Load() { | 	if verbose.Load() { | ||||||
| 		Println(v...) | 		log.Println(v...) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -54,7 +54,7 @@ type Paths struct { | |||||||
| func CopyPaths(os System, v *Paths) { | func CopyPaths(os System, v *Paths) { | ||||||
| 	v.SharePath = path.Join(os.TempDir(), "fortify."+strconv.Itoa(os.Geteuid())) | 	v.SharePath = path.Join(os.TempDir(), "fortify."+strconv.Itoa(os.Geteuid())) | ||||||
| 
 | 
 | ||||||
| 	fmsg.VPrintf("process share directory at %q", v.SharePath) | 	fmsg.Verbosef("process share directory at %q", v.SharePath) | ||||||
| 
 | 
 | ||||||
| 	if r, ok := os.LookupEnv(xdgRuntimeDir); !ok || r == "" || !path.IsAbs(r) { | 	if r, ok := os.LookupEnv(xdgRuntimeDir); !ok || r == "" || !path.IsAbs(r) { | ||||||
| 		// fall back to path in share since fortify has no hard XDG dependency | 		// fall back to path in share since fortify has no hard XDG dependency | ||||||
| @ -65,5 +65,5 @@ func CopyPaths(os System, v *Paths) { | |||||||
| 		v.RunDirPath = path.Join(v.RuntimePath, "fortify") | 		v.RunDirPath = path.Join(v.RuntimePath, "fortify") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	fmsg.VPrintf("runtime directory at %q", v.RunDirPath) | 	fmsg.Verbosef("runtime directory at %q", v.RunDirPath) | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ package linux | |||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"io/fs" | 	"io/fs" | ||||||
|  | 	"log" | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"os/user" | 	"os/user" | ||||||
| @ -11,7 +12,6 @@ import ( | |||||||
| 	"sync" | 	"sync" | ||||||
| 	"syscall" | 	"syscall" | ||||||
| 
 | 
 | ||||||
| 	"git.gensokyo.uk/security/fortify/helper/proc" |  | ||||||
| 	"git.gensokyo.uk/security/fortify/internal" | 	"git.gensokyo.uk/security/fortify/internal" | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||||
| ) | ) | ||||||
| @ -33,13 +33,13 @@ func (s *Std) Geteuid() int                                 { return os.Geteuid( | |||||||
| func (s *Std) LookupEnv(key string) (string, bool)          { return os.LookupEnv(key) } | func (s *Std) LookupEnv(key string) (string, bool)          { return os.LookupEnv(key) } | ||||||
| func (s *Std) TempDir() string                              { return os.TempDir() } | func (s *Std) TempDir() string                              { return os.TempDir() } | ||||||
| func (s *Std) LookPath(file string) (string, error)         { return exec.LookPath(file) } | func (s *Std) LookPath(file string) (string, error)         { return exec.LookPath(file) } | ||||||
| func (s *Std) MustExecutable() string                       { return proc.MustExecutable() } | func (s *Std) MustExecutable() string                       { return internal.MustExecutable() } | ||||||
| func (s *Std) LookupGroup(name string) (*user.Group, error) { return user.LookupGroup(name) } | func (s *Std) LookupGroup(name string) (*user.Group, error) { return user.LookupGroup(name) } | ||||||
| func (s *Std) ReadDir(name string) ([]os.DirEntry, error)   { return os.ReadDir(name) } | func (s *Std) ReadDir(name string) ([]os.DirEntry, error)   { return os.ReadDir(name) } | ||||||
| func (s *Std) Stat(name string) (fs.FileInfo, error)        { return os.Stat(name) } | func (s *Std) Stat(name string) (fs.FileInfo, error)        { return os.Stat(name) } | ||||||
| func (s *Std) Open(name string) (fs.File, error)            { return os.Open(name) } | func (s *Std) Open(name string) (fs.File, error)            { return os.Open(name) } | ||||||
| func (s *Std) EvalSymlinks(path string) (string, error)     { return filepath.EvalSymlinks(path) } | func (s *Std) EvalSymlinks(path string) (string, error)     { return filepath.EvalSymlinks(path) } | ||||||
| func (s *Std) Exit(code int)                                { fmsg.Exit(code) } | func (s *Std) Exit(code int)                                { internal.Exit(code) } | ||||||
| 
 | 
 | ||||||
| const xdgRuntimeDir = "XDG_RUNTIME_DIR" | const xdgRuntimeDir = "XDG_RUNTIME_DIR" | ||||||
| 
 | 
 | ||||||
| @ -74,8 +74,10 @@ func (s *Std) Uid(aid int) (int, error) { | |||||||
| 
 | 
 | ||||||
| 	u.uid = -1 | 	u.uid = -1 | ||||||
| 	if fsu, ok := internal.Check(internal.Fsu); !ok { | 	if fsu, ok := internal.Check(internal.Fsu); !ok { | ||||||
| 		fmsg.Fatal("invalid fsu path, this copy of fortify is not compiled correctly") | 		fmsg.BeforeExit() | ||||||
| 		panic("unreachable") | 		log.Fatal("invalid fsu path, this copy of fortify is not compiled correctly") | ||||||
|  | 		// unreachable | ||||||
|  | 		return 0, syscall.EBADE | ||||||
| 	} else { | 	} else { | ||||||
| 		cmd := exec.Command(fsu) | 		cmd := exec.Command(fsu) | ||||||
| 		cmd.Path = fsu | 		cmd.Path = fsu | ||||||
|  | |||||||
| @ -85,17 +85,17 @@ func (s *multiStore) List() ([]int, error) { | |||||||
| 	for _, e := range entries { | 	for _, e := range entries { | ||||||
| 		// skip non-directories | 		// skip non-directories | ||||||
| 		if !e.IsDir() { | 		if !e.IsDir() { | ||||||
| 			fmsg.VPrintf("skipped non-directory entry %q", e.Name()) | 			fmsg.Verbosef("skipped non-directory entry %q", e.Name()) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// skip non-numerical names | 		// skip non-numerical names | ||||||
| 		if v, err := strconv.Atoi(e.Name()); err != nil { | 		if v, err := strconv.Atoi(e.Name()); err != nil { | ||||||
| 			fmsg.VPrintf("skipped non-aid entry %q", e.Name()) | 			fmsg.Verbosef("skipped non-aid entry %q", e.Name()) | ||||||
| 			continue | 			continue | ||||||
| 		} else { | 		} else { | ||||||
| 			if v < 0 || v > 9999 { | 			if v < 0 || v > 9999 { | ||||||
| 				fmsg.VPrintf("skipped out of bounds entry %q", e.Name()) | 				fmsg.Verbosef("skipped out of bounds entry %q", e.Name()) | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -36,18 +36,18 @@ func (a *ACL) Type() Enablement { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (a *ACL) apply(sys *I) error { | func (a *ACL) apply(sys *I) error { | ||||||
| 	fmsg.VPrintln("applying ACL", a) | 	fmsg.Verbose("applying ACL", a) | ||||||
| 	return fmsg.WrapErrorSuffix(acl.UpdatePerm(a.path, sys.uid, a.perms...), | 	return fmsg.WrapErrorSuffix(acl.UpdatePerm(a.path, sys.uid, a.perms...), | ||||||
| 		fmt.Sprintf("cannot apply ACL entry to %q:", a.path)) | 		fmt.Sprintf("cannot apply ACL entry to %q:", a.path)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (a *ACL) revert(sys *I, ec *Criteria) error { | func (a *ACL) revert(sys *I, ec *Criteria) error { | ||||||
| 	if ec.hasType(a) { | 	if ec.hasType(a) { | ||||||
| 		fmsg.VPrintln("stripping ACL", a) | 		fmsg.Verbose("stripping ACL", a) | ||||||
| 		return fmsg.WrapErrorSuffix(acl.UpdatePerm(a.path, sys.uid), | 		return fmsg.WrapErrorSuffix(acl.UpdatePerm(a.path, sys.uid), | ||||||
| 			fmt.Sprintf("cannot strip ACL entry from %q:", a.path)) | 			fmt.Sprintf("cannot strip ACL entry from %q:", a.path)) | ||||||
| 	} else { | 	} else { | ||||||
| 		fmsg.VPrintln("skipping ACL", a) | 		fmsg.Verbose("skipping ACL", a) | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ package system | |||||||
| import ( | import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"log" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"sync" | 	"sync" | ||||||
| 
 | 
 | ||||||
| @ -47,12 +48,12 @@ func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath st | |||||||
| 	d.proxy = dbus.New(sessionBus, systemBus) | 	d.proxy = dbus.New(sessionBus, systemBus) | ||||||
| 
 | 
 | ||||||
| 	defer func() { | 	defer func() { | ||||||
| 		if fmsg.Verbose() && d.proxy.Sealed() { | 		if fmsg.Load() && d.proxy.Sealed() { | ||||||
| 			fmsg.VPrintln("sealed session proxy", session.Args(sessionBus)) | 			fmsg.Verbose("sealed session proxy", session.Args(sessionBus)) | ||||||
| 			if system != nil { | 			if system != nil { | ||||||
| 				fmsg.VPrintln("sealed system proxy", system.Args(systemBus)) | 				fmsg.Verbose("sealed system proxy", system.Args(systemBus)) | ||||||
| 			} | 			} | ||||||
| 			fmsg.VPrintln("message bus proxy final args:", d.proxy) | 			fmsg.Verbose("message bus proxy final args:", d.proxy) | ||||||
| 		} | 		} | ||||||
| 	}() | 	}() | ||||||
| 
 | 
 | ||||||
| @ -78,9 +79,9 @@ func (d *DBus) Type() Enablement { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (d *DBus) apply(sys *I) error { | func (d *DBus) apply(sys *I) error { | ||||||
| 	fmsg.VPrintf("session bus proxy on %q for upstream %q", d.proxy.Session()[1], d.proxy.Session()[0]) | 	fmsg.Verbosef("session bus proxy on %q for upstream %q", d.proxy.Session()[1], d.proxy.Session()[0]) | ||||||
| 	if d.system { | 	if d.system { | ||||||
| 		fmsg.VPrintf("system bus proxy on %q for upstream %q", d.proxy.System()[1], d.proxy.System()[0]) | 		fmsg.Verbosef("system bus proxy on %q for upstream %q", d.proxy.System()[1], d.proxy.System()[0]) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// this starts the process and blocks until ready | 	// this starts the process and blocks until ready | ||||||
| @ -89,15 +90,15 @@ func (d *DBus) apply(sys *I) error { | |||||||
| 		return fmsg.WrapErrorSuffix(err, | 		return fmsg.WrapErrorSuffix(err, | ||||||
| 			"cannot start message bus proxy:") | 			"cannot start message bus proxy:") | ||||||
| 	} | 	} | ||||||
| 	fmsg.VPrintln("starting message bus proxy:", d.proxy) | 	fmsg.Verbose("starting message bus proxy:", d.proxy) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (d *DBus) revert(_ *I, _ *Criteria) error { | func (d *DBus) revert(_ *I, _ *Criteria) error { | ||||||
| 	// criteria ignored here since dbus is always process-scoped | 	// criteria ignored here since dbus is always process-scoped | ||||||
| 	fmsg.VPrintln("terminating message bus proxy") | 	fmsg.Verbose("terminating message bus proxy") | ||||||
| 	d.proxy.Close() | 	d.proxy.Close() | ||||||
| 	defer fmsg.VPrintln("message bus proxy exit") | 	defer fmsg.Verbose("message bus proxy exit") | ||||||
| 	return fmsg.WrapErrorSuffix(d.proxy.Wait(), "message bus proxy error:") | 	return fmsg.WrapErrorSuffix(d.proxy.Wait(), "message bus proxy error:") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -144,7 +145,7 @@ func (s *scanToFmsg) write(p []byte, a int) (int, error) { | |||||||
| func (s *scanToFmsg) Dump() { | func (s *scanToFmsg) Dump() { | ||||||
| 	s.mu.RLock() | 	s.mu.RLock() | ||||||
| 	for _, msg := range s.msgbuf { | 	for _, msg := range s.msgbuf { | ||||||
| 		fmsg.Println(msg) | 		log.Println(msg) | ||||||
| 	} | 	} | ||||||
| 	s.mu.RUnlock() | 	s.mu.RUnlock() | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										53
									
								
								internal/system/link.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								internal/system/link.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | |||||||
|  | package system | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 
 | ||||||
|  | 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Link registers an Op that links dst to src. | ||||||
|  | func (sys *I) Link(oldname, newname string) *I { return sys.LinkFileType(Process, oldname, newname) } | ||||||
|  | 
 | ||||||
|  | // LinkFileType registers a file linking Op labelled with type et. | ||||||
|  | func (sys *I) LinkFileType(et Enablement, oldname, newname string) *I { | ||||||
|  | 	sys.lock.Lock() | ||||||
|  | 	defer sys.lock.Unlock() | ||||||
|  | 
 | ||||||
|  | 	sys.ops = append(sys.ops, &Hardlink{et, newname, oldname}) | ||||||
|  | 
 | ||||||
|  | 	return sys | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Hardlink struct { | ||||||
|  | 	et       Enablement | ||||||
|  | 	dst, src string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *Hardlink) Type() Enablement { return l.et } | ||||||
|  | 
 | ||||||
|  | func (l *Hardlink) apply(_ *I) error { | ||||||
|  | 	fmsg.Verbose("linking ", l) | ||||||
|  | 	return fmsg.WrapErrorSuffix(os.Link(l.src, l.dst), | ||||||
|  | 		fmt.Sprintf("cannot link %q:", l.dst)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *Hardlink) revert(_ *I, ec *Criteria) error { | ||||||
|  | 	if ec.hasType(l) { | ||||||
|  | 		fmsg.Verbosef("removing hard link %q", l.dst) | ||||||
|  | 		return fmsg.WrapErrorSuffix(os.Remove(l.dst), | ||||||
|  | 			fmt.Sprintf("cannot remove hard link %q:", l.dst)) | ||||||
|  | 	} else { | ||||||
|  | 		fmsg.Verbosef("skipping hard link %q", l.dst) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *Hardlink) Is(o Op) bool { | ||||||
|  | 	l0, ok := o.(*Hardlink) | ||||||
|  | 	return ok && l0 != nil && *l == *l0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *Hardlink) Path() string   { return l.src } | ||||||
|  | func (l *Hardlink) String() string { return fmt.Sprintf("%q from %q", l.dst, l.src) } | ||||||
| @ -40,7 +40,7 @@ func (m *Mkdir) Type() Enablement { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *Mkdir) apply(_ *I) error { | func (m *Mkdir) apply(_ *I) error { | ||||||
| 	fmsg.VPrintln("ensuring directory", m) | 	fmsg.Verbose("ensuring directory", m) | ||||||
| 
 | 
 | ||||||
| 	// create directory | 	// create directory | ||||||
| 	err := os.Mkdir(m.path, m.perm) | 	err := os.Mkdir(m.path, m.perm) | ||||||
| @ -61,11 +61,11 @@ func (m *Mkdir) revert(_ *I, ec *Criteria) error { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if ec.hasType(m) { | 	if ec.hasType(m) { | ||||||
| 		fmsg.VPrintln("destroying ephemeral directory", m) | 		fmsg.Verbose("destroying ephemeral directory", m) | ||||||
| 		return fmsg.WrapErrorSuffix(os.Remove(m.path), | 		return fmsg.WrapErrorSuffix(os.Remove(m.path), | ||||||
| 			fmt.Sprintf("cannot remove ephemeral directory %q:", m.path)) | 			fmt.Sprintf("cannot remove ephemeral directory %q:", m.path)) | ||||||
| 	} else { | 	} else { | ||||||
| 		fmsg.VPrintln("skipping ephemeral directory", m) | 		fmsg.Verbose("skipping ephemeral directory", m) | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ package system | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"log" | ||||||
| 	"os" | 	"os" | ||||||
| 	"sync" | 	"sync" | ||||||
| 
 | 
 | ||||||
| @ -105,9 +106,9 @@ func (sys *I) Commit(ctx context.Context) error { | |||||||
| 		// sp is set to nil when all ops are applied | 		// sp is set to nil when all ops are applied | ||||||
| 		if sp != nil { | 		if sp != nil { | ||||||
| 			// rollback partial commit | 			// rollback partial commit | ||||||
| 			fmsg.VPrintf("commit faulted after %d ops, rolling back partial commit", len(sp.ops)) | 			fmsg.Verbosef("commit faulted after %d ops, rolling back partial commit", len(sp.ops)) | ||||||
| 			if err := sp.Revert(&Criteria{nil}); err != nil { | 			if err := sp.Revert(&Criteria{nil}); err != nil { | ||||||
| 				fmsg.Println("errors returned reverting partial commit:", err) | 				log.Println("errors returned reverting partial commit:", err) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	}() | 	}() | ||||||
|  | |||||||
| @ -27,24 +27,8 @@ func (sys *I) CopyFileType(et Enablement, dst, src string) *I { | |||||||
| 	return sys | 	return sys | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Link registers an Op that links dst to src. |  | ||||||
| func (sys *I) Link(oldname, newname string) *I { |  | ||||||
| 	return sys.LinkFileType(Process, oldname, newname) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // LinkFileType registers a file linking Op labelled with type et. |  | ||||||
| func (sys *I) LinkFileType(et Enablement, oldname, newname string) *I { |  | ||||||
| 	sys.lock.Lock() |  | ||||||
| 	defer sys.lock.Unlock() |  | ||||||
| 
 |  | ||||||
| 	sys.ops = append(sys.ops, &Tmpfile{et, tmpfileLink, newname, oldname}) |  | ||||||
| 
 |  | ||||||
| 	return sys |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const ( | const ( | ||||||
| 	tmpfileCopy uint8 = iota | 	tmpfileCopy uint8 = iota | ||||||
| 	tmpfileLink |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type Tmpfile struct { | type Tmpfile struct { | ||||||
| @ -60,13 +44,9 @@ func (t *Tmpfile) Type() Enablement { | |||||||
| func (t *Tmpfile) apply(_ *I) error { | func (t *Tmpfile) apply(_ *I) error { | ||||||
| 	switch t.method { | 	switch t.method { | ||||||
| 	case tmpfileCopy: | 	case tmpfileCopy: | ||||||
| 		fmsg.VPrintln("publishing tmpfile", t) | 		fmsg.Verbose("publishing tmpfile", t) | ||||||
| 		return fmsg.WrapErrorSuffix(copyFile(t.dst, t.src), | 		return fmsg.WrapErrorSuffix(copyFile(t.dst, t.src), | ||||||
| 			fmt.Sprintf("cannot copy tmpfile %q:", t.dst)) | 			fmt.Sprintf("cannot copy tmpfile %q:", t.dst)) | ||||||
| 	case tmpfileLink: |  | ||||||
| 		fmsg.VPrintln("linking tmpfile", t) |  | ||||||
| 		return fmsg.WrapErrorSuffix(os.Link(t.src, t.dst), |  | ||||||
| 			fmt.Sprintf("cannot link tmpfile %q:", t.dst)) |  | ||||||
| 	default: | 	default: | ||||||
| 		panic("invalid tmpfile method " + strconv.Itoa(int(t.method))) | 		panic("invalid tmpfile method " + strconv.Itoa(int(t.method))) | ||||||
| 	} | 	} | ||||||
| @ -74,11 +54,11 @@ func (t *Tmpfile) apply(_ *I) error { | |||||||
| 
 | 
 | ||||||
| func (t *Tmpfile) revert(_ *I, ec *Criteria) error { | func (t *Tmpfile) revert(_ *I, ec *Criteria) error { | ||||||
| 	if ec.hasType(t) { | 	if ec.hasType(t) { | ||||||
| 		fmsg.VPrintf("removing tmpfile %q", t.dst) | 		fmsg.Verbosef("removing tmpfile %q", t.dst) | ||||||
| 		return fmsg.WrapErrorSuffix(os.Remove(t.dst), | 		return fmsg.WrapErrorSuffix(os.Remove(t.dst), | ||||||
| 			fmt.Sprintf("cannot remove tmpfile %q:", t.dst)) | 			fmt.Sprintf("cannot remove tmpfile %q:", t.dst)) | ||||||
| 	} else { | 	} else { | ||||||
| 		fmsg.VPrintf("skipping tmpfile %q", t.dst) | 		fmsg.Verbosef("skipping tmpfile %q", t.dst) | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @ -94,8 +74,6 @@ func (t *Tmpfile) String() string { | |||||||
| 	switch t.method { | 	switch t.method { | ||||||
| 	case tmpfileCopy: | 	case tmpfileCopy: | ||||||
| 		return fmt.Sprintf("%q from %q", t.dst, t.src) | 		return fmt.Sprintf("%q from %q", t.dst, t.src) | ||||||
| 	case tmpfileLink: |  | ||||||
| 		return fmt.Sprintf("%q from %q", t.dst, t.src) |  | ||||||
| 	default: | 	default: | ||||||
| 		panic("invalid tmpfile method " + strconv.Itoa(int(t.method))) | 		panic("invalid tmpfile method " + strconv.Itoa(int(t.method))) | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -57,7 +57,7 @@ func TestLink(t *testing.T) { | |||||||
| 			sys := New(150) | 			sys := New(150) | ||||||
| 			sys.Link(tc.src, tc.dst) | 			sys.Link(tc.src, tc.dst) | ||||||
| 			(&tcOp{Process, tc.src}).test(t, sys.ops, []Op{ | 			(&tcOp{Process, tc.src}).test(t, sys.ops, []Op{ | ||||||
| 				&Tmpfile{Process, tmpfileLink, tc.dst, tc.src}, | 				&Hardlink{Process, tc.dst, tc.src}, | ||||||
| 			}, "Link") | 			}, "Link") | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @ -76,7 +76,7 @@ func TestLinkFileType(t *testing.T) { | |||||||
| 			sys := New(150) | 			sys := New(150) | ||||||
| 			sys.LinkFileType(tc.et, tc.path, tc.dst) | 			sys.LinkFileType(tc.et, tc.path, tc.dst) | ||||||
| 			tc.test(t, sys.ops, []Op{ | 			tc.test(t, sys.ops, []Op{ | ||||||
| 				&Tmpfile{tc.et, tmpfileLink, tc.dst, tc.path}, | 				&Hardlink{tc.et, tc.dst, tc.path}, | ||||||
| 			}, "LinkFileType") | 			}, "LinkFileType") | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @ -101,10 +101,6 @@ func TestTmpfile_String(t *testing.T) { | |||||||
| 	}{ | 	}{ | ||||||
| 		{tmpfileCopy, "/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse-cookie", "/home/ophestra/xdg/config/pulse/cookie", | 		{tmpfileCopy, "/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse-cookie", "/home/ophestra/xdg/config/pulse/cookie", | ||||||
| 			`"/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse-cookie" from "/home/ophestra/xdg/config/pulse/cookie"`}, | 			`"/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse-cookie" from "/home/ophestra/xdg/config/pulse/cookie"`}, | ||||||
| 		{tmpfileLink, "/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/wayland", "/run/user/1971/wayland-0", |  | ||||||
| 			`"/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/wayland" from "/run/user/1971/wayland-0"`}, |  | ||||||
| 		{tmpfileLink, "/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse", "/run/user/1971/pulse/native", |  | ||||||
| 			`"/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse" from "/run/user/1971/pulse/native"`}, |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, tc := range testCases { | 	for _, tc := range testCases { | ||||||
|  | |||||||
| @ -45,7 +45,7 @@ func (w Wayland) apply(sys *I) error { | |||||||
| 		return fmsg.WrapErrorSuffix(err, | 		return fmsg.WrapErrorSuffix(err, | ||||||
| 			fmt.Sprintf("cannot attach to wayland on %q:", w.pair[1])) | 			fmt.Sprintf("cannot attach to wayland on %q:", w.pair[1])) | ||||||
| 	} else { | 	} else { | ||||||
| 		fmsg.VPrintf("wayland attached on %q", w.pair[1]) | 		fmsg.Verbosef("wayland attached on %q", w.pair[1]) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if sp, err := w.conn.Bind(w.pair[0], w.appID, w.instanceID); err != nil { | 	if sp, err := w.conn.Bind(w.pair[0], w.appID, w.instanceID); err != nil { | ||||||
| @ -53,7 +53,7 @@ func (w Wayland) apply(sys *I) error { | |||||||
| 			fmt.Sprintf("cannot bind to socket on %q:", w.pair[0])) | 			fmt.Sprintf("cannot bind to socket on %q:", w.pair[0])) | ||||||
| 	} else { | 	} else { | ||||||
| 		sys.sp = sp | 		sys.sp = sp | ||||||
| 		fmsg.VPrintf("wayland listening on %q", w.pair[0]) | 		fmsg.Verbosef("wayland listening on %q", w.pair[0]) | ||||||
| 		return fmsg.WrapErrorSuffix(errors.Join(os.Chmod(w.pair[0], 0), acl.UpdatePerm(w.pair[0], sys.uid, acl.Read, acl.Write, acl.Execute)), | 		return fmsg.WrapErrorSuffix(errors.Join(os.Chmod(w.pair[0], 0), acl.UpdatePerm(w.pair[0], sys.uid, acl.Read, acl.Write, acl.Execute)), | ||||||
| 			fmt.Sprintf("cannot chmod socket on %q:", w.pair[0])) | 			fmt.Sprintf("cannot chmod socket on %q:", w.pair[0])) | ||||||
| 	} | 	} | ||||||
| @ -61,16 +61,16 @@ func (w Wayland) apply(sys *I) error { | |||||||
| 
 | 
 | ||||||
| func (w Wayland) revert(_ *I, ec *Criteria) error { | func (w Wayland) revert(_ *I, ec *Criteria) error { | ||||||
| 	if ec.hasType(w) { | 	if ec.hasType(w) { | ||||||
| 		fmsg.VPrintf("removing wayland socket on %q", w.pair[0]) | 		fmsg.Verbosef("removing wayland socket on %q", w.pair[0]) | ||||||
| 		if err := os.Remove(w.pair[0]); err != nil && !errors.Is(err, os.ErrNotExist) { | 		if err := os.Remove(w.pair[0]); err != nil && !errors.Is(err, os.ErrNotExist) { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		fmsg.VPrintf("detaching from wayland on %q", w.pair[1]) | 		fmsg.Verbosef("detaching from wayland on %q", w.pair[1]) | ||||||
| 		return fmsg.WrapErrorSuffix(w.conn.Close(), | 		return fmsg.WrapErrorSuffix(w.conn.Close(), | ||||||
| 			fmt.Sprintf("cannot detach from wayland on %q:", w.pair[1])) | 			fmt.Sprintf("cannot detach from wayland on %q:", w.pair[1])) | ||||||
| 	} else { | 	} else { | ||||||
| 		fmsg.VPrintf("skipping wayland cleanup on %q", w.pair[0]) | 		fmsg.Verbosef("skipping wayland cleanup on %q", w.pair[0]) | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -24,18 +24,18 @@ func (x XHost) Type() Enablement { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (x XHost) apply(_ *I) error { | func (x XHost) apply(_ *I) error { | ||||||
| 	fmsg.VPrintf("inserting entry %s to X11", x) | 	fmsg.Verbosef("inserting entry %s to X11", x) | ||||||
| 	return fmsg.WrapErrorSuffix(xcb.ChangeHosts(xcb.HostModeInsert, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)), | 	return fmsg.WrapErrorSuffix(xcb.ChangeHosts(xcb.HostModeInsert, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)), | ||||||
| 		fmt.Sprintf("cannot insert entry %s to X11:", x)) | 		fmt.Sprintf("cannot insert entry %s to X11:", x)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (x XHost) revert(_ *I, ec *Criteria) error { | func (x XHost) revert(_ *I, ec *Criteria) error { | ||||||
| 	if ec.hasType(x) { | 	if ec.hasType(x) { | ||||||
| 		fmsg.VPrintf("deleting entry %s from X11", x) | 		fmsg.Verbosef("deleting entry %s from X11", x) | ||||||
| 		return fmsg.WrapErrorSuffix(xcb.ChangeHosts(xcb.HostModeDelete, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)), | 		return fmsg.WrapErrorSuffix(xcb.ChangeHosts(xcb.HostModeDelete, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)), | ||||||
| 			fmt.Sprintf("cannot delete entry %s from X11:", x)) | 			fmt.Sprintf("cannot delete entry %s from X11:", x)) | ||||||
| 	} else { | 	} else { | ||||||
| 		fmsg.VPrintf("skipping entry %s in X11", x) | 		fmsg.Verbosef("skipping entry %s in X11", x) | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										75
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										75
									
								
								main.go
									
									
									
									
									
								
							| @ -5,6 +5,7 @@ import ( | |||||||
| 	_ "embed" | 	_ "embed" | ||||||
| 	"flag" | 	"flag" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"log" | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/signal" | 	"os/signal" | ||||||
| 	"os/user" | 	"os/user" | ||||||
| @ -20,10 +21,10 @@ import ( | |||||||
| 	"git.gensokyo.uk/security/fortify/helper/seccomp" | 	"git.gensokyo.uk/security/fortify/helper/seccomp" | ||||||
| 	"git.gensokyo.uk/security/fortify/internal" | 	"git.gensokyo.uk/security/fortify/internal" | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/app" | 	"git.gensokyo.uk/security/fortify/internal/app" | ||||||
|  | 	init0 "git.gensokyo.uk/security/fortify/internal/app/init" | ||||||
|  | 	"git.gensokyo.uk/security/fortify/internal/app/shim" | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" | 	"git.gensokyo.uk/security/fortify/internal/fmsg" | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/linux" | 	"git.gensokyo.uk/security/fortify/internal/linux" | ||||||
| 	init0 "git.gensokyo.uk/security/fortify/internal/priv/init" |  | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/priv/shim" |  | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/state" | 	"git.gensokyo.uk/security/fortify/internal/state" | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/system" | 	"git.gensokyo.uk/security/fortify/internal/system" | ||||||
| ) | ) | ||||||
| @ -37,6 +38,8 @@ var ( | |||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func init() { | func init() { | ||||||
|  | 	fmsg.Prepare("fortify") | ||||||
|  | 
 | ||||||
| 	flag.BoolVar(&flagVerbose, "v", false, "Verbose output") | 	flag.BoolVar(&flagVerbose, "v", false, "Verbose output") | ||||||
| 	flag.BoolVar(&flagJSON, "json", false, "Format output in JSON when applicable") | 	flag.BoolVar(&flagJSON, "json", false, "Format output in JSON when applicable") | ||||||
| } | } | ||||||
| @ -62,13 +65,12 @@ func main() { | |||||||
| 	init0.TryArgv0() | 	init0.TryArgv0() | ||||||
| 
 | 
 | ||||||
| 	if err := internal.PR_SET_DUMPABLE__SUID_DUMP_DISABLE(); err != nil { | 	if err := internal.PR_SET_DUMPABLE__SUID_DUMP_DISABLE(); err != nil { | ||||||
| 		fmsg.Printf("cannot set SUID_DUMP_DISABLE: %s", err) | 		log.Printf("cannot set SUID_DUMP_DISABLE: %s", err) | ||||||
| 		// not fatal: this program runs as the privileged user | 		// not fatal: this program runs as the privileged user | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if os.Geteuid() == 0 { | 	if os.Geteuid() == 0 { | ||||||
| 		fmsg.Fatal("this program must not run as root") | 		log.Fatal("this program must not run as root") | ||||||
| 		panic("unreachable") |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	flag.CommandLine.Usage = func() { | 	flag.CommandLine.Usage = func() { | ||||||
| @ -96,12 +98,12 @@ func main() { | |||||||
| 		fmt.Println() | 		fmt.Println() | ||||||
| 	} | 	} | ||||||
| 	flag.Parse() | 	flag.Parse() | ||||||
| 	fmsg.SetVerbose(flagVerbose) | 	fmsg.Store(flagVerbose) | ||||||
| 
 | 
 | ||||||
| 	args := flag.Args() | 	args := flag.Args() | ||||||
| 	if len(args) == 0 { | 	if len(args) == 0 { | ||||||
| 		flag.CommandLine.Usage() | 		flag.CommandLine.Usage() | ||||||
| 		fmsg.Exit(0) | 		internal.Exit(0) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	switch args[0] { | 	switch args[0] { | ||||||
| @ -111,16 +113,20 @@ func main() { | |||||||
| 		} else { | 		} else { | ||||||
| 			fmt.Println("impure") | 			fmt.Println("impure") | ||||||
| 		} | 		} | ||||||
| 		fmsg.Exit(0) | 		internal.Exit(0) | ||||||
|  | 
 | ||||||
| 	case "license": // print embedded license | 	case "license": // print embedded license | ||||||
| 		fmt.Println(license) | 		fmt.Println(license) | ||||||
| 		fmsg.Exit(0) | 		internal.Exit(0) | ||||||
|  | 
 | ||||||
| 	case "template": // print full template configuration | 	case "template": // print full template configuration | ||||||
| 		printJSON(os.Stdout, false, fst.Template()) | 		printJSON(os.Stdout, false, fst.Template()) | ||||||
| 		fmsg.Exit(0) | 		internal.Exit(0) | ||||||
|  | 
 | ||||||
| 	case "help": // print help message | 	case "help": // print help message | ||||||
| 		flag.CommandLine.Usage() | 		flag.CommandLine.Usage() | ||||||
| 		fmsg.Exit(0) | 		internal.Exit(0) | ||||||
|  | 
 | ||||||
| 	case "ps": // print all state info | 	case "ps": // print all state info | ||||||
| 		set := flag.NewFlagSet("ps", flag.ExitOnError) | 		set := flag.NewFlagSet("ps", flag.ExitOnError) | ||||||
| 		var short bool | 		var short bool | ||||||
| @ -130,7 +136,8 @@ func main() { | |||||||
| 		_ = set.Parse(args[1:]) | 		_ = set.Parse(args[1:]) | ||||||
| 
 | 
 | ||||||
| 		printPs(os.Stdout, time.Now().UTC(), state.NewMulti(sys.Paths().RunDirPath), short) | 		printPs(os.Stdout, time.Now().UTC(), state.NewMulti(sys.Paths().RunDirPath), short) | ||||||
| 		fmsg.Exit(0) | 		internal.Exit(0) | ||||||
|  | 
 | ||||||
| 	case "show": // pretty-print app info | 	case "show": // pretty-print app info | ||||||
| 		set := flag.NewFlagSet("show", flag.ExitOnError) | 		set := flag.NewFlagSet("show", flag.ExitOnError) | ||||||
| 		var short bool | 		var short bool | ||||||
| @ -142,6 +149,7 @@ func main() { | |||||||
| 		switch len(set.Args()) { | 		switch len(set.Args()) { | ||||||
| 		case 0: // system | 		case 0: // system | ||||||
| 			printShowSystem(os.Stdout, short) | 			printShowSystem(os.Stdout, short) | ||||||
|  | 
 | ||||||
| 		case 1: // instance | 		case 1: // instance | ||||||
| 			name := set.Args()[0] | 			name := set.Args()[0] | ||||||
| 			config, instance := tryShort(name) | 			config, instance := tryShort(name) | ||||||
| @ -149,14 +157,15 @@ func main() { | |||||||
| 				config = tryPath(name) | 				config = tryPath(name) | ||||||
| 			} | 			} | ||||||
| 			printShowInstance(os.Stdout, time.Now().UTC(), instance, config, short) | 			printShowInstance(os.Stdout, time.Now().UTC(), instance, config, short) | ||||||
| 		default: |  | ||||||
| 			fmsg.Fatal("show requires 1 argument") |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		fmsg.Exit(0) | 		default: | ||||||
|  | 			log.Fatal("show requires 1 argument") | ||||||
|  | 		} | ||||||
|  | 		internal.Exit(0) | ||||||
|  | 
 | ||||||
| 	case "app": // launch app from configuration file | 	case "app": // launch app from configuration file | ||||||
| 		if len(args) < 2 { | 		if len(args) < 2 { | ||||||
| 			fmsg.Fatal("app requires at least 1 argument") | 			log.Fatal("app requires at least 1 argument") | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// config extraArgs... | 		// config extraArgs... | ||||||
| @ -166,6 +175,7 @@ func main() { | |||||||
| 		// invoke app | 		// invoke app | ||||||
| 		runApp(config) | 		runApp(config) | ||||||
| 		panic("unreachable") | 		panic("unreachable") | ||||||
|  | 
 | ||||||
| 	case "run": // run app in permissive defaults usage pattern | 	case "run": // run app in permissive defaults usage pattern | ||||||
| 		set := flag.NewFlagSet("run", flag.ExitOnError) | 		set := flag.NewFlagSet("run", flag.ExitOnError) | ||||||
| 
 | 
 | ||||||
| @ -208,8 +218,7 @@ func main() { | |||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if aid < 0 || aid > 9999 { | 		if aid < 0 || aid > 9999 { | ||||||
| 			fmsg.Fatalf("aid %d out of range", aid) | 			log.Fatalf("aid %d out of range", aid) | ||||||
| 			panic("unreachable") |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// resolve home/username from os when flag is unset | 		// resolve home/username from os when flag is unset | ||||||
| @ -219,13 +228,13 @@ func main() { | |||||||
| 			passwdFunc = func() { | 			passwdFunc = func() { | ||||||
| 				var us string | 				var us string | ||||||
| 				if uid, err := sys.Uid(aid); err != nil { | 				if uid, err := sys.Uid(aid); err != nil { | ||||||
| 					fmsg.Fatalf("cannot obtain uid from fsu: %v", err) | 					log.Fatalf("cannot obtain uid from fsu: %v", err) | ||||||
| 				} else { | 				} else { | ||||||
| 					us = strconv.Itoa(uid) | 					us = strconv.Itoa(uid) | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				if u, err := user.LookupId(us); err != nil { | 				if u, err := user.LookupId(us); err != nil { | ||||||
| 					fmsg.VPrintf("cannot look up uid %s", us) | 					fmsg.Verbosef("cannot look up uid %s", us) | ||||||
| 					passwd = &user.User{ | 					passwd = &user.User{ | ||||||
| 						Uid:      us, | 						Uid:      us, | ||||||
| 						Gid:      us, | 						Gid:      us, | ||||||
| @ -267,7 +276,7 @@ func main() { | |||||||
| 				config.Confinement.SessionBus = dbus.NewConfig(fid, true, mpris) | 				config.Confinement.SessionBus = dbus.NewConfig(fid, true, mpris) | ||||||
| 			} else { | 			} else { | ||||||
| 				if c, err := dbus.NewConfigFromFile(dbusConfigSession); err != nil { | 				if c, err := dbus.NewConfigFromFile(dbusConfigSession); err != nil { | ||||||
| 					fmsg.Fatalf("cannot load session bus proxy config from %q: %s", dbusConfigSession, err) | 					log.Fatalf("cannot load session bus proxy config from %q: %s", dbusConfigSession, err) | ||||||
| 				} else { | 				} else { | ||||||
| 					config.Confinement.SessionBus = c | 					config.Confinement.SessionBus = c | ||||||
| 				} | 				} | ||||||
| @ -276,7 +285,7 @@ func main() { | |||||||
| 			// system bus proxy is optional | 			// system bus proxy is optional | ||||||
| 			if dbusConfigSystem != "nil" { | 			if dbusConfigSystem != "nil" { | ||||||
| 				if c, err := dbus.NewConfigFromFile(dbusConfigSystem); err != nil { | 				if c, err := dbus.NewConfigFromFile(dbusConfigSystem); err != nil { | ||||||
| 					fmsg.Fatalf("cannot load system bus proxy config from %q: %s", dbusConfigSystem, err) | 					log.Fatalf("cannot load system bus proxy config from %q: %s", dbusConfigSystem, err) | ||||||
| 				} else { | 				} else { | ||||||
| 					config.Confinement.SystemBus = c | 					config.Confinement.SystemBus = c | ||||||
| 				} | 				} | ||||||
| @ -291,17 +300,18 @@ func main() { | |||||||
| 
 | 
 | ||||||
| 		// invoke app | 		// invoke app | ||||||
| 		runApp(config) | 		runApp(config) | ||||||
|  | 		panic("unreachable") | ||||||
| 
 | 
 | ||||||
| 	// internal commands | 	// internal commands | ||||||
| 	case "shim": | 	case "shim": | ||||||
| 		shim.Main() | 		shim.Main() | ||||||
| 		fmsg.Exit(0) | 		internal.Exit(0) | ||||||
| 	case "init": | 	case "init": | ||||||
| 		init0.Main() | 		init0.Main() | ||||||
| 		fmsg.Exit(0) | 		internal.Exit(0) | ||||||
| 
 | 
 | ||||||
| 	default: | 	default: | ||||||
| 		fmsg.Fatalf("%q is not a valid command", args[0]) | 		log.Fatalf("%q is not a valid command", args[0]) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	panic("unreachable") | 	panic("unreachable") | ||||||
| @ -313,15 +323,15 @@ func runApp(config *fst.Config) { | |||||||
| 		syscall.SIGINT, syscall.SIGTERM) | 		syscall.SIGINT, syscall.SIGTERM) | ||||||
| 	defer stop() // unreachable | 	defer stop() // unreachable | ||||||
| 
 | 
 | ||||||
| 	if fmsg.Verbose() { | 	if fmsg.Load() { | ||||||
| 		seccomp.CPrintln = fmsg.Println | 		seccomp.CPrintln = log.Println | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if a, err := app.New(sys); err != nil { | 	if a, err := app.New(sys); err != nil { | ||||||
| 		fmsg.Fatalf("cannot create app: %s\n", err) | 		log.Fatalf("cannot create app: %s", err) | ||||||
| 	} else if err = a.Seal(config); err != nil { | 	} else if err = a.Seal(config); err != nil { | ||||||
| 		logBaseError(err, "cannot seal app:") | 		logBaseError(err, "cannot seal app:") | ||||||
| 		fmsg.Exit(1) | 		internal.Exit(1) | ||||||
| 	} else if err = a.Run(ctx, rs); err != nil { | 	} else if err = a.Run(ctx, rs); err != nil { | ||||||
| 		if !rs.Start { | 		if !rs.Start { | ||||||
| 			logBaseError(err, "cannot start app:") | 			logBaseError(err, "cannot start app:") | ||||||
| @ -334,8 +344,7 @@ func runApp(config *fst.Config) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if rs.WaitErr != nil { | 	if rs.WaitErr != nil { | ||||||
| 		fmsg.Println("inner wait failed:", rs.WaitErr) | 		log.Println("inner wait failed:", rs.WaitErr) | ||||||
| 	} | 	} | ||||||
| 	fmsg.Exit(rs.ExitCode) | 	internal.Exit(rs.ExitCode) | ||||||
| 	panic("unreachable") |  | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										21
									
								
								parse.go
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								parse.go
									
									
									
									
									
								
							| @ -4,6 +4,7 @@ import ( | |||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"io" | 	"io" | ||||||
|  | 	"log" | ||||||
| 	"os" | 	"os" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| @ -21,11 +22,10 @@ func tryPath(name string) (config *fst.Config) { | |||||||
| 	if name != "-" { | 	if name != "-" { | ||||||
| 		r = tryFd(name) | 		r = tryFd(name) | ||||||
| 		if r == nil { | 		if r == nil { | ||||||
| 			fmsg.VPrintln("load configuration from file") | 			fmsg.Verbose("load configuration from file") | ||||||
| 
 | 
 | ||||||
| 			if f, err := os.Open(name); err != nil { | 			if f, err := os.Open(name); err != nil { | ||||||
| 				fmsg.Fatalf("cannot access configuration file %q: %s", name, err) | 				log.Fatalf("cannot access configuration file %q: %s", name, err) | ||||||
| 				panic("unreachable") |  | ||||||
| 			} else { | 			} else { | ||||||
| 				// finalizer closes f | 				// finalizer closes f | ||||||
| 				r = f | 				r = f | ||||||
| @ -33,7 +33,7 @@ func tryPath(name string) (config *fst.Config) { | |||||||
| 		} else { | 		} else { | ||||||
| 			defer func() { | 			defer func() { | ||||||
| 				if err := r.(io.ReadCloser).Close(); err != nil { | 				if err := r.(io.ReadCloser).Close(); err != nil { | ||||||
| 					fmsg.Printf("cannot close config fd: %v", err) | 					log.Printf("cannot close config fd: %v", err) | ||||||
| 				} | 				} | ||||||
| 			}() | 			}() | ||||||
| 		} | 		} | ||||||
| @ -42,8 +42,7 @@ func tryPath(name string) (config *fst.Config) { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if err := json.NewDecoder(r).Decode(&config); err != nil { | 	if err := json.NewDecoder(r).Decode(&config); err != nil { | ||||||
| 		fmsg.Fatalf("cannot load configuration: %v", err) | 		log.Fatalf("cannot load configuration: %v", err) | ||||||
| 		panic("unreachable") |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return | 	return | ||||||
| @ -51,7 +50,7 @@ func tryPath(name string) (config *fst.Config) { | |||||||
| 
 | 
 | ||||||
| func tryFd(name string) io.ReadCloser { | func tryFd(name string) io.ReadCloser { | ||||||
| 	if v, err := strconv.Atoi(name); err != nil { | 	if v, err := strconv.Atoi(name); err != nil { | ||||||
| 		fmsg.VPrintf("name cannot be interpreted as int64: %v", err) | 		fmsg.Verbosef("name cannot be interpreted as int64: %v", err) | ||||||
| 		return nil | 		return nil | ||||||
| 	} else { | 	} else { | ||||||
| 		fd := uintptr(v) | 		fd := uintptr(v) | ||||||
| @ -59,7 +58,7 @@ func tryFd(name string) io.ReadCloser { | |||||||
| 			if errors.Is(errno, syscall.EBADF) { | 			if errors.Is(errno, syscall.EBADF) { | ||||||
| 				return nil | 				return nil | ||||||
| 			} | 			} | ||||||
| 			fmsg.Fatalf("cannot get fd %d: %v", fd, errno) | 			log.Fatalf("cannot get fd %d: %v", fd, errno) | ||||||
| 		} | 		} | ||||||
| 		return os.NewFile(fd, strconv.Itoa(v)) | 		return os.NewFile(fd, strconv.Itoa(v)) | ||||||
| 	} | 	} | ||||||
| @ -83,11 +82,11 @@ func tryShort(name string) (config *fst.Config, instance *state.State) { | |||||||
| 
 | 
 | ||||||
| 	// try to match from state store | 	// try to match from state store | ||||||
| 	if likePrefix && len(name) >= 8 { | 	if likePrefix && len(name) >= 8 { | ||||||
| 		fmsg.VPrintln("argument looks like prefix") | 		fmsg.Verbose("argument looks like prefix") | ||||||
| 
 | 
 | ||||||
| 		s := state.NewMulti(sys.Paths().RunDirPath) | 		s := state.NewMulti(sys.Paths().RunDirPath) | ||||||
| 		if entries, err := state.Join(s); err != nil { | 		if entries, err := state.Join(s); err != nil { | ||||||
| 			fmsg.Printf("cannot join store: %v", err) | 			log.Printf("cannot join store: %v", err) | ||||||
| 			// drop to fetch from file | 			// drop to fetch from file | ||||||
| 		} else { | 		} else { | ||||||
| 			for id := range entries { | 			for id := range entries { | ||||||
| @ -99,7 +98,7 @@ func tryShort(name string) (config *fst.Config, instance *state.State) { | |||||||
| 					break | 					break | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				fmsg.VPrintf("instance %s skipped", v) | 				fmsg.Verbosef("instance %s skipped", v) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | |||||||
							
								
								
									
										30
									
								
								print.go
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								print.go
									
									
									
									
									
								
							| @ -4,6 +4,7 @@ import ( | |||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
|  | 	"log" | ||||||
| 	"slices" | 	"slices" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| @ -12,7 +13,6 @@ import ( | |||||||
| 
 | 
 | ||||||
| 	"git.gensokyo.uk/security/fortify/dbus" | 	"git.gensokyo.uk/security/fortify/dbus" | ||||||
| 	"git.gensokyo.uk/security/fortify/fst" | 	"git.gensokyo.uk/security/fortify/fst" | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/fmsg" |  | ||||||
| 	"git.gensokyo.uk/security/fortify/internal/state" | 	"git.gensokyo.uk/security/fortify/internal/state" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| @ -24,7 +24,7 @@ func printShowSystem(output io.Writer, short bool) { | |||||||
| 
 | 
 | ||||||
| 	// get fid by querying uid of aid 0 | 	// get fid by querying uid of aid 0 | ||||||
| 	if uid, err := sys.Uid(0); err != nil { | 	if uid, err := sys.Uid(0); err != nil { | ||||||
| 		fmsg.Fatalf("cannot obtain uid from fsu: %v", err) | 		log.Fatalf("cannot obtain uid from fsu: %v", err) | ||||||
| 	} else { | 	} else { | ||||||
| 		info.User = (uid / 10000) - 100 | 		info.User = (uid / 10000) - 100 | ||||||
| 	} | 	} | ||||||
| @ -190,12 +190,12 @@ func printShowInstance( | |||||||
| func printPs(output io.Writer, now time.Time, s state.Store, short bool) { | func printPs(output io.Writer, now time.Time, s state.Store, short bool) { | ||||||
| 	var entries state.Entries | 	var entries state.Entries | ||||||
| 	if e, err := state.Join(s); err != nil { | 	if e, err := state.Join(s); err != nil { | ||||||
| 		fmsg.Fatalf("cannot join store: %v", err) | 		log.Fatalf("cannot join store: %v", err) | ||||||
| 	} else { | 	} else { | ||||||
| 		entries = e | 		entries = e | ||||||
| 	} | 	} | ||||||
| 	if err := s.Close(); err != nil { | 	if err := s.Close(); err != nil { | ||||||
| 		fmsg.Printf("cannot close store: %v", err) | 		log.Printf("cannot close store: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !short && flagJSON { | 	if !short && flagJSON { | ||||||
| @ -212,13 +212,13 @@ func printPs(output io.Writer, now time.Time, s state.Store, short bool) { | |||||||
| 	for id, instance := range entries { | 	for id, instance := range entries { | ||||||
| 		// gracefully skip nil states | 		// gracefully skip nil states | ||||||
| 		if instance == nil { | 		if instance == nil { | ||||||
| 			fmsg.Printf("got invalid state entry %s", id.String()) | 			log.Printf("got invalid state entry %s", id.String()) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// gracefully skip inconsistent states | 		// gracefully skip inconsistent states | ||||||
| 		if id != instance.ID { | 		if id != instance.ID { | ||||||
| 			fmsg.Printf("possible store corruption: entry %s has id %s", | 			log.Printf("possible store corruption: entry %s has id %s", | ||||||
| 				id.String(), instance.ID.String()) | 				id.String(), instance.ID.String()) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| @ -273,8 +273,7 @@ func printJSON(output io.Writer, short bool, v any) { | |||||||
| 		encoder.SetIndent("", "  ") | 		encoder.SetIndent("", "  ") | ||||||
| 	} | 	} | ||||||
| 	if err := encoder.Encode(v); err != nil { | 	if err := encoder.Encode(v); err != nil { | ||||||
| 		fmsg.Fatalf("cannot serialise: %v", err) | 		log.Fatalf("cannot serialise: %v", err) | ||||||
| 		panic("unreachable") |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -284,31 +283,26 @@ type tp struct{ *tabwriter.Writer } | |||||||
| 
 | 
 | ||||||
| func (p *tp) Printf(format string, a ...any) { | func (p *tp) Printf(format string, a ...any) { | ||||||
| 	if _, err := fmt.Fprintf(p, format, a...); err != nil { | 	if _, err := fmt.Fprintf(p, format, a...); err != nil { | ||||||
| 		fmsg.Fatalf("cannot write to tabwriter: %v", err) | 		log.Fatalf("cannot write to tabwriter: %v", err) | ||||||
| 		panic("unreachable") |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| func (p *tp) Println(a ...any) { | func (p *tp) Println(a ...any) { | ||||||
| 	if _, err := fmt.Fprintln(p, a...); err != nil { | 	if _, err := fmt.Fprintln(p, a...); err != nil { | ||||||
| 		fmsg.Fatalf("cannot write to tabwriter: %v", err) | 		log.Fatalf("cannot write to tabwriter: %v", err) | ||||||
| 		panic("unreachable") |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| func (p *tp) MustFlush() { | func (p *tp) MustFlush() { | ||||||
| 	if err := p.Writer.Flush(); err != nil { | 	if err := p.Writer.Flush(); err != nil { | ||||||
| 		fmsg.Fatalf("cannot flush tabwriter: %v", err) | 		log.Fatalf("cannot flush tabwriter: %v", err) | ||||||
| 		panic("unreachable") |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| func mustPrint(output io.Writer, a ...any) { | func mustPrint(output io.Writer, a ...any) { | ||||||
| 	if _, err := fmt.Fprint(output, a...); err != nil { | 	if _, err := fmt.Fprint(output, a...); err != nil { | ||||||
| 		fmsg.Fatalf("cannot print: %v", err) | 		log.Fatalf("cannot print: %v", err) | ||||||
| 		panic("unreachable") |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| func mustPrintln(output io.Writer, a ...any) { | func mustPrintln(output io.Writer, a ...any) { | ||||||
| 	if _, err := fmt.Fprintln(output, a...); err != nil { | 	if _, err := fmt.Fprintln(output, a...); err != nil { | ||||||
| 		fmsg.Fatalf("cannot print: %v", err) | 		log.Fatalf("cannot print: %v", err) | ||||||
| 		panic("unreachable") |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user