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