diff --git a/cmd/fpkg/bundle.go b/cmd/fpkg/bundle.go index 5c9db72..5692d5c 100644 --- a/cmd/fpkg/bundle.go +++ b/cmd/fpkg/bundle.go @@ -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() - fmsg.Fatalf("cannot open bundle: %v", err) + log.Fatalf("cannot open bundle: %v", err) } else if err = json.NewDecoder(f).Decode(&bundle); err != nil { beforeFail() - fmsg.Fatalf("cannot parse bundle metadata: %v", err) + log.Fatalf("cannot parse bundle metadata: %v", err) } 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 } if bundle.ID == "" { beforeFail() - fmsg.Fatal("application identifier must not be empty") + log.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 { - fmsg.Printf("cannot get hostname: %v", err) + log.Printf("cannot get hostname: %v", err) return "fortify-" + name } else { return h + "-" + name diff --git a/cmd/fpkg/install.go b/cmd/fpkg/install.go index 7c06461..d1643b4 100644 --- a/cmd/fpkg/install.go +++ b/cmd/fpkg/install.go @@ -3,10 +3,12 @@ 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" ) @@ -25,12 +27,12 @@ func actionInstall(args []string) { args = set.Args() if len(args) != 1 { - fmsg.Fatal("invalid argument") + log.Fatal("invalid argument") } pkgPath := args[0] if !path.IsAbs(pkgPath) { if dir, err := os.Getwd(); err != nil { - fmsg.Fatalf("cannot get current directory: %v", err) + log.Fatalf("cannot get current directory: %v", err) } else { pkgPath = path.Join(dir, pkgPath) } @@ -54,7 +56,7 @@ func actionInstall(args []string) { var workDir string 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 { workDir = p } @@ -78,19 +80,17 @@ func actionInstall(args []string) { if s, err := os.Stat(pathSet.metaPath); err != nil { if !os.IsNotExist(err) { cleanup() - fmsg.Fatalf("cannot access %q: %v", pathSet.metaPath, err) - panic("unreachable") + log.Fatalf("cannot access %q: %v", pathSet.metaPath, err) } // did not modify app, clean installation condition met later } else if s.IsDir() { cleanup() - fmsg.Fatalf("metadata path %q is not a file", pathSet.metaPath) - panic("unreachable") + log.Fatalf("metadata path %q is not a file", pathSet.metaPath) } else { app = loadBundleInfo(pathSet.metaPath, cleanup) if app.ID != bundle.ID { 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 } @@ -102,21 +102,20 @@ func actionInstall(args []string) { app.Launcher == bundle.Launcher && app.ActivationPackage == bundle.ActivationPackage { cleanup() - fmsg.Printf("package %q is identical to local application %q", pkgPath, app.ID) - fmsg.Exit(0) + log.Printf("package %q is identical to local application %q", pkgPath, app.ID) + internal.Exit(0) } // AppID determines uid if app.AppID != bundle.AppID { cleanup() - fmsg.Fatalf("package %q app id %d differs from installed %d", pkgPath, bundle.AppID, app.AppID) - panic("unreachable") + log.Fatalf("package %q app id %d differs from installed %d", pkgPath, bundle.AppID, app.AppID) } // 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 { - fmsg.VPrintf("application %q clean installation", bundle.ID) + fmsg.Verbosef("application %q clean installation", bundle.ID) // sec: should install credentials } @@ -174,21 +173,18 @@ 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() - fmsg.Fatalf("cannot create metadata file: %v", err) - panic("unreachable") + log.Fatalf("cannot create metadata file: %v", err) } else if err = json.NewEncoder(f).Encode(bundle); err != nil { cleanup() - fmsg.Fatalf("cannot write metadata: %v", err) - panic("unreachable") + log.Fatalf("cannot write metadata: %v", err) } 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 } if err := os.Rename(pathSet.metaPath+"~", pathSet.metaPath); err != nil { cleanup() - fmsg.Fatalf("cannot rename metadata file: %v", err) - panic("unreachable") + log.Fatalf("cannot rename metadata file: %v", err) } cleanup() diff --git a/cmd/fpkg/main.go b/cmd/fpkg/main.go index f348cb5..e95a36c 100644 --- a/cmd/fpkg/main.go +++ b/cmd/fpkg/main.go @@ -2,8 +2,10 @@ package main import ( "flag" + "log" "os" + "git.gensokyo.uk/security/fortify/internal" "git.gensokyo.uk/security/fortify/internal/fmsg" ) @@ -11,7 +13,7 @@ const shell = "/run/current-system/sw/bin/bash" func init() { 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() { - fmsg.SetPrefix("fpkg") + fmsg.Prepare("fpkg") flag.Parse() - fmsg.SetVerbose(flagVerbose) + fmsg.Store(flagVerbose) args := flag.Args() if len(args) < 1 { - fmsg.Fatal("invalid argument") + log.Fatal("invalid argument") } switch args[0] { @@ -41,8 +43,8 @@ func main() { actionStart(args[1:]) default: - fmsg.Fatal("invalid argument") + log.Fatal("invalid argument") } - fmsg.Exit(0) + internal.Exit(0) } diff --git a/cmd/fpkg/paths.go b/cmd/fpkg/paths.go index cf36c09..231388e 100644 --- a/cmd/fpkg/paths.go +++ b/cmd/fpkg/paths.go @@ -1,6 +1,7 @@ package main import ( + "log" "os" "os/exec" "path" @@ -25,8 +26,8 @@ func init() { func lookPath(file string) string { if p, err := exec.LookPath(file); err != nil { - fmsg.Fatalf("%s: command not found", file) - panic("unreachable") + log.Fatalf("%s: command not found", file) + return "" } else { return p } @@ -35,15 +36,14 @@ func lookPath(file string) string { var beforeRunFail = new(atomic.Pointer[func()]) 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.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)() } - fmsg.Fatalf("%s: %v", name, err) - panic("unreachable") + log.Fatalf("%s: %v", name, err) } } diff --git a/cmd/fpkg/proc.go b/cmd/fpkg/proc.go index 7fa6df5..4413496 100644 --- a/cmd/fpkg/proc.go +++ b/cmd/fpkg/proc.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "io" + "log" "os" "os/exec" @@ -25,14 +26,12 @@ func fortifyApp(config *fst.Config, beforeFail func()) { ) if p, ok := internal.Path(Fmain); !ok { beforeFail() - fmsg.Fatal("invalid fortify path, this copy of fpkg is not compiled correctly") - panic("unreachable") + log.Fatal("invalid fortify path, this copy of fpkg is not compiled correctly") } else if r, w, err := os.Pipe(); err != nil { beforeFail() - fmsg.Fatalf("cannot pipe: %v", err) - panic("unreachable") + log.Fatalf("cannot pipe: %v", err) } else { - if fmsg.Verbose() { + if fmsg.Load() { cmd = exec.Command(p, "-v", "app", "3") } else { cmd = exec.Command(p, "app", "3") @@ -45,26 +44,22 @@ func fortifyApp(config *fst.Config, beforeFail func()) { go func() { if err := json.NewEncoder(st).Encode(config); err != nil { beforeFail() - fmsg.Fatalf("cannot send configuration: %v", err) - panic("unreachable") + log.Fatalf("cannot send configuration: %v", err) } }() if err := cmd.Start(); err != nil { beforeFail() - fmsg.Fatalf("cannot start fortify: %v", err) - panic("unreachable") + log.Fatalf("cannot start fortify: %v", err) } if err := cmd.Wait(); err != nil { var exitError *exec.ExitError if errors.As(err, &exitError) { beforeFail() - fmsg.Exit(exitError.ExitCode()) - panic("unreachable") + internal.Exit(exitError.ExitCode()) } else { beforeFail() - fmsg.Fatalf("cannot wait: %v", err) - panic("unreachable") + log.Fatalf("cannot wait: %v", err) } } } diff --git a/cmd/fpkg/start.go b/cmd/fpkg/start.go index 4a88211..71e0028 100644 --- a/cmd/fpkg/start.go +++ b/cmd/fpkg/start.go @@ -2,11 +2,12 @@ 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/fmsg" + "git.gensokyo.uk/security/fortify/internal" ) func actionStart(args []string) { @@ -26,7 +27,7 @@ func actionStart(args []string) { args = set.Args() if len(args) < 1 { - fmsg.Fatal("invalid argument") + log.Fatal("invalid argument") } /* @@ -37,7 +38,7 @@ func actionStart(args []string) { pathSet := pathSetByApp(id) app := loadBundleInfo(pathSet.metaPath, func() {}) 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() {}) - fmsg.Exit(0) + internal.Exit(0) } func appendGPUFilesystem(config *fst.Config) { diff --git a/cmd/fpkg/with.go b/cmd/fpkg/with.go index 03718dd..6f5b225 100644 --- a/cmd/fpkg/with.go +++ b/cmd/fpkg/with.go @@ -6,7 +6,7 @@ import ( "git.gensokyo.uk/security/fortify/fst" "git.gensokyo.uk/security/fortify/helper/bwrap" - "git.gensokyo.uk/security/fortify/internal/fmsg" + "git.gensokyo.uk/security/fortify/internal" ) func withNixDaemon( @@ -95,7 +95,7 @@ func fortifyAppDropShell(config *fst.Config, dropShell bool, beforeFail func()) config.Command = []string{shell, "-l"} fortifyApp(config, beforeFail) beforeFail() - fmsg.Exit(0) + internal.Exit(0) } fortifyApp(config, beforeFail) } diff --git a/error.go b/error.go index 7e55874..b3d1933 100644 --- a/error.go +++ b/error.go @@ -2,6 +2,7 @@ package main import ( "errors" + "log" "git.gensokyo.uk/security/fortify/internal/app" "git.gensokyo.uk/security/fortify/internal/fmsg" @@ -10,13 +11,13 @@ import ( func logWaitError(err error) { var e *fmsg.BaseError if !fmsg.AsBaseError(err, &e) { - fmsg.Println("wait failed:", err) + log.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 - fmsg.Print(e.Message()) + log.Print(e.Message()) } else { // inner error are either unwrapped store errors // or joined errors returned by *appSealTx revert @@ -24,7 +25,7 @@ func logWaitError(err error) { var ej app.RevertCompoundError if !errors.As(se.InnerErr, &ej) { // does not require special handling - fmsg.Print(e.Message()) + log.Print(e.Message()) } else { errs := ej.Unwrap() @@ -33,10 +34,10 @@ func logWaitError(err error) { var eb *fmsg.BaseError if !errors.As(ei, &eb) { // unreachable - fmsg.Println("invalid error type returned by revert:", ei) + log.Println("invalid error type returned by revert:", ei) } else { // 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 if fmsg.AsBaseError(err, &e) { - fmsg.Print(e.Message()) + log.Print(e.Message()) } else { - fmsg.Println(message, err) + log.Println(message, err) } } diff --git a/fst/sandbox.go b/fst/sandbox.go index 0c3918a..a6b1eb5 100644 --- a/fst/sandbox.go +++ b/fst/sandbox.go @@ -53,7 +53,7 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) { } 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 @@ -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.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) } 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 } else if ok { 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) { return err } - fmsg.VPrintf("path %q does not yet exist", *v) + fmsg.Verbosef("path %q does not yet exist", *v) } else { *v = p } diff --git a/helper/bwrap/config_test.go b/helper/bwrap/config_test.go index ca6e963..d48f4f4 100644 --- a/helper/bwrap/config_test.go +++ b/helper/bwrap/config_test.go @@ -1,6 +1,7 @@ package bwrap_test import ( + "log" "os" "slices" "testing" @@ -8,11 +9,10 @@ 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 = fmsg.Println + seccomp.CPrintln = log.Println t.Cleanup(func() { seccomp.CPrintln = nil }) testCases := []struct { diff --git a/helper/seccomp/export_test.go b/helper/seccomp/export_test.go index 8e21fe1..40f81c7 100644 --- a/helper/seccomp/export_test.go +++ b/helper/seccomp/export_test.go @@ -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 = fmsg.Println + seccomp.CPrintln = log.Println t.Cleanup(func() { seccomp.CPrintln = nil }) e := seccomp.New(tc.opts) diff --git a/helper/stub.go b/helper/stub.go index 5891302..12daa9f 100644 --- a/helper/stub.go +++ b/helper/stub.go @@ -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/fmsg" + "git.gensokyo.uk/security/fortify/internal" ) // InternalChildStub is an internal function but exported because it is cross-package; @@ -40,7 +40,7 @@ func InternalChildStub() { genericStub(flagRestoreFiles(4, ap, sp)) } - fmsg.Exit(0) + internal.Exit(0) } // InternalReplaceExecCommand is an internal function but exported because it is cross-package; diff --git a/internal/app/init/early.go b/internal/app/init/early.go index cc1c2f7..ac2fedb 100644 --- a/internal/app/init/early.go +++ b/internal/app/init/early.go @@ -4,7 +4,7 @@ import ( "os" "path" - "git.gensokyo.uk/security/fortify/internal/fmsg" + "git.gensokyo.uk/security/fortify/internal" ) // used by the parent process @@ -13,6 +13,6 @@ import ( func TryArgv0() { if len(os.Args) > 0 && path.Base(os.Args[0]) == "init" { Main() - fmsg.Exit(0) + internal.Exit(0) } } diff --git a/internal/app/init/main.go b/internal/app/init/main.go index 4c69312..bf9ecba 100644 --- a/internal/app/init/main.go +++ b/internal/app/init/main.go @@ -2,6 +2,7 @@ package init0 import ( "errors" + "log" "os" "os/exec" "os/signal" @@ -24,17 +25,15 @@ const ( func Main() { // sharing stdout with shim // USE WITH CAUTION - fmsg.SetPrefix("init") + fmsg.Prepare("init") // setting this prevents ptrace if err := internal.PR_SET_DUMPABLE__SUID_DUMP_DISABLE(); err != nil { - fmsg.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err) - panic("unreachable") + log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err) } if os.Getpid() != 1 { - fmsg.Fatal("this process must run as pid 1") - panic("unreachable") + log.Fatal("this process must run as pid 1") } // receive setup payload @@ -44,30 +43,29 @@ func Main() { ) if f, err := proc.Receive(Env, &payload); err != nil { if errors.Is(err, proc.ErrInvalid) { - fmsg.Fatal("invalid config descriptor") + log.Fatal("invalid config descriptor") } 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) - panic("unreachable") + log.Fatalf("cannot decode init setup payload: %v", err) } else { - fmsg.SetVerbose(payload.Verbose) + fmsg.Store(payload.Verbose) closeSetup = f // child does not need to see this 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 } else { - fmsg.VPrintln("received configuration") + fmsg.Verbose("received configuration") } } // die with parent 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) @@ -76,13 +74,13 @@ func Main() { cmd.Env = os.Environ() 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() // close setup pipe as setup is now complete if err := closeSetup(); err != nil { - fmsg.Println("cannot close setup pipe:", err) + log.Println("cannot close setup pipe:", err) // not fatal } @@ -119,7 +117,7 @@ func Main() { } } if !errors.Is(err, syscall.ECHILD) { - fmsg.Println("unexpected wait4 response:", err) + log.Println("unexpected wait4 response:", err) } close(done) @@ -132,9 +130,12 @@ func Main() { for { select { case s := <-sig: - fmsg.VPrintln("received", s.String()) - fmsg.Resume() // output could still be withheld at this point, so resume is called - fmsg.Exit(0) + if fmsg.Resume() { + fmsg.Verbosef("terminating on %s after process start", s.String()) + } else { + fmsg.Verbosef("terminating on %s", s.String()) + } + internal.Exit(0) case w := <-info: if w.wpid == cmd.Process.Pid { // initial process exited, output is most likely available again @@ -155,10 +156,10 @@ func Main() { }() } case <-done: - fmsg.Exit(r) + internal.Exit(r) case <-timeout: - fmsg.Println("timeout exceeded waiting for lingering processes") - fmsg.Exit(r) + log.Println("timeout exceeded waiting for lingering processes") + internal.Exit(r) } } } diff --git a/internal/app/seal.go b/internal/app/seal.go index c78add9..fb3b3ce 100644 --- a/internal/app/seal.go +++ b/internal/app/seal.go @@ -191,7 +191,7 @@ func (a *app) Seal(config *fst.Config) error { // map sandbox config to bwrap 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 conf := &fst.SandboxConfig{ @@ -264,7 +264,7 @@ func (a *app) Seal(config *fst.Config) error { } // 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 app and release lock diff --git a/internal/app/share.go b/internal/app/share.go index 9e7d0f1..c7f0c7a 100644 --- a/internal/app/share.go +++ b/internal/app/share.go @@ -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.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) } 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.VPrintln("direct wayland access, PROCEED WITH CAUTION") + fmsg.Verbose("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.VPrintln(strings.TrimSpace(err.(*fmsg.BaseError).Message())) + fmsg.Verbose(strings.TrimSpace(err.(*fmsg.BaseError).Message())) } else { dst := path.Join(seal.share, "pulse-cookie") innerDst := fst.Tmp + "/pulse-cookie" diff --git a/internal/app/shim/main.go b/internal/app/shim/main.go index 39b7d4f..7616945 100644 --- a/internal/app/shim/main.go +++ b/internal/app/shim/main.go @@ -3,6 +3,7 @@ package shim import ( "context" "errors" + "log" "os" "os/exec" "os/signal" @@ -25,12 +26,11 @@ import ( func Main() { // sharing stdout with fortify // USE WITH CAUTION - fmsg.SetPrefix("shim") + fmsg.Prepare("shim") // setting this prevents ptrace if err := internal.PR_SET_DUMPABLE__SUID_DUMP_DISABLE(); err != nil { - fmsg.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err) - panic("unreachable") + log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err) } // receive setup payload @@ -40,21 +40,20 @@ func Main() { ) if f, err := proc.Receive(Env, &payload); err != nil { if errors.Is(err, proc.ErrInvalid) { - fmsg.Fatal("invalid config descriptor") + log.Fatal("invalid config descriptor") } 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) - panic("unreachable") + log.Fatalf("cannot decode shim setup payload: %v", err) } else { - fmsg.SetVerbose(payload.Verbose) + fmsg.Store(payload.Verbose) closeSetup = f } if payload.Bwrap == nil { - fmsg.Fatal("bwrap config not supplied") + log.Fatal("bwrap config not supplied") } // restore bwrap sync fd @@ -65,7 +64,7 @@ func Main() { // close setup socket if err := closeSetup(); err != nil { - fmsg.Println("cannot close setup pipe:", err) + log.Println("cannot close setup pipe:", err) // not fatal } @@ -73,15 +72,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 { - fmsg.Fatalf("cannot create home directory: %v", err) + log.Fatalf("cannot create home directory: %v", err) } } else { - fmsg.Fatalf("cannot access home directory: %v", err) + log.Fatalf("cannot access home directory: %v", err) } // home directory is created, proceed } else if !s.IsDir() { - fmsg.Fatalf("data path %q is not a directory", payload.Home) + log.Fatalf("data path %q is not a directory", payload.Home) } var ic init0.Payload @@ -95,10 +94,10 @@ func Main() { // no argv, look up shell instead var ok bool 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 { - 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} @@ -110,20 +109,20 @@ func Main() { // serve setup payload if fd, encoder, err := proc.Setup(&extraFiles); err != nil { - fmsg.Fatalf("cannot pipe: %v", err) + log.Fatalf("cannot pipe: %v", err) } else { conf.SetEnv[init0.Env] = strconv.Itoa(fd) go func() { - fmsg.VPrintln("transmitting config to init") + fmsg.Verbose("transmitting config to init") 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 - if fmsg.Verbose() { - seccomp.CPrintln = fmsg.Println + if fmsg.Load() { + seccomp.CPrintln = log.Println } if b, err := helper.NewBwrap( conf, path.Join(fst.Tmp, "sbin/init"), @@ -131,7 +130,7 @@ func Main() { extraFiles, syncFd, ); err != nil { - fmsg.Fatalf("malformed sandbox config: %v", err) + log.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) @@ -139,15 +138,15 @@ func Main() { // run and pass through exit code 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 { var exitError *exec.ExitError if !errors.As(err, &exitError) { - fmsg.Println("wait:", err) - fmsg.Exit(127) + log.Printf("wait: %v", err) + internal.Exit(127) panic("unreachable") } - fmsg.Exit(exitError.ExitCode()) + internal.Exit(exitError.ExitCode()) panic("unreachable") } } diff --git a/internal/app/shim/manager.go b/internal/app/shim/manager.go index 106e486..c460563 100644 --- a/internal/app/shim/manager.go +++ b/internal/app/shim/manager.go @@ -4,6 +4,7 @@ import ( "context" "encoding/gob" "errors" + "log" "os" "os/exec" "strconv" @@ -54,8 +55,7 @@ func (s *Shim) Start( // prepare user switcher invocation var fsu string if p, ok := internal.Path(internal.Fsu); !ok { - fmsg.Fatal("invalid fsu path, this copy of fortify is not compiled correctly") - panic("unreachable") + log.Fatal("invalid fsu path, this copy of fortify is not compiled correctly") } else { fsu = p } @@ -75,7 +75,7 @@ func (s *Shim) Start( // format fsu supplementary groups 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.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.VPrintln("starting shim via fsu:", s.cmd) + fmsg.Verbose("starting shim via fsu:", s.cmd) // withhold messages to stderr fmsg.Suspend() if err := s.cmd.Start(); err != nil { diff --git a/internal/app/start.go b/internal/app/start.go index 171e707..467735e 100644 --- a/internal/app/start.go +++ b/internal/app/start.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "log" "os/exec" "path/filepath" "strings" @@ -81,7 +82,7 @@ func (a *app) Run(ctx context.Context, rs *RunState) error { Bwrap: a.seal.sys.bwrap, Home: a.seal.sys.user.data, - Verbose: fmsg.Verbose(), + Verbose: fmsg.Load(), }); err != nil { return err } @@ -119,8 +120,8 @@ func (a *app) Run(ctx context.Context, rs *RunState) error { } else { rs.ExitCode = a.shim.Unwrap().ProcessState.ExitCode() } - if fmsg.Verbose() { - fmsg.VPrintf("process %d exited with exit code %d", a.shim.Unwrap().Process.Pid, rs.ExitCode) + if fmsg.Load() { + 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 @@ -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 case err := <-a.shim.WaitFallback(): 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 case <-ctx.Done(): - fmsg.VPrintln("alternative exit path selected") + fmsg.Verbose("alternative exit path selected") } // child process exited, resume output @@ -163,10 +164,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.VPrintln("no other launchers active, will clean up globals") + fmsg.Verbose("no other launchers active, will clean up globals") ec.Set(system.User) } 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 @@ -174,7 +175,7 @@ func (a *app) Run(ctx context.Context, rs *RunState) error { if s.Config != nil { *rt |= s.Config.Confinement.Enablements } 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) } } - if fmsg.Verbose() { + if fmsg.Load() { labels := make([]string, 0, system.ELen+1) for i := system.Enablement(0); i < system.Enablement(system.ELen+2); i++ { if ec.Has(i) { @@ -192,7 +193,7 @@ func (a *app) Run(ctx context.Context, rs *RunState) error { } } if len(labels) > 0 { - fmsg.VPrintln("reverting operations labelled", strings.Join(labels, ", ")) + fmsg.Verbose("reverting operations labelled", strings.Join(labels, ", ")) } } diff --git a/helper/proc/self.go b/internal/executable.go similarity index 70% rename from helper/proc/self.go rename to internal/executable.go index 6dc92eb..86ffab9 100644 --- a/helper/proc/self.go +++ b/internal/executable.go @@ -1,10 +1,9 @@ -package proc +package internal import ( + "log" "os" "sync" - - "git.gensokyo.uk/security/fortify/internal/fmsg" ) var ( @@ -14,7 +13,7 @@ var ( func copyExecutable() { if name, err := os.Executable(); err != nil { - fmsg.Fatalf("cannot read executable path: %v", err) + log.Fatalf("cannot read executable path: %v", err) } else { executable = name } diff --git a/internal/exit.go b/internal/exit.go new file mode 100644 index 0000000..6781943 --- /dev/null +++ b/internal/exit.go @@ -0,0 +1,9 @@ +package internal + +import ( + "os" + + "git.gensokyo.uk/security/fortify/internal/fmsg" +) + +func Exit(code int) { fmsg.BeforeExit(); os.Exit(code) } diff --git a/internal/fmsg/defer.go b/internal/fmsg/defer.go deleted file mode 100644 index 677f5ce..0000000 --- a/internal/fmsg/defer.go +++ /dev/null @@ -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...) -} diff --git a/internal/fmsg/fmsg.go b/internal/fmsg/fmsg.go index d402720..4853838 100644 --- a/internal/fmsg/fmsg.go +++ b/internal/fmsg/fmsg.go @@ -2,39 +2,85 @@ package fmsg import ( + "bytes" + "io" "log" "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) { - prefix += ": " - std.SetPrefix(prefix) - std.SetPrefix(prefix) +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 Print(v ...any) { - dequeueOnce.Do(dequeue) - queue(dPrint(v)) +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 Printf(format string, v ...any) { - dequeueOnce.Do(dequeue) - queue(&dPrintf{format, 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 Println(v ...any) { - dequeueOnce.Do(dequeue) - queue(dPrintln(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 Fatal(v ...any) { - Print(v...) - Exit(1) -} - -func Fatalf(format string, v ...any) { - Printf(format, v...) - Exit(1) +func BeforeExit() { + if Resume() { + log.Printf("beforeExit reached on suspended output") + } } diff --git a/internal/fmsg/verbose.go b/internal/fmsg/verbose.go index 72a92a6..a8e4c81 100644 --- a/internal/fmsg/verbose.go +++ b/internal/fmsg/verbose.go @@ -1,25 +1,23 @@ package fmsg -import "sync/atomic" +import ( + "log" + "sync/atomic" +) var verbose = new(atomic.Bool) -func Verbose() bool { - return verbose.Load() -} +func Load() bool { return verbose.Load() } +func Store(v bool) { verbose.Store(v) } -func SetVerbose(v bool) { - verbose.Store(v) -} - -func VPrintf(format string, v ...any) { +func Verbosef(format string, v ...any) { if verbose.Load() { - Printf(format, v...) + log.Printf(format, v...) } } -func VPrintln(v ...any) { +func Verbose(v ...any) { if verbose.Load() { - Println(v...) + log.Println(v...) } } diff --git a/internal/linux/interface.go b/internal/linux/interface.go index 6812936..cf25ecb 100644 --- a/internal/linux/interface.go +++ b/internal/linux/interface.go @@ -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.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) { // 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.VPrintf("runtime directory at %q", v.RunDirPath) + fmsg.Verbosef("runtime directory at %q", v.RunDirPath) } diff --git a/internal/linux/std.go b/internal/linux/std.go index 1434f1b..4850cf9 100644 --- a/internal/linux/std.go +++ b/internal/linux/std.go @@ -3,6 +3,7 @@ package linux import ( "errors" "io/fs" + "log" "os" "os/exec" "os/user" @@ -11,9 +12,7 @@ import ( "sync" "syscall" - "git.gensokyo.uk/security/fortify/helper/proc" "git.gensokyo.uk/security/fortify/internal" - "git.gensokyo.uk/security/fortify/internal/fmsg" ) // Std implements System using the standard library. @@ -33,13 +32,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 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) 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) { fmsg.Exit(code) } +func (s *Std) Exit(code int) { internal.Exit(code) } const xdgRuntimeDir = "XDG_RUNTIME_DIR" @@ -74,8 +73,9 @@ func (s *Std) Uid(aid int) (int, error) { u.uid = -1 if fsu, ok := internal.Check(internal.Fsu); !ok { - fmsg.Fatal("invalid fsu path, this copy of fortify is not compiled correctly") - panic("unreachable") + log.Fatal("invalid fsu path, this copy of fortify is not compiled correctly") + // unreachable + return 0, syscall.EBADE } else { cmd := exec.Command(fsu) cmd.Path = fsu diff --git a/internal/state/multi.go b/internal/state/multi.go index bb3764f..ee43ccf 100644 --- a/internal/state/multi.go +++ b/internal/state/multi.go @@ -85,17 +85,17 @@ func (s *multiStore) List() ([]int, error) { for _, e := range entries { // skip non-directories if !e.IsDir() { - fmsg.VPrintf("skipped non-directory entry %q", e.Name()) + fmsg.Verbosef("skipped non-directory entry %q", e.Name()) continue } // skip non-numerical names 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 } else { 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 } diff --git a/internal/system/acl.go b/internal/system/acl.go index 935bee4..e6e6e37 100644 --- a/internal/system/acl.go +++ b/internal/system/acl.go @@ -36,18 +36,18 @@ func (a *ACL) Type() Enablement { } 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...), fmt.Sprintf("cannot apply ACL entry to %q:", a.path)) } func (a *ACL) revert(sys *I, ec *Criteria) error { if ec.hasType(a) { - fmsg.VPrintln("stripping ACL", a) + fmsg.Verbose("stripping ACL", a) return fmsg.WrapErrorSuffix(acl.UpdatePerm(a.path, sys.uid), fmt.Sprintf("cannot strip ACL entry from %q:", a.path)) } else { - fmsg.VPrintln("skipping ACL", a) + fmsg.Verbose("skipping ACL", a) return nil } } diff --git a/internal/system/dbus.go b/internal/system/dbus.go index fd995bb..f52adf1 100644 --- a/internal/system/dbus.go +++ b/internal/system/dbus.go @@ -3,6 +3,7 @@ package system import ( "bytes" "errors" + "log" "strings" "sync" @@ -47,12 +48,12 @@ func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath st d.proxy = dbus.New(sessionBus, systemBus) defer func() { - if fmsg.Verbose() && d.proxy.Sealed() { - fmsg.VPrintln("sealed session proxy", session.Args(sessionBus)) + if fmsg.Load() && d.proxy.Sealed() { + fmsg.Verbose("sealed session proxy", session.Args(sessionBus)) 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 { - 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 { - 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 @@ -89,15 +90,15 @@ func (d *DBus) apply(sys *I) error { return fmsg.WrapErrorSuffix(err, "cannot start message bus proxy:") } - fmsg.VPrintln("starting message bus proxy:", d.proxy) + fmsg.Verbose("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.VPrintln("terminating message bus proxy") + fmsg.Verbose("terminating message bus proxy") 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:") } @@ -144,7 +145,7 @@ func (s *scanToFmsg) write(p []byte, a int) (int, error) { func (s *scanToFmsg) Dump() { s.mu.RLock() for _, msg := range s.msgbuf { - fmsg.Println(msg) + log.Println(msg) } s.mu.RUnlock() } diff --git a/internal/system/link.go b/internal/system/link.go index cbde3a1..7e690c7 100644 --- a/internal/system/link.go +++ b/internal/system/link.go @@ -28,18 +28,18 @@ type Hardlink struct { func (l *Hardlink) Type() Enablement { return l.et } func (l *Hardlink) apply(_ *I) error { - fmsg.VPrintln("linking ", l) + 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.VPrintf("removing hard link %q", l.dst) + 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.VPrintf("skipping hard link %q", l.dst) + fmsg.Verbosef("skipping hard link %q", l.dst) return nil } } diff --git a/internal/system/mkdir.go b/internal/system/mkdir.go index b634da4..855b339 100644 --- a/internal/system/mkdir.go +++ b/internal/system/mkdir.go @@ -40,7 +40,7 @@ func (m *Mkdir) Type() Enablement { } func (m *Mkdir) apply(_ *I) error { - fmsg.VPrintln("ensuring directory", m) + fmsg.Verbose("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.VPrintln("destroying ephemeral directory", m) + fmsg.Verbose("destroying ephemeral directory", m) return fmsg.WrapErrorSuffix(os.Remove(m.path), fmt.Sprintf("cannot remove ephemeral directory %q:", m.path)) } else { - fmsg.VPrintln("skipping ephemeral directory", m) + fmsg.Verbose("skipping ephemeral directory", m) return nil } } diff --git a/internal/system/op.go b/internal/system/op.go index 8a98fa7..08b5794 100644 --- a/internal/system/op.go +++ b/internal/system/op.go @@ -3,6 +3,7 @@ package system import ( "context" "errors" + "log" "os" "sync" @@ -105,9 +106,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.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 { - fmsg.Println("errors returned reverting partial commit:", err) + log.Println("errors returned reverting partial commit:", err) } } }() diff --git a/internal/system/tmpfiles.go b/internal/system/tmpfiles.go index 7dff7c2..8208e03 100644 --- a/internal/system/tmpfiles.go +++ b/internal/system/tmpfiles.go @@ -44,7 +44,7 @@ func (t *Tmpfile) Type() Enablement { func (t *Tmpfile) apply(_ *I) error { switch t.method { case tmpfileCopy: - fmsg.VPrintln("publishing tmpfile", t) + fmsg.Verbose("publishing tmpfile", t) return fmsg.WrapErrorSuffix(copyFile(t.dst, t.src), fmt.Sprintf("cannot copy tmpfile %q:", t.dst)) default: @@ -54,11 +54,11 @@ func (t *Tmpfile) apply(_ *I) error { func (t *Tmpfile) revert(_ *I, ec *Criteria) error { 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), fmt.Sprintf("cannot remove tmpfile %q:", t.dst)) } else { - fmsg.VPrintf("skipping tmpfile %q", t.dst) + fmsg.Verbosef("skipping tmpfile %q", t.dst) return nil } } diff --git a/internal/system/wayland.go b/internal/system/wayland.go index 9646b46..ac3634a 100644 --- a/internal/system/wayland.go +++ b/internal/system/wayland.go @@ -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.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 { @@ -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.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)), 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.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) { 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(), fmt.Sprintf("cannot detach from wayland on %q:", w.pair[1])) } else { - fmsg.VPrintf("skipping wayland cleanup on %q", w.pair[0]) + fmsg.Verbosef("skipping wayland cleanup on %q", w.pair[0]) return nil } } diff --git a/internal/system/xhost.go b/internal/system/xhost.go index 57168b8..89e63db 100644 --- a/internal/system/xhost.go +++ b/internal/system/xhost.go @@ -24,18 +24,18 @@ func (x XHost) Type() Enablement { } 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)), fmt.Sprintf("cannot insert entry %s to X11:", x)) } func (x XHost) revert(_ *I, ec *Criteria) error { 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)), fmt.Sprintf("cannot delete entry %s from X11:", x)) } else { - fmsg.VPrintf("skipping entry %s in X11", x) + fmsg.Verbosef("skipping entry %s in X11", x) return nil } } diff --git a/main.go b/main.go index d925e56..9883afc 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( _ "embed" "flag" "fmt" + "log" "os" "os/signal" "os/user" @@ -37,6 +38,8 @@ var ( ) func init() { + fmsg.Prepare("fortify") + flag.BoolVar(&flagVerbose, "v", false, "Verbose output") flag.BoolVar(&flagJSON, "json", false, "Format output in JSON when applicable") } @@ -62,13 +65,12 @@ func main() { init0.TryArgv0() 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 } if os.Geteuid() == 0 { - fmsg.Fatal("this program must not run as root") - panic("unreachable") + log.Fatal("this program must not run as root") } flag.CommandLine.Usage = func() { @@ -96,12 +98,12 @@ func main() { fmt.Println() } flag.Parse() - fmsg.SetVerbose(flagVerbose) + fmsg.Store(flagVerbose) args := flag.Args() if len(args) == 0 { flag.CommandLine.Usage() - fmsg.Exit(0) + internal.Exit(0) } switch args[0] { @@ -111,16 +113,20 @@ func main() { } else { fmt.Println("impure") } - fmsg.Exit(0) + internal.Exit(0) + case "license": // print embedded license fmt.Println(license) - fmsg.Exit(0) + internal.Exit(0) + case "template": // print full template configuration printJSON(os.Stdout, false, fst.Template()) - fmsg.Exit(0) + internal.Exit(0) + case "help": // print help message flag.CommandLine.Usage() - fmsg.Exit(0) + internal.Exit(0) + case "ps": // print all state info set := flag.NewFlagSet("ps", flag.ExitOnError) var short bool @@ -130,7 +136,8 @@ func main() { _ = set.Parse(args[1:]) printPs(os.Stdout, time.Now().UTC(), state.NewMulti(sys.Paths().RunDirPath), short) - fmsg.Exit(0) + internal.Exit(0) + case "show": // pretty-print app info set := flag.NewFlagSet("show", flag.ExitOnError) var short bool @@ -142,6 +149,7 @@ func main() { switch len(set.Args()) { case 0: // system printShowSystem(os.Stdout, short) + case 1: // instance name := set.Args()[0] config, instance := tryShort(name) @@ -149,14 +157,15 @@ func main() { config = tryPath(name) } 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 if len(args) < 2 { - fmsg.Fatal("app requires at least 1 argument") + log.Fatal("app requires at least 1 argument") } // config extraArgs... @@ -166,6 +175,7 @@ func main() { // invoke app runApp(config) panic("unreachable") + case "run": // run app in permissive defaults usage pattern set := flag.NewFlagSet("run", flag.ExitOnError) @@ -208,8 +218,7 @@ func main() { } if aid < 0 || aid > 9999 { - fmsg.Fatalf("aid %d out of range", aid) - panic("unreachable") + log.Fatalf("aid %d out of range", aid) } // resolve home/username from os when flag is unset @@ -219,13 +228,13 @@ func main() { passwdFunc = func() { var us string 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 { us = strconv.Itoa(uid) } 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{ Uid: us, Gid: us, @@ -267,7 +276,7 @@ func main() { config.Confinement.SessionBus = dbus.NewConfig(fid, true, mpris) } else { 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 { config.Confinement.SessionBus = c } @@ -276,7 +285,7 @@ func main() { // system bus proxy is optional if dbusConfigSystem != "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 { config.Confinement.SystemBus = c } @@ -291,17 +300,18 @@ func main() { // invoke app runApp(config) + panic("unreachable") // internal commands case "shim": shim.Main() - fmsg.Exit(0) + internal.Exit(0) case "init": init0.Main() - fmsg.Exit(0) + internal.Exit(0) default: - fmsg.Fatalf("%q is not a valid command", args[0]) + log.Fatalf("%q is not a valid command", args[0]) } panic("unreachable") @@ -313,15 +323,15 @@ func runApp(config *fst.Config) { syscall.SIGINT, syscall.SIGTERM) defer stop() // unreachable - if fmsg.Verbose() { - seccomp.CPrintln = fmsg.Println + if fmsg.Load() { + seccomp.CPrintln = log.Println } 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 { logBaseError(err, "cannot seal app:") - fmsg.Exit(1) + internal.Exit(1) } else if err = a.Run(ctx, rs); err != nil { if !rs.Start { logBaseError(err, "cannot start app:") @@ -334,8 +344,7 @@ func runApp(config *fst.Config) { } } if rs.WaitErr != nil { - fmsg.Println("inner wait failed:", rs.WaitErr) + log.Println("inner wait failed:", rs.WaitErr) } - fmsg.Exit(rs.ExitCode) - panic("unreachable") + internal.Exit(rs.ExitCode) } diff --git a/parse.go b/parse.go index 85dbfca..7212c92 100644 --- a/parse.go +++ b/parse.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "io" + "log" "os" "strconv" "strings" @@ -21,11 +22,10 @@ func tryPath(name string) (config *fst.Config) { if name != "-" { r = tryFd(name) if r == nil { - fmsg.VPrintln("load configuration from file") + fmsg.Verbose("load configuration from file") if f, err := os.Open(name); err != nil { - fmsg.Fatalf("cannot access configuration file %q: %s", name, err) - panic("unreachable") + log.Fatalf("cannot access configuration file %q: %s", name, err) } 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 { - 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 { - fmsg.Fatalf("cannot load configuration: %v", err) - panic("unreachable") + log.Fatalf("cannot load configuration: %v", err) } return @@ -51,7 +50,7 @@ func tryPath(name string) (config *fst.Config) { func tryFd(name string) io.ReadCloser { 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 } else { fd := uintptr(v) @@ -59,7 +58,7 @@ func tryFd(name string) io.ReadCloser { if errors.Is(errno, syscall.EBADF) { 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)) } @@ -83,11 +82,11 @@ func tryShort(name string) (config *fst.Config, instance *state.State) { // try to match from state store if likePrefix && len(name) >= 8 { - fmsg.VPrintln("argument looks like prefix") + fmsg.Verbose("argument looks like prefix") s := state.NewMulti(sys.Paths().RunDirPath) 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 } else { for id := range entries { @@ -99,7 +98,7 @@ func tryShort(name string) (config *fst.Config, instance *state.State) { break } - fmsg.VPrintf("instance %s skipped", v) + fmsg.Verbosef("instance %s skipped", v) } } } diff --git a/print.go b/print.go index 7660353..3c0c219 100644 --- a/print.go +++ b/print.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io" + "log" "slices" "strconv" "strings" @@ -12,7 +13,6 @@ 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 { - fmsg.Fatalf("cannot obtain uid from fsu: %v", err) + log.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 { - fmsg.Fatalf("cannot join store: %v", err) + log.Fatalf("cannot join store: %v", err) } else { entries = e } if err := s.Close(); err != nil { - fmsg.Printf("cannot close store: %v", err) + log.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 { - fmsg.Printf("got invalid state entry %s", id.String()) + log.Printf("got invalid state entry %s", id.String()) continue } // gracefully skip inconsistent states 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()) continue } @@ -273,8 +273,7 @@ func printJSON(output io.Writer, short bool, v any) { encoder.SetIndent("", " ") } if err := encoder.Encode(v); err != nil { - fmsg.Fatalf("cannot serialise: %v", err) - panic("unreachable") + log.Fatalf("cannot serialise: %v", err) } } @@ -284,31 +283,26 @@ type tp struct{ *tabwriter.Writer } func (p *tp) Printf(format string, a ...any) { if _, err := fmt.Fprintf(p, format, a...); err != nil { - fmsg.Fatalf("cannot write to tabwriter: %v", err) - panic("unreachable") + log.Fatalf("cannot write to tabwriter: %v", err) } } func (p *tp) Println(a ...any) { if _, err := fmt.Fprintln(p, a...); err != nil { - fmsg.Fatalf("cannot write to tabwriter: %v", err) - panic("unreachable") + log.Fatalf("cannot write to tabwriter: %v", err) } } func (p *tp) MustFlush() { if err := p.Writer.Flush(); err != nil { - fmsg.Fatalf("cannot flush tabwriter: %v", err) - panic("unreachable") + log.Fatalf("cannot flush tabwriter: %v", err) } } func mustPrint(output io.Writer, a ...any) { if _, err := fmt.Fprint(output, a...); err != nil { - fmsg.Fatalf("cannot print: %v", err) - panic("unreachable") + log.Fatalf("cannot print: %v", err) } } func mustPrintln(output io.Writer, a ...any) { if _, err := fmt.Fprintln(output, a...); err != nil { - fmsg.Fatalf("cannot print: %v", err) - panic("unreachable") + log.Fatalf("cannot print: %v", err) } }